Jan 8, 2026 · 2 min read
A CI/CD pipeline that actually saves time
A minimal GitHub Actions setup that runs typecheck, lint, tests, and deploys — without becoming a YAML maintenance project.
CI is most useful when it's boring. The pipeline should fail fast, cache aggressively, and stay out of your way. Here's the shape I keep landing on.
The skeleton
name: ci
on:
push:
branches: [main]
pull_request:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
verify:
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 typecheck
- run: npm run lint
- run: npm test -- --reporter=dot
build:
needs: verify
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 buildWhy each piece earns its spot
concurrencycancels stale runs when you push twice in 30 seconds. Free time savings, no downside.cache: npmuses the lockfile hash as the cache key. No manual cache step required.needs: verifykeeps the build from running if typecheck or tests failed. Fast feedback wins.
Don't do these things
- Don't use
actions/cachefornode_modules. The setup-node cache caches npm's content-addressable store, which is the right layer. - Don't run
npm installinstead ofnpm ci. CI must be deterministic. - Don't deploy from the same job that runs tests. Split delivery into a
separate job that runs only on
mainand only ifverifypassed.
Adding deploy
For Vercel, the platform's GitHub integration handles deploys; you don't need a deploy job at all. For self-hosted, the simplest thing that works is SSH into the box and run a script:
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: cd /srv/app && ./deploy.shA real deploy script does atomic swaps and a healthcheck before flipping the symlink. That's a separate post.
The principle
Every minute you save in CI compounds. A 4-minute pipeline run 30 times a day is two hours; a 10-minute one is five. Treat CI time as a budget, audit it quarterly, and you'll keep the tax low.