The Minimum CI Pipeline Worth Having
04 Feb 2026Most CI pipelines accumulate stages the way garages accumulate boxes — someone added it for a reason that made sense at the time, and now it’s permanent and nobody remembers why. Here’s the pipeline that’s actually worth having, and what to leave out until you have a specific reason.
The three stages
name: CI
on: [push, pull_request]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Lint. Catches the class of bug that’s cheap to catch and expensive to review-comment about repeatedly — unused imports, obvious type errors, formatting drift. If your linter takes more than a few seconds, it’s probably also trying to do something more complex; split that out.
Test. Your actual correctness gate. If you don’t have tests, this step is npm test running nothing, and that’s fine as a placeholder — but the absence should be visible in the pipeline, not silently skipped.
Build. Confirms the thing that ships actually compiles/bundles. Catches the “works with ts-node but fails the production build” class of bug that lint and unit tests both miss.
That’s it. Three steps, one job, runs on every push and PR. Nothing exotic.
What to add later, not now
Matrix testing across Node/Python versions. Unless you actually ship a library that other people install across a range of runtime versions, you’re testing hypothetical compatibility nobody’s relying on. Test against the one version you deploy.
Deploy previews for internal tools. Great for products with external stakeholders reviewing UI. Overkill for an internal script or admin panel three people touch.
Separate jobs per test type (unit, integration, e2e) with complex needs: graphs. Fine once your test suite is slow enough that parallelizing matters. Premature when your whole suite runs in under two minutes as one job — the orchestration overhead costs more than it saves.
Auto-generated changelogs, semantic-release, conventional commit enforcement. These solve a coordination problem that shows up with more contributors and more frequent releases. A solo dev or a two-person team doesn’t have that coordination problem yet.
Security scanning (Snyk, Dependabot alerts as a blocking check, SAST tools). Worth having as a non-blocking, scheduled job — but don’t make it a required check on every PR from day one. It generates noise before you’ve tuned it, and a noisy required check trains people to just re-run until it passes, which defeats the point.
The test that tells you when to add a stage
Before adding a CI stage, ask: what actually breaks in production that this would have caught? If you can’t name a real incident or near-miss, you’re adding process speculatively. Add the stage after the second time something ships broken that a specific check would have caught — not before the first time, and not “just in case.”
The one thing worth doing early: make it fast
A CI pipeline people wait on for ten minutes gets circumvented — people push straight to main, or batch up commits to avoid re-running it. Keep the minimal pipeline minimal specifically so it stays fast: cache dependencies, run lint/test/build in one job instead of three sequential jobs paying container startup cost three times, and don’t add stages that don’t change your confidence in the code.
Takeaway
Lint, test, build, in that order, in one job. Everything else on this list is real and eventually worth having — just not on day one, and not until a specific failure justifies it.