GitOps & Continuous Delivery
GitOps flips the traditional deployment model on its head. Instead of a CI job pushing changes into the cluster with kubectl apply, a controller running inside the cluster continuously pulls the desired state from Git and reconciles the live cluster to match it. Git becomes the single source of truth: every change is a commit, every rollback is a git revert, and the cluster is guaranteed to converge on whatever the repository declares. This is how teams safely operate dozens of clusters and hundreds of services without handing production credentials to their CI system.
- Explain the four core GitOps principles
- Define ArgoCD Application and ApplicationSet manifests
- Structure environments with Kustomize overlays and the App-of-Apps pattern
- Wire a CI pipeline that updates manifests for automated delivery
- Roll back safely using Git history
The Four GitOps Principles
The OpenGitOps working group defines GitOps with four principles. A system is only "GitOps" if it satisfies all of them:
- Declarative — the entire desired state is described declaratively (Kubernetes manifests, Helm values, Kustomize overlays), never as imperative scripts.
- Versioned & immutable — that state is stored in Git, giving you a complete, immutable, auditable history.
- Pulled automatically — software agents automatically pull the declared state from the source.
- Continuously reconciled — agents continuously observe live state and reconcile any drift back to the declared state.
Note
Defining an ArgoCD Application
ArgoCD models each deployable unit as an Application custom resource that points at a repo path and a destination cluster/namespace. The syncPolicy decides whether ArgoCD merely reports drift or actively corrects it.
yamlapiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: myapp namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: https://github.com/org/k8s-manifests path: apps/myapp/overlays/production targetRevision: main destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true # delete resources removed from Git selfHeal: true # revert manual kubectl changes syncOptions: - CreateNamespace=true
Warning
prune: true deletes any live resource that no longer exists in Git. Combined with selfHeal: true, a manual kubectl edit on production will be silently reverted within minutes — which is the point, but it surprises people the first time.Sync waves and hooks
Ordering matters when a database migration must run before the app rolls out. ArgoCD uses annotations to control ordering (sync-wave, lower runs first) and lifecycle hooks (PreSync, Sync, PostSync).
yamlapiVersion: batch/v1 kind: Job metadata: name: db-migrate annotations: argocd.argoproj.io/hook: PreSync argocd.argoproj.io/hook-delete-policy: HookSucceeded argocd.argoproj.io/sync-wave: "-1" spec: template: spec: restartPolicy: Never containers: - name: migrate image: ghcr.io/org/myapp:1.4.2 command: ["npm", "run", "migrate"]
ArgoCD vs Flux
Both are CNCF-graduated GitOps controllers. ArgoCD ships a rich web UI and models apps as CRDs; Flux is a set of composable controllers driven entirely by manifests and integrates tightly with Kustomize and Helm.
Pro Tip
Structuring Environments
Keep one base and per-environment overlays with Kustomize. The overlay only expresses the difference from the base (replica count, image tag, resource limits), which keeps promotions to a tiny, reviewable diff.
bashapps/myapp/ ├── base/ │ ├── deployment.yaml │ ├── service.yaml │ └── kustomization.yaml └── overlays/ ├── staging/ │ └── kustomization.yaml └── production/ └── kustomization.yaml
yaml# overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base images: - name: ghcr.io/org/myapp newTag: 1.4.2 replicas: - name: myapp count: 5
The App-of-Apps pattern
Rather than registering every Application by hand, use ApplicationSet to generate one Application per environment from a Git directory. Add a new folder, and a new Application appears automatically.
yamlapiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: myapp-envs namespace: argocd spec: generators: - git: repoURL: https://github.com/org/k8s-manifests revision: main directories: - path: apps/myapp/overlays/* template: metadata: name: 'myapp-{{path.basename}}' spec: project: default source: repoURL: https://github.com/org/k8s-manifests targetRevision: main path: '{{path}}' destination: server: https://kubernetes.default.svc namespace: '{{path.basename}}' syncPolicy: automated: { prune: true, selfHeal: true }
Wiring CI to GitOps CD
The pipeline splits cleanly in two. CI builds and pushes an immutable image, then bumps the image tag in the manifest repo. CD (ArgoCD/Flux) notices the commit and deploys. CI never touches the cluster.
yamlname: build-and-promote on: push: branches: [main] jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Build & push image run: | IMAGE=ghcr.io/org/myapp:${GITHUB_SHA::7} docker build -t "$IMAGE" . echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin docker push "$IMAGE" - name: Bump tag in manifests repo run: | git clone https://x:${{ secrets.MANIFESTS_TOKEN }}@github.com/org/k8s-manifests.git cd k8s-manifests/apps/myapp/overlays/staging kustomize edit set image ghcr.io/org/myapp=ghcr.io/org/myapp:${GITHUB_SHA::7} git -C ../../../.. commit -am "deploy myapp ${GITHUB_SHA::7} to staging" git -C ../../../.. push
Note
Promotion and Rollback
Promote from staging to production by copying the tested tag into the production overlay — usually a pull request that a reviewer approves. Because the whole state lives in Git, rolling back is just reverting the commit that caused the incident:
bash# Roll production back to the previous known-good state git revert <bad-commit-sha> git push # ArgoCD detects the new HEAD and re-syncs production automatically.
Pro Tip
Common Pitfalls
Warning
Wrapping Up
You have now taken the Kubernetes fundamentals course from a single pod all the way to fully automated, Git-driven delivery. With declarative manifests, an in-cluster reconciler, environment overlays, and CI that only ever touches Git, you have a production-grade deployment platform where every change is reviewed, audited, and instantly reversible.