Safeguard
Container Security

Security best practices for Azure Container Apps and secr...

A step-by-step guide to Azure Container Apps security best practices: managed identity, Key Vault-backed secrets, network ingress, and supply chain hardening.

Karan Patel
Cloud Security Engineer
7 min read

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:system or 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=value instead of a keyvaultref. 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.type to internal.
  • Image pull fails after disabling ACR admin: Verify the managed identity has AcrPull role on the registry scope, not just registry attachment via az 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.