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?
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, andHelmRepositorysources. - kustomize-controller -- renders and applies manifests described by
Kustomizationresources, including Kustomize overlays and plain YAML. - helm-controller -- installs and upgrades charts declared by
HelmReleaseresources, driven by releases stored inHelmChartobjects. - 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.
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.
| Dimension | Flux CD | ArgoCD |
|---|---|---|
| Interface | CLI and CRDs (YAML-first) | Rich web UI + CLI |
| Install footprint | ~200 MB memory across 5 controllers | ~500 MB+ including UI, Redis, Dex |
| CRD model | One CRD per concern (GitRepository, Kustomization, HelmRelease) | One Application CRD that wraps everything |
| Multi-tenancy | Native via ServiceAccounts + namespace RBAC | AppProjects + SSO roles |
| Helm support | First-class HelmRelease with drift detection | Template rendering + native sync |
| Image automation | Built-in (image-reflector / image-automation) | External (Argo Image Updater) |
| OCI sources | Yes (OCIRepository) | Yes (since 2.6) |
| Notifications | notification-controller, dozens of providers | argocd-notifications, similar scope |
| Secrets | SOPS, sealed-secrets, external-secrets integrations | Same |
| Governance | CNCF Graduated (2023) | CNCF Graduated (2022) |
| Best for | Platform teams who prefer YAML, smaller clusters, multi-cluster fleets | Teams who want a polished sync UI, app-centric mental model |
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
reposcope on GitHub, orapi+read_repository+write_repositoryon 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:
ssh root@your-server-ipStep 1: Update Ubuntu and Confirm Cluster Access
Start by refreshing package indexes and applying security updates:
sudo apt update && sudo apt upgrade -yConfirm that kubectl can reach your cluster. If you installed k3s with the companion guide, the kubeconfig is at /etc/rancher/k3s/k3s.yaml:
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodesExpected output:
NAME STATUS ROLES AGE VERSION
flux-demo-01 Ready control-plane,master 5m v1.30.3+k3s1For convenience, persist KUBECONFIG in your shell profile:
echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' | sudo tee -a /etc/profile.d/kubeconfig.sh
source /etc/profile.d/kubeconfig.shAlso confirm outbound DNS and HTTPS work -- Flux fetches from GitHub/GitLab and from container registries:
curl -sI https://api.github.com | head -1Expected output:
HTTP/2 200Step 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):
curl -s https://fluxcd.io/install.sh | sudo bashExpected output (abbreviated):
[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/fluxVerify the install:
flux --versionExpected output:
flux version 2.3.0Enable shell completion for convenience:
flux completion bash | sudo tee /etc/bash_completion.d/flux > /dev/null
source /etc/bash_completion.d/fluxStep 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:
flux check --preExpected output:
► checking prerequisites
✔ Kubernetes 1.30.3+k3s1 >=1.28.0-0
✔ prerequisites checks passedIf 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:
flux-system namespace.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:
export GITHUB_TOKEN=ghp_your_personal_access_token
export GITHUB_USER=your-github-usernameRun the bootstrap against a new or existing repository:
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=flux-demo \
--branch=main \
--path=clusters/production \
--personal \
--privateFlag meanings:
--ownerand--repositoryname the repo. Use--personalfor 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.
► 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 healthyOption B: Bootstrap with GitLab
For GitLab (Cloud or self-hosted):
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
flux checkExpected output:
► 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 passedkubectl get pods -n flux-systemExpected output:
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 2mClone the repo Flux just created locally -- you will add manifests here going forward:
git clone https://github.com/$GITHUB_USER/flux-demo.git
cd flux-demoInside 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:
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: |
/*
!/kustomizeCommit and push:
git add clusters/production/podinfo-source.yaml
git commit -m "feat: add podinfo GitRepository"
git pushWithin a minute (the interval), source-controller notices the change and fetches the repo. Watch the source appear:
flux get sources gitExpected output:
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:
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: defaultField 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 inReconcilinguntil the deployment is ready.targetNamespace-- override the namespace in the source manifests.
git add clusters/production/podinfo-kustomization.yaml
git commit -m "feat: reconcile podinfo"
git pushForce a reconciliation instead of waiting:
flux reconcile source git flux-system
flux reconcile kustomization podinfo --with-sourceExpected output:
► annotating Kustomization podinfo in flux-system namespace
✔ Kustomization annotated
◎ waiting for Kustomization reconciliation
✔ applied revision master@sha1:efgh5678Confirm the app is running:
kubectl get pods -n default -l app=podinfoExpected output:
NAME READY STATUS RESTARTS AGE
podinfo-9f5bd8ff4-2gqzp 1/1 Running 0 30s
podinfo-9f5bd8ff4-xbtvm 1/1 Running 0 30sStep 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:
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 repoCreate the HelmRelease
Create clusters/production/redis-helmrelease.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: 8GiCommit and push:
git add clusters/production/bitnami-repo.yaml clusters/production/redis-helmrelease.yaml
git commit -m "feat: add redis helm release"
git pushForce the reconcile and watch:
flux reconcile kustomization flux-system --with-source
flux get helmreleasesExpected output:
NAME REVISION SUSPENDED READY MESSAGE
redis 19.3.0 False True Release reconciliation succeededhelm 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 viavaluesFromon 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:
flux bootstrap github \
--owner=$GITHUB_USER \
--repository=flux-demo \
--branch=main \
--path=clusters/production \
--personal \
--components-extra=image-reflector-controller,image-automation-controllerConfirm both pods are running:
kubectl get pods -n flux-system | grep imageExpected output:
image-automation-controller-7d9f7c6c5b-abcde 1/1 Running 0 1m
image-reflector-controller-854f4c7c78-fghij 1/1 Running 0 1mDefine an ImageRepository and ImagePolicy
Create clusters/production/podinfo-image.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:
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:
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:
kubectl -n flux-system create secret generic slack-url \
--from-literal=address=https://hooks.slack.com/services/T000/B000/XXXXCreate clusters/production/slack-alerts.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:
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-tokenReference 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:
flux reconcile kustomization podinfo --with-sourceFor 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:
---
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-alphaPoint the tenant Kustomization at its ServiceAccount
tenants/team-alpha/apps.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-alphaBecause 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:
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=trueEnable drift detection
For HelmReleases, set driftDetection.mode: enabled so Flux re-applies when someone edits a Helm-managed resource with kubectl edit:
spec:
driftDetection:
mode: enabledPin 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
| Problem | Cause | Solution |
|---|---|---|
flux bootstrap fails with 401 Bad credentials | PAT missing repo scope or expired | Generate a fresh token with full repo scope (api for GitLab), export it as GITHUB_TOKEN, re-run. |
Kustomization not ready: dependency 'x' is not ready | A dependsOn target has not reconciled | Run flux get kustomizations -A to find the failing dependency. Fix it or remove the dependsOn. |
context deadline exceeded during reconcile | timeout too short or cluster is slow | Raise spec.timeout on the Kustomization (e.g. 10m). Check cluster load with kubectl top nodes. |
HelmRelease stuck InstallFailed | Release has remediation retries exhausted | flux suspend helmrelease redis, fix values, flux resume helmrelease redis. |
| Image automation never commits | Missing # {"$imagepolicy": "..."} marker, or wrong policy name | Verify the marker matches namespace:name. Check flux get images policy -A. |
failed to decode SOPS file | Wrong age key mounted in kustomize-controller | Mount your sops-age secret and reference it from the Kustomization's spec.decryption. |
| Slack alerts silent | Secret key must be named address | Confirm with kubectl -n flux-system get secret slack-url -o yaml. Key must be address, value the full webhook URL. |
| Controllers OOMKilled | Too many large HelmCharts cached in source-controller | Raise memory requests in gotk-components.yaml, or move large charts to OCIRepository pull. |
Useful debug commands
# Full event stream for the flux-system namespace
kubectl get events -n flux-system --sort-by=.lastTimestampPer-controller logs
flux logs --level=error --all-namespacesOne-off reconcile of everything
flux reconcile source git flux-system
flux reconcile kustomization flux-system --with-sourceExport 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.yamlFAQ
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-stackvia a HelmRelease and scrape the Flux controller/metricsendpoints. 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.