Skip to content

CI/CD with GitHub Actions

  • Status: Accepted
  • Date: 2026-06-06
  • Deciders: Engineering, Platform

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.

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.

  1. 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:check
    • pnpm test (Vitest unit) and pnpm test:e2e (Playwright) — ADR 0012
    • axe accessibility checks and Lighthouse CI against the performance/a11y budgets
    • token freshness: rebuild tokens and fail if tokens.css is stale (ADR 0003)
    • terraform plan for changed environments (no apply on PRs)
  2. Preview deploy (PRs). Build the Astro site and publish a per-PR Cloudflare Workers preview so reviewers inspect the actual deployed result.
  3. Deploy application (push to main). Merging to main runs the Deploy Web workflow: pnpm build, then wrangler deploy ships the Astro site as a single Cloudflare Worker (static assets + the same-origin /api/contact and /api/careers routes) and applies any pending D1 migrations. Staging deploys on every push behind STAGING_DEPLOY_ENABLED; production runs the same job behind DEPLOY_ENABLED.
  4. Reconcile infrastructure (manual, out of band). terraform apply runs only by gated manual workflow_dispatch — never on push — backed by terraform plan on infra PRs and a weekly scheduled drift check. Infrastructure is reconciled deliberately, ahead of any deploy that depends on new resources.
  5. Releases (Release Drafter). release-drafter.yml runs on push to main and maintains a rolling draft GitHub Release with categorized notes and a suggested version derived from Conventional PR titles. Publishing the draft tags vX.Y.Z; releases are communication checkpoints and never gate deploys. See the release runbook.
  • Required status checks (validate suite + budgets) must pass before merge.
  • Application deploys run only from main, behind the STAGING_DEPLOY_ENABLED / DEPLOY_ENABLED gate variables. terraform apply runs only by manual workflow_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).

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.
  • 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.