What Is CI/CD
Continuous Integration (CI): Every code push automatically triggers a build + test run. Goal: catch integration problems early, before they reach production.
Continuous Delivery (CD): Every passing build is automatically deployable (one click or auto). The pipeline carries changes safely from code to production.
Push → Lint → Test → Build → Deploy to staging → Deploy to prod
Pipeline Stages
| Stage | What it does | Fails on |
|---|---|---|
| Lint / Type-check | Static analysis, formatting | Type errors, lint violations |
| Unit tests | Fast isolated tests | Logic regressions |
| Integration tests | Tests across services/DB | API contract breaks |
| Build | Compiles/bundles the app | Build errors, missing env vars |
| Deploy (staging) | Ships to pre-prod environment | Deploy errors, smoke test failures |
| Deploy (prod) | Ships to production | Gated by staging passing |
Fail fast: put cheap checks (lint, types) first, expensive ones (e2e, build) later.
GitHub Actions
Workflow anatomy
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, development]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm run test
Key concepts
| Concept | What it is |
|---|---|
on | Trigger — push, PR, schedule, manual (workflow_dispatch) |
jobs | Parallel units of work; each runs on a fresh runner |
steps | Sequential commands within a job |
uses | Reference a pre-built action from the marketplace |
run | Run a shell command |
env | Environment variables scoped to a job or step |
secrets | Encrypted repo/org secrets accessed via ${{ secrets.NAME }} |
Common patterns
PR check (block merge until CI passes):
on:
pull_request:
branches: [main]
Set this as a required status check in branch protection rules.
Deploy on merge to main:
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- run: npx vercel --prod --token=${{ secrets.VERCEL_TOKEN }}
Environment-specific secrets:
jobs:
deploy:
environment: production # maps to a GitHub Environment with its own secrets
Matrix builds (test across multiple Node versions):
strategy:
matrix:
node: [22, 24] # Node 22 (Maintenance LTS) + Node 24 (Active LTS) as of mid-2026
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
Secrets Management
- Store secrets in Settings → Secrets and variables → Actions (repo or org level)
- Never log secrets — mask them:
echo "::add-mask::$MY_SECRET" - Use GitHub Environments for prod-specific secrets + required reviewers
- Rotate secrets if a repo ever becomes public or a contributor leaves
GitHub Actions vs Alternatives
| GitHub Actions | GitLab CI | CircleCI | |
|---|---|---|---|
| Config file | .github/workflows/*.yml | .gitlab-ci.yml | .circleci/config.yml |
| Free tier | 2,000 min/month (public: unlimited) | 400 min/month | 6,000 credits/month |
| Hosted runners | ubuntu/windows/mac | Linux only (free) | Linux/mac |
| Marketplace | Large action ecosystem | Limited | Orbs ecosystem |
| Best for | GitHub repos | GitLab self-hosted | Complex pipelines |
GitHub Actions is the default choice if your code is already on GitHub.