CI/CD
CI/CD Pipeline
Sub-chapter 2 of Git & CI/CD · Shipping
A pipeline is the contract between your code and production. "It works on my machine" is rejected by the contract. CI is the friend who insists on running your tests before you embarrass yourself. CD is the friend who deploys it for you so you stop fat-fingering ssh prod && git pull.
At Welzin, the merge button is the deploy button. If you can't trust the merge button, the pipeline is broken - and we fix the pipeline, we don't work around it.
Outline
- What CI/CD actually is - Continuous Integration vs Continuous Delivery vs Deployment
- GitHub Actions in 20 minutes - workflows, jobs, steps, matrices
- The standard pipeline - install → lint → typecheck → test → build → deploy
- Caching -
actions/cache, lockfile-based keys, why your CI is slow - Secrets management - repo secrets, environments, OIDC, never
echo $SECRET - Preview deployments - every PR gets a URL
- Deployment strategies - blue/green, canary, rolling, the cost of each
- Rollback - the single most important capability
- Branch protection - required checks, code owners, signed commits
- Observability - knowing the deploy succeeded for users, not just CI
101 Primer
CI vs CD vs CD
- Continuous Integration - every commit is automatically built and tested.
- Continuous Delivery - every passing commit is deployable with one click.
- Continuous Deployment - every passing commit on
mainis automatically deployed to production.
Most teams want CI + Continuous Delivery, with a human pressing deploy. Some mature teams do full Continuous Deployment with strong tests and instant rollback. Pick honestly based on test coverage.
Anatomy of a workflow
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test -- --run
- run: npm run build
Key ideas:
- Workflow - a YAML file under
.github/workflows/. - Trigger -
on:block. PRs, pushes, schedules, manualworkflow_dispatch. - Job - runs on one runner. Multiple jobs run in parallel by default.
- Step - either a shell command (
run:) or a reusable action (uses:).
The standard pipeline
Every Welzin repo should have, at minimum, these stages running on PR:
- Install -
npm ci/pip install -r requirements.lock. Use lockfiles. - Lint -
eslint/ruff. Style issues fail the build. - Typecheck -
tsc --noEmit/mypy. Noanyslipping in. - Test - unit tests first, integration tests with a real DB in a service container.
- Build - final artifact (Next.js build, Docker image).
- Security -
npm audit --productionor Trivy on the image.
If any stage fails, the merge button is disabled. No exceptions.
Caching - the difference between 90s and 9 minutes
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm' # caches ~/.npm keyed on package-lock.json
For Python:
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
For Docker layers, use docker/build-push-action with cache-from/cache-to: type=gha. The first build of the day is slow; the rest are seconds.
Secrets - there's only one safe way
- Store secrets in GitHub Actions → Settings → Secrets and variables.
- Reference them as
${{ secrets.NAME }}. - Never
echothem. GitHub masks them in logs if they're stored as secrets, but if you concatenate them into a URL andcurlthat URL, the URL ends up in logs. - For AWS, use OIDC, not long-lived access keys. Configure GitHub as a trusted OIDC provider in AWS, assume a role from the workflow. Rotates automatically, no secret to leak.
permissions:
id-token: write
contents: read
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/gh-deploy
aws-region: ap-south-1
Preview deployments - every PR is a live URL
Vercel and Cloudflare Pages do this automatically. For custom infra, build a Docker image tagged with the PR number, deploy to a per-PR namespace, post the URL as a PR comment via the GitHub API.
Why this matters: a designer or PM can review the actual UI on the actual data shape before merge. Code review without preview is half a review.
Deployment strategies
- Recreate - stop old, start new. Downtime. Fine for internal tools.
- Rolling - replace instances one at a time. Default for K8s. Slow rollback.
- Blue/green - two environments, swap traffic. Instant rollback, double the infra cost during deploy.
- Canary - send 1% / 5% / 50% / 100% over an hour, watch metrics. Best for high-traffic services.
For a 5-person team shipping a Next.js app on Vercel, rolling with instant atomic deploy is the default and is fine. Don't over-engineer.
Rollback is the deploy feature
The most important question about your deploy pipeline is not "how fast can it deploy?" - it's "how fast can you roll back?" Three rules:
- Every deploy creates a tag/release.
v2026.05.30-1. - Rollback is a one-button operation. Vercel "Promote to Production" of a prior deployment. K8s
kubectl rollout undo. - You have practised it. A rollback you've never tested is not a rollback.
Branch protection - the safety net
In main settings, enable:
- Require PR before merge.
- Require status checks (your CI workflow) to pass.
- Require at least one approving review.
- Require linear history (rebases, not merge commits) if your team prefers.
- Require signed commits for the paranoid + production-touching repos.
A branch protection rule is worth ten "please remember to..." in onboarding docs.
Observability after deploy
CI green ≠ users happy. After every deploy, you watch (or your pipeline watches) for:
- Error rate in Sentry / Datadog. Spike → auto-rollback if you've wired it.
- p95/p99 latency of key routes.
- Business metric (signups, logins, jobs queued) - flat or up, not down.
A 5-minute watch window after auto-deploy catches almost all bad deploys. Wire it.
Hands-on Checkpoints
- Add a
.github/workflows/ci.ymlto a repo with lint, typecheck, test, build stages. - Make it fail intentionally (broken test), watch it block a PR merge.
- Cache dependencies, measure before/after.
- Add a deploy step using OIDC (no long-lived keys) to any cloud you have access to.
- Wire a Vercel or Cloudflare Pages preview URL into PRs.
- Practise a rollback on a dev environment. Time it.
Further reading
- GitHub Actions docs - well-written
- Configure OIDC for AWS
- Accelerate - Forsgren, Humble, Kim. The DORA-metrics book. Read once, internalise.
- trunkbaseddevelopment.com - for the "why" of short-lived branches
Welzin opinion: A pipeline that takes 12 minutes to fail breeds the habit of pushing without thinking. Optimise CI time aggressively - it shapes how the team writes code.