You ship a new image to Azure Container Apps, and for a moment two versions of the app run side by side: the old one keeps every request until the new one is ready. Then you rotate an API key, and the app keeps using the old one. Both come from the same idea, the revision.
This explainer walks through a full deployment: what a Container Apps environment is, how to deploy with the CLI and with YAML, how settings and secrets reach the container, how it pulls from a private registry without a password, and how to check that a deployment really works. One picture helps: the environment is a city, every container app is a metro line, and every revision is a train, built once from one image and one set of settings and never changed.
Key points
- The environment is the boundary. Apps in one environment share a virtual network and, by default, one Log Analytics workspace. Use separate environments for development, test and production.
- Every template change builds a new revision. In single revision mode (the default), the old revision keeps all the traffic until the new one is provisioned, scaled up and has passed its health probes.
- Secrets are application-scope. Changing a secret builds no new revision; the running containers keep the value they read at start until the revision restarts.
- Pull with a managed identity, check from the outside in.
AcrPullon Azure Container Registry, no password; thenshow, logs, revisions and replicas.
The environment
An environment is the boundary your container apps live in. Apps in the same environment share one virtual network and send their logs to the same place, by default one Log Analytics workspace. That makes it the natural home for services that belong together, such as an API and the background worker that feeds it. It is also how you separate stages: development, test and production each get their own environment, so a broken test revision never touches production.
Inside the environment, every app decides who may reach it:
| Ingress | Who can call the app |
|---|---|
| External | callers outside the environment (the internet, for a public environment) and apps inside it |
| Internal | only other apps in the same environment; outside callers get a 404 |
Either way, the container listens on its target port, and Container Apps serves it over HTTPS on port 443.
az containerapp env create \--name aca-env-demo \--resource-group rg-aca-demo \--location centralus
New environments use workload profiles by default, with a Consumption profile that scales to zero: you pay for what your apps use.
Deploy and update
The fastest first deployment is az containerapp up: one command can create the environment and everything around it, deploy the image and print the app's address. Great for a first try. For real environments, create the environment first and deploy into it with az containerapp create: the same image, port and ingress, but nothing is created behind your back.
az containerapp create -n ai-api -g rg-aca-demo \--environment aca-env-demo \--image mcr.microsoft.com/k8se/quickstart:latest \--target-port 80 --ingress external
Either way, Container Apps builds the first revision: a snapshot of the app with its image, settings and scale rules. A revision never changes. Shipping version two creates a new one next to the running one:
az containerapp update -n ai-api -g rg-aca-demo \--image myregistry.azurecr.io/ai-api:v2 \--revision-suffix v2
Single revision mode (the default): the old revision keeps all the traffic while the new one starts, scales up and passes its health probes. Only then does the traffic switch, and the old revision is shut down. If the new revision never gets ready, nothing switches and users stay on the old version. Multiple revision mode lets you decide yourself and split the traffic, for example 90 % to 10 %.
YAML for teams: keep the whole app in a YAML file in source control and review changes like code. With --yaml, the file is the full truth: the CLI ignores every other flag on that command.
properties:configuration:ingress: { external: true, targetPort: 8000 }template:containers:- name: ai-apiimage: myregistry.azurecr.io/ai-api:v2
Settings and secrets
The same image should run in test and in production; what differs is configuration. Non-sensitive settings such as a log level or a feature flag go into environment variables. --set-env-vars adds or changes one and keeps the rest.
Sensitive values such as an API key become secrets. A secret belongs to the app, not to a revision, and is stored encrypted. An environment variable points at it with secretref: followed by the secret's name, and the name must match exactly, including its case:
az containerapp secret set -n ai-api -g rg-aca-demo \--secrets embeddings-api-key=<value>az containerapp update -n ai-api -g rg-aca-demo \--set-env-vars \EMBEDDINGS_API_KEY=secretref:embeddings-api-key
In YAML it's secretRef, and the file only holds the name, never the value:
properties:template:containers:- name: ai-apienv:- name: LOG_LEVELvalue: info- name: EMBEDDINGS_API_KEYsecretRef: embeddings-api-key
The code doesn't know any of this. It just reads an environment variable:
import oskey = os.environ["EMBEDDINGS_API_KEY"]
Two parts of a container app. This is the idea behind the opening question:
| Part | Contains | A change … |
|---|---|---|
Template (properties.template) | image, environment variables, scale rules | builds a new revision |
Configuration (properties.configuration) | secrets, ingress, registries, revision mode | applies to every revision, builds none |
So when you update a secret, no new revision appears. The running containers read their secrets when they started, so they keep the old value. To pick up the new one, restart the revision or deploy a new one:
az containerapp revision restart -n ai-api \-g rg-aca-demo --revision ai-api--v2
If the secret lives in Azure Key Vault, reference it through a managed identity instead. Point at the secret without a version, and Container Apps fetches a new version within 30 minutes and restarts the active revisions that use it.
Pull from a private registry
Production images usually live in a private registry such as Azure Container Registry, so the app needs permission to pull. A username and a password work with almost any registry, but they are one more secret to store and rotate, in every app that pulls. With Azure Container Registry, a managed identity is the better way: the app gets its own identity in Microsoft Entra ID, the registry trusts that identity, and no password exists anywhere.
az containerapp create -n ai-api -g rg-aca-demo \--environment aca-env-demo \--image myregistry.azurecr.io/ai-api:v1 \--registry-server myregistry.azurecr.io \--registry-identity system \--ingress external --target-port 8000
The CLI uses the app's identity and, where it can, assigns the AcrPull role on the registry. AcrPull is the least it needs: it can pull images, never push them. When several apps pull from the same registry, Microsoft recommends a user-assigned identity: it exists before the app, and many apps can share it.
Tags: Container Apps checks the registry for the image every time a container starts. If a tag like latest moves, a restart or a scale-out can quietly run different code, so use a unique tag for every release. After any registry change, deploy a revision and watch it start before you trust it.
Check that it works
A command that finished without an error is not a working app. Check from the outside in:
- The app.
az containerapp showreturns the configuration. Query the ingress for the address, check that it's external if you expect callers from outside, then call a health endpoint. - Console logs are what the container writes: a missing variable, a crash, a wrong port. By default,
logs showprints the last 20 lines from one replica of the latest revision;--followstreams them live. - System logs (
--type system) are what the platform writes: image pulls, probes, restarts. When the container never even started, look there. - Revisions.
revision listshows which revisions are active and their running state: running, activating, degraded or failed. After every update a new one should appear. - Replicas are the running copies of a revision. An empty list doesn't always mean a crash: by default an app scales between zero and ten replicas, so with no traffic it simply has none. The next request starts one and waits for it; if that cold start hurts, set the minimum replicas to one.
az containerapp show -n ai-api -g rg-aca-demo \--query properties.configuration.ingress.fqdnaz containerapp logs show -n ai-api -g rg-aca-demo \--follow --tail 30az containerapp logs show -n ai-api -g rg-aca-demo \--type systemaz containerapp revision list -n ai-api -g rg-aca-demoaz containerapp replica list -n ai-api -g rg-aca-demo
Five quick fixes
| Symptom | Fix |
|---|---|
| You rotated a secret and nothing changed | restart the revision |
| The app works from the inside, not from the internet | its ingress is internal |
| The revision never gets ready | the target port doesn't match the port the container listens on |
| A flag had no effect | you also passed a YAML file, and the file wins |
| The same tag runs different code after a restart | give every release its own tag |




