Skip to content

Registry and deployment

  • Lesson 13
  • 50 min
  • Module 5: Deploy and clean up
Lesson 13 of 15 · Docker and Cloud Essentials for AI DevelopersRegistry and deployment

What you will be able to do

Put the image where a cloud service can get it, with a version number that never changes its meaning. Then start it on a managed container service that you can delete in one step.

Before you startWhat to know and to have readyAbout 50 minutes

What you need to know

What you need on your computer

Tested with Docker Desktop 4.81.0 (Docker Engine 29.6.1, Buildx 0.35.0) on macOS, Apple silicon. The Azure steps were run with Azure CLI 2.88.0 and the containerapp extension 1.3.0b5, in region canadacentral. The Windows and Linux steps are not tested.

This module has two routes. Choose one before you start, and use the same one for all three lessons.

Live Simulated
Where the API runs Azure Container Apps, with a public HTTPS address A container on your computer, from deploy/local-cloud.yaml
Registry Azure Container Registry A registry container on 127.0.0.1:5000
Cost A few cents, if you delete everything the same day Free
You need An Azure subscription Only Docker

Every output in these lessons has a label. Live outputs come from the real Azure run. Simulated outputs come from Docker on a Mac on the same day. A simulated output never claims that something ran in a cloud. Both routes give you the evidence for the module practice and the final assignment.

Amira starts with a small experiment on her computer. She runs a registry: a server that stores images, so that other computers can download them. Docker’s official registry image is one, and it runs on her computer like any other container:

Terminal (simulated route)
docker run -d --name ticket-api-registry -p 127.0.0.1:5000:5000 -v ticket-api-registry-data:/var/lib/registry registry:3.1.2

She gives her image a second name, which starts with the registry’s address, and pushes it (sends it to the registry):

Terminal (simulated route)
docker tag ticket-api:1.1.0 127.0.0.1:5000/ticket-api:1.1.0
docker push 127.0.0.1:5000/ticket-api:1.1.0

The output (simulated, shortened):

The push refers to repository [127.0.0.1:5000/ticket-api]
0b13ca62e0be: Pushed
4732e419703d: Pushed
...
bbeda6b4abb7: Pushed
1.1.0: digest: sha256:1ba724a2b1215f0d2225b7276fa6ab5f5226c1e10c46c98ee9845af3fec459ee size: 856

Then she asks the registry what it has. A registry answers HTTP requests, so curl can ask:

Terminal (simulated route)
curl -s http://127.0.0.1:5000/v2/_catalog
curl -s http://127.0.0.1:5000/v2/ticket-api/tags/list

The output (simulated):

{"repositories":["ticket-api"]}
{"name":"ticket-api","tags":["1.1.0"]}

Now the names. A full image name has three parts: <registry>/<repository>:<tag>.

Part Here Live route
Registry: the server 127.0.0.1:5000 ticketapi4821.azurecr.io
Repository: one image’s versions, together ticket-api ticket-api
Tag: one version 1.1.0 1.1.0

A name without a registry, such as ticket-api:1.1.0, means Docker Hub, or only your computer if the image was never pushed. The digest (sha256:1ba7…) is the exact content. A tag is a label that points to a digest, and anyone who can push can move it to a different digest. That is why every change gets a new version number: 1.1.0, then 1.1.1. Never deploy only latest. In a month, nobody can say which code latest was.

Each Pushed line is one layer. The registry stores layers, not whole images, so a second push of a similar image sends only the layers that changed. The push moves the compressed layers: the content size column of docker image ls (73MB for ticket-api:1.1.0), not the disk usage (345MB).

A deployment is four actions: build, push, deploy, verify. Each one can fail in a way that you can see, if you know where to look.

On your computer: 1, build with docker buildx build --platform linux/amd64. 2, check with imagetools inspect that the image is linux/amd64; if it is arm64 only, build again. 3, push ticketapi4821.azurecr.io/ticket-api:1.1.0. The image is then in the registry. On Azure: 4, deploy with az containerapp create or update, which makes a new revision. 5, is the revision ready (image pulled, startup probe passes)? If no, the old revision keeps the traffic; read the system logs, fix and deploy again. If yes, 6, smoke checks; if a check fails, fix or deploy the last good tag. If all pass, 7, record the address, revision and image in DEPLOY.md.
Build, push, deploy, verify. Steps 1 to 3 run on your computer. Steps 4 to 7 run against the cloud. Every failure branch goes back to an earlier step, and the old version keeps serving while you fix it.
Text description of the diagram

The build makes an image for linux/amd64, the CPU type of the cloud servers. Check the platform before you push. The push puts it in the registry under a versioned tag. The deploy makes a new revision: a fixed copy of the app’s settings and image. If the revision cannot start, the old one keeps the traffic. You read the system logs, fix the cause and deploy again. If it starts, the traffic moves to it, and you run the smoke checks. When they pass, you record what you deployed. This lesson does steps 1 to 4. Operational configuration does step 5, and Verify and remove does steps 6 and 7.

Azure Container Apps runs linux/amd64 images only. A plain docker build on an Apple silicon Mac makes linux/arm64. Amira checks what her image holds:

Terminal
docker buildx imagetools inspect 127.0.0.1:5000/ticket-api:1.1.0

The output (simulated, shortened):

Name: 127.0.0.1:5000/ticket-api:1.1.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:1ba724a2b1215f0d2225b7276fa6ab5f5226c1e10c46c98ee9845af3fec459ee
Manifests:
Name: 127.0.0.1:5000/ticket-api:1.1.0@sha256:97dd1c69...
Platform: linux/arm64
Name: 127.0.0.1:5000/ticket-api:1.1.0@sha256:c66bcb26...
Platform: unknown/unknown

Only linux/arm64. The unknown/unknown entry is the build’s attestation: a record of how the image was built, not a second image. On an Intel or AMD computer, you see linux/amd64 here, and the next build is the same as yours.

Predict: what happens if a computer with an amd64 CPU asks for this image? Amira asks for it as an amd64 computer would:

Terminal
docker pull --platform linux/amd64 127.0.0.1:5000/ticket-api:1.1.0-arm64

The output (simulated; she pushed the same arm64 image with the tag 1.1.0-arm64 first):

Error response from daemon: no matching manifest for linux/amd64 in the manifest list entries: no match for platform in manifest: not found

Docker says it clearly. Azure does not, as you see in Operational configuration. So Amira builds for the cloud with --platform:

Terminal (simulated route)
docker buildx build --platform linux/amd64 -t 127.0.0.1:5000/ticket-api:1.1.0 --push .

The output (simulated, the end):

#12 naming to 127.0.0.1:5000/ticket-api:1.1.0 done
#12 pushing layers 0.0s done
#12 pushing manifest for 127.0.0.1:5000/ticket-api:1.1.0@sha256:204c4f72f635011840ac1cb898cbd2de5e1aa6c189905078971cfe8283f002f6 0.1s done
#12 DONE 0.2s

The amd64 layers were in the build cache from an earlier build, so this took less than a second. Without the cache, the same build took 23 s, and RUN pip install took 19.7 s instead of 11.8 s. An Apple silicon Mac runs amd64 programs with emulation: a program that translates the other CPU’s instructions. --push sends the result to the registry at once. Now imagetools inspect says Platform: linux/amd64.

Look at the digest: it is not 1ba724… any more. The same tag, 1.1.0, now points to different content. Remember this for the common mistake at the end of the lesson.

Your computer runs docker buildx build --platform linux/amd64 and, as step 3, pushes ticket-api:1.1.0 to the registry. Everything else is in the resource group rg-ticket-api-practice (step 1, canadacentral, tags purpose=practice and course=C18): the registry ticketapi4821, Basic (step 2); the managed identity id-ticket-api (step 4), which has AcrPull on this registry only and, optionally, Secrets User on the Key Vault kv-ticket-api-4821; the Container Apps environment cae-ticket-api (step 5) with the container app ticket-api inside it (step 6), which uses id-ticket-api, has 0.25 vCPU, 0.5 GiB and 0 to 2 replicas, and pulls the image from the registry; and the Log Analytics workspace that step 5 makes automatically, which receives the logs. Clients on the internet, and programs that scan it, reach the app over HTTPS through external ingress on target port 8000.
What the live route makes, in the order of the steps. Six resources in one resource group (five without the optional Key Vault). Step 5 makes two of them.
Text description of the diagram

The numbers are the order of the steps below. The resource group holds everything, so deleting it deletes everything (with one exception, the Key Vault, which Verify and remove handles). The identity can only pull from this one registry. The environment makes a Log Analytics workspace that you did not ask for, and it can charge for log data.

Amira follows the design from Module 4. She puts the names in shell variables first, so that each command is shorter and she cannot mistype a name. Choose your own registry name: it must be unique in all of Azure, 5 to 50 letters and numbers, with no hyphens. Add your own digits to ticketapi.

Terminal
RG=rg-ticket-api-practice
LOCATION=canadacentral
ACR=ticketapi4821

The variables live only in this terminal window. If you open a new one, set them again. The commands below use $RG, $ACR and the others, which works the same way in both shells. Where a command differs, there are tabs.

Before step 1: sign in and prepare the subscription

Section titled “Before step 1: sign in and prepare the subscription”
Terminal
az login
az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
az provider register --namespace Microsoft.ContainerRegistry

az login opens the browser for your Azure sign-in. The extension adds the az containerapp commands. A resource provider is the part of Azure that makes one kind of resource. A new subscription must register each provider once. It can take a few minutes. Check it with az provider show -n Microsoft.App --query registrationState -o tsv: the answer must be Registered. In the test subscription, all three were registered already.

The extension prints WARNING: The behavior of this command has been altered by the following extension: containerapp on every az containerapp command. It is harmless. The outputs below leave it out.

Terminal
az group create --name $RG --location $LOCATION --tags purpose=practice course=C18 -o table

The output (live):

Location Name
------------- ----------------------
canadacentral rg-ticket-api-practice

The tags say why the group exists. They help a person who reads the subscription’s list later. They do not help a search for its resources, as Verify and remove shows.

Terminal
az acr create --resource-group $RG --name $ACR --sku Basic -o table

The output (live):

NAME RESOURCE GROUP LOCATION SKU LOGIN SERVER CREATION DATE ADMIN ENABLED
------------- ---------------------- ------------- ----- ------------------------ -------------------- ---------------
ticketapi4821 rg-ticket-api-practice canadacentral Basic ticketapi4821.azurecr.io 2026-10-07T16:10:57Z False

LOGIN SERVER is the registry part of the image name. ADMIN ENABLED False: the registry’s admin user, a shared password, is off. Keep it off. The app pulls with its identity, so it needs no password.

Terminal
az acr login --name $ACR
docker buildx build --platform linux/amd64 -t $ACR.azurecr.io/ticket-api:1.1.0 --push .

Run the build in the project folder: the . at the end is the build context. The output (live, shortened):

Login Succeeded
...
#13 exporting to image
#13 pushing layers 11.8s done
#13 pushing manifest for ticketapi4821.azurecr.io/ticket-api:1.1.0@sha256:cfd04194eb1be66f49a40575f75a0b2105a74e172f953df26a355291a5191afd 1.0s done
#13 DONE 14.7s

az acr login gives Docker a token for this registry. The token expires after a few hours: if a later push says unauthorized, run az acr login again. The whole build and push took 27 s. Check the tag and the platform:

Terminal
az acr repository show-tags -n $ACR --repository ticket-api -o table
docker buildx imagetools inspect $ACR.azurecr.io/ticket-api:1.1.0

The output (live, shortened):

Result
--------
1.1.0
Name: ticketapi4821.azurecr.io/ticket-api:1.1.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:cfd04194eb1be66f49a40575f75a0b2105a74e172f953df26a355291a5191afd
Manifests:
Name: ticketapi4821.azurecr.io/ticket-api:1.1.0@sha256:b08764e2...
Platform: linux/amd64
Name: ticketapi4821.azurecr.io/ticket-api:1.1.0@sha256:eaa1e2c6...
Platform: unknown/unknown

linux/amd64: the cloud can run it.

Terminal
az identity create --resource-group $RG --name id-ticket-api -o table
IDENTITY_ID=$(az identity show -g $RG -n id-ticket-api --query id -o tsv)
PRINCIPAL_ID=$(az identity show -g $RG -n id-ticket-api --query principalId -o tsv)
REGISTRY_ID=$(az acr show -n $ACR --query id -o tsv)
az role assignment create --assignee-object-id $PRINCIPAL_ID --assignee-principal-type ServicePrincipal --role AcrPull --scope $REGISTRY_ID -o table

The output (live, columns shortened):

ClientId Location Name PrincipalId ResourceGroup
-------------- ------------- ------------- -------------- ----------------------
faf6b35b-... canadacentral id-ticket-api d2066809-... rg-ticket-api-practice
CreatedOn PrincipalType Scope
-------------------------------- ---------------- ---------------------------------------------------------------------------------------
2026-10-07T16:12:09.352851+00:00 ServicePrincipal /subscriptions/.../resourceGroups/rg-ticket-api-practice/providers/Microsoft.ContainerRegistry/registries/ticketapi4821

The scope is the registry, not the group or the subscription. The identity can pull images from this one registry, and do nothing else: least privilege, as in Module 4. --assignee-principal-type ServicePrincipal avoids an error when the new identity is not yet known to the role service.

Terminal
az containerapp env create --resource-group $RG --name cae-ticket-api --location $LOCATION -o table

The output (live, shortened):

WARNING: No Log Analytics workspace provided.
WARNING: Generating a Log Analytics workspace with name "workspace-rgticketapipracticeXXXX"
...
Container Apps environment created. To deploy a container app, use: az containerapp create --help
Location Name ResourceGroup
-------------- -------------- ----------------------
Canada Central cae-ticket-api rg-ticket-api-practice

The environment is the shared space where container apps run: one network and one place for logs. It took about 2 to 3 minutes. Read the two warnings: Azure made a Log Analytics workspace for the logs. You did not ask for it, and it can charge for log data. Your random characters differ from XXXX. Add it to your inventory now:

Terminal
az resource list -g $RG -o table

The output (live):

Name ResourceGroup Location Type Status
--------------------------------- ---------------------- ------------- ------------------------------------------------ ---------
ticketapi4821 rg-ticket-api-practice canadacentral Microsoft.ContainerRegistry/registries Succeeded
id-ticket-api rg-ticket-api-practice canadacentral Microsoft.ManagedIdentity/userAssignedIdentities Succeeded
workspace-rgticketapipracticeXXXX rg-ticket-api-practice canadacentral Microsoft.OperationalInsights/workspaces Succeeded
cae-ticket-api rg-ticket-api-practice canadacentral Microsoft.App/managedEnvironments Succeeded

Four resources, and you asked for three of them. The role assignment from step 4 is not in the list: az resource list does not show role assignments.

Terminal
KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))")
az containerapp create --resource-group $RG --name ticket-api --environment cae-ticket-api \
--image $ACR.azurecr.io/ticket-api:1.1.0 \
--user-assigned $IDENTITY_ID --registry-server $ACR.azurecr.io --registry-identity $IDENTITY_ID \
--ingress external --target-port 8000 \
--cpu 0.25 --memory 0.5Gi --min-replicas 0 --max-replicas 2 \
--secrets api-key="$KEY" \
--env-vars API_KEY=secretref:api-key REQUIRE_API_KEY=true SHOW_DOCS=false

The shell history keeps $KEY, the name of the variable, not the key. The flags, one by one:

Flag What it does
--environment cae-ticket-api Runs the app in the environment from step 5.
--image …/ticket-api:1.1.0 The exact version to run.
--user-assigned $IDENTITY_ID Gives the app the identity from step 4.
--registry-server, --registry-identity Pulls from the registry as that identity: no password.
--ingress external --target-port 8000 Gives the app a public HTTPS address, and sends requests to port 8000 in the container.
--cpu 0.25 --memory 0.5Gi The size of each replica. The smallest size.
--min-replicas 0 --max-replicas 2 No replicas when nobody calls; at most two.
--secrets api-key="$KEY" Stores the key as the app’s secret api-key.
--env-vars API_KEY=secretref:api-key … Sets API_KEY from the secret, and two plain settings.

The output (live):

WARNING: User identity /subscriptions/.../userassignedidentities/id-ticket-api is already assigned to containerapp
Container app created. Access your app at https://ticket-api.nicewater-a307f95a.canadacentral.azurecontainerapps.io/

23 s. The warning is harmless: --user-assigned and --registry-identity name the same identity. Your address has a different middle part. Check the state:

Terminal
az containerapp show -g $RG -n ticket-api --query "{rev:properties.latestRevisionName, status:properties.runningStatus, prov:properties.provisioningState}" -o table

The output (live):

Rev Status Prov
------------------- -------- ---------
ticket-api--nz14cir Running Succeeded

The first revision is ticket-api--nz14cir. Succeeded means that Azure accepted the settings. It does not prove that the API answers: that is the next two lessons.

The simulated route: the same steps on your computer

Section titled “The simulated route: the same steps on your computer”

The simulated route keeps the ideas and leaves out the account. The table maps each live part to its local version:

Live (Azure) Simulated (your computer)
Registry ticketapi4821.azurecr.io The registry container on 127.0.0.1:5000
az acr login Not needed: only your computer can reach this registry
The image for linux/amd64 The same build, pushed to the local registry
Container app ticket-api The service api in deploy/local-cloud.yaml
The secret api-key, secretref: A Compose secret from secrets/api_key.txt, read with API_KEY_FILE
--cpu 0.25 --memory 0.5Gi cpus: 0.25 and mem_limit: 512m
HTTPS address, external ingress http://127.0.0.1:8080: no HTTPS
Revisions, scaling, the registry pull Not simulated

The simulation runs the image that you built for your own computer, ticket-api:1.1.0. It cannot run the amd64 image quickly on Apple silicon: emulation works, but it is slow. deploy/local-cloud.yaml is in the project:

deploy/local-cloud.yaml
name: ticket-api-cloud-sim
services:
api:
image: ticket-api:1.1.0
ports:
- "127.0.0.1:8080:8000"
environment:
API_KEY_FILE: /run/secrets/api_key
REQUIRE_API_KEY: "true"
SHOW_DOCS: "false"
secrets:
- api_key
cpus: 0.25
mem_limit: 512m
restart: on-failure
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
secrets:
api_key:
file: ../secrets/api_key.txt

The secret’s path is ../secrets/api_key.txt, because Compose reads paths from the folder of the Compose file, deploy/. Operational configuration explains the health check and the restart policy. Start it from the project folder:

Terminal (simulated route)
docker compose -f deploy/local-cloud.yaml up -d
docker compose -f deploy/local-cloud.yaml ps

The output (simulated):

Network ticket-api-cloud-sim_default Creating
Network ticket-api-cloud-sim_default Created
Container ticket-api-cloud-sim-api-1 Creating
Container ticket-api-cloud-sim-api-1 Created
Container ticket-api-cloud-sim-api-1 Starting
Container ticket-api-cloud-sim-api-1 Started
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
ticket-api-cloud-sim-api-1 ticket-api:1.1.0 "fastapi run --host …" api 13 seconds ago Up 12 seconds (healthy) 127.0.0.1:8080->8000/tcp

-f names the Compose file, because it is not compose.yaml. The name ticket-api-cloud-sim keeps it apart from the database project from Module 3, so both can exist together. The status changes from health: starting to healthy after the first health check, about 10 s after the start.

Guided practice: push a version and deploy it

Section titled “Guided practice: push a version and deploy it”

Use your route. Work in the project folder.

  1. Write in docs/cloud-design.md which route you use.
  2. Simulated route: start the local registry with the docker run … registry:3.1.2 command from Registry, repository and tag. Live route: sign in, then do steps 1 and 2 of the live route.
  3. Build for linux/amd64 with --push, to your registry, with the tag 1.1.0.
  4. Check the platform with docker buildx imagetools inspect. Expected: Platform: linux/amd64, and one unknown/unknown entry.
  5. Check the tag list. Simulated: curl -s http://127.0.0.1:5000/v2/ticket-api/tags/list shows "tags":["1.1.0"]. Live: az acr repository show-tags shows 1.1.0.
  6. Simulated route: start deploy/local-cloud.yaml, and wait for (healthy). Live route: do steps 4 to 6, and wait for Running and Succeeded.
  7. Write down the image name with its digest, and (live) the address and the first revision’s name. You need them in the next two lessons.
Show the expected result

Simulated: docker compose -f deploy/local-cloud.yaml ps shows ticket-api-cloud-sim-api-1 Up … (healthy) on 127.0.0.1:8080->8000/tcp, and the local registry has ticket-api with the tag 1.1.0 for linux/amd64.

Live: az resource list -g rg-ticket-api-practice -o table lists five resources: the registry, the identity, the workspace, the environment and the container app. az containerapp show says Running and Succeeded. Your notes have something like this:

Image: ticketapi4821.azurecr.io/ticket-api:1.1.0@sha256:cfd04194eb1b...
Address: https://ticket-api.nicewater-a307f95a.canadacentral.azurecontainerapps.io
Revision: ticket-api--nz14cir

Build the image again, but leave out --platform, and push it with the tag 1.1.0-native:

Terminal (simulated route)
docker buildx build -t 127.0.0.1:5000/ticket-api:1.1.0-native --push .

Before you run imagetools inspect on it, predict the platform. Then answer: on which computers can this tag run, and could Azure Container Apps run it?

Show the answer

Without --platform, Buildx builds for the computer that runs the build. On Apple silicon, imagetools inspect shows Platform: linux/arm64. On an Intel or AMD computer, it shows linux/amd64. An arm64-only image runs on arm64 computers, and an amd64 computer cannot pull it: docker pull --platform linux/amd64 gives no matching manifest for linux/amd64. Azure Container Apps runs only amd64, so it could not run this tag on a Mac with Apple silicon. Always write --platform linux/amd64 for a cloud build, even on a computer where it is the default. Then the command does the same thing on every computer.

Common mistake

The same tag, different content

Symptom: Amira pushed 127.0.0.1:5000/ticket-api:1.1.0 twice: first the arm64 build, then the amd64 build. The tag list still shows only "tags":["1.1.0"], and nothing warned her. But the digest changed from sha256:1ba724a2… to sha256:204c4f72…. A deployment that says 1.1.0 now runs different content from yesterday’s deployment with the same name.

Cause: a tag is a label that points to a digest. A push with an existing tag moves the label. Most registries allow it by default, Azure Container Registry too.

Fix:

  1. Give every change a new version: 1.1.1 for a fix, 1.2.0 for a new feature. Never push a different build under a version that was deployed.
  2. Record the digest with the tag in DEPLOY.md. The digest proves what ran.
  3. For the strictest pin, deploy by digest: ticketapi4821.azurecr.io/ticket-api@sha256:cfd04194…. Nobody can move a digest.

For a real AI service: an image often carries a trained model. If someone moves the tag 2.0.0 to a new model, the service’s predictions change, and the deployment record still says 2.0.0. Nobody can then explain why the results changed. A version must mean one model and one piece of code.

Check your understanding

Name the three parts of ticketapi4821.azurecr.io/ticket-api:1.1.0, and say which part a push can change without any warning.

Show the answer

ticketapi4821.azurecr.io is the registry, the server that stores the images. ticket-api is the repository, the versions of one image together. 1.1.0 is the tag, one version. A push with the same name moves the tag to new content without a warning. Only the digest (sha256:…) always means the same content. That is why each change gets a new version number, and why DEPLOY.md records the digest too.

Check your understanding

Predict: on an Apple silicon Mac, you run docker build -t ticket-api:1.1.0 . and push it to the Azure registry. Will Azure Container Apps run it? What do you change?

Show the answer

No. A plain build on Apple silicon makes a linux/arm64 image, and Azure Container Apps runs only linux/amd64. Build with docker buildx build --platform linux/amd64 -t ticketapi4821.azurecr.io/ticket-api:1.1.0 --push ., then check with docker buildx imagetools inspect that the platform is linux/amd64 before you deploy.

Check your understanding

The container app needs images from the registry. Why does the course give it an identity with AcrPull on the registry, and not the registry's admin user?

Show the answer

The admin user is one shared password with full rights on the registry. Anyone who sees it can push and delete images, and it must be stored and changed by hand. The identity needs no password: Azure gives the app its tokens. Its role, AcrPull, lets it only pull, and its scope is this one registry. If the app is misused, the attacker cannot push a changed image or reach anything else. That is least privilege.

Recap

  • A registry stores images. A full name is <registry>/<repository>:<tag>. A tag can move; a digest cannot.
  • Give every change a new version, and never deploy only latest.
  • Build for the cloud with --platform linux/amd64, and check with docker buildx imagetools inspect before you deploy.
  • Live: a tagged resource group, a registry (with the admin user off), an environment (which makes a Log Analytics workspace), an identity with AcrPull on the registry, and the container app with a new key as its secret.
  • Simulated: a local registry on 127.0.0.1:5000, and deploy/local-cloud.yaml with the same settings and limits.

The app is running, but the platform knows only that something listens on port 8000. The next lesson tells it how to check the app and how large it may grow. It also says what must happen when a new version cannot start.

Next: Operational configuration