CAMPUX Cloud Bootcamp Lab · Compute · Containers ← All labs
Hands-On Lab · Intermediate
~35 min · Scale-to-zero · Cloud Shell
ACR & Container Apps · torn down
Compute · Containers

Containers without the AKS bill.

Not every workload needs a Kubernetes cluster and the salary of the person who runs it. Azure Container Apps runs your container, scales it to zero when idle, and pulls the image from your registry with a managed identity — no admin password anywhere. The container pattern the job wants, at a cost that rounds to nothing.

Fig. 1 · Container Apps + ACR, no admin password
Resource group · rg-lab-containerappspull image · via managed identityContainer Appscales to zeroManaged identityrole: AcrPullContainer Registryadmin password: OFFno admin password; identity earns the pull
● Screen walkthrough Not yet recorded · ~10 min
Reel · 00:00 / 10:00

Push to ACR with a managed-identity pull, deploy to Container Apps, then watch scale-to-zero wake on traffic.

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

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.

Before you begin — one-time setup

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.

What you need & what it costs

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.

Setup

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)
Checkpoint The registry exists with 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.
Step 1

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"
Checkpoint The role assignment returns JSON with "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.)
Step 2

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"
Checkpoint The app is created and prints an FQDN. That the image pull succeeded at all proves the managed identity worked — a registry with no admin account could not have been reached any other way. If the pull had failed, the revision would be unhealthy; instead it is running.
Step 3

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
Checkpoint The curl returns 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.
Down

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
Checkpoint az group exists returns false. The Basic registry was the only thing billing while idle, and it is now gone.
End

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.

Footnotes
  1. 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 AcrPull can be scoped, audited, and revoked without rotating a password everyone shares.
  2. Scale-to-zero suits APIs and web apps that tolerate an occasional cold start. For latency-sensitive or always-warm workloads set --min-replicas 1 and accept the standing cost; the point of the lab is that the choice is yours per app, not a platform default you cannot change.