0009. Container Apps module and the dev environment
- Status: accepted
- Date: 2026-10-08
- Issue: #9
Context
Bootstrap (#8, ADR 0008) created resource groups, remote state, and GitHub OIDC. Pixel still was not running. This stack deploys the bot into pixel-dev as a single Container App. Prod (#12) should be the same module with a different env root.
Ground truth is the architecture Deployment section and the comments on #9, not the original issue body (optional volume, Key Vault for members.yaml, and a block-list are all stale).
Decision
infra/modules/pixeldescribes one environment.infra/envs/devcalls it. The module does not create the env resource group; it deploys intopixel-dev. Remote state is thedevcontainer onpixelbartfstate.- Exactly one replica (
min_replicas = 1,max_replicas = 1),revision_mode = Single, no public ingress. Discord needs an always-on process. Two replicas answer every command twice. Overlap during a revision swap is #11 (stop-then-start). There is no database and no lock. - Writable Azure Files volume (required) at
/app/persistformembers.yaml,config/home-assistant/, andPIXEL_DATA_DIR(schedules.yamlmust not be lost;home-switches.statelost → doors start off). Mount options setuid=1000,gid=1000so the image'snodeuser can write after the mount replaces/app/data. Daily Azure Backup of the share, 14-day keep, plus 14-day share soft-delete. admins.yamlis a read-only Key Vault secret, mounted as a file. Admin membership is never changed by a command.- Secret values never go in Terraform state. They may be passed as ephemeral write-only apply inputs (
write_secrets = true,value_woonazurerm_key_vault_secret) from gitignoredterraform.tfvars/TF_VAR_*, or set withaz/ the portal. CI plans withterraform.example.tfvars, which leaveswrite_secretsoff. The Container App references secrets by URI only. - Azure Files SMB still needs the storage account key to register the share on the Container Apps environment. Identity-based mounts are not supported there yet. That key is a platform credential and will appear in state. Pixel tokens and YAML do not. NFS was rejected because it needs a VNet and a premium 100 GiB share.
- Home Assistant: a Tailscale sidecar on the replica is the preferred path (
tailscale_enabled). It runs userspace (no TUN).HOME_ASSISTANT_URL+HOME_ASSISTANT_TOKENstay as today, so a Nabu Casa URL works if Tailscale is not ready. Both URL and token or neither. Dev must not control the real doors. - Images:
ghcr.io/pixelbar/pixel:<sha>is #10. First apply may use a public GHCR tag (container_image). On dev, Terraform then ignorestemplate[0].container[0].imageonazurerm_container_app.thisso apply cannot roll the bot back to a stale tfvars SHA (#73).just deploy-dev/az containerapp updateowns the running pin (ADR 0010). Prod (#12) usesazurerm_container_app.managedwith no image ignore: apply setscontainer_image. Apply still manages vault, volume, env, and replica count. Do not apply from CD to "fix" dev image drift. If the package is still private, the module accepts an optional registry (container_registry_server+ username + Key Vaultghcr-pull-token). The image containscontent/only. - Logs: Log Analytics, 30 days (documented in
docs/privacy.md). Sentry retention is the same period once set in the project. - CI:
just tf-validateformat-checks and validates bootstrap andenvs/dev.terraform planonenvs/devuses GitHub EnvironmentdevOIDC and fails closed ifARM_*is missing. No apply on merge.
Consequences
- Applying this stack does not make Discord answer until secrets, volume files, and an image exist, and until nobody else uses that bot token.
- GitHub Environments
dev/prodand theirARM_*variables are still needed for #10 OIDC, not for writing this Terraform. - Changing replica count requires editing the module; it is not a variable.
- A later dev apply cannot roll the bot back to a stale
container_imagein tfvars. To change the running dev image, usejust deploy-devoraz containerapp update. Prod apply will setcontainer_imagewhen that env exists. - Prod (#12) is another env root, with purge protection on and no purge-on-destroy.