Skip to content

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:

  1. A safe place for developers to iterate without affecting production.
  2. A validated pre-production gate before changes reach members.
  3. Environment-specific configuration (connection strings, keys, feature flags) that never mixes across tiers.

Decision ​

1. Three-tier environment model ​

TierPurposeDeployed from
devDaily development iteration; latest code; may be unstablemain branch, on every merge
testPre-release QA; stable build candidaterelease/* branch, on push
prodLive, member-facingGit 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:

Resourcedevtestprod
Resource grouphch-dev-rghch-test-rghch-prod-rg
Container Apps envhch-dev-acahch-test-acahch-prod-aca
PostgreSQL serverhch-dev-pghch-test-pghch-prod-pg
Blob Storage accounthchdevstoragehchteststoragehchprodstorage
Key Vaultkv-hcs-dev-01kv-hcs-test-01kv-hcs-vault-01
Container Registryshared hchacrprodshared— (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 ​

TierTarget monthly costNotes
dev~$40–60 USDConsumption-tier Container Apps, Burstable B1ms PostgreSQL, LRS storage
test~$80–120 USDGeneral Purpose D2s PostgreSQL, slightly higher ACA allocation
prodper existing budgetNo 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.bicep with an environmentName parameter drives all three tiers.
  • Secrets in CI/CD (GitHub Actions) must be duplicated per environment (GitHub Environments feature).

Heritage Community Hub — Internal. Access restricted via Cloudflare Access + Entra ID.