Phase 9: CI/CD
Automate the path from "code committed" to "code in production." The factory assembly line for software.
Phase 9: CI/CD
In one line: CI runs tests automatically on every commit. CD ships passing builds to production automatically. Together: the factory assembly line for software.
"CI" and "CD" are scary acronyms for simple ideas:
- CI (Continuous Integration) — Every time someone commits code, an automated system pulls it, runs the tests, and reports back. The "integration" part means combining everyone's work and proving the combined result still works. Without CI, ten developers can break each other's code without realizing.
- CD (Continuous Deployment / Delivery) — Once CI passes, an automated system deploys the code to a server where users (or testers) can access it. The deploy happens without anyone manually copying files anywhere.
In practice, this is a YAML file that lives in your repo. GitHub Actions, GitLab CI, and CircleCI read that YAML and run the steps it lists when you push a commit.
CI (Continuous Integration) — in detail
Every commit runs a pipeline of automated checks. If anything fails, the change is blocked.
A typical CI pipeline:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Lint and format check
run: bunx biome check .
- name: Type check
run: bunx tsc --noEmit
- name: Unit tests
run: bun run test
- name: Build
run: bun run build
- name: Security audit
run: bun audit
In English: Trigger this job on every push to
mainand every pull request. Boot a fresh Ubuntu VM, install Bun, then run six gates in sequence: install with the lockfile pinned (no surprise upgrades), check formatting/lint, type-check, run tests, run the production build, and finally audit dependencies for known vulnerabilities. Any failure blocks the PR.
That YAML, committed to your repo, is now a quality gate. Every commit, every PR, every merge runs through it.
CD (Continuous Deployment vs Delivery)
These terms get confused:
- Continuous Delivery: Every change is deployable. A human pushes the button to release.
- Continuous Deployment: Every change is deployed automatically after passing tests.
True continuous deployment requires high trust in your test suite. Many companies do continuous delivery (deploys are automated but gated) rather than full continuous deployment.
Deployment strategies
| Strategy | How it works | When to use |
|---|---|---|
| Direct deployment | Replace the running version with the new one | Simple apps; brief downtime is OK |
| Blue/Green | Run two identical environments; switch traffic from old to new | Zero-downtime deploys with easy rollback |
| Canary | Deploy to 1% of users first; monitor; gradually expand | High-stakes changes |
| Feature flags | Code ships always; new features gated behind flags | Decouple deploy from release |
| Rolling | Replace instances one at a time | Kubernetes default |
Modern deployments often combine these: canary deploys controlled by feature flags, monitored for SLO breaches with automated rollback.
Branching strategies
| Strategy | Notes |
|---|---|
| Trunk-based development (2026 standard) | Everyone commits to main directly (or via very short-lived branches that merge within hours). Incomplete features hidden behind feature flags. |
| GitHub Flow | Branch from main, commit, PR, merge to main, deploy. Simple, popular for web apps. |
| Git Flow (declining) | Multiple long-lived branches (main, develop, feature/*, release/*). Heavy for modern web apps. |
For most modern teams, trunk-based or GitHub Flow is the right default.
Tools in 2026
| Tool | Notes |
|---|---|
| GitHub Actions | Dominant for most projects. Free for public, generous free tier for private. |
| GitLab CI | All-in-one DevOps; popular when using GitLab. |
| CircleCI | Strong for parallel testing. |
| Buildkite | Hybrid (cloud control, your own compute); popular at scale. |
| Jenkins | Legacy, still common in enterprises. |
| Argo CD / Flux | GitOps for Kubernetes. |
| Vercel / Netlify / Cloudflare Pages | Built-in CI/CD for their hosted apps. |
This documentation site has .github/workflows/deploy.yml:
- On every push to
main, GitHub Actions runsnpm run build. - The built static site is uploaded to GitHub Pages.
- The new version is live on
modernwebdevguide.com/within ~2 minutes. - You can roll back to a previous version by reverting the commit and pushing.
That's a complete CI/CD pipeline for a static site, all expressed in one YAML file under 50 lines.
A 30-minute CI loop kills productivity — developers context-switch, lose focus, accept worse code. A 5-minute loop keeps them in flow.
Common ways to speed up slow CI:
- Parallelize jobs (separate lint, test, build, type-check into parallel steps).
- Cache dependencies (
actions/setup-node@v4withcache: 'npm'). - Run only what's needed (skip the heavy E2E tests on docs-only PRs).
- Use Turborepo or Nx remote cache for monorepos.
Treat CI time as a budget. Over 10 minutes = problem to solve.
Common anti-patterns
- Skipping tests in CI: "Just merge it." The tests exist for a reason.
- Long CI times: A 30-minute CI loop kills productivity.
- Flaky tests in CI: Erodes trust until people retry until green.
- No staging environment: Deploy straight to prod with crossed fingers.
- Manual deployment steps: "First SSH in, then run this script..." Should be one button (or zero).
Common mistakes
- Conflating "deploy" with "release." Code can be deployed (running in production) without being released (visible to users). Feature flags decouple the two. Treating them as the same forces big-bang launches and makes rollback mean a revert+redeploy cycle instead of a flag flip.
- Using
npm install(notnpm ci) in CI.installhappily updates your lockfile mid-pipeline, so CI runs against versions your local machine never saw. Always use the frozen-lockfile variant (npm ci,bun install --frozen-lockfile,pnpm install --frozen-lockfile). - Retrying flaky jobs until they go green. Once "just rerun it" becomes a habit, your CI is no longer a quality gate — it's a slot machine. Either fix the flake or quarantine the test; never normalize the retry.
- Burying secrets in workflow YAML. A workflow that prints
$API_KEYin a log line, or passes it to a curl that echoes the headers, leaks it to anyone who can see the build. Use the platform's secret masking, preferGITHUB_TOKENover long-lived PATs, and assume every log is read by an attacker. - Optimizing CI before measuring it. Engineers parallelize, cache, and shard for a week — and the real bottleneck was a 90-second
apt-getor one Playwright test waiting on a 30s timeout. Look at the timing breakdown in the run summary first; speed up the actual long pole.
Page checkpoint
Did CI/CD stick?
RequiredWhat's next
→ Continue to Phase 10: Deployment & Hosting where we get the code running on the public internet, reliably.