$linuxjunkies
>

Install Flux on k3s

Bootstrap Flux CD on a k3s cluster, set up GitRepository and Kustomization reconciliation, and deploy applications via HelmRelease — step by step.

AdvancedUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • k3s cluster running with kubectl access and cluster-admin privileges
  • GitHub account with a personal access token (repo scope)
  • git installed and configured locally
  • flux CLI installed (version 2.3+ recommended)

Flux CD turns a Git repository into the single source of truth for your Kubernetes cluster. On k3s — the lightweight Kubernetes distribution common on bare-metal, VMs, and edge nodes — Flux works without modification, but a few k3s-specific details trip people up. This guide walks through bootstrapping Flux against a GitHub repository, creating a GitRepository + Kustomization reconciliation loop, and deploying an application via a HelmRelease.

Prerequisites and Environment

You need a running k3s cluster (single-node or multi-node), kubectl configured with cluster-admin rights, and the flux CLI installed. You also need a GitHub personal access token (PAT) with repo scope.

Install the Flux CLI

Debian/Ubuntu and Fedora/RHEL users can pull the official install script. Arch users can use the AUR.

# Debian / Ubuntu
curl -s https://fluxcd.io/install.sh | sudo bash
# Fedora / RHEL 9 / Rocky 9
curl -s https://fluxcd.io/install.sh | sudo bash
# Arch
yay -S flux-bin

Verify the CLI is available:

flux version --client

Point kubectl at k3s

k3s writes its kubeconfig to /etc/rancher/k3s/k3s.yaml. Export it for your session or copy it to ~/.kube/config.

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

Pre-flight Check

Run flux check --pre before bootstrapping. It validates Kubernetes version compatibility and required API groups.

flux check --pre

All checks should return . k3s ships with all required APIs enabled by default; if you see a CRD warning it usually means your k3s version is too old (1.24 minimum, 1.28+ recommended).

Bootstrap Flux to GitHub

Bootstrap installs the Flux controllers into the flux-system namespace and commits their manifests to your Git repository. From that point on, the cluster reconciles itself from Git.

Export credentials

export GITHUB_TOKEN=ghp_yourTokenHere
export GITHUB_USER=your-github-username

Run the bootstrap command

Replace fleet-infra with your target repository name. The repository will be created if it does not exist.

flux bootstrap github \
  --owner=${GITHUB_USER} \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/my-k3s \
  --personal \
  --private

Flux commits the controller manifests under clusters/my-k3s/flux-system/ and then applies them. Watch the rollout:

kubectl -n flux-system get pods -w

You should see source-controller, kustomize-controller, helm-controller, and notification-controller reach Running status. Output will vary but looks like:

# Example output (abbreviated)
NAME                                      READY   STATUS    RESTARTS
helm-controller-xxxxxxxxx-xxxxx           1/1     Running   0
kustomize-controller-xxxxxxxxx-xxxxx      1/1     Running   0
notification-controller-xxxxxxxxx-xxxxx   1/1     Running   0
source-controller-xxxxxxxxx-xxxxx         1/1     Running   0

k3s-specific note on storage

Flux controllers do not need persistent volumes by default. However, if you later enable image-reflector-controller or image-automation-controller, their image database requires a PVC. k3s ships with local-path as the default StorageClass, which satisfies this without extra configuration.

Create a GitRepository and Kustomization

With Flux running, you now define where your application manifests live and how they are applied. Clone your fleet-infra repo locally and work inside it.

git clone https://github.com/${GITHUB_USER}/fleet-infra
cd fleet-infra

Add a GitRepository source

Create the directory layout Flux expects and write a GitRepository manifest that points at an application repository.

mkdir -p clusters/my-k3s/apps
cat > clusters/my-k3s/apps/podinfo-source.yaml <<'EOF'
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/stefanprodan/podinfo
  ref:
    branch: master
EOF

Add a Kustomization

A Kustomization tells Flux which path inside the GitRepository to apply and how often to reconcile.

cat > clusters/my-k3s/apps/podinfo-kustomization.yaml <<'EOF'
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 5m
  path: ./kustomize
  prune: true
  sourceRef:
    kind: GitRepository
    name: podinfo
  targetNamespace: podinfo
EOF

prune: true means Flux deletes resources from the cluster when they are removed from Git — essential for keeping drift at zero.

Commit and push

git add clusters/
git commit -m "feat: add podinfo GitRepository and Kustomization"
git push

Flux polls every minute by default. Force an immediate reconcile with:

flux reconcile source git flux-system

Then watch the Kustomization apply:

flux get kustomizations --watch

Deploy an Application with a HelmRelease

Flux's helm-controller manages Helm charts declaratively. You define a HelmRepository (the chart source) and a HelmRelease (the release spec).

Add the HelmRepository

cat > clusters/my-k3s/apps/nginx-helmrepo.yaml <<'EOF'
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 12h
  url: https://kubernetes.github.io/ingress-nginx
EOF

Add the HelmRelease

cat > clusters/my-k3s/apps/nginx-helmrelease.yaml <<'EOF'
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  interval: 1h
  chart:
    spec:
      chart: ingress-nginx
      version: ">=4.10.0 <5.0.0"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  install:
    createNamespace: true
  values:
    controller:
      service:
        type: LoadBalancer
EOF

k3s ships with Klipper as its built-in load-balancer, so type: LoadBalancer works without a cloud provider. On a single-node cluster the service will get an external IP equal to the node IP.

Commit and verify the HelmRelease

git add clusters/my-k3s/apps/
git commit -m "feat: add ingress-nginx HelmRelease"
git push
flux reconcile source git flux-system
flux get helmreleases -A

The READY column shows True once Helm has applied the chart. MESSAGE will read Helm install succeeded.

Verification

flux check
flux get sources git -A
flux get kustomizations -A
flux get helmreleases -A

All sources and workloads should show READY=True. Confirm the ingress controller is running:

kubectl -n ingress-nginx get pods,svc

Troubleshooting

Bootstrap fails with authentication error

Confirm your PAT has the repo scope (not just read:packages). Expired tokens are the most common cause. Regenerate and re-export GITHUB_TOKEN before retrying bootstrap.

Kustomization stuck in "not ready"

Inspect events on the specific object:

flux describe kustomization podinfo -n flux-system

A path not found error means the spec.path in your manifest does not match the actual directory layout in the source repository. A namespace not found error usually means targetNamespace doesn't exist and you need to add a namespace manifest or set spec.commonMetadata.annotations with a namespace creation hook.

HelmRelease fails with "chart not found"

Check that the HelmRepository is itself ready before the HelmRelease reconciles:

flux get sources helm -A

If the repository shows an error, the URL may be unreachable from the cluster. On k3s behind a corporate proxy, set HTTPS_PROXY in the source-controller deployment or use an internal chart mirror.

Controllers crashlooping after bootstrap

k3s runs containerd; pull image failures are the usual cause on air-gapped nodes. Pre-pull the Flux controller images with k3s ctr images pull or configure a registry mirror in /etc/rancher/k3s/registries.yaml.

tested on:Ubuntu 24.04Debian 12Rocky 9Arch rolling

Frequently asked questions

Can I bootstrap Flux using GitLab or Gitea instead of GitHub?
Yes. Flux supports flux bootstrap gitlab and flux bootstrap gitea with equivalent flags. For self-hosted instances add --hostname to point at your server.
Does Flux conflict with k3s's built-in Helm controller?
k3s ships its own HelmChart CRD for bootstrapping cluster components. Flux's helm-controller manages a separate HelmRelease CRD and the two coexist without conflict, but do not mix both approaches for the same release.
How do I store Kubernetes Secrets in Git safely with Flux?
Use Mozilla SOPS or Sealed Secrets. Flux's kustomize-controller has native SOPS decryption support; add a .sops.yaml and an age or GPG key reference in your Kustomization spec.
How do I force Flux to reconcile immediately without waiting for the interval?
Run flux reconcile source git flux-system to re-fetch Git, then flux reconcile kustomization <name> or flux reconcile helmrelease <name> to re-apply a specific resource immediately.
What happens to my cluster if the Git repository is temporarily unreachable?
Flux continues running the last successfully applied state. It will not delete or modify existing resources; it simply retries the source fetch until Git is reachable again.

Related guides