CI/CD Pipeline (Backend)
Purpose
Make every backend deploy automated, gated, and reversible — containerized build, quality gates, migrations applied safely as an explicit step, and a deploy strategy that can roll back without downtime.
Universal — the gated pipeline (build → test → migrate → deploy), SHA-tagged images, and blue-green/canary with rollback are CI/CD principles; the CI platform and registry differ.
Procedure
- Quality gates first (parallel)
type-check, lint, test (unit + integration + contract) run on every PR — parallel jobs
- Failure blocks merge (branch protection)
- Concurrency cancellation: cancel the stale in-progress run when a new push to the same PR arrives (
concurrency: { group, cancel-in-progress: true } on GHA) — saves minutes + CI cost
- Filter triggers (
paths-ignore for docs-only) so a README edit doesn't run the full pipeline
1b. Harden the pipeline itself (it runs with your secrets)
- Least-privilege
permissions: at the top (contents: read); widen per-job only as needed — the default token is over-permissioned
- Pin third-party actions to a full commit SHA, not a moving tag; let Dependabot bump them
- Never expose secrets to untrusted fork PRs —
pull_request (default) has no secret access; pull_request_target runs with secrets against PR code, use only with extreme care
- Use OIDC for cloud deploys (no long-lived static keys in secrets)
Containerized build, SHA-tagged
- Multi-stage Dockerfile (build stage → slim runtime, e.g., distroless/Alpine)
- Tag the image with the commit SHA (immutable, traceable) — not just
latest
- Cache dependency + build layers
DB migration as an EXPLICIT, gated step — before the deploy
- Run
migrate deploy as its own pipeline step, before routing traffic to new code
- Migrations must be expand-contract safe (see
migration-strategy) so old code still works during rollout
- A failed migration halts the deploy (don't deploy code expecting a schema that didn't apply)
Deploy strategy with rollback
- Blue-green: deploy to idle environment, switch traffic, keep old for instant rollback
- Canary: route a small % to new version, watch metrics, ramp or roll back
- Gate production behind environment protection rules + health checks
Health checks + auto-rollback
- Readiness/liveness probes; deploy waits for healthy before shifting traffic
- Auto-rollback (or fast manual) on health-check failure or error-rate spike (tie to
observability-setup)
Security in the pipeline
- Dependency audit (
npm audit), secret scan (gitleaks), image scan (trivy)
- Fail or warn on Critical/High vulns (see
backend-security-audit)
Validate (validation loop)
- Run a real deploy to staging; verify: migration gate runs before code, health check gates traffic switch, rollback actually restores the previous version
- Force a failing health check → verify traffic does NOT switch / auto-rolls-back
Anti-patterns
| ❌ Anti-pattern |
✅ Correct |
| Migrations run inside app startup |
Explicit gated migration step before deploy |
latest image tag |
SHA-tagged immutable images |
| Deploy with no rollback path |
Blue-green / canary with instant rollback |
| Traffic switched before health check |
Health-gated traffic shift |
| Sequential type-check→lint→test |
Parallel quality-gate jobs |
| Stale runs piling up on every PR push |
concurrency: cancel-in-progress |
Workflow with default (over-permissioned) GITHUB_TOKEN |
Least-privilege permissions: contents: read |
uses: action@v4 (moving tag — hijack risk) |
Pin to a full commit SHA |
Secrets exposed via pull_request_target against fork code |
Use pull_request; pull_request_target only with extreme care |
| Long-lived cloud access keys in CI secrets |
OIDC federation to the cloud |
Severity tiers
| Tier |
Examples |
Action SLA |
| Critical |
No rollback path; migrations run unsafely on startup with no gate; secrets not scanned (leak risk); secrets exposed to untrusted fork PRs via pull_request_target |
Fix immediately |
| Major |
latest tags (non-reproducible deploys); no health-gated traffic switch; default over-permissioned GITHUB_TOKEN; third-party actions pinned to a moving tag |
Fix this sprint |
| Minor |
Sequential CI jobs (slow); image scan not yet wired; no concurrency: cancel-in-progress (stale runs pile up) |
Schedule within 2 sprints |
Completion Criteria
Output
- Pipeline config:
.github/workflows/*.yml (build/test/migrate/deploy stages)
- Dockerfile: multi-stage, slim runtime
- Deploy config: blue-green/canary + health checks + rollback
- Commit format:
chore(ci): containerized build + migration gate / chore(deploy): blue-green with rollback
Implementation
TypeScript + Node + Docker (default)
- GitHub Actions:
docker/build-push-action + setup-buildx; multi-stage Dockerfile (deps → build → distroless runtime)
- Layer cache: npm cache + Docker layer cache keyed on lockfile
- Migration gate: a job running
prisma migrate deploy before the deploy job; deploy needs: it
- Deploy: Vercel (serverless) / Fly.io / Railway / k8s; SHA-tagged image; environment protection rules
Other stacks
- Python / FastAPI: same GHA + Docker;
alembic upgrade head as the migration gate
- Go: multi-stage build to a scratch/distroless image (tiny);
golang-migrate gate
- Universal: SHA-tagging, gated migrations, blue-green/canary, and health-gated rollout are platform-agnostic; GitLab CI / CircleCI use the same job-DAG structure
Related skills
migration-strategy — migrations run as a gated pipeline step
test-strategy — tests + contract verification gate the build
backend-security-audit — dependency + secret scanning in CI
Reference
- Key insight encoded: Tag images with commit SHA, gate prod behind environment protection, run DB migrations as an explicit pipeline gate before the deploy step, and deploy staging→prod with auto-rollback on health-check failure.
1---2name: cicd-pipeline3description: Build a backend CI/CD pipeline — containerized builds, type-check/lint/test gates, DB migration as an explicit gate, SHA-tagged images, and blue-green/canary deploy with rollback. Use at project init, when deploys are manual/risky, or when migrations break production. Not for designing the migration itself (use migration-strategy) or the test pyramid (use test-strategy).4license: MIT5---67# CI/CD Pipeline (Backend)89## Purpose10Make every backend deploy automated, gated, and reversible — containerized build, quality gates, migrations applied safely as an explicit step, and a deploy strategy that can roll back without downtime.1112**Universal** — the gated pipeline (build → test → migrate → deploy), SHA-tagged images, and blue-green/canary with rollback are CI/CD principles; the CI platform and registry differ.1314## Procedure15161. **Quality gates first (parallel)**17 - `type-check`, `lint`, `test` (unit + integration + contract) run on every PR — parallel jobs18 - Failure blocks merge (branch protection)19 - **Concurrency cancellation**: cancel the stale in-progress run when a new push to the same PR arrives (`concurrency: { group, cancel-in-progress: true }` on GHA) — saves minutes + CI cost20 - Filter triggers (`paths-ignore` for docs-only) so a README edit doesn't run the full pipeline21221b. **Harden the pipeline itself (it runs with your secrets)**23 - **Least-privilege `permissions:`** at the top (`contents: read`); widen per-job only as needed — the default token is over-permissioned24 - **Pin third-party actions to a full commit SHA**, not a moving tag; let Dependabot bump them25 - **Never expose secrets to untrusted fork PRs** — `pull_request` (default) has no secret access; `pull_request_target` runs with secrets against PR code, use only with extreme care26 - **Use OIDC** for cloud deploys (no long-lived static keys in secrets)27282. **Containerized build, SHA-tagged**29 - Multi-stage Dockerfile (build stage → slim runtime, e.g., distroless/Alpine)30 - Tag the image with the commit SHA (immutable, traceable) — not just `latest`31 - Cache dependency + build layers32333. **DB migration as an EXPLICIT, gated step — before the deploy**34 - Run `migrate deploy` as its own pipeline step, before routing traffic to new code35 - Migrations must be expand-contract safe (see `migration-strategy`) so old code still works during rollout36 - A failed migration halts the deploy (don't deploy code expecting a schema that didn't apply)37384. **Deploy strategy with rollback**39 - **Blue-green**: deploy to idle environment, switch traffic, keep old for instant rollback40 - **Canary**: route a small % to new version, watch metrics, ramp or roll back41 - Gate production behind environment protection rules + health checks42435. **Health checks + auto-rollback**44 - Readiness/liveness probes; deploy waits for healthy before shifting traffic45 - Auto-rollback (or fast manual) on health-check failure or error-rate spike (tie to `observability-setup`)46476. **Security in the pipeline**48 - Dependency audit (`npm audit`), secret scan (`gitleaks`), image scan (`trivy`)49 - Fail or warn on Critical/High vulns (see `backend-security-audit`)50517. **Validate (validation loop)**52 - Run a real deploy to staging; verify: migration gate runs before code, health check gates traffic switch, rollback actually restores the previous version53 - Force a failing health check → verify traffic does NOT switch / auto-rolls-back5455## Anti-patterns5657| ❌ Anti-pattern | ✅ Correct |58|---|---|59| Migrations run inside app startup | Explicit gated migration step before deploy |60| `latest` image tag | SHA-tagged immutable images |61| Deploy with no rollback path | Blue-green / canary with instant rollback |62| Traffic switched before health check | Health-gated traffic shift |63| Sequential type-check→lint→test | Parallel quality-gate jobs |64| Stale runs piling up on every PR push | `concurrency: cancel-in-progress` |65| Workflow with default (over-permissioned) `GITHUB_TOKEN` | Least-privilege `permissions: contents: read` |66| `uses: action@v4` (moving tag — hijack risk) | Pin to a full commit SHA |67| Secrets exposed via `pull_request_target` against fork code | Use `pull_request`; `pull_request_target` only with extreme care |68| Long-lived cloud access keys in CI secrets | OIDC federation to the cloud |6970## Severity tiers7172| Tier | Examples | Action SLA |73|---|---|---|74| **Critical** | No rollback path; migrations run unsafely on startup with no gate; secrets not scanned (leak risk); secrets exposed to untrusted fork PRs via `pull_request_target` | Fix immediately |75| **Major** | `latest` tags (non-reproducible deploys); no health-gated traffic switch; default over-permissioned `GITHUB_TOKEN`; third-party actions pinned to a moving tag | Fix this sprint |76| **Minor** | Sequential CI jobs (slow); image scan not yet wired; no `concurrency: cancel-in-progress` (stale runs pile up) | Schedule within 2 sprints |7778## Completion Criteria79- [ ] Quality gates (type/lint/test) parallel + blocking80- [ ] SHA-tagged container images81- [ ] Migration runs as an explicit gate before deploy (expand-contract safe)82- [ ] Blue-green/canary deploy with rollback verified83- [ ] Health-gated traffic switch84- [ ] Dependency + secret + image scanning in pipeline8586## Output87- **Pipeline config**: `.github/workflows/*.yml` (build/test/migrate/deploy stages)88- **Dockerfile**: multi-stage, slim runtime89- **Deploy config**: blue-green/canary + health checks + rollback90- **Commit format**: `chore(ci): containerized build + migration gate` / `chore(deploy): blue-green with rollback`9192## Implementation9394### TypeScript + Node + Docker (default)95- GitHub Actions: `docker/build-push-action` + `setup-buildx`; multi-stage Dockerfile (deps → build → distroless runtime)96- Layer cache: npm cache + Docker layer cache keyed on lockfile97- Migration gate: a job running `prisma migrate deploy` before the deploy job; deploy `needs:` it98- Deploy: Vercel (serverless) / Fly.io / Railway / k8s; SHA-tagged image; environment protection rules99100### Other stacks101- **Python / FastAPI**: same GHA + Docker; `alembic upgrade head` as the migration gate102- **Go**: multi-stage build to a scratch/distroless image (tiny); `golang-migrate` gate103- **Universal**: SHA-tagging, gated migrations, blue-green/canary, and health-gated rollout are platform-agnostic; GitLab CI / CircleCI use the same job-DAG structure104105## Related skills106- `migration-strategy` — migrations run as a gated pipeline step107- `test-strategy` — tests + contract verification gate the build108- `backend-security-audit` — dependency + secret scanning in CI109110## Reference111- **Key insight encoded**: Tag images with commit SHA, gate prod behind environment protection, run DB migrations as an explicit pipeline gate before the deploy step, and deploy staging→prod with auto-rollback on health-check failure.