Appearance
Heritage Community Hub — Environment Strategy ​
Owner: Kristopher Turner
Date: 2026-06-26
Status: Draft (pending AB#4479 implementation)
ADR: ADR 0044
Epic: AB#4479 — Establish Development and Test Environments
Overview ​
This document covers the detailed design of the dev/test environment infrastructure for the Heritage Community Hub. It translates the policy decisions in ADR 0044 into concrete Azure resources, Bicep templates, CI/CD workflow changes, and operational runbooks.
1. Resource Inventory ​
1.1 Shared resources (already exist, not duplicated) ​
| Resource | Name | Notes |
|---|---|---|
| Azure Container Registry | hchacrprod (or existing name) | All envs push/pull images here |
| Azure Tenant | hybridsolutions.cloud | Single tenant |
| Azure Subscription | existing | All envs in same sub |
| GitHub repo | Heritage-Virginia/heritage-community-hub | Single repo, multiple branches |
1.2 Dev environment resources ​
| Resource | Name | SKU / Tier | Region |
|---|---|---|---|
| Resource Group | hch-dev-rg | — | East US (match prod) |
| Container Apps Environment | hch-dev-aca | Consumption | East US |
| Container App — API | hch-api-dev | 0.25 vCPU / 0.5 GiB | auto-scale 0–3 |
| Container App — Web | hch-web-dev | 0.25 vCPU / 0.5 GiB | auto-scale 0–2 |
| PostgreSQL Flexible Server | hch-dev-pg | Burstable B1ms, 32 GiB | East US |
| Blob Storage Account | hchdevstorage | LRS, Standard | East US |
| Key Vault | kv-hcs-dev-01 | Standard | East US |
| Managed Identity — API | id-hch-api-dev | — | East US |
| Managed Identity — Web | id-hch-web-dev | — | East US |
1.3 Test environment resources ​
| Resource | Name | SKU / Tier | Region |
|---|---|---|---|
| Resource Group | hch-test-rg | — | East US |
| Container Apps Environment | hch-test-aca | Consumption | East US |
| Container App — API | hch-api-test | 0.5 vCPU / 1 GiB | auto-scale 1–5 |
| Container App — Web | hch-web-test | 0.25 vCPU / 0.5 GiB | auto-scale 0–3 |
| PostgreSQL Flexible Server | hch-test-pg | General Purpose D2s v3, 64 GiB | East US |
| Blob Storage Account | hchteststorage | LRS, Standard | East US |
| Key Vault | kv-hcs-test-01 | Standard | East US |
| Managed Identity — API | id-hch-api-test | — | East US |
| Managed Identity — Web | id-hch-web-test | — | East US |
2. Bicep Infrastructure-as-Code ​
The Bicep templates live at infrastructure/. A single parameterized module drives all environments.
2.1 Parameter file structure ​
infrastructure/
├── modules/
│ ├── container-apps.bicep — Container Apps environment + apps
│ ├── postgresql.bicep — PostgreSQL Flexible Server
│ ├── storage.bicep — Blob Storage account + containers
│ └── key-vault.bicep — Key Vault + RBAC assignments
├── environments/
│ ├── dev.bicepparam — dev environment parameter overrides
│ ├── test.bicepparam — test environment parameter overrides
│ └── prod.bicepparam — prod environment parameters
└── main.bicep — top-level orchestration module2.2 Key main.bicep parameters ​
bicep
@allowed(['dev', 'test', 'prod'])
param environmentName string
param location string = 'eastus'
param containerRegistryName string
param imageTag string = 'latest'All resource names are derived: 'hch-${environmentName}-rg', 'kv-hcs-${environmentName}-01', etc.
2.3 Deployment command ​
powershell
az deployment group create `
--resource-group "hch-${env}-rg" `
--template-file infrastructure/main.bicep `
--parameters infrastructure/environments/${env}.bicepparam `
--parameters imageTag=$imageTag3. CI/CD Pipeline Design ​
3.1 GitHub Actions workflow structure ​
.github/workflows/
├── ci.yml — build + test (runs on all PRs and branch pushes)
├── deploy-dev.yml — deploy to dev (triggers on main merge)
├── deploy-test.yml — deploy to test (triggers on release/* branch push)
└── deploy-prod.yml — deploy to prod (triggers on v*.*.* tag, requires test approval)3.2 GitHub Environments ​
Three GitHub Environments are configured in repo settings:
| Environment | Protection rules | Secrets |
|---|---|---|
dev | None — auto-deploy | AZURE_CLIENT_ID_DEV, DEV_CONNECTION_STRING, etc. |
test | Require status check from dev | AZURE_CLIENT_ID_TEST, etc. |
prod | Required reviewer: Kristopher Turner | AZURE_CLIENT_ID_PROD, etc. |
3.3 Deploy-dev workflow (simplified) ​
yaml
name: Deploy to Dev
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
az acr build --registry $ACR_NAME --image hch-api:$GITHUB_SHA .
deploy:
needs: build
environment: dev
steps:
- name: Deploy to Container Apps (dev)
run: |
az containerapp update \
--name hch-api-dev \
--resource-group hch-dev-rg \
--image $ACR_NAME/hch-api:$GITHUB_SHA3.4 Promotion to test ​
Test deployment is triggered by pushing a release/x.y.z branch. This is the manual release-cut gesture:
bash
git checkout -b release/0.10.0
git push origin release/0.10.0This automatically triggers deploy-test.yml.
3.5 Production deployment ​
bash
git tag v0.10.0
git push origin v0.10.0Triggers deploy-prod.yml, which requires a manual approval in the prod GitHub Environment before it proceeds.
4. Secret Naming Convention ​
Each Key Vault holds the same set of secret names across environments. This allows the API to use @Microsoft.KeyVault(VaultName=kv-hcs-${env}-01;SecretName=...) without environment-specific code paths.
| Secret name | Purpose |
|---|---|
database-connection-string | PostgreSQL connection string |
clerk-secret-key | Clerk backend API key (separate per-env Clerk application) |
clerk-publishable-key | Clerk frontend publishable key |
azure-storage-connection-string | Blob storage connection string |
cloudflare-stream-api-token | Cloudflare Stream API token |
ado-pat | Azure DevOps PAT for in-app feedback |
Dev and test use separate Clerk applications — never share production Clerk credentials with non-prod environments. Create a separate Clerk app in the Clerk dashboard for dev and test.
5. Data Seeding ​
5.1 Seed script location ​
apps/api/prisma/
├── seed.ts — main seed entry point
├── seed/
│ ├── members.ts — synthetic member + family group data
│ ├── media.ts — media series + items (synthetic)
│ └── announcements.ts5.2 Synthetic data policy ​
- All member names are randomly generated (no real people).
- Email addresses use the
@heritage-test.localdomain. - Phone numbers follow the
(555) 000-XXXXpattern (reserved in NANP). - No production database backup is ever restored to dev or test.
- The seed command is run after each environment provisioning and can be re-run at any time (idempotent via
upsert).
5.3 Seed command ​
bash
pnpm --filter @hch/api db:seed
# or: cd apps/api && npx prisma db seed6. Domain and DNS ​
Dev and test environments use subdomains of hybridsolutions.cloud (or a separate heritage-test.cloud domain):
| Environment | API URL | Web app URL |
|---|---|---|
| dev | api-dev.heritage-community.app | app-dev.heritage-community.app |
| test | api-test.heritage-community.app | app-test.heritage-community.app |
| prod | api.heritage-community.app | app.heritage-community.app |
Custom domain configuration is applied via Azure Container Apps custom domain settings + Azure DNS records. TLS certificates are managed by Azure (auto-renewal).
7. Build Environment ​
The Heritage Community Hub is developed on a Windows Server 2025 Datacenter Azure Edition host running in Azure. Development and CI/CD builds can originate from one of three options:
7.1 Current developer workstation ​
| Component | Details |
|---|---|
| Host OS | Windows Server 2025 Datacenter Azure Edition |
| Shell | PowerShell 7 (primary), Git Bash (secondary) |
| WSL | WSL2 available for Linux-native tooling (Node.js, Docker, pnpm) |
| Hyper-V | Enabled — Hyper-V VMs can be run locally on the host |
| Platform | Azure VM (Standard_D or E series) |
For local development, the preferred approach is:
- WSL2 Ubuntu for running
pnpm dev,prisma migrate dev, Docker builds - PowerShell 7 for Azure CLI, ADO CLI, and deployment scripts
- Git Bash available as a fallback for POSIX scripts
7.2 Self-hosted GitHub Actions Runner ​
Decision: GitHub Actions self-hosted runner runs on an inexpensive Azure Linux VM, NOT on Azure Container Apps. Container Apps runners were removed from scope due to cold-start overhead and complexity.
The self-hosted runner is a persistent Linux VM that handles all CI/CD:
| Property | Value |
|---|---|
| Resource name | hch-runner-vm |
| Resource group | hch-infra-rg (shared infrastructure, not per-env) |
| VM size | Standard_B2s (2 vCPU, 4 GiB RAM) — ~$30/month |
| OS | Ubuntu 22.04 LTS |
| Disk | 64 GiB Premium SSD P4 |
| Region | East US (same as all other resources) |
| Runner label | self-hosted, linux, heritage |
Installed toolchain on runner VM:
Node.js 20 LTS
pnpm 9.x
Docker CE (for container builds)
Azure CLI
GitHub Actions runner service (systemd)Why a VM instead of Container Apps runners?
- No cold-start delay (container runners take 30–60s to provision)
- Persistent tool cache:
node_modules, Docker layer cache, pnpm store - Simple to maintain — one
apt update+pnpminstall - Significantly cheaper at ~$30/month vs. Container Apps consumption billing for frequent builds
7.3 Hyper-V build VM (optional local option) ​
As an alternative for local validation before pushing to the remote runner, a Hyper-V Ubuntu VM can be created on the developer host:
| Property | Value |
|---|---|
| VM name | hch-build-vm |
| OS | Ubuntu 22.04 LTS |
| Memory | 4 GiB dynamic |
| Disk | 60 GiB VHDX |
| Network | Internal + NAT switch (internet access via host) |
| Use case | Local integration tests, Prisma migrations, Docker builds without pushing |
This is optional and not part of the required infrastructure — it's a convenience for the developer.
8. Cost Estimate ​
| Resource | Dev/month | Test/month | Notes |
|---|---|---|---|
| Container Apps (Consumption) | ~$5–15 | ~$10–25 | Scale-to-zero in dev |
| PostgreSQL Flexible Server | ~$15 (B1ms) | ~$50 (D2s) | |
| Blob Storage | ~$1 | ~$2 | LRS |
| Key Vault | ~$1 | ~$1 | Standard tier |
| DNS / custom domains | ~$1 | ~$1 | Azure DNS |
| Subtotal (per env) | ~$23–33 | ~$64–79 |
Shared infrastructure (one-time):
| Resource | Monthly cost | Notes |
|---|---|---|
Self-hosted runner VM (Standard_B2s) | ~$30 | Persistent Linux VM, all environments |
| Runner VM storage (64 GiB P4) | ~$5 | |
| Runner subtotal | ~$35 | Replaces Container Apps runner (~$0–80/month variable) |
Total additional monthly cost: ~$122–147/month (dev + test envs + shared runner).
8. Open Questions ​
- Clerk application setup: Does the team have credits/plan that covers 2 additional Clerk apps for dev and test?
- Cloudflare Stream: Use the same account with a dev-prefixed namespace, or separate accounts?
- Database tier for test: D2s vs. Burstable B2ms — depends on expected QA test volume.
- Auto-shutdown for dev: Enable Container Apps scale-to-zero after 30min of inactivity? This adds ~10s cold start on first request.
- ADO pipeline integration: Should CI/CD use Azure Pipelines instead of GitHub Actions for closer ADO integration?