# CI/CD for a small team: the minimum that pays off

> Which checks to run on every push, which tests to write first, why branches deserve preview deploys, and why production should ship on a button.

Published: 2025-04-25 · Source: https://subxcat.com/articles/ci-cd-small-team · Author: SUBXCAT

On a team of two or three, deploying often means someone builds the project on their laptop and uploads it. That works as long as that person remembers every step, isn't on holiday, and didn't forget to commit one file. A CI/CD pipeline isn't there to look professional. It's there so that shipping stops depending on one person's memory.

A small team doesn't need a big pipeline. It needs a short one that runs on every change and that everyone trusts.

## What to check on every push

The minimum set of steps:

- install dependencies strictly from the lockfile;
- lint and type-check;
- run the tests;
- do a production build.

The last one gets skipped surprisingly often. A project that runs fine in dev mode can still fail to build for production. Keep the whole run under ten minutes. Past that, people start merging without waiting for the result, and the checks become a formality.

```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: 20
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
      - run: npm run build
```

## Which tests to write first

Coverage percentages don't tell you much at this stage. What matters is that tests cover the places where a break costs money or trust:

- signup and login;
- payment, and granting whatever was paid for;
- access checks: a user can't see someone else's data;
- migrations apply cleanly to an empty database.

That's a few dozen tests, not a few hundred. They take a couple of days to write and catch most of what breaks during edits, especially edits made with an AI assistant that's happy to rewrite neighbouring code while it's in there.

## A preview for every branch

A preview is a separate copy of the app for a pull request, with its own URL. You can open it on a phone, show it to a designer or a client, and click through it before merging. Many hosts do this out of the box.

What matters is what the preview is connected to. Its own database with test data, or a shared staging one, is fine. The production database and live payment keys are not. People who open a preview don't expect their clicks to move real money.

## Production on a button

Auto-deploying `main` to staging is convenient. Auto-deploying to production is a debatable choice for a small team. A manual approval costs five seconds and gives you a moment to notice that it's Friday evening and the release contains a migration.

Two rules that prevent a lot of strange bugs:

- production gets the same built artifact that was tested on staging, not a fresh rebuild;
- every release has a version tag, so you can always tell which commit is live.

## Rollback

Rolling back should be one command or one button: redeploy the previous artifact. If it requires remembering what you did last time, it won't happen fast enough. Keep in mind that rolling back code is easy and rolling back a database isn't, so write migrations that the previous version of the code can still live with.

## Secrets in the pipeline

Keys live in the CI settings, not in the repository. Production keys are available only to the step that deploys to production, not to every test run on every branch. And every few months, check who actually has access to them.

If you want this pipeline in a few days rather than assembled over many evenings, SUBXCAT can set it up for your project.
