Pipelines
A pipeline is the sequence of automated steps that turn a code change into a deployed artifact. Stages (lint, test, build, deploy) run linearly; jobs inside a stage run in parallel.
Anatomy of a robust pipeline
EXAMPLE
# Pipeline principles (apply to GitHub Actions / GitLab CI / CircleCI / Buildkite / Jenkins)
# 1) Stages, in order
# [ check ] → [ test ] → [ build ] → [ deploy-staging ] → [ smoke ] → [ deploy-prod ]
#
# Stages are gates: if any job fails, downstream stages don't run.
# Jobs within a stage run in parallel.
# 2) Check stage — fast feedback (under 2 minutes)
# - Lint: eslint / ruff / golangci-lint / clippy
# - Format: prettier --check / black --check / gofmt -l
# - Type-check: tsc --noEmit / mypy / go vet
# - Dependency: audit (npm audit, pip-audit), license check
# 3) Test stage
# - Unit: fast, isolated, no IO, parallelisable
# - Integration: hit a real DB (testcontainers / docker-compose)
# - E2E: Playwright / Cypress against a built app
# - Coverage: gate at a minimum (don't let it drop)
# 4) Build stage
# - Compile / bundle
# - Build Docker image with cache mount + multi-stage
# - Tag: semver + sha
# - Push to registry with provenance + SBOM
# 5) Deploy stage(s)
# - Auto-deploy to STAGING on main
# - Smoke tests against staging
# - Manual approval gate for PROD (in most teams)
# - Roll out canary first; then full
# === GitHub Actions example ===
name: ci
on: { push: { branches: [main] }, pull_request: {} }
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
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 && npm run typecheck
test:
runs-on: ubuntu-latest
needs: check
services:
postgres:
image: postgres:16
env: { POSTGRES_PASSWORD: test }
ports: ['5432:5432']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm test -- --coverage
- uses: codecov/codecov-action@v4
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
- uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy_staging:
runs-on: ubuntu-latest
needs: build
environment: { name: staging, url: https://staging.example.com }
steps:
- run: ./deploy.sh staging ${{ github.sha }}
deploy_prod:
runs-on: ubuntu-latest
needs: deploy_staging
environment: { name: production, url: https://example.com }
steps:
- run: ./deploy.sh prod ${{ github.sha }}
# === Pipeline best practices ===
# Fast feedback
# • Lint / typecheck < 2 min (run on every push)
# • Test suite < 10 min for the bulk; full E2E can take longer in nightly
# • Cache aggressively (deps, Docker layers, bundler output)
# Reliability
# • Flaky tests = silent erosion. Quarantine + fix; don't ignore
# • Pin tool versions (Node 20.x, not 'latest')
# • Reproducible builds (lockfiles checked in)
# Security
# • Use OIDC / short-lived creds (no AWS access keys in secrets)
# • Scan dependencies (Dependabot, Renovate)
# • Sign images (cosign keyless), publish SBOMs
# Observability
# • Annotate failures with logs, screenshots (E2E), test reports (JUnit XML)
# • Send deploy events to Sentry / Datadog (releases for source map upload)
# • Dashboard: build time, success rate, MTTR
# Approval + rollback
# • Manual gate before prod (or fully auto if you have good canary + monitoring)
# • Rollback is one click — keep `./deploy.sh prod <previous-sha>` ready
# • Database migrations: split deploy + apply (so rollback doesn't unwind DB changes)
# Cost
# • Cancel in-progress runs on new pushes (concurrency)
# • Use cheaper runners for short jobs; bigger for E2E
# • Self-hosted runners for high-volume orgs
# Anti-patterns
# • Long single jobs that test everything — break into parallel
# • Builds that work on one machine and fail in CI — make CI the source of truth
# • Manual steps in the deploy — automate or document; never “run this command from your laptop”
Why it matters
A good pipeline is one where developers want the gate. Fast (under 10 min), reliable (no flake), and a clear single button for prod — that’s the difference between “velocity” and “CI rage.”
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# A pipeline is an ordered set of jobs triggered by an event. # Typical stages: lint → test → build → package → deploy.Try it Yourself »
Discussion
Loading…