Environment Variables & Secrets — Cross-Cutting Rules
Applies to ALL compute targets (App Service, Container Apps, Functions). For Container Apps-specific Bicep patterns (secretRef, identity), see bicep-container-apps.md.
Environment Variable Value Derivation
⛔ Never invent env var values — derive from the app's config class. Cross-reference
.env.example,.env.sample,docker-compose.yml, and the app's config module. Verify each value:
- Type validation: URL-typed fields need valid URLs — not
*or placeholders- Defaults: Use app defaults unless overriding with deployed URL
- Required: Fields without defaults must be provided
Pitfalls:
CORS_ORIGINS=["*"]→ invalid for strict validators (use actual URLs).DATABASE_URL=changethis→ use KV ref. JSON array env vars need Bicep variable escaping:var corsOrigins = '["https://${containerApp.properties.configuration.ingress.fqdn}"]' { name: 'CORS_ORIGINS', value: corsOrigins }
Key Vault Secret Dependency Chain
⛔ Chicken-and-egg: CA references KV secrets that don't exist yet at deploy time. KV secret values (DB connection strings, passwords) are only known AFTER IaC creates the database.
Correct ordering:
- IaC Phase 1: Key Vault → RBAC → Database → Container App with
secrets: [](placeholder image, no KV refs yet) - Deploy phase seeds KV:
az keyvault secret set --vault-name {kv} --name db-connection-string --value {value} - IaC Phase 2: Redeploy Bicep with
isPlaceholder=false→ activates KVsecretRefentries + real image + ACR registries
IaC pattern — reference secrets by name, gated by isPlaceholder:
⛔ Container Apps: KV
secretRefentries MUST be gated behindisPlaceholder(see bicep-container-apps.md § Two-Phase Wiring). Phase 1 deploys withsecrets: []because the CA's managed identity has no RBAC yet. Phase 2 activates KV refs after RBAC propagates.
// Phase 1: secrets: [] (isPlaceholder == true)
// Phase 2: KV secretRef entries activated after RBAC propagates
secrets: isPlaceholder ? [] : [
{
name: 'db-connection-string'
keyVaultUrl: 'https://${keyVault.name}.vault.azure.net/secrets/db-connection-string'
identity: 'system'
}
]⛔ Do NOT hardcode secrets in committed files — not in
main.parameters.json,terraform.tfvars, env vars, or any generated file. (@secure()Bicep params ARE the correct way to pass a secret at deploy time — the ban is on committing the value, not on the parameter.) The deploy phase seeds secrets into Key Vault viaaz keyvault secret setafter database provisioning — see code-deployment-appservice.md or code-deployment-container-apps.md § Database Post-Deploy.
Azure Managed Service SSL/TLS Requirements
⛔ Azure managed databases and caches enforce TLS. Local docker-compose configs typically don't. This mismatch causes container crashes post-deploy.
Check prereq-output.json.warnings[] for warnings with fixPhase: "scaffold". Each warning's fix field describes the required IaC change. Read the app's config loader for the actual env var name.
⛔ Prefer env var override over code change. Only modify source if no env override path exists AND user approves. ⛔ Self-review: If any
fixPhase: "scaffold"warning exists and IaC lacks the fix → flag as FLAGGED.
Key Vault Secret Naming
⛔ KV secret names allow only alphanumeric characters and hyphens. Map env var names:
SECRET_KEY→secret-key,DATABASE_URL→database-url. Do NOT use underscores — Azure rejects them withSecretNameInvalid.
Compose → Azure PaaS Credential Mapping
⛔ Azure managed databases only create the
administratorLoginuser. Docker-composePOSTGRES_USER/MYSQL_USERauto-creates a database user — Azure PostgreSQL/MySQL Flexible Server does NOT. Map compose user env vars to theadministratorLoginvalue from your Bicep, not the compose username.