Skip to content

Build an approval-gated production release pipeline#590

Merged
kelvinkipruto merged 3 commits into
mainfrom
claude/issue-580
Jul 21, 2026
Merged

Build an approval-gated production release pipeline#590
kelvinkipruto merged 3 commits into
mainfrom
claude/issue-580

Conversation

@kelvinkipruto

Copy link
Copy Markdown
Contributor

Closes #580

Stacked on #589 (issue #578). This is blocked by #578, so the PR targets claude/issue-578; rebase onto main once #589 merges. The diff shown here is only the #580 changes.

What changed

Build once, promote by digest (AC1, AC2)

  • build-image.yml builds the immutable runtime and migrator images once and outputs each pinned by content digest (image@sha256:…). Deploy/release jobs promote the exact digest — mutable tags are never deployed.
  • Builds need no database. The Dockerfile defaults DATABASE_URI/PAYLOAD_SECRET to build-time placeholders (next build imports the config but never connects), so producing the artifact requires no reachable — or real — DB.

Approval-gated production (AC3)

  • release.yml: verify → build → staging → production. The production job runs in the production GitHub environment; enabling required reviewers there makes production deployment (including migrations) pause for human approval.

Single-instance, observable migrations (AC4, AC5)

  • A dedicated migrator image (same commit, retains the Payload CLI + migrations/ that the slim runner prunes) runs pnpm migrate once as a one-off container in promote.yml — never in every replica (the entrypoint runs no migrations). The migrate step runs before the promote step, so a migration failure fails the job and traffic is never switched. Forward-fix/rollback handling is documented.

Bounded liveness & readiness (AC6, AC7)

  • GET /api/health/live — process-only, no dependency calls (so a dep outage never kills a healthy process).
  • GET /api/health/ready — time-bounded MongoDB (mongoose ping) + Apache Tika (HTTP, skipped when TIKA_ENABLED=0) checks; 200/503.
  • Wired into the Docker HEALTHCHECK (liveness) and the Dokku app.json startup/liveness/readiness probes, and used as the post-deploy readiness gate in promote.yml. The timeout races the dependency call so a hung driver can't outlast the bound.

Rollback & restore, rehearsed (AC8)

Verification (run locally)

  • Full integration suite 245/245 passing (incl. new health.int.spec.ts); tsc --noEmit clean; lint clean; all workflow YAML + app.json parse.
  • Migrator image built and run end-to-end against a fresh mongo:7 — all three migrations applied once, cleanly.
  • DB restore rehearsal: seed → mongodump → drop → mongorestore --drop recovered the document exactly; image-by-digest run confirmed.

Notes / follow-ups

  • Requires repo config: staging/production environments (required reviewers on production), plus STAGING_DATABASE_URI/STAGING_PAYLOAD_SECRET secrets. Documented in the runbook.
  • The migrate step and readiness gate assume the runner can reach the target DB/URL; the runbook gives the dokku run alternative for network-isolated environments.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
PromiseTracker Ready Ready Preview, Comment Jul 21, 2026 8:44am

Request Review

kelvinkipruto and others added 2 commits July 21, 2026 11:22
- Build once, promote by digest: build-image.yml builds immutable
  runtime + migrator images and outputs image@sha256 refs; deploy and
  release workflows promote the exact digest (never a mutable tag)
- DB-independent builds: Dockerfile defaults DATABASE_URI/PAYLOAD_SECRET
  to build-time placeholders, so artifact creation needs no reachable DB
- Approval gate: release.yml production job runs in the production
  GitHub environment (required reviewers) before any prod change
- Single-instance migrations: dedicated migrator image runs pnpm migrate
  once as a one-off before traffic switches (never in every replica);
  migrate step precedes promote so a failure blocks the traffic switch
- Bounded health endpoints: /api/health/live (process only) and
  /api/health/ready (time-bounded Mongo + Tika checks), wired to the
  Docker HEALTHCHECK and Dokku app.json liveness/readiness/startup probes
  and used as the post-deploy readiness gate
- Docs: release-pipeline and rollback-and-restore runbooks; image
  rollback + mongodump/mongorestore procedures rehearsed locally

Closes #580

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the CodeQL permissions fix to the build-image, promote, release,
and refactored deploy-dev workflows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base automatically changed from claude/issue-578 to main July 21, 2026 08:27
Delivers issue #580 AC1 — 'production artifact creation does not require a
reachable application database'.

Root cause: importing several modules opened a MongoDB connection at build
time (Next collects page data during `next build`):
- Four src/lib/data/*.ts modules and src/components/Newsletter/Newsletter.tsx
  ran a top-level `await getGlobalPayload()`. Made them resolve lazily
  per-call (getGlobalPayload is memoized, so no extra connections at runtime).
- The subdomain-multitenant (frontend) layout and its routes were statically
  analyzed at build. Marked the layout + home, catch-all, and promise-detail
  routes `force-dynamic` (they are per-request by design and must never be
  prerendered).
- Pinned `outputFileTracingRoot` so `.next/standalone/server.js` lands at a
  deterministic path (matches the Dockerfile COPY), independent of parent
  lockfiles (e.g. git worktrees).

Verified: `next build` completes with an UNREACHABLE DATABASE_URI and emits
.next/standalone/server.js; 245 integration tests pass; lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kelvinkipruto
kelvinkipruto merged commit 877ddea into main Jul 21, 2026
9 checks passed
@kelvinkipruto
kelvinkipruto deleted the claude/issue-580 branch July 21, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build an approval-gated production release pipeline

1 participant