How to Implement GitOps with ArgoCD
Implement GitOps with Argo CD using pinned installation assets, constrained projects, safe automated sync, drift detection, and tested recovery.
GitOps treats a Git repository as the authoritative source of truth for what should be running in a cluster — instead of applying changes with kubectl or helm directly, you commit changes to Git and let a controller reconcile the cluster to match. Argo CD is one of the most widely used tools implementing this pattern.
Step 1: install Argo CD into the cluster
kubectl create namespace argocd
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/VERSION/manifests/install.yaml
Replace VERSION with a reviewed release tag compatible with the cluster. The stable branch is convenient for a lab but mutable; production changes should be reproducible. Review the release notes and manifest diff, then record the selected version in infrastructure code. For a highly available control plane, follow the official HA installation guidance rather than assuming the default manifest is sufficient.
Step 2: access the Argo CD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
Open https://localhost:8080 — the initial admin password is auto-generated and retrievable from a Kubernetes secret:
kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d
Use the initial account only for bootstrap. Configure SSO, RBAC, TLS, and audited access; rotate or remove the initial secret and disable the built-in admin account once recovery access is proven. Exposing the server through an ingress before authentication and certificates are correctly configured creates an avoidable control-plane risk.
Step 3: prepare a Git repository describing your desired cluster state
myapp-gitops-repo/
manifests/
deployment.yaml
service.yaml
This can be plain Kubernetes YAML, a Helm chart, or Kustomize overlays — ArgoCD supports all three as sources of truth.
Step 4: constrain the application with an AppProject
An Application can otherwise point at broad repositories, clusters, and namespaces allowed by the Argo CD installation. Define an AppProject that allowlists the intended source repository and destination, denies cluster-scoped resources unless required, and maps roles to the correct teams. This is a security boundary, not merely a folder for organizing applications.
Step 5: define an Argo CD Application pointing at that repository
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
source:
repoURL: https://github.com/you/myapp-gitops-repo
path: manifests
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
automated.selfHeal: true makes Argo CD reconcile eligible drift from Git. prune: true also deletes resources no longer present in the desired state, so enable it only after reviewing what the Application owns. A bad commit can be reconciled just as efficiently as a good one. Protect the repository, review generated manifests, and consider manual sync for high-risk namespaces.
Step 6: apply the Application resource
kubectl apply -f myapp-application.yaml
Step 7: watch Argo CD sync the cluster to match Git
Argo CD UI → Applications → myapp → observe sync status
The UI shows a diff between live and desired resources and separate sync and health states. Synced does not necessarily mean the application works: health checks, hooks, tests, and external monitoring must verify the customer path. Configure ignore-differences rules narrowly; broad exclusions can hide real drift.
Step 8: deploy a change by committing to Git, not by running kubectl
# edit manifests/deployment.yaml, changing the image tag
git commit -am "bump myapp to v1.1.0"
git push
Argo CD detects the new commit and applies it according to the configured sync policy. Pin container images by digest in the desired state; a mutable tag can change without a Git commit and defeats the audit trail. Use sync waves or hooks only when ordering is truly required, and make every hook idempotent because retries and partial failures happen.
Step 9: roll back by reverting the Git commit
git revert HEAD
git push
Reverting restores declarative Kubernetes resources, but does not undo database writes, external API calls, queues, or destructive migrations. Use backward-compatible migrations and a documented data-recovery procedure. Verify the revert commit actually became the observed revision and run the same health checks used for promotion.
Back up and test the GitOps control plane
Git stores desired state, not every Argo CD setting or credential. Keep declarative Projects, Applications, RBAC, repository configuration, and notifications in protected configuration where possible. Back up required secrets through an approved secrets system and test recovery into a clean cluster. Monitor reconciliation latency, repository errors, expired credentials, orphaned resources, and repeated sync failures.
Avoid giving engineers routine direct production write access while claiming Git is authoritative. Preserve a break-glass path for incidents, make it time-limited and audited, and require any emergency live change to be represented or reverted in Git immediately afterward.
Why “Git as source of truth” is a meaningfully different model, not just automation
Traditional CI/CD pipelines push changes to a cluster by running commands against it; GitOps inverts this — a controller running inside the cluster continuously pulls from Git and reconciles differences. This means every change to production has an inherent audit trail (Git history), rollback is a Git operation rather than a separate manual procedure, and drift — someone making an unreviewed manual change directly against the cluster — gets automatically detected and corrected rather than silently persisting until someone happens to notice.
Related:
- How to Write a Helm Chart from Scratch
- Helm Architecture and Release Lifecycle: Charts, Values, Hooks, Rollbacks, and Supply Chain
Sources: