Skip to content

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) ​

ResourceNameNotes
Azure Container Registryhchacrprod (or existing name)All envs push/pull images here
Azure Tenanthybridsolutions.cloudSingle tenant
Azure SubscriptionexistingAll envs in same sub
GitHub repoHeritage-Virginia/heritage-community-hubSingle repo, multiple branches

1.2 Dev environment resources ​

ResourceNameSKU / TierRegion
Resource Grouphch-dev-rgEast US (match prod)
Container Apps Environmenthch-dev-acaConsumptionEast US
Container App — APIhch-api-dev0.25 vCPU / 0.5 GiBauto-scale 0–3
Container App — Webhch-web-dev0.25 vCPU / 0.5 GiBauto-scale 0–2
PostgreSQL Flexible Serverhch-dev-pgBurstable B1ms, 32 GiBEast US
Blob Storage AccounthchdevstorageLRS, StandardEast US
Key Vaultkv-hcs-dev-01StandardEast US
Managed Identity — APIid-hch-api-devEast US
Managed Identity — Webid-hch-web-devEast US

1.3 Test environment resources ​

ResourceNameSKU / TierRegion
Resource Grouphch-test-rgEast US
Container Apps Environmenthch-test-acaConsumptionEast US
Container App — APIhch-api-test0.5 vCPU / 1 GiBauto-scale 1–5
Container App — Webhch-web-test0.25 vCPU / 0.5 GiBauto-scale 0–3
PostgreSQL Flexible Serverhch-test-pgGeneral Purpose D2s v3, 64 GiBEast US
Blob Storage AccounthchteststorageLRS, StandardEast US
Key Vaultkv-hcs-test-01StandardEast US
Managed Identity — APIid-hch-api-testEast US
Managed Identity — Webid-hch-web-testEast 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 module

2.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=$imageTag

3. 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:

EnvironmentProtection rulesSecrets
devNone — auto-deployAZURE_CLIENT_ID_DEV, DEV_CONNECTION_STRING, etc.
testRequire status check from devAZURE_CLIENT_ID_TEST, etc.
prodRequired reviewer: Kristopher TurnerAZURE_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_SHA

3.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.0

This automatically triggers deploy-test.yml.

3.5 Production deployment ​

bash
git tag v0.10.0
git push origin v0.10.0

Triggers 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 namePurpose
database-connection-stringPostgreSQL connection string
clerk-secret-keyClerk backend API key (separate per-env Clerk application)
clerk-publishable-keyClerk frontend publishable key
azure-storage-connection-stringBlob storage connection string
cloudflare-stream-api-tokenCloudflare Stream API token
ado-patAzure 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.ts

5.2 Synthetic data policy ​

  • All member names are randomly generated (no real people).
  • Email addresses use the @heritage-test.local domain.
  • Phone numbers follow the (555) 000-XXXX pattern (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 seed

6. Domain and DNS ​

Dev and test environments use subdomains of hybridsolutions.cloud (or a separate heritage-test.cloud domain):

EnvironmentAPI URLWeb app URL
devapi-dev.heritage-community.appapp-dev.heritage-community.app
testapi-test.heritage-community.appapp-test.heritage-community.app
prodapi.heritage-community.appapp.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 ​

ComponentDetails
Host OSWindows Server 2025 Datacenter Azure Edition
ShellPowerShell 7 (primary), Git Bash (secondary)
WSLWSL2 available for Linux-native tooling (Node.js, Docker, pnpm)
Hyper-VEnabled — Hyper-V VMs can be run locally on the host
PlatformAzure 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:

PropertyValue
Resource namehch-runner-vm
Resource grouphch-infra-rg (shared infrastructure, not per-env)
VM sizeStandard_B2s (2 vCPU, 4 GiB RAM) — ~$30/month
OSUbuntu 22.04 LTS
Disk64 GiB Premium SSD P4
RegionEast US (same as all other resources)
Runner labelself-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 + pnpm install
  • 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:

PropertyValue
VM namehch-build-vm
OSUbuntu 22.04 LTS
Memory4 GiB dynamic
Disk60 GiB VHDX
NetworkInternal + NAT switch (internet access via host)
Use caseLocal 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 ​

ResourceDev/monthTest/monthNotes
Container Apps (Consumption)~$5–15~$10–25Scale-to-zero in dev
PostgreSQL Flexible Server~$15 (B1ms)~$50 (D2s)
Blob Storage~$1~$2LRS
Key Vault~$1~$1Standard tier
DNS / custom domains~$1~$1Azure DNS
Subtotal (per env)~$23–33~$64–79

Shared infrastructure (one-time):

ResourceMonthly costNotes
Self-hosted runner VM (Standard_B2s)~$30Persistent Linux VM, all environments
Runner VM storage (64 GiB P4)~$5
Runner subtotal~$35Replaces Container Apps runner (~$0–80/month variable)

Total additional monthly cost: ~$122–147/month (dev + test envs + shared runner).


8. Open Questions ​

  1. Clerk application setup: Does the team have credits/plan that covers 2 additional Clerk apps for dev and test?
  2. Cloudflare Stream: Use the same account with a dev-prefixed namespace, or separate accounts?
  3. Database tier for test: D2s vs. Burstable B2ms — depends on expected QA test volume.
  4. Auto-shutdown for dev: Enable Container Apps scale-to-zero after 30min of inactivity? This adds ~10s cold start on first request.
  5. ADO pipeline integration: Should CI/CD use Azure Pipelines instead of GitHub Actions for closer ADO integration?

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