Minimum viable CI pipelines

When I start a new project, and sometimes when I join an existing project, there are no CI (continuous integration) pipelines, no automated tests, and sometimes no local environment configuration.

In that case, where should you start when adding a CI pipeline?

I like to start with the simplest solution to get a passing build and to prove the concept - even if it’s a “Hello, world” message. I know that the pipeline is configured correctly and runs when expected, and gives the output that I expect.

I like to use Docker for my development environments, partly because it’s very easy to reuse the same set up within a CI pipeline just by running docker image build or docker compose build.

Having a task that ensures the project builds correctly is a great next step.

Within a Dockerfile, I run commands to validate my lock files, download and install dependencies from public and private repositories, and often apply patch files to third-party code. If a lock file is no longer in sync with its composer.json or package.json file, or a patch no longer applies, this would cause Docker and the CI pipeline to fail and the error can be caught and fixed within the pipeline.

Next, I’d look to run the automated tests. If there aren’t any tests, I’d create an example test that will pass to prove the concept, and expect to see the number of tests grow as new features are added and as bugs are fixed.

The big reason to have automated tests running in a pipeline is that all the tests are run every time, ensuring that the test suite is always passing and preventing regressions across the codebase. If any test fails, the pipeline fails. This is knows as continuous delivery - ensuring that code is always in a releasable state.

From there, I’d look to add additional tasks such as static analysis and code linting, as well as anything else to validate, build or deploy the code and grow confidence that a passing CI pipeline means that the code is releasable.

As more tasks are added to the pipeline, and the more of the code the tasks cover (e.g. test coverage) the more it can be replied upon.

If there is a failure that wasn’t caught in the CI pipeline, then the pipeline itself should be iterated on and improved.

Having a CI pipeline allows you to identify issues sooner and fix them quicker, encourages best practices like automated testing and test-driven development, and enables continuous deployment where code is automatically deployed after a passing build.

If you have a project without a CI pipeline, I’d encourage you to add one, to start small, and continuously iterate on it over time - adding tasks that are useful and valuable, and that build confidence that you can safely release when you need to.