Capstone Project: A Complete GitOps Delivery Pipeline
Why this matters
This is where the whole track becomes one system. You've learned pipeline
anatomy (module 00), GitHub Actions in depth (01), testing that gates a
merge (02), traceable image builds (03), deployment strategies (04), GitOps
with ArgoCD (05), progressive delivery (06), and pipeline security (07) — as
separate pieces. Real delivery is all of them wired together: a pull request
runs tests that block a broken merge; merging to main builds and pushes a
commit-SHA-tagged image to ACR and records that tag in a Git config repo;
and ArgoCD notices the committed change and syncs it to AKS through a
progressive (canary or blue/green) rollout — with no long-lived credentials
anywhere and a human gate protecting production. Building this end to end,
and watching a single code change flow from PR to a metric-gated rollout
without you touching the cluster, is what "you can do CI/CD and GitOps"
actually means.
Treat this as a project, not a checklist of isolated exercises — each piece depends on the ones before it, in the order you'd build them in the real world. There is no solution YAML here; you have every technique you need from modules 00-07.
The project
Build a complete, automated delivery pipeline for a small application, combining push-based CI (build/test/package) with pull-based GitOps CD, on your real AKS cluster and ACR (tracks 07/09).
Use a two-repo structure (module 05): an app repo (source code + CI workflows) and a config/GitOps repo (the Kubernetes manifests ArgoCD watches). The app's CI writes the new image tag into the config repo; ArgoCD delivers it.
The end-to-end flow you're building:
- A developer opens a pull request in the app repo. CI runs unit and
integration tests (module 02); a required status check blocks the
merge if anything fails (module 02, track 08). Broken code cannot reach
main. - On merge to
main, CI builds a container image, tags it with the commit SHA (module 03), authenticates to ACR via OIDC with no stored secret (module 07, track 07 module 08), and pushes it to your Terraform-provisioned ACR (module 03, track 09). - The same workflow then commits the new SHA tag into the deployment
manifest in the config repo (module 05) — CI's job ends at a Git commit;
it never runs
kubectl. - ArgoCD (installed on AKS, module 05), watching the config repo with automated sync, notices the change and deploys it — as an Argo Rollouts canary or blue/green progressive rollout (module 06), metric-gated so a bad version auto-aborts.
- Production is protected (module 07): a human approves before a production deploy (protected environment / required reviewer), and no long-lived cloud credential exists anywhere in the pipeline.
Build it in that order — get each stage green before adding the next, exactly as module 01's exercises taught. Don't wire the canary before the plain GitOps sync works; don't add the manual approval before the happy path flows.
Acceptance checklist
Work through these in order; each depends on the previous ones actually working, not just existing.
- Two repos exist: an app repo (code +
.github/workflows/) and a config repo (Kubernetes/Rollout manifests). Application code and deployment state are cleanly separated (module 05). - A
pull_request-triggered workflow runs unit and integration tests (with a service container for the integration dependency, module 02), caching dependencies keyed on the lockfile. - Branch protection on the app repo's
mainmarks that test workflow as a required status check and requires the branch be up to date; you have proven a PR with a failing test cannot be merged (module 02). - A
push-to-mainworkflow builds the image, tags it with${{ github.sha }}(neverlatest), and pushes it to your ACR using OIDC federation — confirm noAZURE_CREDENTIALS-style long-lived secret exists (modules 03/07). -
az acr repository show-tagsshows the SHA-tagged image in the Terraform-provisioned ACR, and the ACR login server is referenced in the workflow via a variable (from a Terraform output), not hard-coded (module 03, track 09). - The same workflow commits the new SHA tag into the config repo's
manifest and pushes — and the workflow contains no
kubectl/helmcommand against the cluster (module 05). - ArgoCD is installed on AKS with an Application watching the config
repo, automated sync on, and
argocd app getshows itSyncedandHealthyafter a change (module 05). - The deployed workload is an Argo Rollouts
Rollout(canary or blue/green) with at least two/three progressive steps and anAnalysisTemplategating promotion on a real health signal (module 06). - A healthy release, triggered purely by a code change + merge,
flows all the way to a fully-promoted rollout with you running no
docker/az/kubectlcommand by hand — you have watched it happen. - A broken release (compiles, but crashes or returns errors at runtime) is automatically aborted and rolled back by the canary analysis before it reaches all users — you have evidence users saw at most the canary weight's worth of errors (module 06).
- The
productionenvironment is protected with a required reviewer, so a production deploy pauses for human approval (module 07); the OIDC subject is scoped to the branch or, better, the environment. - You can roll back the last deploy purely by
git reverton the config repo (module 05), and you have done it once. - You can explain, for every piece, what is push-based vs. pull-based, where every credential lives and how long it's valid, and what each gate protects against — if you can't explain a piece, that's a signal to go back and understand it, not just copy a command that worked.
Hints
- Build the spine first, decorate later. Get the dumbest possible version working end to end — a plain Deployment synced by ArgoCD from a manually- edited config repo — before adding CI, before the canary, before the approval gate. A working thin pipeline you extend beats a fully-featured one you can't debug (module 01's layer-by-layer lesson).
- The trickiest new integration is CI writing to the config repo. The app repo's workflow needs push access to the config repo — a deploy key or a scoped token, not cluster credentials (module 07's least-privilege point). Compute the SHA tag once and use it both to push the image and to edit the manifest (module 03's single-source-of-tag rule).
- If ArgoCD shows
OutOfSyncforever after a healthy-looking sync, suspect a field-ownership conflict (an HPA vs. a pinnedreplicas) or a manifest error — readargocd app diffand the sync result, not just the status (module 05's diagnose exercise). - If the canary won't progress, first determine why it's waiting: a manual
pause: {}, an analysis still running, or pods notReady/ analysis failing.kubectl argo rollouts get rollout <name>tells you which (module 06's diagnose exercise). Build your analysis against a signal you can deliberately break so you can watch the auto-abort actually fire. - If a deploy authenticates locally but the pipeline gets a blank credential
or an OIDC rejection, check secret/environment scoping and the federated-
credential subject against the run's context (module 07's diagnose
exercise; track 07 module 08's
id-token: write). - Reuse names, patterns, and the OIDC identity you already validated in earlier modules and track 07 rather than inventing new configuration — the goal is integration, not new discovery.
- Keep a running note of every resource and credential you create (ACR, cluster, ArgoCD Application, federated credentials, config-repo deploy key) so cleanup and the security review are checklists, not archaeology.
Cleanup
- Tear down cluster spend deliberately (track 07's cost warning):
az group delete --name <rg> --yes --no-waitfor any resource group you created just for this. An idle AKS cluster keeps billing. - Remove the federated credentials, role assignments, and any config-repo deploy key/token you created, so no standing access outlives the project (module 07).
- Confirm nothing lingers:
az aks list -o tableandaz acr list -o tableacross your subscription; an empty result for anything you made for this capstone is your signal you're no longer billing or exposing it.
Before you move on
Don't consider this finished the moment it's green. Wait a few days, then — with no notes and none of the earlier modules open — rebuild the core of it from memory: a PR that gates a merge on tests, a merge that builds a SHA- tagged image to ACR via OIDC and commits the tag to a config repo, and ArgoCD syncing that change to AKS as a metric-gated canary that auto-aborts a bad release. Rebuilding it cold, and noticing exactly where you stall, is the truest retention check there is — and passing it is what "you can build a GitOps delivery pipeline" actually means. Tear it back down afterward.
One thing to notice about what you built: it delivers whatever image the tests pass, but the tests only check that the code works — nothing here checks that the image is safe. There's no scan for known-vulnerable dependencies or base images, no signature proving the image is the one your pipeline built, and no admission policy stopping a non-compliant image from running on the cluster. Those are exactly the gaps the next track fills.
Next
11-security-deep-dive — this exact pipeline is the starting point. There you add image scanning (catching vulnerable dependencies and base images before they deploy), image signing and verification, and admission policy gates that refuse to run an image that didn't pass — turning "the pipeline that ships working code" into "the pipeline that ships safe, verified, policy-compliant code."