CoursesAzure Administrator AssociateContainers: ACI, AKS & ACR

Containers: ACI, AKS & ACR

Single containers, managed Kubernetes, registry.

Intermediate30 min · lesson 6 of 15

A container image is a sealed shipping crate. The application, the runtime it needs, and every library it depends on all go into one box, the box gets nailed shut, and nobody can change what is inside after that. Put it on any machine running a container engine and it behaves the same way. Azure sells you three services built around that crate. Azure Container Registry (ACR) is the bonded warehouse where crates are stored, versioned, and inspected. Azure Container Instances (ACI) is a courier you hire one delivery at a time: hand over a crate, it runs, you pay by the second. Azure Kubernetes Service (AKS) is an entire logistics company, where a dispatcher (the Kubernetes *control plane*, run by Microsoft) keeps a fleet of trucks (your *node pools*, which are ordinary virtual machines) moving hundreds of crates, and reroutes around any truck that breaks down.

You will rarely be the person who writes the application. You will always be the person who creates the registry, picks which runtime it runs on, wires up the authentication between the two, and stops the monthly bill from getting silly. That is the slice of containers AZ-104 (the Microsoft certification exam for Azure administrators) actually tests, so that is the slice you will work through here. One image, both runtimes, hands on the keyboard.

The warehouse: Azure Container Registry

ACR is a private registry that Microsoft runs for you. It stores OCI artifacts, and OCI (the Open Container Initiative) is the group that wrote down the image format Docker, Kubernetes, and ACI all agree on. It comes in three SKUs, which is Microsoft's word for pricing tiers. Basic includes 10 GiB of storage and suits dev and test. Standard includes 100 GiB, which covers a lot of real production shops. Premium includes 500 GiB plus the features exam questions hang on: *geo-replication*, which keeps a synchronized copy of the registry in several regions so pulls come from a nearby one, *private endpoints*, and encryption keys you own rather than Microsoft. Zone redundancy used to be a Premium perk and is now built into every tier. The feature admins underrate most is ACR Tasks. Run az acr build and Azure uploads your source code, builds the image on Microsoft's own compute, and pushes the finished image into the registry. No Docker on your laptop, no daemon running anywhere.

build & push — ACR
# Resource group + registry (name must be globally unique, 5-50 alphanumeric chars)
az group create --name rg-containers --location eastus
az acr create \
--resource-group rg-containers \
--name secopslogacr \
--sku Standard
# {
# "loginServer": "secopslogacr.azurecr.io",
# "sku": { "name": "Standard", "tier": "Standard" },
# "adminUserEnabled": false,
# "provisioningState": "Succeeded"
# }
# Build in the cloud — ACR Tasks, no local Docker needed
az acr build --registry secopslogacr --image hello-api:v1 .
# Packing source code into tar to upload...
# Sending context (154.2 KiB) to registry: secopslogacr...
# Queued a build with ID: ca1
# Step 1/4 : FROM mcr.microsoft.com/dotnet/aspnet:8.0
# ...
# - image:
# registry: secopslogacr.azurecr.io
# repository: hello-api
# tag: v1
# digest: sha256:9f2c04a1e7b3...
# Run ID: ca1 was successful after 1m12s
az acr repository show-tags --name secopslogacr --repository hello-api --output table
# Result
# --------
# v1

The loginServer value, secopslogacr.azurecr.io, is the address every runtime pulls from. Now look at adminUserEnabled: false in that output. Leave it exactly like that.

The ACR admin account is one shared login for everybody
az acr update --name secopslogacr --admin-enabled true switches on a single admin username, with two regenerable passwords, for the whole registry. It feels convenient, and a few portal deployment flows still ask for it. It is also one credential set shared by every human and every workload that touches the registry, it never rotates unless somebody remembers to rotate it, it cannot be scoped to anything narrower than full access to the entire registry, and az acr credential show will hand it to anyone holding Contributor rights. Use managed identities with the AcrPull and AcrPush roles instead, and leave the admin account off. When an exam scenario says *without storing credentials*, this is the habit it is testing.

ACI: one container, running in under a minute

ACI is what people mean by *serverless containers*. No virtual machine to size, no orchestrator to operate, no operating system to patch. You state how much CPU and memory you want, Azure finds the capacity, and the meter ticks per second while the thing runs. Fractional vCPUs are fine for an individual container, though the group as a whole has to add up to at least 1 vCPU and 1 GB. What ACI actually deploys is a container group: one or more containers sharing a lifecycle, a network namespace, and any storage volumes you mount. If you know Kubernetes, that is a pod wearing a different name. Two details the exam likes to poke at: multi-container groups only work on Linux, and a container group can be placed inside a virtual network so it can reach private resources. Good fits are batch jobs, CI (continuous integration) build agents, one-off migrations, and overflow when traffic spikes.

Pulling a private image means proving to ACR who you are. In production the answer is a managed identity, an identity in Microsoft Entra ID (the directory service formerly called Azure AD) that Azure creates and rotates on your behalf, so no password exists anywhere to leak or forget. Same keyless idea you saw in the RBAC (role-based access control) lesson. Create the identity, give it the built-in AcrPull role scoped to the registry, then attach it to the container:

run it — ACI + managed identity
# Identity that will pull the image — no passwords anywhere
az identity create --resource-group rg-containers --name id-aci-pull
ACR_ID=$(az acr show --name secopslogacr --query id --output tsv)
SP_ID=$(az identity show -g rg-containers -n id-aci-pull --query principalId -o tsv)
ID_RES=$(az identity show -g rg-containers -n id-aci-pull --query id -o tsv)
az role assignment create --assignee $SP_ID --role AcrPull --scope $ACR_ID
# Run the container: 1 vCPU, 1.5 GB, public DNS label
# (.NET 8+ images listen on port 8080 by default)
az container create \
--resource-group rg-containers \
--name hello-aci \
--image secopslogacr.azurecr.io/hello-api:v1 \
--cpu 1 --memory 1.5 \
--ports 8080 \
--dns-name-label hello-secopslog \
--assign-identity $ID_RES \
--acr-identity $ID_RES
# "provisioningState": "Succeeded",
# "ipAddress": {
# "fqdn": "hello-secopslog.eastus.azurecontainer.io",
# "ip": "20.62.145.9", "ports": [{ "port": 8080, "protocol": "TCP" }]
# }
az container logs --resource-group rg-containers --name hello-aci
# info: Microsoft.Hosting.Lifetime[14]
# Now listening on: http://[::]:8080

Give it about a minute and that FQDN (fully qualified domain name, the full public address Azure hands you) answers on port 8080, which is where .NET 8 and later images listen by default. az container delete stops the meter. Here is the trade-off worth burning into memory: per-second billing makes ACI cheap for anything you measure in hours per month, and expensive for anything that runs around the clock. Once a workload never sleeps, an AKS node or a plain virtual machine costs less.

AKS: when one container turns into a fleet

Kubernetes is a container *orchestrator*, which is a grand name for a very stubborn control loop. You describe the state you want, say three copies of this image sitting behind a load balancer, and Kubernetes compares that wish against reality over and over, forever. Whenever the two drift apart, it repairs the gap: restarts a container that crashed, spreads copies across different machines, rolls out a new version without dropping traffic. AKS is Kubernetes with the hardest piece handed to somebody else. Microsoft runs the control plane, meaning the API server, the scheduler, and the etcd database holding cluster state. That costs nothing on the Free tier, or roughly $0.10 per cluster per hour on the Standard tier, which buys an SLA (service level agreement, a promise with money behind it) on API server uptime: 99.9%, rising to 99.95% for clusters that use availability zones. You run the node pools that carry the actual workloads, and you pay ordinary virtual machine prices for them.

create the cluster — AKS
# Two-node cluster; --attach-acr wires up registry pulls in one flag
az aks create \
--resource-group rg-containers \
--name aks-lab \
--node-count 2 \
--node-vm-size Standard_D2s_v5 \
--enable-managed-identity \
--attach-acr secopslogacr \
--generate-ssh-keys
# Runs ~5 minutes, returns a large JSON blob:
# "provisioningState": "Succeeded",
# "currentKubernetesVersion": "1.35.4"
# Merge cluster credentials into ~/.kube/config
az aks get-credentials --resource-group rg-containers --name aks-lab
# Merged "aks-lab" as current context in /home/you/.kube/config
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# aks-nodepool1-38225763-vmss000000 Ready <none> 4m v1.35.4
# aks-nodepool1-38225763-vmss000001 Ready <none> 4m v1.35.4
kubectl create deployment hello --image=secopslogacr.azurecr.io/hello-api:v1 --replicas=3
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# hello-6d4b9c8f7d-2xqln 1/1 Running 0 20s
# hello-6d4b9c8f7d-8fj2p 1/1 Running 0 20s
# hello-6d4b9c8f7d-tk9vw 1/1 Running 0 20s

Two flags carry most of the weight here. --enable-managed-identity gives the cluster keyless credentials for its own Azure operations. It is already the default for new clusters, but spelling it out tells the next person reading the script what you meant. --attach-acr grants the node pools' *kubelet identity*, the identity the agent on each node uses, the AcrPull role on the registry. One flag doing the identity plumbing you wired by hand for ACI. There is a catch: it creates that role assignment using *your* permissions, so whoever runs the command needs the right to assign roles on the registry, which in practice means Owner or User Access Administrator. Production clusters usually split their nodes as well, keeping a *system node pool* for Kubernetes' own components and separate *user node pools* for applications, added later with az aks nodepool add. Those extra pools can run Windows or GPU nodes alongside the Linux ones. From there, day-2 work is squarely the administrator's job:

day-2 — scale & clean up
# Manual scale: add a third node
az aks scale --resource-group rg-containers --name aks-lab --node-count 3
# Better: let the cluster autoscaler decide within bounds
az aks nodepool update \
--resource-group rg-containers \
--cluster-name aks-lab \
--name nodepool1 \
--enable-cluster-autoscaler \
--min-count 2 --max-count 5
# "enableAutoScaling": true, "minCount": 2, "maxCount": 5
# Everything in this lab bills while it exists — tear it down when done
az group delete --name rg-containers --yes --no-wait

Picking one, locking it down, paying for it

A rule of thumb for choosing. If the workload fits in one sentence, "run this image until it exits", pick ACI. If your sentence has to mention replicas, rolling deployments, service discovery, or several teams sharing one platform, pick AKS. App Service, from the previous lesson, sits in the gap between them: it will happily run a container when what you actually wanted was a web app rather than a platform to operate.

Security stacks up in three layers, and you already know the tools for each one. *Supply chain*: Microsoft Defender for Containers scans the images sitting in ACR for known CVEs (Common Vulnerabilities and Exposures, the public catalogue of published security flaws) and watches how the cluster behaves once things are running. *Access*: AKS plugs into Microsoft Entra ID, so kubectl commands can be authorized by Azure RBAC roles instead of a separate pile of hand-managed Kubernetes accounts. One identity model across the whole estate. *Network*: traffic between pods inside a cluster is wide open until you write network policies, and a Premium registry belongs behind a private endpoint, which this course sets up later. Cost follows the same habits as virtual machines. Node pools are virtual machine scale sets underneath, so reservations and right-sizing still apply. The autoscaler you turned on a moment ago is a cost control as much as a scaling feature. And the lab cluster somebody forgot about is a classic bill shock, which is why az group delete is sitting at the end of that script.

Choosing a container runtime for the same ACR image
Image built & versioned in ACR
az acr build pushes to the private registry; now pick where it runs
"Run this image until it exits": short-lived or bursty
ACI
Serverless container group; per-second billing; no orchestrator or OS to operate
Needs replicas, rolling deploys, service discovery, or multiple teams
AKS
Microsoft-run control plane; your node pools; self-heals and autoscales
You actually want a web app, not a platform
App Service
Runs the container but it's really a PaaS web host (prior lesson)
ACI is cheap for work measured in hours per month and pricey once it runs around the clock, where an AKS node or a plain VM costs less. Every runtime pulls the same image from ACR using an AcrPull managed identity.

What the exam actually asks

AZ-104 cares about the mapping far more than the internals. Short-lived or bursty single container goes to ACI. Orchestration, self-healing, and scale go to AKS. Storing and geo-replicating images goes to ACR, with Premium required for geo-replication and private endpoints. Know the split of responsibility too. Microsoft runs the control plane, but *you* are still the one who triggers Kubernetes version upgrades: az aks upgrade for the control plane, az aks nodepool upgrade for the pools. And remember that a *container group* is ACI's answer to a pod, its containers sharing one lifecycle, one IP address, and any mounted volumes.

Notice what every container in this lesson is missing: anywhere durable to keep data. Whatever a container writes to its own filesystem vanishes the moment it stops, so real workloads mount storage from outside. AKS persistent volumes sit on Azure disks and Azure Files, ACI mounts Azure Files shares directly, and ACR keeps your image layers on storage Azure manages quietly in the background. All of it lands back on Azure Storage, and its types, performance tiers, and redundancy options are exactly where you go next.

These three products get confused with each other constantly, so keep them apart in your head. Azure Container Registry is a private Docker Hub that only your subscription can see. Azure Container Instances is "run this image now" with no kubelet to look after, which is why it suits batch jobs and labs. Azure Kubernetes Service is a managed control plane for systems made of many moving services: nodes, networking, RBAC, and upgrades land in your lap, while etcd and keeping the API server highly available stay with Microsoft.

Your standing checklist as an administrator has three parts. Registry hygiene: geo-replication where you need it, retention policies, admin user off. ACI networking: public address or tucked inside a virtual network. AKS day-two work: node pool sizes, upgrades, Azure CNI (Container Networking Interface) versus kubenet for pod networking, and Entra ID integration. The exam wants proof you can pick the right service for a given job and lock down the registry every other service pulls from.

Try this

Build a registry, get one small public image into it (importing is quicker than pushing), then run that image once on Azure Container Instances. Print the container's public address to prove it started.

terminal
RG=rg-lab-ctr
az group create -n $RG -l eastus
az acr create -g $RG -n contosolabacr$RANDOM --sku Basic
ACR=$(az acr list -g $RG --query "[0].name" -o tsv)
az acr import -n $ACR --source docker.io/library/nginx:alpine -t nginx:lab
az container create -g $RG -n aci-nginx --image $ACR.azurecr.io/nginx:lab \
--registry-login-server $ACR.azurecr.io --cpu 1 --memory 1 \
--ip-address Public --ports 80 \
--registry-username $(az acr credential show -n $ACR --query username -o tsv) \
--registry-password $(az acr credential show -n $ACR --query "passwords[0].value" -o tsv)
az container show -g $RG -n aci-nginx --query "{state:instanceView.state,fqdn:ipAddress.fqdn}" -o json
output
$ az container show -g rg-lab-ctr -n aci-nginx --query "{state:instanceView.state,fqdn:ipAddress.fqdn}" -o json
{
"state": "Running",
"fqdn": "aci-nginx.eastus.azurecontainer.io"
}
# Sample output — AKS is the next step when you need orchestration, not one-shot containers.

Takeaway

ACR holds the images. ACI runs a container once, with no cluster to build or babysit. AKS is managed Kubernetes for the day you need orchestration, scale, and service discovery.

Two habits to carry into real environments: use managed identities and keep the ACR admin account switched off, and put the AKS API server and node pools behind private networking whenever the workload is sensitive.

Quick check
01An AZ-104 question describes an ACI container that has to pull a private image from ACR "without storing any credentials." Which approach meets that?
Correct — A managed identity is an Entra ID identity Azure creates and rotates for you, so no password ever exists, and AcrPull grants exactly the pull access needed and nothing more. This is the pattern "without storing credentials" is pointing at.
Incorrect — No. The admin account is one shared credential for the whole registry, it never rotates on its own, and it cannot be scoped below full access. Handing it to a container is the opposite of what the question asks for.
Incorrect — No. A client secret is a stored credential that a human has to rotate, and here it would sit in the container's environment. That is precisely what the requirement rules out.
Incorrect — No. That puts private images on the open internet. It dodges the requirement rather than meeting it.
02A company runs several AKS clusters in different Azure regions, all pulling the same image from one ACR. They want to cut pull latency by having each region served from a local copy of the registry. Which registry capability does that, and what does it need?
Incorrect — Not this one. ACR Tasks builds images on Microsoft-hosted compute with az acr build. It does nothing about serving pulls from more than one region.
Correct — Geo-replication keeps synchronized copies of the registry in several regions so each cluster pulls from a nearby one, and the lesson lists it as Premium only.
Incorrect — No. Zone redundancy is built into every tier now, and it protects against one availability zone failing rather than against cross-region pull latency.
Incorrect — No. The admin account is a single shared credential for signing in. It has nothing to do with where copies of the registry live.
03A data team has a cleanup job whose container runs for roughly 20 minutes every night and then exits. The image already sits in Azure Container Registry. Which service runs it most cheaply?
Correct — Per-second billing is what makes ACI cheap for short, bursty work like a 20-minute nightly job.
Incorrect — No. Those node virtual machines bill around the clock, so you pay all day for 20 minutes of work.
Incorrect — No. A standing VM bills for about 23.5 idle hours a day, the textbook case where per-second ACI undercuts it.
Incorrect — No. App Service is a PaaS web host that bills continuously, a poor match for a container that runs briefly and exits.

Related