Most small teams don't need a platform team to ship safely. They need a pipeline that does the same boring checks on every push, so a release is a button press instead of an afternoon. This is the checklist we work through when we set one up, in the order we build it.
1. Every push runs the same checks
The first job of CI is to make "works on my machine" irrelevant. On every push and every pull request, the pipeline should:
- Install dependencies from the lockfile, never from loose version ranges.
- Run the linter, the formatter check and the type checker.
- Run unit tests, then integration tests.
- Build the production artifact exactly as it will ship.
Here is the shape of it in GitHub Actions for a Node project:
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: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run buildIf any step fails, the merge is blocked. Branch protection on your main branch is what turns these checks from advice into a rule.
2. Keep it fast, or people will route around it
A pipeline that takes 40 minutes gets skipped. Aim for feedback in under 10 minutes on a typical change:
- Cache dependencies and build outputs between runs.
- Run independent jobs in parallel (lint and tests don't need to wait for each other).
- Split slow end-to-end suites so they run on pull requests to main, not on every commit to a draft branch.
3. Build once, deploy the same artifact everywhere
Build the container image or bundle once, tag it with the commit hash, and promote that exact artifact from staging to production. Rebuilding for each environment is how "it passed staging" turns into a production surprise.
4. Put security checks in the pipeline
Security reviews that happen once a quarter miss most problems. Cheap automated checks catch the common ones on every change:
- Secret scanning, so an API key in a commit fails the build instead of reaching your repository history.
- Dependency scanning for known vulnerabilities in the packages you ship.
- Container image scanning if you deploy containers.
Start with these in warning mode, fix the backlog, then make them blocking.
5. Preview environments for every pull request
A preview deploy per pull request lets a reviewer, a designer or a client click through the change before it merges. It also catches the bugs that only appear in a real environment: missing environment variables, broken routes, wrong headers.
6. Deploys that can't take the site down
The goal is that a bad deploy is boring:
- Zero-downtime rollouts (rolling or blue-green), with health checks that stop a rollout when the new version fails.
- Database migrations that are additive first: add the new column, ship code that uses it, then remove the old one in a later release.
- One-command rollback to the previous artifact.
7. Know when something breaks
A pipeline that deploys without monitoring just moves problems faster. At minimum, alert on error rates and response times after each deploy, and tag each deploy in your monitoring tool so a spike lines up with the change that caused it.
Where to start
If you have nothing today, start with steps 1 and 3: one pipeline that tests and builds on every push, and deploys the artifact it built. Everything else layers on top.
Want a second pair of eyes on your current setup? Our DevOps health check is free, or see how we set up CI/CD pipelines.