By the end of this lab, you'll be able to:
- Switch the image tag from
$(Build.BuildId)tolatest— enabling the pipeline to always use the most recent build - Add
imagePullPolicy: Always— forcing AKS to pull the latest image on every pod restart or rolling update - Understand zero-downtime rolling updates — how Kubernetes replaces pods without service interruption
- Verify the automated deployment — by observing new pods running the
latestimage after a pipeline run
⏱️ Estimated Time: ~15 minutes
Before starting, ensure you have:
kubectlinstalled and configured (AKS credentials from Lab 4)- Completed Lab 5.1 — CI/CD Trigger
- Application successfully deployed to AKS (Lab 4)
In Lab 4, the app.yaml had a hardcoded image tag:
image: devopsjourneyapr2026acr.azurecr.io/repository:626Problems with this approach:
- Every new pipeline run produces a new tag (
627,628, ...) but the manifest still references626 - To update the running pods, you had to manually delete the Deployment and re-run the pipeline
- No zero-downtime rolling update — the old pods are torn down before new ones are ready
- Not truly automated — human intervention required for every deployment
-
📝 Open the Kubernetes manifest
-
✏️ Update the image reference
Change:
image: devopsjourneyapr2026acr.azurecr.io/repository:626
To:
image: devopsjourneyapr2026acr.azurecr.io/repository:latest imagePullPolicy: Always
imagePullPolicyoptions explained:Value Behaviour IfNotPresentOnly pull if not cached locally — may run stale images AlwaysAlways query the registry and pull if the digest has changed — use for CI/CD NeverNever pull — only uses locally cached images; fails if not cached 💡
imagePullPolicy: Alwayscombined with thelatesttag ensures every pod restart pulls the newest image from ACR. This enables truly automated rolling updates — no manifest changes needed per pipeline run.
-
📝 Open the pipeline YAML
-
✏️ Change the Docker build tag
Find the Docker task and change the
tagsparameter:From:
tags: $(Build.BuildId)
To:
tags: 'latest'
This tells the
Docker@2task to push the image to ACR with thelatesttag, which is what theapp.yamlnow references.
-
💾 Commit all changes
git add pipelines/lab5pipeline.yaml pipelines/scripts/app.yaml git commit -m "Switch to latest tag with imagePullPolicy Always for automated CI/CD" git push origin main -
⚡ The CI trigger fires automatically
The push to
maintriggers the pipeline (from Lab 5.1). Watch the pipeline run in Azure DevOps. -
🔍 Verify the
latesttag in ACRAfter the Build stage completes:
az acr repository show-tags \ --name devopsjourneyapr2026acr \ --repository repository \ --orderby time_desc \ --top 3 -o table
✅ Expected Output:
Result ------ latest -
🔍 Verify new pods are running the
latestimageAfter the Deploy stage completes:
kubectl describe pod \ $(kubectl get pods -n thomasthorntoncloud -o jsonpath='{.items[0].metadata.name}') \ -n thomasthorntoncloud \ | grep Image:
✅ Expected Output:
Image: devopsjourneyapr2026acr.azurecr.io/repository:latest
Deployment checklist:
app.yamluseslatesttag andimagePullPolicy: Always- Pipeline YAML uses
tags: 'latest' - ACR shows
latesttag after pipeline run - AKS pods show
repository:latestimage - Application still responds correctly via ALB FQDN
Full validation script:
#!/bin/bash
echo "=== Checking ACR for latest tag ==="
az acr repository show-tags \
--name devopsjourneyapr2026acr \
--repository repository \
--orderby time_desc --top 3 -o table
echo ""
echo "=== Checking AKS pod image ==="
POD=$(kubectl get pods -n thomasthorntoncloud -o jsonpath='{.items[0].metadata.name}')
kubectl describe pod "$POD" -n thomasthorntoncloud | grep "Image:"
echo ""
echo "=== Testing application availability ==="
FQDN=$(kubectl get gateway gateway-01 -n thomasthorntoncloud \
-o jsonpath='{.status.addresses[0].value}')
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://$FQDN")
echo "HTTP Status: $HTTP_CODE"
[ "$HTTP_CODE" = "200" ] && echo "✅ Application is healthy" || echo "❌ Application check failed"🔧 Troubleshooting (click to expand)
Common issues:
# Problem: Pods still running the old image tag after pipeline runs
# Solution: Verify imagePullPolicy is set to Always in app.yaml
kubectl get deployment -n thomasthorntoncloud -o yaml | grep -A2 imagePullPolicy
# Problem: ACR still shows old tag, not "latest"
# Solution: Confirm the pipeline tag was updated to 'latest' (not $(Build.BuildId))
grep "tags:" pipelines/lab5pipeline.yaml
# Problem: Rolling update causes brief downtime
# Solution: Ensure the Deployment has a readiness probe configured
# With readiness probes, Kubernetes waits for new pods to be ready before terminating old ones
kubectl describe deployment -n thomasthorntoncloud | grep -A5 "Readiness"
# Problem: "ErrImagePull" in pods after tag change
# Solution: Verify the WIF service principal has AcrPull role
az role assignment list \
--scope "$(az acr show --name devopsjourneyapr2026acr --query id -o tsv)" \
--query "[].{Principal:principalName,Role:roleDefinitionName}" -o table- Switching from
$(Build.BuildId)tolatestmeans the manifest never needs to change per build. WithimagePullPolicy: Always, AKS pulls the updated image from ACR every time a pod is restarted, enabling truly automated deployments. IfNotPresentwithlatestserves stale images — Kubernetes reuses the cached version even though ACR has a newer image under the same tag.Alwaysbypasses the local cache.- Rolling update process: (1) Create new pods with updated spec; (2) wait for readiness probe to pass; (3) terminate old pods; (4) repeat until all replicas are replaced — no traffic interruption.
latestis convenient for CI/CD but reduces traceability. Explicit version tags are better for production: you know exactly which build is running and can roll back by changing the tag. A common pattern is to uselatestin CI and promote explicit tags to production.
You now have a fully automated CI/CD pipeline — every push to main builds, tags, and deploys your application to AKS with zero manual steps. In the next lab you'll add observability by connecting Application Insights to your running application.
← Back to Lab 5.1 | Continue to Lab 6 →