Skip to content

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 \
  --registry $ACR_NAME \
  --image hello-app:v1 \
  .

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:

az aks update \
  --resource-group rg-aks-demo \
  --name aks-demo \
  --attach-acr $ACR_NAME

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

  1. Create an ACR instance (Basic SKU, admin disabled) and build a small image using az acr build (no local Docker needed).
  2. Run that image as a public-facing ACI container using --acr-identity [system], and curl it at its .azurecontainer.io FQDN.
  3. Run a second container with --restart-policy Never that just prints something and exits, and confirm its exit state with az container show.
  4. Attach the ACR to the AKS cluster from Module 04 with az aks update --attach-acr, and redeploy the hello-aks Deployment to instead reference your own image.
  5. Delete the resource group when finished.