Handbooks / CI/CD & DevOps / Chapter 1

CI/CD Pipelines

7 pages · ~4 min✓ Reviewed

Start here. Next up: Testing in the Pipeline.

CI/CD & DevOpsPage 1

CI vs Delivery vs Deployment

1. The Three Terms

  • Continuous integration: merge small changes to main often; every push builds and runs tests
  • Continuous delivery: every passing build is releasable; a human approves the production release
  • Continuous deployment: every passing build goes to production automatically, no manual gate

2. Where They Differ

CIDeliveryDeployment
Build + test each pushyesyesyes
Last stepStops at testedWaits for approvalAuto-releases
Manual gatenonebefore productionnone
  1. Push
  2. Build + testCI ends here
  3. Approvedelivery only
  4. Productiondeployment skips approval

3. Fail Fast

  • A red stage stops every stage after it
  • A broken main branch is fixed first, before new work
Remember

The three differ only in the last step: tested, approved, or auto-released.

CI/CD & DevOpsPage 2

Pipeline Stages and Triggers

1. Stage Order

  1. Checkout
  2. Install
  3. Lint
  4. Test
  5. Build
  6. Package
  7. Staging
  8. Production
  • Cheap, fast checks (lint, unit tests) run first
  • Failures surface early, before slow build and deploy stages

2. Triggers

  • push and pull_request: code changes
  • schedule (cron): nightly or periodic runs
  • workflow_dispatch: manual run button
  • release: a release is published
  • Filter with branches, tags, paths: docs-only changes skip the full pipeline
yaml
on:
  push:
    branches: [main]
    paths-ignore: ['docs/**']
  pull_request:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:

push runs only for main, and never for docs-only edits

3. Gating Production

  • needs: orders jobs; without it jobs run in parallel
  • An environment with required reviewers pauses for approval
yaml
deploy-prod:
  needs: [test, deploy-staging]
  environment: production
  runs-on: ubuntu-latest
CI/CD & DevOpsPage 3

GitHub Actions Workflow Anatomy

1. Anatomy

  • Workflow = YAML file in .github/workflows/
  • Contains jobs; each job contains steps
  • Jobs run in parallel, each on its own runner (runs-on: ubuntu-latest)
  • needs: chains jobs in order

2. Steps and a minimal workflow

  • uses: = reusable action, pinned: actions/checkout@v4
  • run: = shell command
  • Trigger on: [push] → jobs: test: → steps
yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm test

Add on: [push] above jobs:

3. Passing config

KeyGives youExample
env:plain variablesNODE_ENV: test
secrets.NAMEencrypted values${{ secrets.NPM_TOKEN }}
matrix:one job per combinationnode: [18, 20], os: [ubuntu-latest, windows-latest]
Order

Jobs have no order unless you add needs:. Steps always run top to bottom.

CI/CD & DevOpsPage 4

Artifacts and Caching

1. Artifacts pass outputs

  • Artifacts = build outputs (a binary, a test report)
  • Save: actions/upload-artifact; fetch later: download-artifact
  • Jobs run on fresh runners and share no files
  • Passing a build between jobs needs an artifact
  1. build jobupload-artifact
  2. artifact storekept for download
  3. deploy jobdownload-artifact

2. Cache speeds installs

  • actions/cache stores dependencies, needs path: and key:
  • Key = lockfile hash: hashFiles('**/package-lock.json')
  • restore-keys: = fallback prefix when no exact match
yaml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-

3. Artifact vs cache

ArtifactCache
Roleoutput of a runinput reused
Goaldownload, pass to jobssave time
Lifetimekeptcan be evicted
Key on the lockfile

No hash in the key: stale dependencies are restored, or the cache never updates.

CI/CD & DevOpsPage 5

Common CI/CD Mistakes

1. Mistakes and Fixes

MistakeResultFix
Cache key, no lockfile hashStale deps, or cache never updateshashFiles('**/package-lock.json')
Secrets in YAML or logsLeaked credentialssecrets.NAME; forked PRs get none
@main action, latest runnerBuild breaks unexpectedlyPin a version or SHA
Missing needs:Deploy runs before tests passneeds: test
Missing permissions:Token has too much accessLeast privilege

2. Safer Job

  • Pin the runner and action, order with needs, shrink the token with permissions
yaml
permissions:
  contents: read
jobs:
  deploy:
    needs: test
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

3. Slow and Flaky

  • Slow, flaky pipelines get ignored
  • Parallelise independent jobs
  • Cache dependencies
  • Fix or quarantine flaky tests
Secrets

Never echo a secret into logs. Forked PRs do not receive secrets.

CI/CD & DevOpsPage 6

CI/CD Cheat Sheet

1. CI vs Delivery vs Deployment

Every pushProduction
CIbuild + testnot covered
Deliveryreleasablemanual approval
Deploymentreleasableauto-release

2. Workflow File and Triggers

yaml
on: [push, workflow_dispatch]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci

.github/workflows/ci.yml: on → jobs → runs-on → steps

  • Triggers: push, pull_request, schedule, workflow_dispatch
  • Filter with branches, tags and paths
  • Step = uses (action) or run (shell)

3. Sharing and Safety

  • needs: orders jobs
  • Artifacts pass files between jobs
  • actions/cache key from the lockfile hash: faster installs
Common mistake

No needs: means deploy can start before tests pass.

CI/CD & DevOpsPage 7

Check yourself

deploy has no needs: and test fails. What happens to deploy?
  • Jobs run in parallel, so deploy still starts
  • Fix: add needs: test
Cache key is the fixed string npm-cache. You add a dependency. What is restored?
  • The old cache, because the key still matches and is never updated
  • Fix: add hashFiles('**/package-lock.json') to the key
build writes dist/, then deploy (with needs: build) cannot find it. Why?
  • Each job gets a fresh runner, so no files are shared
  • Fix: upload-artifact in build, download-artifact in deploy