Skip to main content

ADR-0016: Continuous promotion behind the staging gate; configuration in GitHub Secrets

Status: Accepted Date: 2026-07-30

Context

ADR-0015 introduced the staging tier with a manual promote button, and its consequences accepted "promotion is a human decision". In practice the operator wants zero routine manual steps: a merge should reach production by itself, with the machinery — not a person — deciding whether it's safe enough to continue. Separately, the VPS env files (.env, .env.stage) were hand-created over SSH: unversioned, unrepeatable, and easy to drift.

Decision

  • Continuous promotion. deploy.yml gained a deploy-production job that runs only when the staging deploy turned healthy, promoting the same run's exact tag via deploy.sh promote <tag>. The staging health gate — boot, migrations, config fail-fasts — is the sole automated gate between a merge and production. promote.yml is demoted to an exception tool (re-promote an explicit tag, or redeploy with fresh Secrets); rollback stays manual by nature.
  • Configuration lives in GitHub Secrets. Two secrets, PROD_ENV_FILE and STAGE_ENV_FILE, hold the full dotenv payloads (templates: deploy/.env.*.example). Every deploy/promote run rewrites /srv/motorph/.env{,.stage} from them (passed via envs: — shell-escaped, multiline-safe, never ${{ }}-interpolated — written with umask 077). The VPS copies are disposable; changing a value means editing the Secret and re-running Deploy.

Consequences

Positive

  • Merge → production with no human in the loop; staging still stops anything that fails to boot or migrate, automatically.
  • Configuration is applied by exactly one actor (the pipeline), so the VPS can never drift from the declared config for longer than one run.
  • Standing up a replacement VPS is: harden, clone, set six Secrets, run the pipeline.

Negative

  • No human staging window. The health gate catches "doesn't boot", not "computes the wrong payroll" — the local test suites are the only behavior gate, and a bad-but-bootable merge is live in minutes. A change that needs soak time on staging requires deliberately holding the merge.
  • GitHub Secrets are write-only — the filled payloads must be kept in a password manager, or a lost laptop means reconstructing secrets that can't be read back from anywhere.
  • Anyone who can push to main or edit workflows can deploy to production and exfiltrate the Secrets (a workflow edit can print them). Mitigations are branch protection and a tight collaborator list — accepted for a single-operator repo.
  • A Secret edit takes effect only on the next pipeline run — an "applied" config change that skipped the re-run is a documented troubleshooting case, not an error anyone is told about.

References