Handbooks / CI/CD & DevOps / Chapter 1
CI/CD Pipelines
7 pages · ~4 min✓ Reviewed
Start here. Next up: Testing in the Pipeline.
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
| CI | Delivery | Deployment | |
|---|---|---|---|
| Build + test each push | yes | yes | yes |
| Last step | Stops at tested | Waits for approval | Auto-releases |
| Manual gate | none | before production | none |
- Push
- Build + testCI ends here
- Approvedelivery only
- 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.
Pipeline Stages and Triggers
1. Stage Order
- Checkout
- Install
- Lint
- Test
- Build
- Package
- Staging
- Production
- Cheap, fast checks (lint, unit tests) run first
- Failures surface early, before slow build and deploy stages
2. Triggers
pushandpull_request: code changesschedule(cron): nightly or periodic runsworkflow_dispatch: manual run buttonrelease: 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
environmentwith required reviewers pauses for approval
yaml
deploy-prod: needs: [test, deploy-staging] environment: production runs-on: ubuntu-latest
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@v4run:= 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 testAdd on: [push] above jobs:
3. Passing config
| Key | Gives you | Example |
|---|---|---|
env: | plain variables | NODE_ENV: test |
secrets.NAME | encrypted values | ${{ secrets.NPM_TOKEN }} |
matrix: | one job per combination | node: [18, 20], os: [ubuntu-latest, windows-latest] |
Order
Jobs have no order unless you add needs:. Steps always run top to bottom.
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
- build jobupload-artifact
- artifact storekept for download
- deploy jobdownload-artifact
2. Cache speeds installs
actions/cachestores dependencies, needspath:andkey:- 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
| Artifact | Cache | |
|---|---|---|
| Role | output of a run | input reused |
| Goal | download, pass to jobs | save time |
| Lifetime | kept | can be evicted |
Key on the lockfile
No hash in the key: stale dependencies are restored, or the cache never updates.
Common CI/CD Mistakes
1. Mistakes and Fixes
| Mistake | Result | Fix |
|---|---|---|
| Cache key, no lockfile hash | Stale deps, or cache never updates | hashFiles('**/package-lock.json') |
| Secrets in YAML or logs | Leaked credentials | secrets.NAME; forked PRs get none |
@main action, latest runner | Build breaks unexpectedly | Pin a version or SHA |
Missing needs: | Deploy runs before tests pass | needs: test |
Missing permissions: | Token has too much access | Least 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@v43. 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 Cheat Sheet
1. CI vs Delivery vs Deployment
| Every push | Production | |
|---|---|---|
| CI | build + test | not covered |
| Delivery | releasable | manual approval |
| Deployment | releasable | auto-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) orrun(shell)
3. Sharing and Safety
needs:orders jobs- Artifacts pass files between jobs
actions/cachekey from the lockfile hash: faster installs
Common mistake
No needs: means deploy can start before tests pass.
Check yourself
deploy has no needs: and test fails. What happens to deploy?
- Jobs run in parallel, so
deploystill 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-artifactinbuild,download-artifactindeploy