Push to ACR with a managed-identity pull, deploy to Container Apps, then watch scale-to-zero wake on traffic.
The 90% of container work that isn't AKS
Job descriptions love the word Kubernetes, and AKS is a real skill — but most container workloads are a web service or an API that needs to run, scale with traffic, and cost nothing when nobody is calling it. Reaching for a full AKS cluster there means paying for control-plane nodes and owning cluster upgrades for a job a managed platform does better. Azure Container Apps is that platform: you hand it an image, it gives you HTTPS, autoscaling, and — the detail that makes it lovable for side projects and real services alike — scale-to-zero, so an idle app runs no replicas and bills accordingly. This lab runs a container the professional way: the image lives in your own Azure Container Registry, and the app pulls it using a managed identity with the AcrPull role, so there is no registry admin password to leak.
You will create a registry with its admin account switched off, import an image, grant a managed identity pull rights, deploy the app scaled to zero, and confirm it serves traffic — all without a single stored credential.
Scale-to-zero is the feature that makes "always deployed" and "costs nothing idle" the same sentence.
You need a free Azure account, the Azure CLI (az), then be signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs (winget / brew / apt), and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash), preinstalled and already signed in.
Azure Cloud Shell (Bash); source in github.com/kloudcaptain/campux-labs under lab-container-apps-acr. A Basic registry is a few pennies a day and the Container App scales to zero, so the lab is effectively free if torn down. This lab does provision a registry, so run the teardown.
A registry with no admin password
Create the registry with the admin account disabled — the whole point is that access comes from identity, not a shared password — and import a ready-made sample image so there is something to run.
# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux) export MSYS_NO_PATHCONV=1 RG="campux-lab-aca-rg" az group create -n "$RG" -l eastus ACR="campuxacr$RANDOM" az acr create -n "$ACR" -g "$RG" --sku Basic --admin-enabled false # import Microsoft's Container Apps sample image into your registry az acr import -n "$ACR" \ --source mcr.microsoft.com/k8se/quickstart:latest \ --image quickstart:v1 ACR_ID=$(az acr show -n "$ACR" -g "$RG" --query id -o tsv)
adminUserEnabled: false (confirm with az acr show -n "$ACR" -g "$RG" --query adminUserEnabled) and holds quickstart:v1. There is deliberately no password to copy — access must come from an identity, which you create next.An identity that may pull
Create a user-assigned managed identity and grant it exactly one right on the registry: AcrPull. Least privilege — it can pull images and do nothing else.
az identity create -n campux-aca-id -g "$RG" ID_ID=$(az identity show -n campux-aca-id -g "$RG" --query id -o tsv) ID_PRINCIPAL=$(az identity show -n campux-aca-id -g "$RG" --query principalId -o tsv) az role assignment create \ --assignee-object-id "$ID_PRINCIPAL" \ --assignee-principal-type ServicePrincipal \ --role AcrPull \ --scope "$ACR_ID"
"roleDefinitionName": "AcrPull" scoped to your registry. This identity is now the only thing that needs to be trusted to fetch the image — and it holds no secret you could accidentally commit. (We pass --assignee-object-id with --assignee-principal-type ServicePrincipal rather than --assignee: a brand-new managed identity takes a moment to appear in the Entra graph, and the name-resolving --assignee form would fail with "Cannot find … in graph database" until it does. Passing the object id and type skips that lookup.)Deploy, scaled to zero
Create the Container Apps environment, then the app — telling it to pull from your registry using the managed identity and to scale between zero and three replicas.
az containerapp env create -n campux-aca-env -g "$RG" -l eastus az containerapp create -n campux-quickstart -g "$RG" \ --environment campux-aca-env \ --image "$ACR.azurecr.io/quickstart:v1" \ --registry-server "$ACR.azurecr.io" \ --registry-identity "$ID_ID" \ --user-assigned "$ID_ID" \ --ingress external --target-port 80 \ --min-replicas 0 --max-replicas 3 FQDN=$(az containerapp show -n campux-quickstart -g "$RG" \ --query properties.configuration.ingress.fqdn -o tsv) echo "https://$FQDN"
Prove it serves — and scales to zero
Call the app, then read back the scale rule that lets it cost nothing idle.
curl -s -o /dev/null -w "app: %{http_code}\n" "https://$FQDN"
az containerapp show -n campux-quickstart -g "$RG" \
--query "properties.template.scale.{min:minReplicas, max:maxReplicas}" -o table
app: 200 — your container, pulled by identity, is serving. The scale query shows min: 0, which means once traffic stops the platform drains it to zero replicas and you pay for none; the next request pays a brief cold start to wake it. That trade — a little latency for near-zero idle cost — is exactly why Container Apps beats a standing AKS cluster for spiky or low-traffic workloads.Tear it down
One resource group holds the registry, identity, environment, and app. Delete it.
az group delete -n campux-lab-aca-rg --yes
az group exists -n campux-lab-aca-rg # -> false
az group exists returns false. The Basic registry was the only thing billing while idle, and it is now gone.What you can now honestly claim
You published a container image to Azure Container Registry, ran it on Azure Container Apps with a user-assigned managed identity for the pull, kept the registry's admin account disabled the whole time, and configured scale-to-zero. That is credential-free container delivery on a managed platform — the pragmatic answer to "experience with containers and orchestration" that most workloads actually call for, and a sober counterpoint you can offer in an interview to "did you just reach for AKS?". When a workload genuinely needs cluster-level control, AKS is there; knowing when it does not is the senior judgement.
- Disabling the ACR admin account and pulling with a managed identity is the recommended production pattern: the admin account is a single shared username and password with full registry rights, exactly the kind of long-lived credential that leaks. An identity with only
AcrPullcan be scoped, audited, and revoked without rotating a password everyone shares. - Scale-to-zero suits APIs and web apps that tolerate an occasional cold start. For latency-sensitive or always-warm workloads set
--min-replicas 1and accept the standing cost; the point of the lab is that the choice is yours per app, not a platform default you cannot change.