Appearance
CI/CD pipeline setup ​
Status: Live — implemented 2026-06-21 (ADO AB#3082). Workflow files are in .github/workflows/.
ADR references: ADR 0004 (CI/CD tool choice: GitHub Actions), ADR 0024 (containerised compute: Azure Container Apps + Docker), ADR 0029 (Azure authentication: managed identity with OIDC), ADR 0001 (monorepo structure: pnpm + Turborepo), ADR 0008 (platform composition: API-first, single SDK entry point).
Overview ​
Heritage Community Hub uses GitHub Actions (.github/workflows/deploy.yml) for all continuous integration (CI) and continuous deployment (CD). Azure DevOps (ADO) Boards is the work-tracking tool only — all pipeline YAML lives in GitHub.
The CI/CD design is shaped by three decisions:
- GitHub Actions is the CI/CD tool (ADR 0004). The code lives on GitHub, so keeping CI/CD there eliminates cross-system token management.
- The API is a containerised Node.js application (ADR 0024). The CI pipeline builds a Docker image and pushes it to GitHub Container Registry (GHCR); CD deploys it to Azure Container Apps. The web app is a Vite/React SPA deployed to Azure Static Web Apps.
- Azure authentication uses a user-assigned managed identity with OIDC workload identity federation (ADR 0029). No client secrets are stored in GitHub.
Pipeline structure ​
One workflow file handles both CI and CD: .github/workflows/deploy.yml.
Jobs ​
| Job | Trigger | What it does |
|---|---|---|
build-and-push | Every push to main | Builds the API Docker image and pushes to GHCR (ghcr.io/heritage-virginia/heritage-community-hub/api) |
deploy-api | AZURE_DEPLOY_API_ENABLED == 'true' | Authenticates to Azure via OIDC; runs az containerapp update with the new image tag |
deploy-web | AZURE_DEPLOY_WEB_ENABLED == 'true' | Builds apps/web with Vite (baking in VITE_CLERK_PUBLISHABLE_KEY and VITE_API_BASE_URL); uploads pre-built dist/ to Azure SWA via Azure/static-web-apps-deploy@v1 |
smoke-check | After deploy-api succeeds | curl -f /health against the live Container App URL |
Environment variables baked into the web build ​
| Variable | Value | Notes |
|---|---|---|
VITE_CLERK_PUBLISHABLE_KEY | from GitHub secret | Must be the Clerk publishable key (pk_live_...). Set once per Clerk app creation. |
VITE_API_BASE_URL | https://ca-heritageva-api-prod.orangeriver-77e27b61.eastus.azurecontainerapps.io/api/v1 | Hardcoded in the workflow; update if the Container App FQDN changes |
Deployment gates (repository variables) ​
| Variable | Default | Effect |
|---|---|---|
AZURE_DEPLOY_API_ENABLED | true | Enables deploy-api and smoke-check jobs |
AZURE_DEPLOY_WEB_ENABLED | true | Enables deploy-web job |
Set to false to suspend deployment while keeping builds running (e.g., during infrastructure changes).
Single environment ​
There is one environment: production. There are no dev, staging, or test environments (AB#3080 deleted). The GitHub Actions environment: production block enforces the approval gate.
Secrets management ​
No secrets, tokens, or credentials are stored in pipeline YAML or repository files.
| Secret category | Source | Mechanism |
|---|---|---|
| Database connection string | Azure Key Vault (kv-heritageva-prod) | Container App system-assigned managed identity reads Key Vault at startup |
| Clerk secret key | Azure Key Vault (kv-heritageva-prod, secret: clerk-secret-key) | Same system-assigned managed identity |
| Twilio credentials | Azure Key Vault (kv-heritageva-prod) | Same system-assigned managed identity |
| Azure → GitHub Actions auth | User-assigned managed identity id-heritageva-github-actions | OIDC workload identity federation — see ADR 0029. No client secret stored. |
| Azure SWA deployment token | GitHub Actions secret (AZURE_STATIC_WEB_APPS_API_TOKEN) | Set once by the platform owner; rotated annually |
| Clerk publishable key (web build) | GitHub Actions secret (VITE_CLERK_PUBLISHABLE_KEY) | Public key baked into the Vite bundle at build time; safe to store as a secret |
| GHCR push | secrets.GITHUB_TOKEN (automatic) | GitHub provides this automatically; no configuration needed |
GitHub Actions → Azure authentication ​
The workflow uses azure/login@v2 with the user-assigned managed identity (ADR 0029):
yaml
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}The managed identity id-heritageva-github-actions has a federated credential scoped to repo:Heritage-Virginia/heritage-community-hub:ref:refs/heads/main. The azure/login action exchanges the GitHub OIDC token for a short-lived Azure access token — no stored secret.
The workflow has permissions: id-token: write to enable OIDC token issuance.
Container App → Key Vault authentication ​
The Container App uses its system-assigned managed identity (separate from the GitHub Actions managed identity) with Key Vault Secrets User role on kv-heritageva-prod. Application secrets are injected as Container App secrets (backed by Key Vault references) at container startup.
Container registry ​
API images are pushed to GHCR (GitHub Container Registry), not Azure Container Registry.
| Detail | Value |
|---|---|
| Registry | ghcr.io |
| Image | ghcr.io/heritage-virginia/heritage-community-hub/api |
| Tags | sha-<7-char-git-sha> (primary) + latest |
| Auth | docker/login-action@v3 with secrets.GITHUB_TOKEN |
The GHCR package is private. The Container App pulls via a registry credential (ghcr-token secret on the Container App).
Branch strategy ​
| Rule | Detail |
|---|---|
main is production | No direct commits to main. All changes arrive via pull request. |
| Feature branches | Named feat/, fix/, chore/, or docs/ following the project commit convention |
| No long-lived branches | Feature branches are merged and deleted |
| PR required to merge | Branch protection on main requires at least one approving review and passing CI |
| Mobile release branches | release/mobile/<version> for EAS Build (Expo Application Services) — separate from the web/API pipeline (ADR 0002) |
ADO integration ​
- Commit messages include
AB#<id>references. ADO automatically transitions linked work items when the commit reachesmain. - Pull request descriptions include
AB#<id>to link to the ADO work item. - ADO Pipelines is not used (ADR 0004). All pipeline YAML lives in
.github/workflows/.