← All posts

A CI/CD pipeline for a small team: what to automate first

How a one- or two-person team can get reliable, boring deployments: a pipeline that tests, builds Docker images, deploys with rollbacks, handles migrations and ships mobile builds, without a platform team.

Author
Elie Chaaban
Published
Reading time
5 min
Topics
DevOps, CI/CD, Docker, Automation

Small teams often skip CI/CD because it sounds like infrastructure for big companies. In practice, the smaller the team, the more it helps. When one person builds, deploys and supports a product, every manual deployment step is a chance to forget something at the worst moment, and every hour spent deploying by hand is an hour not spent on the product.

All my products, including NeonTask and Pro Clean+, run with a CI/CD pipeline that builds and ships every change. Here is what I automate, in the order I would recommend.

The goal: boring deployments

A good pipeline makes deploying a non-event. You merge a change, it is tested, built and released, and if something goes wrong you roll back in a minute. That changes how you work: you ship small changes often, which are easier to test and easier to undo, instead of big risky releases every few weeks.

Step 1: Everything in version control

Before any automation:

Step 2: Checks on every push

The first automated stage runs on every push and pull request:

Keep this fast, ideally a few minutes. A slow pipeline gets skipped. Cache dependencies between runs.

You do not need full test coverage to start. Tests on the logic that would hurt most if broken give most of the value.

Step 3: Build an artifact once

Build a Docker image for each service, tag it with the commit hash, and push it to a container registry. The image that passed tests is exactly the image that runs in production. Never rebuild on the server from source, because then what you tested is not what you ship.

Use multi-stage Dockerfiles so production images contain only what they need, which keeps them small and reduces the attack surface.

Step 4: Automated deployment

For a single server or a few servers running Docker Compose, deployment can be simple:

  1. The pipeline connects to the server over SSH with a dedicated deploy key.
  2. It updates the image tag in the environment or compose file.
  3. It pulls the new images and restarts the changed services.
  4. It runs a health check and fails the deployment if the service does not come up healthy.

With a reverse proxy in front and health checks, restarts are brief. For true zero-downtime, run two instances and switch traffic once the new one is healthy. I describe the single-server setup in running Docker Compose in production.

For projects where each client company runs its own deployment, as with NeonTask, the pipeline deploys the same tested image to each client's environment in turn, which keeps every client on a known version.

Step 5: Rollbacks

Because every image is tagged with a commit, rolling back means redeploying the previous tag. Make it one command or one button, and test it before you need it. Keep a few previous images available on the server or in the registry.

The exception is the database. Rolling back code is easy; rolling back a schema change is not, which brings us to migrations.

Step 6: Database migrations

Schema changes should be versioned migrations that run automatically as part of the deploy, using your framework's tool (Entity Framework migrations for .NET, Prisma or similar for Node).

Rules that keep them safe:

Step 7: Staging, if it earns its keep

A staging environment that mirrors production is valuable when changes need client review or testing with realistic data. The pipeline deploys main to staging automatically, and production after approval or on a tag. For very small projects, good tests plus quick rollbacks can be enough without staging. Decide based on the cost of a bad release.

Step 8: Mobile app builds

Mobile releases benefit from automation even more than servers, because the manual steps (signing, versioning, uploading) are fiddly. Tools like Fastlane or a hosted mobile build service can:

The store review is still manual, but getting a tested build in front of testers becomes one step. For the store side of releases, see publishing your app on the App Store and Google Play.

Step 9: Notifications and visibility

Post deployment results to wherever you will see them: success, failure, which version, who triggered it. Tag error tracking with the release version so you can spot when a deploy introduced a problem. See monitoring and alerts for a small product.

Choosing a CI system

Most hosted Git platforms include a CI service, and for a small team the built-in option is usually the right one: no extra accounts, secrets live next to the code, and pipelines are defined in a file in the repository. Self-hosted runners make sense when builds need access to private networks or when hosted minutes become expensive. Whatever you pick, keep the pipeline definition in the repo so it is versioned and reviewed like code.

Secrets and access

Common mistakes

Where to start this week

  1. Add a pipeline that lints, type checks, tests and builds on every push.
  2. Build and tag Docker images in CI.
  3. Automate the deploy with a health check.
  4. Write down and test the rollback.

Everything else can follow. If you want help setting up CI/CD for your product, see my work and get in touch.

More from the blog

Let's work
together