iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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 &ldquo;run this command from your laptop&rdquo;

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…