Continuous integration sounds like enterprise ceremony until the day it saves you from yourself. Mine was a Friday evening push that broke the production build — caught not by me, but by a little red X next to the commit, before I merged anything. That red X is the whole point: a robot that checks your work on every push, so "works on my machine" stops being a thing you say out loud.
GitHub Actions is the easiest way to get that robot, and you do not need to understand YAML deeply to use it. You need one file. Let us build it together and then pull apart what each line does.
The one-file setup
In your repository, create .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
Push that file, open the Actions tab in your repository, and watch the first run happen. That is a complete CI pipeline for a Node project: checkout, install, lint, test, build — the same commands you run locally, now running on a clean machine every time.
(Not a Node project? The shape is identical. Python: actions/setup-python@v5 then pip install -r requirements.txt and pytest. Go: actions/setup-go@v5 then go test ./.... Swap the setup step and the commands, keep everything else.)
What each line actually means
-
on: push / pull_request— when the robot wakes up. Every push tomain, and every pull request against anything. These two triggers cover 95% of what you want -
runs-on: ubuntu-latest— a fresh VM GitHub spins up per job. "Fresh" is the important word: no cached global packages, no leftover config, so the build really does prove your project works from scratch -
uses:— a prebuilt action someone maintains.checkoutclones your repo,setup-nodeinstalls Node and caches dependencies -
run:— any shell command. If it exits non-zero, the step fails and the run goes red
One subtlety worth knowing: npm ci (not npm install) installs exactly what is in your lockfile. In CI you want that strictness — a lockfile drift should fail the build, not silently change it.
Make it enforce itself
A CI pipeline you can ignore is just a comment generator. Turn on branch protection: repository Settings → Branches → Add rule for main, then enable Require status checks to pass and select your CI job. Now pull requests literally cannot merge while the tests are red. This is the moment CI goes from "nice to have" to "the reason main is never broken".
The mistakes I made so you can skip them
Secrets in the YAML. Anything you paste into the workflow file is public if the repo is public. Tokens and passwords belong in Settings → Secrets and variables → Actions, referenced as ${{ secrets.MY_TOKEN }}. Treat anything named PUBLIC_ or NEXT_PUBLIC_ as visible to everyone, because it is.
Testing only the happy path locally. If your CI installs dependencies differently than you do, you will eventually hit "works locally, fails in CI" — almost always a lockfile issue, an environment variable you forgot to set, or a test that secretly depends on your machine's timezone or files.
Mega-workflows on day one. Start with lint + test + build in one job. Split into parallel jobs, matrices of Node versions, and deployment steps only when you actually feel the pain. A simple pipeline that runs in two minutes gets kept; a clever one that takes twenty gets bypassed.
No caching. cache: npm in the example is one line that can cut your install time in half. Free speed, take it.
Where to go next
Once green checks feel normal, the natural next steps are: a matrix to test against multiple versions (node-version: [20, 22]), artifacts to keep build outputs, and a deploy job that runs only when CI passes on main (if: github.ref == 'refs/heads/main'). But honestly, the file above plus branch protection already puts you ahead of most personal projects on GitHub.
Set it up on one repository this week. The first time that little red X stops a bad merge at midnight, you will get it — and you will never want to go back to merging on faith.
Top comments (0)