Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Flux Ubuntu
GUIDEInstall Guides

How to Install Flux CD on Ubuntu 24.04 VPS: GitOps Toolkit for Kubernetes

25 min read

How to Install Flux CD on Ubuntu 24.04 VPS: GitOps Toolkit for Kubernetes

Flux CD turns your Git repository into the single source of truth for a Kubernetes cluster. Instead of running kubectl apply from a laptop or a brittle CI job, you commit YAML, and Flux reconciles the cluster to match. This guide walks you from a freshly provisioned Ubuntu 24.04 VPS to a production-ready Flux install with GitHub or GitLab bootstrap, Kustomizations, HelmReleases, automated image updates, and Slack or Discord notifications.

New to Kubernetes? Start by deploying k3s on the same VPS using How to Install k3s on Ubuntu 24.04, then return here to add GitOps on top.

Table of Contents

  • What is Flux CD?
  • Why Self-Host Flux vs ArgoCD
  • Prerequisites
  • Step 1: Update Ubuntu and Confirm Cluster Access
  • Step 2: Install the Flux CLI
  • Step 3: Run Pre-Flight Checks
  • Step 4: Bootstrap Flux with GitHub or GitLab
  • Step 5: Create Your First GitRepository
  • Step 6: Reconcile with a Kustomization
  • Step 7: Deploy a Helm Chart with HelmRelease
  • Step 8: Enable Image Automation
  • Step 9: Configure the Notification Controller
  • Step 10: Multi-Tenancy with ServiceAccounts
  • Post-Install Hardening
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Flux CD?

    Flux CD is a CNCF graduated GitOps toolkit for Kubernetes. It is not a monolithic agent -- it is a set of focused controllers that each handle one concern, communicating with each other through custom resources:

    • source-controller -- fetches content from GitRepository, OCIRepository, Bucket, and HelmRepository sources.
    • kustomize-controller -- renders and applies manifests described by Kustomization resources, including Kustomize overlays and plain YAML.
    • helm-controller -- installs and upgrades charts declared by HelmRelease resources, driven by releases stored in HelmChart objects.
    • notification-controller -- emits events and dispatches them to Slack, Discord, Microsoft Teams, webhooks, and many other providers.
    • image-reflector-controller and image-automation-controller -- scan container registries for new tags and push commits back to Git when policies match.
    Because the controllers are small and single-purpose, Flux runs comfortably on a 4 vCPU / 8 GB VPS even with a dozen workloads under management. Everything Flux does is declarative: there is no flux deploy command, only Git commits and reconciliation loops.

    Typical Flux use cases include continuous deployment for microservices, platform engineering (installing cluster add-ons like ingress controllers, cert-manager, or monitoring stacks), disaster recovery (re-create an entire cluster from one Git repo), and multi-tenant internal developer platforms where each team owns a directory in Git.

    Why Self-Host Flux vs ArgoCD

    Flux and ArgoCD both implement GitOps on Kubernetes, but the developer experience and operational model differ meaningfully. Pick the one that matches how your team works.

    DimensionFlux CDArgoCD
    InterfaceCLI and CRDs (YAML-first)Rich web UI + CLI
    Install footprint~200 MB memory across 5 controllers~500 MB+ including UI, Redis, Dex
    CRD modelOne CRD per concern (GitRepository, Kustomization, HelmRelease)One Application CRD that wraps everything
    Multi-tenancyNative via ServiceAccounts + namespace RBACAppProjects + SSO roles
    Helm supportFirst-class HelmRelease with drift detectionTemplate rendering + native sync
    Image automationBuilt-in (image-reflector / image-automation)External (Argo Image Updater)
    OCI sourcesYes (OCIRepository)Yes (since 2.6)
    Notificationsnotification-controller, dozens of providersargocd-notifications, similar scope
    SecretsSOPS, sealed-secrets, external-secrets integrationsSame
    GovernanceCNCF Graduated (2023)CNCF Graduated (2022)
    Best forPlatform teams who prefer YAML, smaller clusters, multi-cluster fleetsTeams who want a polished sync UI, app-centric mental model
    Choose Flux if you want the smallest possible control plane, prefer PR-driven review over a UI, manage many clusters from one repo, or already run k3s on a modestly sized VPS. Choose ArgoCD if your operators live in the sync UI, your auditors want a visual app tree, or you need multi-user RBAC out of the box.

    You are not locked in either direction -- both can run on the same cluster for different namespaces during a migration. If you are evaluating both side by side, our companion tutorial How to Install ArgoCD on Ubuntu 24.04 mirrors this guide.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access.
    • A running Kubernetes cluster reachable via kubectl. A single-node k3s install on the same VPS works well. Full k8s, kind, microk8s, and managed clusters (EKS, GKE, AKS) all work the same way.
    • A GitHub or GitLab account with permission to create a repository.
    • A personal access token (PAT) with repo scope on GitHub, or api + read_repository + write_repository on GitLab. Flux uses this once during bootstrap to create the repo and deploy keys.
    • Helm installed locally if you plan to write HelmReleases. See How to Install Helm on Ubuntu 24.04.
    • At least 8 GB of RAM on the node that will run the Flux controllers.
    Recommended Plan: CloudCore Professional
    >
    For a k3s cluster plus Flux controllers plus a reasonable set of workloads, we recommend the CloudCore Professional plan:
    >
    - 6 vCPU cores
    - 12 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 19.99/month
    >
    This gives you headroom for the 5 Flux controllers (~200 MB RAM total), the k3s control plane, and a dozen or so application pods without paging.

    Connect to your VPS via SSH:

    bash
    ssh root@your-server-ip

    Step 1: Update Ubuntu and Confirm Cluster Access

    Start by refreshing package indexes and applying security updates:

    bash
    sudo apt update && sudo apt upgrade -y

    Confirm that kubectl can reach your cluster. If you installed k3s with the companion guide, the kubeconfig is at /etc/rancher/k3s/k3s.yaml:

    bash
    export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
    kubectl get nodes

    Expected output:

    text
    NAME           STATUS   ROLES                  AGE   VERSION
    flux-demo-01   Ready    control-plane,master   5m    v1.30.3+k3s1

    For convenience, persist KUBECONFIG in your shell profile:

    bash
    echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' | sudo tee -a /etc/profile.d/kubeconfig.sh
    source /etc/profile.d/kubeconfig.sh

    Also confirm outbound DNS and HTTPS work -- Flux fetches from GitHub/GitLab and from container registries:

    bash
    curl -sI https://api.github.com | head -1

    Expected output:

    text
    HTTP/2 200

    Step 2: Install the Flux CLI

    The Flux CLI (flux) is a single Go binary. You only need it on any machine that talks to the cluster -- the controllers themselves live inside Kubernetes.

    Install using the official script, which auto-detects the architecture (amd64, arm64):

    bash
    curl -s https://fluxcd.io/install.sh | sudo bash

    Expected output (abbreviated):

    text
    [INFO] Downloading metadata https://api.github.com/repos/fluxcd/flux2/releases/latest
    [INFO] Using 2.3.0 as release
    [INFO] Downloading hash https://github.com/fluxcd/flux2/releases/download/v2.3.0/flux_2.3.0_checksums.txt
    [INFO] Downloading binary https://github.com/fluxcd/flux2/releases/download/v2.3.0/flux_2.3.0_linux_amd64.tar.gz
    [INFO] Verifying binary download
    [INFO] Installing flux to /usr/local/bin/flux

    Verify the install:

    bash
    flux --version

    Expected output:

    text
    flux version 2.3.0

    Enable shell completion for convenience:

    bash
    flux completion bash | sudo tee /etc/bash_completion.d/flux > /dev/null
    source /etc/bash_completion.d/flux

    Step 3: Run Pre-Flight Checks

    Flux ships a check --pre command that validates your cluster meets its minimum requirements (Kubernetes >= 1.28, sufficient RBAC, etc.) without changing anything:

    bash
    flux check --pre

    Expected output:

    text
    ► checking prerequisites
    ✔ Kubernetes 1.30.3+k3s1 >=1.28.0-0
    ✔ prerequisites checks passed

    If you see warnings about an older Kubernetes version, upgrade your cluster before continuing. Flux 2.x requires Kubernetes 1.28 or newer.

    Step 4: Bootstrap Flux with GitHub or GitLab

    flux bootstrap is a one-time command that does three things:

  • Installs the Flux controllers into the flux-system namespace.
  • Creates (or updates) a Git repository that will hold your cluster state.
  • Commits the controllers' own manifests into that repo and configures Flux to reconcile itself from it.
  • After bootstrap, Flux manages its own upgrades -- you can bump the version in Git and the controllers roll forward.

    Option A: Bootstrap with GitHub

    Export your GitHub personal access token:

    bash
    export GITHUB_TOKEN=ghp_your_personal_access_token
    export GITHUB_USER=your-github-username

    Run the bootstrap against a new or existing repository:

    bash
    flux bootstrap github \
      --owner=$GITHUB_USER \
      --repository=flux-demo \
      --branch=main \
      --path=clusters/production \
      --personal \
      --private

    Flag meanings:

    • --owner and --repository name the repo. Use --personal for a user-owned repo; omit it for an organization.
    • --branch=main -- the branch Flux reads and writes.
    • --path=clusters/production -- everything Flux reconciles lives under this path. Use a different subdirectory per cluster if you manage more than one.
    • --private -- creates the repo as private if it does not already exist.
    Expected output:

    text
    ► connecting to github.com
    ► cloning branch "main" from Git repository "https://github.com/yourname/flux-demo.git"
    ► generating component manifests
    ► writing component manifests
    ► installing components in "flux-system" namespace
    ◎ verifying installation
    ✔ helm-controller: deployment ready
    ✔ kustomize-controller: deployment ready
    ✔ notification-controller: deployment ready
    ✔ source-controller: deployment ready
    ✔ all components are healthy

    Option B: Bootstrap with GitLab

    For GitLab (Cloud or self-hosted):

    bash
    export GITLAB_TOKEN=glpat-your_token
    export GITLAB_USER=your-gitlab-username

    flux bootstrap gitlab \ --owner=$GITLAB_USER \ --repository=flux-demo \ --branch=main \ --path=clusters/production \ --personal \ --private

    For self-hosted GitLab, add --hostname=gitlab.yourcompany.com.

    Verify the install

    bash
    flux check

    Expected output:

    text
    ► checking prerequisites
    ✔ Kubernetes 1.30.3+k3s1 >=1.28.0-0
    ► checking controllers
    ✔ helm-controller: deployment ready
    ✔ kustomize-controller: deployment ready
    ✔ notification-controller: deployment ready
    ✔ source-controller: deployment ready
    ✔ all checks passed
    bash
    kubectl get pods -n flux-system

    Expected output:

    text
    NAME                                       READY   STATUS    RESTARTS   AGE
    helm-controller-6b5f4d79f4-2xkpt           1/1     Running   0          2m
    kustomize-controller-7f8d9c6c5b-qw8mn      1/1     Running   0          2m
    notification-controller-5f8f7c6c5b-p9xzl   1/1     Running   0          2m
    source-controller-854f4c7c78-tgqkz         1/1     Running   0          2m

    Clone the repo Flux just created locally -- you will add manifests here going forward:

    bash
    git clone https://github.com/$GITHUB_USER/flux-demo.git
    cd flux-demo

    Inside you will find clusters/production/flux-system/ with the controllers' manifests. Everything you add elsewhere in clusters/production/ will be reconciled by Flux.

    Step 5: Create Your First GitRepository

    A GitRepository is a Flux custom resource that tells source-controller where to fetch manifests from. Bootstrap already created one pointing at the cluster repo itself. Now add a second one that points at a separate application repository.

    Inside your cloned flux-demo repo, create a file at clusters/production/podinfo-source.yaml:

    yaml
    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
      ignore: |
        /*
        !/kustomize

    Commit and push:

    bash
    git add clusters/production/podinfo-source.yaml
    git commit -m "feat: add podinfo GitRepository"
    git push

    Within a minute (the interval), source-controller notices the change and fetches the repo. Watch the source appear:

    bash
    flux get sources git

    Expected output:

    text
    NAME           REVISION             SUSPENDED  READY  MESSAGE
    flux-system    main@sha1:abcd1234   False      True   stored artifact for revision 'main@sha1:abcd1234'
    podinfo        master@sha1:efgh5678 False      True   stored artifact for revision 'master@sha1:efgh5678'

    The ignore field is a gitignore-style filter -- here it tells Flux to only download the kustomize directory, saving disk and bandwidth.

    Step 6: Reconcile with a Kustomization

    A Kustomization (the Flux CRD, not to be confused with the upstream Kustomize overlay file) tells kustomize-controller to render and apply a specific path from a source.

    Create clusters/production/podinfo-kustomization.yaml:

    yaml
    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: default
      timeout: 2m
      wait: true
      healthChecks:
        - apiVersion: apps/v1
          kind: Deployment
          name: podinfo
          namespace: default

    Field meanings:

    • path -- the directory inside the GitRepository to render.
    • prune: true -- Flux will delete resources removed from Git. This is what makes GitOps a true source of truth.
    • wait: true + healthChecks -- Kustomization stays in Reconciling until the deployment is ready.
    • targetNamespace -- override the namespace in the source manifests.
    Commit and push:

    bash
    git add clusters/production/podinfo-kustomization.yaml
    git commit -m "feat: reconcile podinfo"
    git push

    Force a reconciliation instead of waiting:

    bash
    flux reconcile source git flux-system
    flux reconcile kustomization podinfo --with-source

    Expected output:

    text
    ► annotating Kustomization podinfo in flux-system namespace
    ✔ Kustomization annotated
    ◎ waiting for Kustomization reconciliation
    ✔ applied revision master@sha1:efgh5678

    Confirm the app is running:

    bash
    kubectl get pods -n default -l app=podinfo

    Expected output:

    text
    NAME                      READY   STATUS    RESTARTS   AGE
    podinfo-9f5bd8ff4-2gqzp   1/1     Running   0          30s
    podinfo-9f5bd8ff4-xbtvm   1/1     Running   0          30s

    Step 7: Deploy a Helm Chart with HelmRelease

    Flux installs Helm charts through HelmRelease objects backed by helm-controller. You never run helm install by hand -- you commit YAML.

    Add a HelmRepository

    Create clusters/production/bitnami-repo.yaml:

    yaml
    apiVersion: source.toolkit.fluxcd.io/v1
    kind: HelmRepository
    metadata:
      name: bitnami
      namespace: flux-system
    spec:
      interval: 30m
      url: https://charts.bitnami.com/bitnami
      type: oci  # remove this line for the classic HTTP repo

    Create the HelmRelease

    Create clusters/production/redis-helmrelease.yaml:

    yaml
    apiVersion: helm.toolkit.fluxcd.io/v2
    kind: HelmRelease
    metadata:
      name: redis
      namespace: default
    spec:
      interval: 5m
      releaseName: redis
      chart:
        spec:
          chart: redis
          version: "19.x"
          sourceRef:
            kind: HelmRepository
            name: bitnami
            namespace: flux-system
          interval: 30m
      install:
        remediation:
          retries: 3
      upgrade:
        remediation:
          retries: 3
        cleanupOnFail: true
      values:
        architecture: standalone
        auth:
          enabled: true
          password: "change-me-in-a-secret"
        master:
          persistence:
            enabled: true
            size: 8Gi

    Commit and push:

    bash
    git add clusters/production/bitnami-repo.yaml clusters/production/redis-helmrelease.yaml
    git commit -m "feat: add redis helm release"
    git push

    Force the reconcile and watch:

    bash
    flux reconcile kustomization flux-system --with-source
    flux get helmreleases

    Expected output:

    text
    NAME    REVISION    SUSPENDED  READY  MESSAGE
    redis   19.3.0      False      True   Release reconciliation succeeded

    helm list -n default also shows the release -- helm-controller uses the standard Helm storage format, so you can still inspect it with the Helm CLI if needed.

    Passing secrets safely

    Never commit plaintext passwords. The two common patterns are:

    • SOPS with age/GPG -- Flux's kustomize-controller natively decrypts SOPS-encrypted YAML. Install SOPS, encrypt a values.yaml, and reference it via valuesFrom on the HelmRelease.
    • External Secrets Operator -- pull values from AWS Secrets Manager, Vault, or 1Password into Kubernetes secrets.

    Step 8: Enable Image Automation

    Out of the box, Flux only reacts to Git changes. With the image-reflector and image-automation controllers, Flux also reacts to new container image tags -- it writes a commit back to Git bumping the image, which triggers the normal reconciliation loop.

    Re-bootstrap Flux with the extra components:

    bash
    flux bootstrap github \
      --owner=$GITHUB_USER \
      --repository=flux-demo \
      --branch=main \
      --path=clusters/production \
      --personal \
      --components-extra=image-reflector-controller,image-automation-controller

    Confirm both pods are running:

    bash
    kubectl get pods -n flux-system | grep image

    Expected output:

    text
    image-automation-controller-7d9f7c6c5b-abcde   1/1   Running   0   1m
    image-reflector-controller-854f4c7c78-fghij    1/1   Running   0   1m

    Define an ImageRepository and ImagePolicy

    Create clusters/production/podinfo-image.yaml:

    yaml
    ---
    apiVersion: image.toolkit.fluxcd.io/v1beta2
    kind: ImageRepository
    metadata:
      name: podinfo
      namespace: flux-system
    spec:
      image: ghcr.io/stefanprodan/podinfo
      interval: 5m
    
    apiVersion: image.toolkit.fluxcd.io/v1beta2
    kind: ImagePolicy
    metadata:
      name: podinfo
      namespace: flux-system
    spec:
      imageRepositoryRef:
        name: podinfo
      policy:
        semver:
          range: ">=6.0.0 <7.0.0"

    The policy selects the newest semver tag in the 6.x range.

    Add an ImageUpdateAutomation

    Create clusters/production/podinfo-automation.yaml:

    yaml
    apiVersion: image.toolkit.fluxcd.io/v1beta1
    kind: ImageUpdateAutomation
    metadata:
      name: flux-system
      namespace: flux-system
    spec:
      interval: 10m
      sourceRef:
        kind: GitRepository
        name: flux-system
      git:
        checkout:
          ref:
            branch: main
        commit:
          author:
            email: [email protected]
            name: fluxcdbot
          messageTemplate: |
            chore(images): automated update

    Files: {{ range $filename, $_ := .Updated.Files -}} - {{ $filename }} {{ end -}} push: branch: main

    Finally, annotate the deployment you want updated. In the podinfo repo's kustomize/deployment.yaml, add an inline marker after the image:

    yaml
    image: ghcr.io/stefanprodan/podinfo:6.5.0 # {"$imagepolicy": "flux-system:podinfo"}

    Commit, push, and wait 10 minutes (or run flux reconcile image update flux-system). When a newer 6.x tag ships, Flux writes a commit like chore(images): automated update bumping the tag, which triggers deployment.

    Step 9: Configure the Notification Controller

    You want to know when reconciliations fail. notification-controller exposes two CRDs:

    • Provider -- how to send a message (Slack, Discord, Teams, webhook, etc.).
    • Alert -- what events trigger the provider and at what severity.

    Slack alerts

    Create an Incoming Webhook in Slack, then store the URL as a secret:

    bash
    kubectl -n flux-system create secret generic slack-url \
      --from-literal=address=https://hooks.slack.com/services/T000/B000/XXXX

    Create clusters/production/slack-alerts.yaml:

    yaml
    ---
    apiVersion: notification.toolkit.fluxcd.io/v1beta3
    kind: Provider
    metadata:
      name: slack
      namespace: flux-system
    spec:
      type: slack
      channel: flux-alerts
      secretRef:
        name: slack-url
    
    apiVersion: notification.toolkit.fluxcd.io/v1beta3
    kind: Alert
    metadata:
      name: on-call-prod
      namespace: flux-system
    spec:
      providerRef:
        name: slack
      eventSeverity: error
      eventSources:
        - kind: GitRepository
          name: "*"
        - kind: Kustomization
          name: "*"
        - kind: HelmRelease
          name: "*"

    Commit, push, and Flux will post to #flux-alerts whenever a Kustomization, HelmRelease, or GitRepository fails to reconcile.

    Discord alerts

    Discord uses the same Provider, with a different type:

    yaml
    apiVersion: notification.toolkit.fluxcd.io/v1beta3
    kind: Provider
    metadata:
      name: discord
      namespace: flux-system
    spec:
      type: discord
      address: https://discord.com/api/webhooks/your-channel-webhook-id/your-token

    Reference it from an Alert the same way as Slack. You can attach multiple providers to the same events -- send errors to Slack and info events to Discord, for example.

    Test an alert manually

    Trigger an event to confirm the pipeline works end to end:

    bash
    flux reconcile kustomization podinfo --with-source

    For a failure test, temporarily break the Kustomization path (set path: "./does-not-exist"), push, and watch your Slack channel light up.

    Step 10: Multi-Tenancy with ServiceAccounts

    By default, every Kustomization and HelmRelease reconciles with the cluster-wide flux-system ServiceAccount, which has cluster-admin. For multi-tenant clusters -- one cluster, multiple teams -- you want each tenant's reconciler restricted to their own namespace.

    Create a tenant namespace and ServiceAccount

    tenants/team-alpha/rbac.yaml:

    yaml
    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: team-alpha
    
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: gitops-reconciler
      namespace: team-alpha
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: gitops-reconciler
      namespace: team-alpha
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: edit
    subjects:
      - kind: ServiceAccount
        name: gitops-reconciler
        namespace: team-alpha

    Point the tenant Kustomization at its ServiceAccount

    tenants/team-alpha/apps.yaml:

    yaml
    ---
    apiVersion: source.toolkit.fluxcd.io/v1
    kind: GitRepository
    metadata:
      name: team-alpha-apps
      namespace: team-alpha
    spec:
      interval: 1m
      url: https://github.com/team-alpha/apps
      ref:
        branch: main
    
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: team-alpha-apps
      namespace: team-alpha
    spec:
      serviceAccountName: gitops-reconciler
      interval: 5m
      path: "./deploy"
      prune: true
      sourceRef:
        kind: GitRepository
        name: team-alpha-apps
      targetNamespace: team-alpha

    Because serviceAccountName is set, kustomize-controller impersonates team-alpha:gitops-reconciler when applying manifests. If team-alpha tries to deploy a ClusterRole or something in the kube-system namespace, the apply fails -- their SA does not have those permissions.

    Pair this with a Git repo permissions model (each tenant only pushes to their own directory) and you have a genuine multi-tenant GitOps platform.

    Post-Install Hardening

    A few things you should do before trusting Flux with production.

    Restrict cross-namespace references

    By default, a Kustomization in namespace A can reference a GitRepository in namespace B. Lock this down by starting each controller with --no-cross-namespace-refs=true. Edit clusters/production/flux-system/kustomization.yaml and add a patch:

    yaml
    patches:
      - target:
          kind: Deployment
          name: (kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)
        patch: |
          - op: add
            path: /spec/template/spec/containers/0/args/-
            value: --no-cross-namespace-refs=true

    Enable drift detection

    For HelmReleases, set driftDetection.mode: enabled so Flux re-applies when someone edits a Helm-managed resource with kubectl edit:

    yaml
    spec:
      driftDetection:
        mode: enabled

    Pin controller versions in Git

    After bootstrap, the controller versions live in clusters/production/flux-system/gotk-components.yaml. Upgrade intentionally by running flux install --export > gotk-components.yaml, reviewing the diff, and pushing.

    Back up the cluster state

    Flux reconciles into a cluster, but Flux's own CRDs and secrets still need backup. Velero or a nightly kubectl get -A --all-namespaces dump to object storage covers you if the etcd/SQLite backend is ever corrupted.

    Troubleshooting

    ProblemCauseSolution
    flux bootstrap fails with 401 Bad credentialsPAT missing repo scope or expiredGenerate a fresh token with full repo scope (api for GitLab), export it as GITHUB_TOKEN, re-run.
    Kustomization not ready: dependency 'x' is not readyA dependsOn target has not reconciledRun flux get kustomizations -A to find the failing dependency. Fix it or remove the dependsOn.
    context deadline exceeded during reconciletimeout too short or cluster is slowRaise spec.timeout on the Kustomization (e.g. 10m). Check cluster load with kubectl top nodes.
    HelmRelease stuck InstallFailedRelease has remediation retries exhaustedflux suspend helmrelease redis, fix values, flux resume helmrelease redis.
    Image automation never commitsMissing # {"$imagepolicy": "..."} marker, or wrong policy nameVerify the marker matches namespace:name. Check flux get images policy -A.
    failed to decode SOPS fileWrong age key mounted in kustomize-controllerMount your sops-age secret and reference it from the Kustomization's spec.decryption.
    Slack alerts silentSecret key must be named addressConfirm with kubectl -n flux-system get secret slack-url -o yaml. Key must be address, value the full webhook URL.
    Controllers OOMKilledToo many large HelmCharts cached in source-controllerRaise memory requests in gotk-components.yaml, or move large charts to OCIRepository pull.

    Useful debug commands

    bash
    # Full event stream for the flux-system namespace
    kubectl get events -n flux-system --sort-by=.lastTimestamp

    Per-controller logs

    flux logs --level=error --all-namespaces

    One-off reconcile of everything

    flux reconcile source git flux-system flux reconcile kustomization flux-system --with-source

    Export the current state of all Flux resources

    flux export source git --all > sources.yaml flux export kustomization --all > kustomizations.yaml flux export helmrelease --all -A > helmreleases.yaml

    FAQ

    What is the difference between Flux CD and ArgoCD?

    Both implement GitOps for Kubernetes, but with different personalities. Flux is a set of single-purpose controllers wired together via CRDs and is fully YAML-driven -- there is no built-in UI. ArgoCD bundles a polished web dashboard and models everything as a single Application CRD. Flux uses roughly half the memory of an equivalent ArgoCD install and is often preferred by platform teams who review via Git PRs. ArgoCD tends to win when operators want to see the sync state visually. Both are CNCF graduated projects, and both support Helm, Kustomize, OCI, and multi-cluster setups. If you are unsure, start with Flux on a single cluster -- you can add ArgoCD to the same cluster later without conflict.

    Do I need a full Kubernetes cluster to run Flux?

    No. Flux runs on any CNCF-conformant Kubernetes, including single-node distributions like k3s, k0s, microk8s, and kind. For evaluation, a 4 vCPU / 8 GB VPS running k3s plus Flux plus a handful of workloads is comfortable. For production with many HelmReleases and image automation enabled, size up to 6 vCPU / 12 GB (our CloudCore Professional plan) or run Flux on a managed control plane like EKS or GKE.

    Can Flux manage multiple clusters from one Git repository?

    Yes. The canonical pattern is one directory per cluster under the repo root: clusters/production, clusters/staging, clusters/dev. Each cluster bootstraps against its own --path and only reconciles that directory. Shared manifests live in a sibling directory (like infrastructure/) and are pulled in via Kustomization dependsOn chains. For truly dynamic fleets, the Cluster API plus Flux's ClusterResourceSet-like patterns let you template per-cluster overlays.

    How does Flux handle secrets?

    Flux does not store secrets itself -- it delegates. The two common patterns are SOPS (encrypt YAML values at rest with age or GPG keys, mount a decryption key into kustomize-controller) and the External Secrets Operator (sync secrets from AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, or 1Password into Kubernetes Secrets). Both work transparently with HelmRelease valuesFrom. Never commit plaintext secrets to Git even in a private repo.

    Can I roll back a deployment with Flux?

    For Kustomizations, roll back by reverting the offending Git commit -- Flux re-applies the previous state within the reconcile interval. For HelmReleases, helm-controller's remediation field automatically rolls back failed upgrades. You can also manually suspend a HelmRelease (flux suspend helmrelease redis), use helm rollback to return to a known-good revision, then resume. Because Git is the source of truth, git revert is your primary rollback mechanism.

    What happens if the Git repository is unavailable?

    Flux continues running the last-known-good state. source-controller caches artifacts on disk, and the other controllers reconcile against those cached sources. If you push to Git but Git is down when Flux polls, the next successful poll picks up the change. Running workloads keep serving traffic throughout. This is one of the strongest operational properties of GitOps compared to push-based CD, where a broken CI job can leave you unable to deploy.

    Does Flux support OCI sources instead of Git?

    Yes. OCIRepository lets Flux pull manifests bundled as an OCI artifact from any OCI-compliant registry (GitHub Container Registry, GHCR, ECR, Harbor, etc.). This is useful for sealed release artifacts -- you build a manifest bundle in CI, push it to a registry with a semver tag, and Flux pulls it like any other image. OCI sources also support cosign signature verification, giving you tamper-evident supply chain integrity.

    Next Steps

    You have a working Flux install. Here is where to go from here.

    • Add monitoring -- Install kube-prometheus-stack via a HelmRelease and scrape the Flux controller /metrics endpoints. Each controller exposes detailed reconciliation timing and error counters.
    • Layer ArgoCD for comparison -- Run ArgoCD on a separate namespace against the same cluster and decide which model fits your team.
    • Master Helm first -- If HelmRelease syntax felt dense, spend an afternoon with How to Install Helm on Ubuntu and read through a chart's values.yaml.
    • Wire CI to cluster -- Have your CI pipeline push built image tags to a container registry. Flux's image automation takes it from there -- no kubectl in CI.
    • Explore the ecosystem -- Browse the Flux guides and integrations for patterns like progressive delivery with Flagger, GitHub Actions bridges, and Cluster API.
    • Harden for production -- Enable --no-cross-namespace-refs, turn on drift detection for HelmReleases, and add an Alert that pages on failed reconciliations outside business hours.

    Want to skip the setup?
    >
    Run k3s + Flux + SOPS on our CloudCore Professional plan -- 6 vCPU, 12 GB RAM, 100 GB NVMe, unmetered traffic, EUR 19.99/month. Provision in under 60 seconds, then follow this guide to bring your Git repo to life.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket