Appearance
ADR 0044 — Development and Test Environment Strategy ​
Status: Accepted
Date: 2026-06-26
Deciders: Kristopher Turner
Supersedes: —
Related: ADR 0004 (cloud hosting stack), ADR 0025 (infrastructure naming), ADR 0029 (CI/CD Azure auth), ADR 0043 (versioning)
ADO: AB#4479 (epic), AB#4485 (this ADR story)
Context ​
The Heritage Community Hub currently ships directly from main to a single production environment. As the platform grows to support church members and their families, we need:
- A safe place for developers to iterate without affecting production.
- A validated pre-production gate before changes reach members.
- Environment-specific configuration (connection strings, keys, feature flags) that never mixes across tiers.
Decision ​
1. Three-tier environment model ​
| Tier | Purpose | Deployed from |
|---|---|---|
| dev | Daily development iteration; latest code; may be unstable | main branch, on every merge |
| test | Pre-release QA; stable build candidate | release/* branch, on push |
| prod | Live, member-facing | Git tag v*.*.*, after test validation |
No environment can promote to a higher tier without the preceding gate passing.
2. Azure resource topology ​
All three environments live within the same Azure tenant and subscription as production, but in separate resource groups:
| Resource | dev | test | prod |
|---|---|---|---|
| Resource group | hch-dev-rg | hch-test-rg | hch-prod-rg |
| Container Apps env | hch-dev-aca | hch-test-aca | hch-prod-aca |
| PostgreSQL server | hch-dev-pg | hch-test-pg | hch-prod-pg |
| Blob Storage account | hchdevstorage | hchteststorage | hchprodstorage |
| Key Vault | kv-hcs-dev-01 | kv-hcs-test-01 | kv-hcs-vault-01 |
| Container Registry | shared hchacrprod | shared | — (same ACR) |
Why shared ACR? Images are environment-agnostic. The same container image promoted from dev → test → prod. Only environment config changes.
3. Naming convention (extension of ADR 0025) ​
All non-prod resources follow the pattern: {service}-{env}-{suffix}
- Resource groups:
hch-{env}-rg - Container Apps environments:
hch-{env}-aca - PostgreSQL:
hch-{env}-pg - Key Vaults:
kv-hcs-{env}-01 - Storage accounts:
hch{env}storage(no hyphens — storage account constraint) - Managed Identities:
id-hch-api-{env},id-hch-web-{env}
4. Build infrastructure ​
All CI/CD runs on a self-hosted GitHub Actions runner — a persistent Azure Linux VM (Standard_B2s, Ubuntu 22.04 LTS), not Azure Container Apps.
Why not Container Apps runners? Cold-start latency (30–60s), no persistent tool cache, and higher variable cost for frequent builds. A single $30/month B2s VM with Docker and pnpm caching is faster and cheaper for this workload.
The runner VM (hch-runner-vm) lives in a shared hch-infra-rg resource group, separate from the per-environment resource groups. It uses a Managed Identity scoped to push to ACR and deploy to Container Apps across all three environments.
Developer workstation: Windows Server 2025 Datacenter Azure Edition with WSL2 Ubuntu for Linux-native builds, and PowerShell 7 for Azure/ADO tooling.
5. CI/CD promotion gates ​
main merge
└─► Build & push image to ACR
└─► Deploy to dev (auto)
└─► Run smoke tests against dev
│
▼ (manual release cut OR scheduled)
push release/x.y.z branch
└─► Deploy to test (auto)
└─► Run integration tests against test
│
▼ (manual approval gate)
git tag vX.Y.Z
└─► Deploy to prod (requires passing test gate)Smoke tests: API health check, auth round-trip, media URL generation. Integration tests: end-to-end member registration + approval workflow.
5. Secrets management ​
Each environment's Key Vault holds its own complete set of secrets. Secrets are never copied between environments. The naming within each vault is identical (e.g., database-connection-string) to allow the same app config code to work across environments.
Dev and test Key Vaults are seeded manually on first provisioning; subsequent secret rotation is done independently per environment.
6. Data policy ​
- dev: Synthetic seed data only. No production data. Seed script generates realistic-looking but entirely fake members, families, and content.
- test: Same synthetic seed data as dev, potentially with a larger volume for load testing.
- prod: Real member data. Never copied to dev or test.
7. Cost governance ​
| Tier | Target monthly cost | Notes |
|---|---|---|
| dev | ~$40–60 USD | Consumption-tier Container Apps, Burstable B1ms PostgreSQL, LRS storage |
| test | ~$80–120 USD | General Purpose D2s PostgreSQL, slightly higher ACA allocation |
| prod | per existing budget | No change |
Dev resources may be auto-stopped outside business hours using Azure Container Apps scale-to-zero. PostgreSQL dev instance uses Burstable tier.
Consequences ​
- Developers can break dev without risking prod — no more "commit directly to prod via main."
- A manual release-cut step is introduced before test deployment.
- Azure costs increase by ~$120–180/month for dev + test combined.
- Bicep IaC must be parameterized by environment — a single
main.bicepwith anenvironmentNameparameter drives all three tiers. - Secrets in CI/CD (GitHub Actions) must be duplicated per environment (GitHub Environments feature).