CAMPUX Cloud Bootcamp Advanced Build · GitOps ← All labs
Advanced Build · Advanced
~4–5 hrs · Costs real money (AKS)
CLI · kubectl · Helm · ArgoCD · torn down
Delivery · GitOps

Deploy by merge, not by hand.

In this build the cluster stops taking orders from your laptop and starts taking them from Git. You declare what each environment should look like in a repository; ArgoCD watches that repo and makes the cluster match it — continuously. Promote to production by merging a one-line change, roll back by reverting it, and wire an Azure DevOps pipeline that closes the loop from commit to running pod without anyone touching kubectl apply.

Fig. 1 · GitOps: the cluster matches the repo
Resource group · rg-lab-gitopscontinuous sync · self-healGit repoHelm charts · 3 envsAKS + ArgoCDthe sync agent in-clusterthe cluster matches the repo, not your memory
● Screen walkthrough Not yet recorded · ~13 min
Reel · 00:00 / 13:00

Merge a change, watch ArgoCD sync it to the cluster, then break a deploy on purpose and watch it heal itself back.

Placeholder — the page below stands alone until the reel lands
Why

The cluster should match the repo, not your memory

The old way of shipping to Kubernetes is a person running kubectl apply from a terminal — which means the real state of production lives in whatever commands happened to be run, in what order, by whom. GitOps inverts that: a Git repository becomes the single source of truth for what every environment should contain, and an agent in the cluster — here, ArgoCD — continuously compares the live state to the repo and reconciles any drift. Deploying becomes a pull request. Rolling back becomes a revert. The audit trail writes itself, because every change is a commit.

You'll stand up ArgoCD on AKS, model an application as a Helm chart promoted across dev → qa → prod by nothing more than which values file each environment points at, make a bad deploy roll itself back, and finally hand the trigger to an Azure DevOps pipeline so a commit to main flows all the way to a running pod untouched by human hands.

If it isn't in Git, it isn't in production.

This one costs money — read before you start

ArgoCD runs on a real AKS cluster that bills by the hour — budget $4–8 for a short sitting and do it in one go. The final step deletes everything; set a budget alert first. A single small node pool is plenty for this lab.

Before you begin — prerequisites

You need a paid Azure subscription, the Azure CLI, kubectl, and Helm, plus a Git repository you can push to (GitHub or Azure Repos). This is an advanced build — it assumes you've met AKS, kubectl, and Helm. If not, start with the identity and container labs first.

Setup

A cluster and a repository

Create a small AKS cluster and a Git repository — the two ends of the GitOps loop. The repo is where you'll declare desired state; the cluster is where ArgoCD makes it real.

# Windows/Git Bash: leave resource-id args alone (harmless on macOS/Linux)
export MSYS_NO_PATHCONV=1

RG="campux-gitops-rg"
az group create -n "$RG" -l eastus
az aks create -g "$RG" -n campux-gitops-aks \
  --node-count 2 --node-vm-size Standard_B2s --generate-ssh-keys
az aks get-credentials -g "$RG" -n campux-gitops-aks

# a repo to hold desired state — push an empty one now, fill it in Step 2
git init campux-gitops && cd campux-gitops
git commit --allow-empty -m "root" && git branch -M main
# create it on GitHub/Azure Repos, then: git remote add origin <url> && git push -u origin main
Checkpoint kubectl get nodes shows two Ready nodes, and you have a Git repo ArgoCD can read. Keep the repo URL handy — every ArgoCD Application points at it.
Step 1

Install ArgoCD, the agent in the cluster

ArgoCD installs as a set of controllers in its own namespace. It watches your Git repo and drives the cluster toward what it finds there. Expose the UI just long enough to log in and get the initial password.

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl rollout status deploy/argocd-server -n argocd

# reach the UI locally (no public LoadBalancer needed for a lab)
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
# initial admin password:
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d; echo
Checkpoint Browse to https://localhost:8080 and log in as admin with that password. The UI is empty — no Applications yet. That's the next step: telling ArgoCD what to watch.
Step 2

One Helm chart, three environments

Model the app once as a Helm chart, and let a values file per environment carry the only differences — replica count, image tag, resource limits. Promotion becomes "point prod at the tag dev has been running." Commit this to the repo you made in Setup.

# repo layout
campux-gitops/
  app/
    Chart.yaml
    values.yaml            # defaults
    values-dev.yaml        # image.tag: dev-latest, replicas: 1
    values-qa.yaml         # image.tag: rc-1.4.0,    replicas: 2
    values-prod.yaml       # image.tag: 1.3.0,       replicas: 3
    templates/deployment.yaml
    templates/service.yaml
# app/values-prod.yaml — the only thing that changes between envs
image:
  repository: mcr.microsoft.com/azuredocs/aks-helloworld
  tag: "v1"
replicaCount: 3
resources:
  requests: { cpu: 50m, memory: 64Mi }
Checkpoint The chart lints (helm lint app) and renders (helm template app -f app/values-prod.yaml). You now have one artefact that describes three environments by data, not by copy-paste — the property that makes promotion a one-line diff.
Step 3

Declare an Application per environment

An ArgoCD Application is itself a Kubernetes object: it says "take this path in this repo, render it with this values file, and keep this namespace in sync." Create one per environment. This is GitOps managing GitOps — the Applications can live in the repo too.

kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: campux-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/YOU/campux-gitops.git
    path: app
    targetRevision: main
    helm:
      valueFiles: [values-prod.yaml]
  destination:
    server: https://kubernetes.default.svc
    namespace: prod
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions: [CreateNamespace=true]
EOF
Checkpoint In the ArgoCD UI the campux-prod Application appears and goes Synced / Healthy — the pods exist in the prod namespace because Git said they should. Change replicaCount in values-prod.yaml, commit, and watch ArgoCD reconcile within a minute. You just deployed with a git push.
Step 4

Make a bad deploy roll itself back

Automation is only trustworthy if failure is handled. selfHeal: true already reverts manual drift; add real health checks so ArgoCD knows when a rollout is unhealthy, and it will hold or roll back rather than leave production broken.

# app/templates/deployment.yaml — probes make health real
livenessProbe:  { httpGet: { path: /, port: 80 }, initialDelaySeconds: 5 }
readinessProbe: { httpGet: { path: /, port: 80 }, initialDelaySeconds: 5 }

Now break it on purpose: commit a values change setting image.tag to something that doesn't exist. ArgoCD tries the new ReplicaSet, the pods never become ready, the Application goes Degraded, and the old ReplicaSet keeps serving traffic.

# prove the old pods still serve while the bad one never goes Ready
kubectl get rs -n prod
argocd app rollback campux-prod   # or: git revert the bad commit — the GitOps way
Checkpoint A deliberately-broken image leaves the Application Degraded but the previous version still answering. The honest GitOps rollback is git revert — the repo, not a person, remains the source of truth even in recovery.
Step 5

Close the loop with Azure DevOps

The last piece: a pipeline that turns a commit of application code into a commit of desired state. Azure DevOps builds and pushes the image, then writes the new tag into values-dev.yaml and pushes that — and ArgoCD, watching the repo, syncs it. CI builds the artefact; Git carries the intent; ArgoCD does the deploy. No pipeline ever touches the cluster.

# azure-pipelines.yml (essentials)
trigger: { branches: { include: [main] } }
pool: { vmImage: ubuntu-latest }
steps:
  - task: Docker@2
    inputs: { command: buildAndPush, repository: campux/app, tags: "$(Build.BuildId)" }
  - script: |
      TAG=$(Build.BuildId)
      yq -i ".image.tag = \"$TAG\"" app/values-dev.yaml
      git config user.email [email protected] && git config user.name "Azure DevOps"
      git commit -am "ci: dev image $TAG" && git push origin HEAD:main
    displayName: "Bump dev tag → let ArgoCD sync"
Checkpoint A push to main runs the pipeline, which lands a ci: dev image … commit on the repo; seconds later the campux-dev Application syncs the new tag. You've built the full path — commit to running pod — with the cluster pulling from Git rather than the pipeline pushing to the cluster. Promotion to qa and prod is now just a values change someone reviews and merges.
Down

Tear it down

The cluster bills until it's gone. One resource-group delete removes AKS, ArgoCD, and everything they ran.

az group delete -n campux-gitops-rg --yes --no-wait
az group exists -n campux-gitops-rg      # -> false once the async delete finishes
Checkpoint The group is deleting, so nothing keeps billing. Your Git repo survives — which is the point of GitOps: the desired state is portable, and a fresh cluster pointed at the same repo would rebuild everything ArgoCD just managed.
End

What you can now honestly claim

You ran GitOps on AKS with ArgoCD — a Helm chart promoted across dev, qa, and prod by data alone, automated sync with self-heal, health-check-driven rollback, and an Azure DevOps pipeline that delivers by committing desired state rather than pushing to the cluster. That is "implemented GitOps continuous delivery on Kubernetes with ArgoCD and Azure DevOps" — a senior platform-engineering line, done rather than described. The durable idea travels to Flux, to any cloud, and to your own weekend projects: make the repository the truth, and let an agent keep the world in step with it.

Footnotes
  1. ArgoCD's default polling reconciles roughly every three minutes; for instant syncs, wire a repo webhook to ArgoCD so a push triggers reconciliation immediately rather than on the timer.
  2. Storing the ArgoCD Application manifests in Git too — the "app of apps" pattern — means even your delivery configuration is version-controlled and reviewable, closing the last gap where state could live outside the repo.
  3. Real progressive delivery (true canary with traffic weighting) is Argo Rollouts, a sibling project, layered on top of ArgoCD. This lab uses health checks + self-heal as the honest floor; Rollouts is the next rung when a service needs weighted traffic shifts.