Shipping ItLesson 13 of 74

Contents

Shipping It

Lesson 13 of 74. Sign in to keep your place.

Module 2 · Making a deploy boring

The checks that run without you

9 min read

What continuous integration is for

Not ceremony. It is one thing: every push runs the same checks on a clean machine, so nobody has to remember.

A pipeline earns its place when it catches the class of mistake that humans reliably make: the forgotten file, the stale lockfile, the test that only passes because of state on your laptop.

The five checks, in this order

Order them so the fastest failures come first. There is no point spending four minutes on tests when the install was going to fail.

  1. Install from the lockfile. npm ci. Fails on a lockfile nobody regenerated.
  2. Typecheck. tsc --noEmit, mypy, whatever your language offers.
  3. Lint. With --max-warnings 0, or the warnings accumulate until they are wallpaper.
  4. Tests.
  5. Build. The real production build, because a build error at deploy time is the worst place to find one.
YAML
name: ci
on: [push, pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: .nvmrc
          cache: npm
      - run: npm ci
      - run: npm run typecheck
      - run: npm run lint
      - run: npm test
      - run: npm run build

cache: npm is doing real work: dependency installation is usually the longest step, and caching it turns three minutes into twenty seconds.

The ten-minute rule

Past about ten minutes, people stop waiting for the pipeline. They push and walk away, and the feedback arrives after they have moved on, which is the same as no feedback, plus a bill.

If you are over ten minutes: cache dependencies, run independent jobs in parallel, and split the slow end-to-end suite into a separate job that runs on the main branch rather than on every push to every branch.

Add a check for the mistake that has bitten you twice

Generic checks catch generic problems. The ones that hurt are specific to your codebase, and the reason they hurt is that the standard tools stay green.

This project has an exact example. A React component marked "use client" must never import a module that reaches the database: do it and the Postgres driver gets pulled into the browser bundle and every page returns a 500. TypeScript compiles it happily. ESLint approves. The site is broken and every check is green.

So there is a script, verify:client-boundary, that walks the import graph from every client component and fails if it reaches a server-only module. It runs in CI, before the build.

That is the pattern worth taking: when a bug gets you twice, the fix is not more care, it is a script that runs without you. Write the crude version (thirty lines of grep and import-walking) because a crude check that runs beats a rigorous one you keep meaning to write.

Make the checks actually block

A pipeline that reports failures nobody has to act on is decoration. In GitHub, mark the checks as required in branch protection, so a red build cannot merge. Elsewhere the setting has a different name and the same purpose.

Secrets in CI

Two rules and a surprise.

Store secrets in the CI provider’s secret store, never in the workflow file. The runner masks them in logs, but only the literal value. Base64 it, print it inside a JSON blob, or pass it to a tool that reformats it, and the mask does not match and your token is in a public build log.

The surprise: pull requests from forks do not get your secrets. This looks broken the first time an outside contributor opens a PR and the deploy step fails. It is deliberate: otherwise anyone could open a PR whose workflow prints your production credentials.

Flaky tests are worse than no tests

A suite you re-run until it goes green is not a test suite; it is a slot machine. Once people start re-running, a real failure gets re-run too.

When a test flakes: fix it, or delete it, or quarantine it into a job that does not block. All three are honest. Leaving it in place and re-running is the only option that quietly destroys the value of everything else in the pipeline.

The free paths

GitHub Actions is free and unlimited on public repositories; private repositories on the free plan get a monthly minute allowance that is generous for a small project. GitLab CI has a free tier. If you would rather not depend on any of them, a self-hosted runner on a $5 virtual machine works, and so does Forgejo or Gitea Actions if you host your own git. The pipeline is a shell script that someone else triggers; keep it in a shape you could run by hand.

Every lesson here is open. Your progress is recorded once you pass the Agents and Automation exam.

Check your understanding

Your CI pipeline takes fourteen minutes, and one integration test fails roughly one run in five for reasons nobody has traced. The team’s habit is to re-run the job until it passes. What is the most serious consequence?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minThe console clicks nobody wrote down

Stuck on something?Ask Tri, your AI tutor

How to cite: Addaly (2026) ‘The checks that run without you’, in Shipping It. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/cloud-and-shipping/a-pipeline-you-trust (Accessed: 13 October 2026).