Git as the single source of truth for Kubernetes: the four OpenGitOps principles, Argo CD and Flux CD command references, repo patterns, and a secrets + safety guide.
The four GitOps principles
The vendor-neutral standard from OpenGitOps (v1.0.0). Any tool that calls itself GitOps should satisfy all four.
1. Declarative
The entire system's desired state is expressed declaratively — what you want, not the steps to get there. YAML manifests, Helm charts, Kustomize overlays.
2. Versioned and Immutable
Desired state is stored so that it is immutable, versioned, and keeps a complete history. Git is the canonical store; every change is a commit.
3. Pulled Automatically
Software agents automatically pull the desired state from the source. The cluster reaches out to Git — nothing outside pushes changes in.
4. Continuously Reconciled
Agents continuously observe actual state and apply the desired state, correcting drift on their own without a human running a deploy.
Push CD vs pull-based GitOps
Push (classic CI/CD)
The pipeline holds cluster credentials and applies changes from outside. Simple, but the pipeline is a standing set of prod keys, and the cluster can silently drift from what CI last ran.
Pull (GitOps)
An agent inside the cluster pulls from Git. No external system needs cluster write-credentials, drift is auto-corrected, and the running state always maps to a Git commit.
Why pull is safer
Prod credentials never leave the cluster. A leaked CI token can't touch prod. This credential-boundary win is the main security argument for GitOps.
Rollback
Reverting a deploy is git revert plus a merge. The agent converges the cluster back to the previous commit automatically.
kubectl apply drift.Install into the cluster
Reach the UI / API and log in
Change the admin password immediately, then treat the initial-password secret as spent.
Point an app at a Git path and sync
| Task | Command | Notes |
|---|---|---|
| List all applications | argocd app list | Shows sync status (Synced/OutOfSync) and health for every app. |
| Show one app + live diff | argocd app get guestbook | The status line tells you whether the cluster matches Git. |
| Manual sync | argocd app sync guestbook | Applies Git state now. With auto-sync on, Argo does this for you. |
| Preview the diff only | argocd app diff guestbook | Desired (Git) vs live (cluster). Run before a manual sync. |
| Roll back to a previous sync | argocd app rollback guestbook <revision> | Reverts the cluster; the durable rollback is still a git revert + merge. |
| Enable auto-sync + self-heal | argocd app set guestbook --sync-policy automated --self-heal --auto-prune | Self-heal reverts manual cluster edits; auto-prune deletes resources removed from Git. |
| Watch history | argocd app history guestbook | Every sync, with its Git revision. |
| Delete an app | argocd app delete guestbook | Add --cascade=false to keep the deployed resources. |
| Refresh from Git now | argocd app get guestbook --refresh | Forces a re-check instead of waiting for the ~3-minute poll. |
| Register another cluster | argocd cluster add <context> | One Argo CD can manage many clusters. |
| Create a Project (tenant boundary) | argocd proj create team-a | Projects restrict which repos, clusters, and namespaces apps may use. |
Check prerequisites
Bootstrap against your Git repo
Creates the repo if needed, commits Flux's manifests under that path, installs the controllers, and points them at that path.
Add a source and a kustomization
| Task | Command | Notes |
|---|---|---|
| Check a running install | flux check | Verifies controllers are healthy after bootstrap. |
| List kustomizations + status | flux get kustomizations | Ready column shows whether each is reconciled; add --watch to follow live. |
| List Git sources | flux get sources git | Shows which revision each source is pinned to. |
| Force a reconcile now | flux reconcile kustomization podinfo --with-source | Skips the interval wait; --with-source pulls Git first. |
| Pause reconciliation | flux suspend kustomization podinfo | Freezes a workload — the safe way to stop auto-updates during an incident. |
| Resume reconciliation | flux resume kustomization podinfo | Un-pauses; Flux catches up to Git. |
| Explain why something failed | flux logs --level=error --all-namespaces | The controllers' own logs — first stop when a sync won't go Ready. |
| Diff a kustomization vs cluster | flux diff kustomization podinfo --path ./kustomize | Local preview of what a reconcile would change. |
| Export a resource as YAML | flux create source git podinfo ... --export | Prints YAML to commit to Git instead of applying imperatively. |
| See everything Flux manages | flux get all --all-namespaces | Sources, kustomizations, helm releases, alerts in one view. |
| Uninstall Flux | flux uninstall | Removes controllers; your Git repo is untouched. |
Repository structure
Separate what changes for different reasons. The most common split is an app-source repo (code + Dockerfile) and a config repo (the manifests GitOps watches).
Separate config repo
Keep manifests out of the app repo so a CI image build doesn't churn deploy history, and so read access to config doesn't mean read access to source.
Base + overlays
Kustomize (or Helm values) express one base and per-environment overlays. Promotion = change the image tag in an overlay via PR.
Folder-per-cluster
Each cluster maps to a directory the agent is pointed at, so adding a cluster is a folder, not a new tool.
Promotion by PR
Move a change from staging to prod by opening a PR that bumps the prod overlay. Review and merge is the deploy.
Core patterns
dependsOn between kustomizations.Terminology
| Term | What it means |
|---|---|
| Desired state | The declarative spec of what should be running, stored in Git. |
| Live / actual state | What is really running in the cluster right now. |
| Reconciliation | The agent's loop that compares desired vs live and applies the difference. |
| Drift | When live state diverges from Git — a manual kubectl edit, a crash, an operator change. |
| Self-heal | Auto-reverting drift back to Git without a human (Argo selfHeal; Flux corrects on each reconcile). |
| Sync / apply | Pushing the Git-defined state into the cluster. |
| Prune | Deleting cluster resources that were removed from Git. Off by default — enable deliberately. |
| Source | A Git (or Helm/OCI) repository a tool watches. Flux models this as a GitRepository. |
| Application (Argo) | An Argo CRD linking a repo path to a destination cluster/namespace. |
| Kustomization (Flux) | A Flux CRD that builds a path from a source and applies it on an interval. |
| Rendered manifests | The final YAML after Kustomize/Helm templating, which is what actually gets applied. |
| Reconcile interval | How often the agent re-checks (Flux per-resource; Argo polls Git ~3 min, or instantly via webhook). |
| App-of-Apps | A parent app whose children are more apps — bootstraps a cluster from one root. |
| Bootstrap | Flux's install step that also commits Flux's own config to Git so it self-manages. |
- The core tension: GitOps wants everything in Git, but a secret in Git is in history forever, readable by anyone who ever clones the repo. Plain Kubernetes
SecretYAML is only base64 — that is not encryption. - Sealed Secrets (Bitnami): encrypt a secret with a cluster-side key so only that cluster can decrypt it. Simplest "just commit it and the controller handles it" option. Back up the controller's private key offline — lose it and every sealed secret is unrecoverable.
- SOPS (+ age/GPG/KMS): encrypts values inside YAML while keys stay readable. Git-native, works for non-Kubernetes files too, integrates with cloud KMS. Both Flux and Argo can decrypt SOPS at apply time.
- External Secrets Operator: don't store the secret at all — store a reference and pull the real value from Vault / AWS Secrets Manager / etc. Best for many clusters and automatic rotation.
- Rule of thumb: Sealed Secrets for a simple single cluster, SOPS for small teams already using KMS, ESO for enterprise/multi-cluster with rotation.
- Protect the config repo like production — it is production. Require PR review, branch protection, and signed commits on the branch the agent tracks. Merge access = deploy access.
- Turn on auto-prune deliberately. Pruning deletes cluster resources removed from Git. Powerful for keeping state clean, dangerous if someone deletes the wrong file. Test in staging first.
- Self-heal will fight your hotfix. With self-heal on, a manual
kubectl editin prod is reverted within minutes. During an incident,suspend/pause the app first, fix, then land the fix in Git and resume. - Scope the agent's RBAC. The in-cluster agent is highly privileged. Use Argo Projects / per-tenant service accounts so one team's app can't deploy into another's namespace.
- Pin sources. Track a branch or a tag you control, not someone else's
main. For third-party charts, pin a version and mirror it. - Least privilege on the pull side. Give the agent a read-only deploy key / token scoped to the config repo only.
Best practices
kubectl, it will be reverted — put it in Git instead.Official docs & further reading
Primary sources. GitOps tooling moves fast — when this page and the docs disagree, the docs win.