07 · Container Instances & Container Registry¶
Module 04 deployed containers to AKS, using a public
image (mcr.microsoft.com/...). This module covers the other half:
building your own image, storing it in Azure Container Registry
(ACR), and running it — either serverlessly with Azure Container
Instances (ACI) (no orchestrator at all) or on AKS pulling from your own
registry instead of a public one.
Azure Container Registry¶
ACR is a private, Azure-hosted Docker registry — docker push/pull
target it exactly like Docker Hub, just with Azure RBAC controlling who
can push or pull.
az group create --name rg-aci-acr --location eastus
az acr create \
--resource-group rg-aci-acr \
--name acrdemo$RANDOM \
--sku Basic \
--admin-enabled false
--admin-enabled false disables the registry-wide admin username/password
login — RBAC (via managed identity or az acr login) is the modern,
auditable way in, and admin credentials are a shared secret with no audit
trail of who used them.
Build and push an image¶
Given a simple Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Option A — build locally, push to ACR:
ACR_NAME=$(az acr list --resource-group rg-aci-acr --query "[0].name" --output tsv)
az acr login --name $ACR_NAME
docker build -t $ACR_NAME.azurecr.io/hello-app:v1 .
docker push $ACR_NAME.azurecr.io/hello-app:v1
Option B — build in the cloud (az acr build), no local Docker
daemon required — useful in CI runners without Docker installed, or just
to offload the build:
az acr build uploads your build context, runs the build on ACR's own
build compute, and pushes the resulting image directly — one command
instead of build-then-push, and no local docker requirement at all.
az acr repository list --name $ACR_NAME --output table
az acr repository show-tags --name $ACR_NAME --repository hello-app --output table
Gotcha: ACR Basic tier has meaningfully less storage and throughput than Standard/Premium — fine for learning, but a real CI pipeline pushing frequently benefits from Standard, and Premium adds geo-replication (useful if AKS clusters in multiple regions pull from the same registry).
Run it serverlessly with Container Instances¶
ACI runs a single container (or a small group) with no cluster, no orchestrator, billed per-second while running — the "just run this one container" option, sitting between "no infrastructure" (App Service) and "full orchestration" (AKS).
az acr update --name $ACR_NAME --anonymous-pull-enabled false
az container create \
--resource-group rg-aci-acr \
--name aci-hello \
--image $ACR_NAME.azurecr.io/hello-app:v1 \
--registry-login-server $ACR_NAME.azurecr.io \
--acr-identity [system] \
--cpu 1 \
--memory 1.5 \
--ports 80 \
--ip-address Public \
--dns-name-label hello-aci-demo
--acr-identity [system] gives the container group a system-assigned
managed identity used purely to authenticate the image pull from ACR — no
registry password stored anywhere, same principle as
Module 06 applied to pulling images
instead of reading secrets.
az container show \
--resource-group rg-aci-acr \
--name aci-hello \
--query "{fqdn:ipAddress.fqdn, state:instanceView.state}" \
--output table
curl http://hello-aci-demo.eastus.azurecontainer.io
az container logs --resource-group rg-aci-acr --name aci-hello
az container exec --resource-group rg-aci-acr --name aci-hello --exec-command "sh"
Gotcha: ACI containers don't restart on crash by default the way a
Kubernetes Deployment does (--restart-policy defaults to Always for the
container process, but the container group itself has no
self-healing across host failures, no rolling updates, and no built-in
load balancing across multiple instances) — for anything needing real
availability guarantees or more than one replica, that's what AKS or App
Service are for. ACI shines for short-lived jobs, batch tasks, and
dev/test — not as a production web tier replacement.
ACI as a background job¶
A common ACI pattern is a one-shot task, not a long-running server:
az container create \
--resource-group rg-aci-acr \
--name aci-batch-job \
--image $ACR_NAME.azurecr.io/report-generator:v1 \
--registry-login-server $ACR_NAME.azurecr.io \
--acr-identity [system] \
--restart-policy Never \
--cpu 2 \
--memory 4
--restart-policy Never means the container runs once to completion and
stops — you're billed only for the seconds it actually ran, then it's
gone (unlike a Web App or AKS pod, which keeps a reserved slot even when
idle). Check its exit code:
az container show \
--resource-group rg-aci-acr \
--name aci-batch-job \
--query "containers[0].instanceView.currentState" \
--output json
Connecting an AKS cluster to ACR¶
If Module 04's cluster needs to pull your own images
instead of mcr.microsoft.com's public ones:
This grants the AKS cluster's kubelet identity AcrPull on the registry
automatically — no image pull secret to create or rotate manually.
How It Actually Works¶
Azure Container Registry stores images as content-addressable
OCI-spec layers and manifests in blob storage: pushing an image
uploads each filesystem layer as an independent, deduplicated blob keyed by
its SHA-256 digest and a small JSON manifest that references those layer
digests plus config — two images sharing a base layer (e.g. the same
python:3.12-slim) physically store that layer's bytes only once in the
registry, which is why layer caching on push/pull is a real storage
optimization, not just a display trick. az acr build doesn't build on
your machine — it uploads your build context to the registry's ACR Tasks
service, which runs the Docker build inside a managed, ephemeral build
container in Azure and pushes the resulting image straight back into the
same registry, meaning your local machine never needs Docker installed at
all for that path.
Azure Container Instances runs a container group by allocating it
directly onto Azure's own multi-tenant container hosting fabric (Azure
calls this the "hyper-scale" hosting substrate) using a lightweight
Hyper-V-isolated sandbox per container group rather than a shared
Kubernetes-style node pool — this is why ACI containers get their own
dedicated vCPU/memory allocation with no noisy-neighbor risk from other
tenants' containers, and why there's no cluster to manage or node to patch:
the isolation boundary is the container group itself, enforced by the
hypervisor. Pulling from ACR into ACI (or AKS) authenticates via either the
registry's admin credentials or, in the RBAC-integrated path, a managed
identity granted the AcrPull role — that role assignment is checked by
ACR's own data-plane API on every pull/token-exchange request, independent
of whatever RBAC role the identity holds on the AKS cluster or ACI resource
itself.
Cheat sheet¶
| Command | Purpose |
|---|---|
az acr create --sku Basic --admin-enabled false |
Create a private registry, RBAC-only access. |
az acr login --name |
Authenticate local docker to the registry. |
docker build/push |
Build and push an image (local Docker daemon). |
az acr build --registry --image . |
Build and push in the cloud, no local Docker needed. |
az acr repository list/show-tags |
Inspect what's stored in the registry. |
az container create --acr-identity [system] |
Run a container from ACR, pulled via managed identity. |
az container logs / exec |
View logs / shell into a running container instance. |
az aks update --attach-acr |
Grant an AKS cluster pull access to a registry. |
Exercise¶
- Create an ACR instance (Basic SKU, admin disabled) and build a small
image using
az acr build(no local Docker needed). - Run that image as a public-facing ACI container using
--acr-identity [system], andcurlit at its.azurecontainer.ioFQDN. - Run a second container with
--restart-policy Neverthat just prints something and exits, and confirm its exit state withaz container show. - Attach the ACR to the AKS cluster from Module 04
with
az aks update --attach-acr, and redeploy thehello-aksDeployment to instead reference your own image. - Delete the resource group when finished.