DevOps
Lesson 1 of 8About 2 min readSuggest an edit

CI/CD basics

CI/CD is the practice of making every change small, verified and easy to release. The terms are often mixed up, so define them first.

  • Continuous integration (CI): everyone merges to the main branch often, at least daily. Every change is built and tested automatically. The goal is to find integration problems within minutes, not at the end of a sprint.
  • Continuous delivery: every change that passes the pipeline is releasable. Deploying to production is a business decision and a single button.
  • Continuous deployment: every change that passes the pipeline is deployed to production automatically, with no human step.

A typical pipeline

  1. Checkout and install dependencies from a lockfile, so every build uses the same versions.
  2. Static checks: formatting, linting, type-checking. These are fast and catch cheap mistakes.
  3. Unit tests: fast and many.
  4. Build an artifact: a container image, a bundle or a binary.
  5. Integration and end-to-end tests against the artifact.
  6. Deploy to staging, then production.

Order the stages so the fastest, most likely to fail checks run first. Developers should hear about a lint error in 30 seconds, not after a 20-minute end-to-end suite.

Build once, deploy many times

Build the artifact once and promote the same artifact through staging and production. Rebuilding for each environment means production runs something that was never tested. Tag artifacts with the commit SHA so you always know exactly what is running. Configuration that differs between environments (database URLs, feature flags, secrets) is injected at deploy time, not baked in.

A small example

# .github/workflows/ci.yml
name: ci
on:
  push:
    branches: [main]
  pull_request:

jobs:
  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 test
      - run: npm run build

npm ci installs exactly what the lockfile says and fails if the lockfile and package.json disagree. That is what you want in CI.

Releasing safely

  • Rollbacks should be one command: redeploy the previous artifact. Test this before you need it.
  • Database migrations must be backwards compatible with the code still running, because old and new versions overlap during a deploy. Add a column first, deploy code that uses it, and only drop the old column in a later release (the “expand and contract” pattern).
  • Progressive delivery limits the blast radius: canary releases send a small share of traffic to the new version, and feature flags let you turn a feature off without redeploying.
  • Watch after deploying. Error rate, latency and saturation dashboards tell you within minutes whether to roll back.

Signs of a healthy pipeline

  • Main is almost always green, and a red main is fixed or reverted immediately.
  • The pipeline takes minutes, not hours.
  • Flaky tests are fixed or quarantined, never retried until they pass.
  • Anyone on the team can deploy, and deploying is boring.

Next: Containers and images

What a container really is, how image layers work, and how to write a small, secure Dockerfile.