Azure Container Apps makes it trivially easy to ship a serverless container to production — az containerapp up and you're live. That speed is exactly why so many teams end up with secrets sitting in plaintext environment variables, container images pulled from unscoped registries, and ingress wide open to the internet. Following solid Azure Container Apps security best practices isn't about slowing down deployment; it's about closing the handful of gaps that attackers actually exploit: leaked credentials, over-permissioned identities, and unverified images moving through the pipeline. In this guide, you'll walk through six concrete, sequential steps to lock down a Container Apps environment — from wiring up managed identity and Key Vault-backed secrets to restricting network ingress and verifying your supply chain — plus a troubleshooting checklist to confirm each control is actually working before you call the environment production-ready.
Step 1: Replace Static Credentials with Container Apps Managed Identity
The single highest-leverage change you can make is eliminating long-lived credentials from your app entirely. Container Apps managed identity lets your app authenticate to Azure Key Vault, Storage, SQL, and other services using an identity that Azure rotates and manages for you — no connection strings, no client secrets sitting in config.
Enable a system-assigned identity on an existing app:
az containerapp identity assign \
--name my-app \
--resource-group my-rg \
--system-assigned
Grab the principal ID and grant it scoped access — for example, Key Vault Secrets User on a specific vault rather than Contributor on the subscription:
PRINCIPAL_ID=$(az containerapp identity show \
--name my-app --resource-group my-rg \
--query principalId -o tsv)
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub-id>/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault
If your app needs to reach a resource in another resource group or subscription, use a user-assigned identity instead so it survives app recreation and can be shared across revisions without re-provisioning permissions each time.
Step 2: Apply Azure Container Apps Security Best Practices to Secrets Management
Container Apps has a built-in secrets store, but it's easy to misuse — secrets defined inline in az containerapp create --secrets land in the app's ARM template and are visible to anyone with read access to the resource. Proper Container Apps secrets management means treating the built-in store as a reference layer, not the source of truth, and backing it with Key Vault.
Reference a Key Vault secret directly instead of hardcoding a value:
az containerapp secret set \
--name my-app \
--resource-group my-rg \
--secrets "db-password=keyvaultref:https://my-vault.vault.azure.net/secrets/db-password,identityref:system"
Then map it to an environment variable in your container spec:
containers:
- name: my-app
image: myregistry.azurecr.io/my-app:latest
env:
- name: DB_PASSWORD
secretRef: db-password
This way the secret value never appears in deployment logs, ARM exports, or az containerapp show output — only the Key Vault reference does. Audit existing apps for inline secrets with:
az containerapp secret list --name my-app --resource-group my-rg -o table
Any secret without a keyVaultUrl populated is a candidate for migration.
Step 3: Lock Down Ingress and Adopt VNET Integration
By default, external ingress on a Container App is reachable from the public internet on the assigned FQDN. For internal services, disable external ingress entirely:
az containerapp ingress update \
--name my-app \
--resource-group my-rg \
--type internal
For workloads that must be internet-facing, deploy the Container Apps environment into a custom VNET and put an Application Gateway or Front Door with a WAF policy in front of it, rather than exposing the environment's default domain directly:
az containerapp env create \
--name my-env \
--resource-group my-rg \
--infrastructure-subnet-resource-id /subscriptions/<sub-id>/resourceGroups/my-rg/providers/Microsoft.Network/virtualNetworks/my-vnet/subnets/infra-subnet
This is where serverless container security Azure practices diverge most from traditional VM hardening — you're not patching an OS, but you are responsible for the network boundary and TLS termination points around each revision.
Step 4: Harden the Container Image Supply Chain
A hardened identity and network posture is undermined if the image itself is untrusted. Restrict Container Apps to pull only from Azure Container Registry (ACR) with content trust and vulnerability scanning enabled, and use managed identity for registry authentication instead of admin credentials:
az containerapp registry set \
--name my-app \
--resource-group my-rg \
--server myregistry.azurecr.io \
--identity system
Disable the ACR admin account entirely once identity-based pull is confirmed working:
az acr update --name myregistry --admin-enabled false
Pin images by digest rather than the latest tag in production revisions, and require image signing (Notary v2 / cosign) as a gate in CI before a build can be pushed to the registry your Container Apps environment pulls from.
Step 5: Enforce Least-Privilege RBAC on the Environment and Revisions
Container Apps environments are frequently shared across teams, which makes RBAC scope the difference between one team's bad deploy and a full-environment incident. Assign roles at the individual Container App level, not the resource group, wherever possible:
az role assignment create \
--assignee user@company.com \
--role "Container Apps Contributor" \
--scope /subscriptions/<sub-id>/resourceGroups/my-rg/providers/Microsoft.App/containerApps/my-app
Separate CI/CD service principals so the pipeline identity that deploys revisions is distinct from the identity apps use at runtime — collapsing these into one identity is a common finding that turns a compromised pipeline into full data-plane access.
Step 6: Enable Diagnostic Logging and Secret Exposure Scanning
Turn on diagnostic settings for the Container Apps environment and route logs to Log Analytics so you can detect anomalous secret access or ingress patterns:
az monitor diagnostic-settings create \
--name containerapp-diagnostics \
--resource /subscriptions/<sub-id>/resourceGroups/my-rg/providers/Microsoft.App/managedEnvironments/my-env \
--workspace /subscriptions/<sub-id>/resourceGroups/my-rg/providers/Microsoft.OperationalInsights/workspaces/my-logs \
--logs '[{"category":"ContainerAppConsoleLogs","enabled":true},{"category":"ContainerAppSystemLogs","enabled":true}]'
Layer in automated scanning for secrets accidentally committed to the app's image or repo — a single hardcoded API key baked into a layer can bypass every control set up in Steps 1 through 5.
Troubleshooting and Verification Checklist
Once the controls above are in place, verify them rather than assuming they took effect:
- Managed identity not authenticating: Confirm the role assignment has propagated (can take up to 5 minutes) and that the app is using
identityref:systemor the correct user-assigned client ID, not a leftover connection string still referenced elsewhere in the spec. - Secret shows as plaintext in
az containerapp show: This means it was set with--secrets key=valueinstead of akeyvaultref. Re-run Step 2 and redeploy the revision — old revisions retain the old secret value until fully deprovisioned. - Internal ingress still reachable externally: Check that DNS and any Front Door/App Gateway rules weren't left pointing at the public FQDN after switching
ingress.typetointernal. - Image pull fails after disabling ACR admin: Verify the managed identity has
AcrPullrole on the registry scope, not just registry attachment viaaz containerapp registry set. - Diagnostic logs missing categories: List available categories with
az monitor diagnostic-settings categories list --resource <env-id>— category names vary slightly between environment types (Consumption vs. Dedicated plan).
Run az containerapp revision list --name my-app --resource-group my-rg -o table periodically to confirm old revisions with deprecated secrets or identities aren't still receiving traffic.
How Safeguard Helps
Manually auditing every Container App, revision, and Key Vault reference across a growing Azure footprint doesn't scale — and it's exactly where these best practices quietly erode over time as new services get shipped under deadline pressure. Safeguard continuously inventories your Container Apps environments, flags secrets that bypass Key Vault, and verifies managed identity role assignments against least-privilege baselines instead of relying on point-in-time manual review.
Beyond runtime configuration, Safeguard traces the full software supply chain feeding each Container App: verifying image provenance, catching unsigned or unpinned images before they reach a revision, and correlating registry pull events with the identity that authenticated them. When a misconfiguration does slip through — an inline secret, an admin-enabled registry, an internal app exposed via a stray DNS record — Safeguard surfaces it with the exact resource and remediation command, so your team fixes the specific drift instead of re-auditing the entire environment from scratch.