Deploying a Sample App via ArgoCD#
What We Built#
A minimal nginx app managed entirely by ArgoCD using Kustomize overlays.
| |
Key Concepts#
The GitOps Loop#
| |
ArgoCD continuously compares desired state (Git) against actual state (cluster). Any drift is automatically corrected because selfHeal: true is set in the sync policy.
We tested this by bumping replicas: 1 → 2, pushing to GitHub, and watching ArgoCD reconcile without any manual intervention.
The Bootstrap Problem#
To sync a private repo, ArgoCD needs credentials - but you can’t store credentials in the repo you’re trying to protect. This is a chicken-and-egg problem every GitOps setup must solve.
Options:
| Approach | Fully GitOps? | Notes |
|---|---|---|
kubectl apply a Secret once, never commit | ~90% | Simplest for learning |
| Sealed Secrets | 100% | Encrypt secret, commit ciphertext |
| SOPS + age | 100% | File-level encryption |
We chose: apply a Kubernetes Secret manually once (bootstrap step). ArgoCD auto-detects any Secret in the argocd namespace labelled argocd.argoproj.io/secret-type: repository.
The secret lives in bootstrap/ which is git-ignored:
| |
Apply it once after a fresh cluster install:
| |
Reconciliation on Non-Manifest Pushes#
ArgoCD watches the entire repo for HEAD changes but only renders and diffs the path specified in the Application (path: config/sample-app/overlays/local). This means:
- Push to
docs/orbacklog.md→ ArgoCD detects HEAD changed → re-renders manifests at the path → sees no diff → does nothing to the cluster - No k8s operations happen, but there is a lightweight reconciliation cycle
There is no native ArgoCD feature to skip reconciliation based on which files changed. The correct solution used in real GitOps setups is repo separation:
| Repo | Contains | Who pushes |
|---|---|---|
app-backend | Source code, docs, tests | Developers |
app-backend-config | Kubernetes manifests only | CI/CD pipeline or humans deploying |
ArgoCD only watches the config repo. A push to the app repo has zero effect on ArgoCD until CI/CD explicitly updates the config repo (e.g. bumps the image tag). This is the standard pattern for production GitOps pipelines.
In this learning repo: the repo is the config repo, so the separation doesn’t apply. Harmless reconciliation cycles on doc/backlog pushes are an acceptable trade-off for simplicity.
Polling vs Webhooks#
ArgoCD polls Git every 3 minutes by default. This is fine for learning but too slow for production.
Faster option - GitHub webhook:
- GitHub pushes a notification to ArgoCD on every commit
- ArgoCD syncs in seconds, not minutes
- Requires ArgoCD to be reachable from the internet (use
ngrokfor local setups)
See backlog: GitHub Webhook for Instant Sync.
ArgoCD Application Manifest#
| |
prune: true - if you delete a file from Git, ArgoCD removes the resource from the cluster.
selfHeal: true - if someone runs kubectl edit directly on the cluster, ArgoCD reverts it to match Git.
Commands Used#
| |
Gotchas#
.idea/committed in first commit - IDE folder slipped in before.gitignorewas created. Added.idea/to.gitignoreand committed a fix.- PAT scope - the GitHub fine-grained PAT only needs
Contents: Read-onlyfor the target repo. No broader permissions required. - HTTPS vs SSH - ArgoCD Application manifests use the HTTPS repo URL. The local git remote can use SSH - they’re independent.