CI/CD with GitHub Actions
- Status: Accepted
- Date: 2026-06-06
- Deciders: Engineering, Platform
Context
Section titled “Context”The repo is a pnpm monorepo on GitHub (quarry-design-group/quarry.team). Deploying the site depends on Cloudflare infrastructure (single provider, ADR 0008) being provisioned and current, but that infrastructure changes rarely and holds stateful resources — a D1 leads database, R2 buckets — that an unreviewed apply must never destroy. We need quality gates that protect the craft bar (types, lint, tests, a11y, performance budgets), per-PR previews so reviewers see real output, fast application deploys on merge, and infrastructure changes that are reviewed and applied deliberately rather than on every push.
Decision
Section titled “Decision”CI/CD runs on GitHub Actions as two intentionally decoupled tracks: application deploys on push to main, and infrastructure reconciled through a reviewed, manually gated Terraform workflow.
Pipeline shape
Section titled “Pipeline shape”- Validate (every PR). Install with pnpm (frozen lockfile), then run, with caching and affected-package awareness where practical:
pnpm typecheck(TypeScript strict +astro check)pnpm lint,pnpm format:checkpnpm test(Vitest unit) andpnpm test:e2e(Playwright) — ADR 0012- axe accessibility checks and Lighthouse CI against the performance/a11y budgets
- token freshness: rebuild tokens and fail if
tokens.cssis stale (ADR 0003) terraform planfor changed environments (no apply on PRs)
- Preview deploy (PRs). Build the Astro site and publish a per-PR Cloudflare Workers preview so reviewers inspect the actual deployed result.
- Deploy application (push to
main). Merging tomainruns the Deploy Web workflow:pnpm build, thenwrangler deployships the Astro site as a single Cloudflare Worker (static assets + the same-origin/api/contactand/api/careersroutes) and applies any pending D1 migrations. Staging deploys on every push behindSTAGING_DEPLOY_ENABLED; production runs the same job behindDEPLOY_ENABLED. - Reconcile infrastructure (manual, out of band).
terraform applyruns only by gated manualworkflow_dispatch— never on push — backed byterraform planon infra PRs and a weekly scheduled drift check. Infrastructure is reconciled deliberately, ahead of any deploy that depends on new resources. - Releases (Release Drafter).
release-drafter.ymlruns on push tomainand maintains a rolling draft GitHub Release with categorized notes and a suggested version derived from Conventional PR titles. Publishing the draft tagsvX.Y.Z; releases are communication checkpoints and never gate deploys. See the release runbook.
Gating
Section titled “Gating”- Required status checks (validate suite + budgets) must pass before merge.
- Application deploys run only from
main, behind theSTAGING_DEPLOY_ENABLED/DEPLOY_ENABLEDgate variables.terraform applyruns only by manualworkflow_dispatch, never automatically — an unreviewed auto-apply could destroy stateful infrastructure. - Releases are deliberate acts: publishing the accumulated draft Release is what cuts a tag. (No tag or Release exists until a maintainer publishes.)
- Secrets/credentials come from GitHub Actions encrypted secrets and provider secret stores — never the repo (ADR 0008,
SECURITY.md).
Consequences
Section titled “Consequences”Positive
- Application and infrastructure changes are each auditable in CI, and their separation keeps a routine app deploy from ever triggering an unreviewed infrastructure change.
- Every PR gets a real preview URL and must clear the full quality bar.
- Performance, a11y, and token-freshness regressions are caught before merge, not after.
Negative / costs
- Splitting deploys across a push-triggered app track and a manually gated infrastructure track means the two must be sequenced by hand when a deploy depends on new infrastructure.
- Full gates add minutes to CI (mitigated by caching and affected-only runs).
- Preview deploys consume Cloudflare preview capacity.
Alternatives considered
Section titled “Alternatives considered”- Provider-native CI (Cloudflare Workers Git integration / Wrangler auto-deploy) only. Convenient, but can’t enforce our full unified quality gates or a reviewed Terraform workflow. Rejected as the orchestrator; we still lean on Cloudflare Workers builds for preview hosting.
- GitLab CI / CircleCI / Jenkins. Capable, but the repo is on GitHub and Actions is the lowest-friction, best-integrated choice. Rejected.
- Manual deploys. Unauditable and error-prone; defeats the purpose. Rejected.