GitOps on Kubernetes with ArgoCD: Declarative Deploys, Auto-Sync, and One-Click Rollbacks
Run ArgoCD on Kubernetes, manage multi-environment deployments with ApplicationSet, automate drift detection, and handle rollbacks correctly for controller-managed applications.
What You'll Build
By the end of this tutorial, ArgoCD will be running on your cluster, syncing two environments (staging and production) from a single GitHub repo via ApplicationSet, with automated drift detection and a tested rollback path.
Prerequisites
- A running Kubernetes cluster with cluster-admin. k3d, kind, EKS, GKE, and AKS all work.
kubectl1.25+ configured against that cluster.- ArgoCD CLI 2.8+. On macOS:
brew install argocd. On Linux, grab the binary from GitHub releases and put it on yourPATH. - A GitHub repo containing Kubernetes manifests under
manifests/staging/andmanifests/production/. Each directory needs at minimum aDeploymentand aService.
Commands below were validated against ArgoCD 2.9.
1. Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Wait for the rollout:
kubectl -n argocd wait --for=condition=available deployment --all --timeout=120s
Grab the initial admin password (flag introduced in v2.4; on older versions use kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d):
argocd admin initial-password -n argocd
Port-forward the server and log in. The tunnel needs a moment to bind, so add a short sleep before the login command:
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
sleep 5
argocd login localhost:8080 --insecure --username admin --password <your-password>
--insecure skips TLS verification, which is fine for a local port-forward. In production, terminate TLS at an ingress or load balancer and drop that flag.
2. Connect Your GitHub Repo
Public repos need no credentials. For a private repo, add a fine-grained personal access token with Contents: Read permission:
argocd repo add https://github.com/yourorg/yourrepo \
--username git \
--password <your-github-token>
Verify the connection:
argocd repo list
The CONNECTION STATUS column should read Successful. If it shows Failed, the token scope is wrong or the URL has a mismatch (SSH vs HTTPS, trailing slash). See Troubleshooting below.
3. Deploy with Auto-Sync
Create an Application pointing at your staging manifests. Do this declaratively so ArgoCD's own config lives in Git:
# argocd-apps/staging.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-staging
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/yourrepo
targetRevision: HEAD
path: manifests/staging
destination:
server: https://kubernetes.default.svc
namespace: staging
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
kubectl apply -f argocd-apps/staging.yaml
prune: true deletes resources removed from Git. selfHeal: true reconciles out-of-band changes, like someone running kubectl edit directly on the cluster. Both should be on for a real GitOps flow. ArgoCD polls your repo every 3 minutes by default; configure a GitHub webhook pointing at https://<argocd-server>/api/webhook to cut that to near-zero latency.
4. Multi-Environment Promotion with ApplicationSet
Maintaining a separate Application manifest per environment doesn't scale. ApplicationSet (part of ArgoCD core since v2.3) generates Application resources from a template plus a parameter list.
Before applying the ApplicationSet, delete the staging Application you created in step 3. The ApplicationSet controller can't adopt an existing Application it doesn't own, so leaving it in place will cause a conflict:
kubectl delete app my-app-staging -n argocd
Now apply the ApplicationSet:
# argocd-apps/appset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app
namespace: argocd
spec:
generators:
- list:
elements:
- env: staging
revision: HEAD
- env: production
revision: v1.2.0
template:
metadata:
name: "my-app-{{env}}"
spec:
project: default
source:
repoURL: https://github.com/yourorg/yourrepo
targetRevision: "{{revision}}"
path: "manifests/{{env}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{env}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
kubectl apply -f argocd-apps/appset.yaml
Staging always tracks HEAD. Production is pinned to the git tag v1.2.0. Promoting to production means cutting a new tag and updating the revision field in this file. Commit that change, and ArgoCD handles the rest. The revision separation is exactly what prevents staging changes from silently rolling into production.
For more realistic setups, replace the flat manifest directories with Kustomize overlays. ArgoCD's source block supports Kustomize natively with no extra config.
5. Rollback
You can inspect deployment history for any ArgoCD-managed application:
argocd app history my-app-production
ID DATE REVISION
0 2024-01-15 09:23:11 +0000 UTC v1.1.0
1 2024-01-20 14:05:44 +0000 UTC v1.2.0
Here's the catch with ApplicationSet-managed applications: the ApplicationSet controller continuously reconciles every Application it owns. If you ran argocd app rollback my-app-production 0, the controller would immediately overwrite your change and push the application back to v1.2.0. The CLI rollback command simply doesn't work in this setup.
The correct approach is to update the revision in the ApplicationSet manifest itself. Edit appset.yaml, change the production element's revision from v1.2.0 to v1.1.0:
# appset.yaml — production element after edit
- env: production
revision: v1.1.0
Then apply (or better, commit and push if ArgoCD is also managing its own config):
kubectl apply -f argocd-apps/appset.yaml
The ApplicationSet controller picks up the new revision and updates the generated Application. ArgoCD syncs it immediately. This change belongs in a commit, not a one-off kubectl apply: it gives you a clear audit trail and keeps the repo as the actual source of truth.
Once you understand the root cause, fix forward in Git. A rollback is just a deploy of an older known-good version, and it should go through the same change process as any other deploy.
Verify It Works
Push a change to a manifest in manifests/staging/ and either wait up to 3 minutes or force a sync:
argocd app sync my-app-staging
Expected output:
TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH
2024-01-20T14:00:00+00:00 apps Deployment staging my-app Synced Healthy
Check both environments at once:
argocd app list
Both apps should show Synced and Healthy. OutOfSync means Git has diverged from the cluster state (expected briefly after a push). Degraded means the workload itself is unhealthy, which is a signal to check your pods, not ArgoCD.
Troubleshooting
ComparisonError: failed to get cluster info
ArgoCD can't reach the destination cluster. For in-cluster deployments, https://kubernetes.default.svc is always correct. For remote clusters, register them first with argocd cluster add <kubectl-context-name> and use the URL ArgoCD assigns.
Sync stuck in Progressing / OutOfSync after applying
Something in the cluster isn't becoming Healthy: a pending PVC, a failing readiness probe, an image that won't pull. Run argocd app get <name> --show-operation for the full resource diff, then kubectl describe on the stuck resource.
permission denied applying resources to a namespace
The default ArgoCD project whitelists all destination namespaces. If you created a custom project, check its allowed destinations. The ArgoCD service account also needs RBAC permission in the target namespace; CreateNamespace=true in syncOptions only creates the namespace, it doesn't grant permissions.
Repo shows Unknown or Failed connection status
Token is misconfigured or the repo URL doesn't match exactly. Run argocd repo get https://github.com/yourorg/yourrepo to see the raw error. Remove and re-add with argocd repo rm <url> followed by argocd repo add.
Next Steps
- Secrets: ArgoCD deliberately doesn't manage secrets. Pair it with External Secrets Operator or Sealed Secrets to keep credentials out of Git while staying declarative.
- Notifications: ArgoCD Notifications (built into v2.4+) posts Slack messages or fires webhooks on sync failures, health changes, or degraded status.
- Progressive delivery: Argo Rollouts integrates directly with ArgoCD to add canary and blue-green strategies, driven by the same GitOps commit flow.
- Multi-cluster at scale: Swap the
listgenerator in yourApplicationSetfor theclustergenerator to fan deployments out across every registered cluster automatically.
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 0
No comments yet
Be the first to weigh in.