All competitive technology organizations use continuous integration and delivery (CI/CD) to establish a fast and predictable software release cycle. A CI/CD pipeline uses automation to mobilize all the processes required to build, test, and release or deploy your software at a speed that is not possible with any other method.
We teamed up with Semaphore—a CI/CD solution for high-performance engineering teams—to write this article, where we’ll learn the basics of CI/CD and how to make the most out of pipelines.

What is CI/CD?

Continuous integration (CI) is the industry standard for software development as it allows developers to release features at a predictable and sustainable rate. When following CI practices, developers merge their changes into the main branch several times per day—hence the "continuous" in continuous integration.

Each code push initiates an automatic build and test stage that:

  1. Verifies the application is still in a buildable state (i.e. there are no compilation or dependency errors).
  2. Informs developers about the quality of the code.
  3. Detects errors as soon as they are introduced—allowing developers to fix them, typically in a few minutes, or revert the change and try again later.

The CI process can be extended with CD:

  • Continuous delivery: builds deployable artifacts to be released or deployed. The job of continuous delivery is to confirm that the application is always in a releasable state.
  • Continuous deployment: extends delivery by automating deployment. Continuous deployment takes the artifact created by continuous delivery and installs it in a live system.

What is a pipeline?

The CI/CD process is frequently depicted as a pipeline, with code entering on one side and an artifact or deployment emerging from the other. A pipeline is a series of jobs—performed by a dedicated CI machine—that accomplishes all the steps required to achieve the organization’s goals.

A continuous integration pipeline builds and tests an application from different angles.
A continuous integration pipeline builds and tests an application from different angles.

A pipeline can be separated into many stages:

  • The build stage compiles or otherwise builds the application. This includes downloading and assembling the required dependencies, building a container image, or producing an executable file.
  • In the test stage, we check the quality of the code by running a battery of tests.
  • The delivery and deployment stages prepare a releasable package and deploy it in various environments such as staging, testing, or production.
All the stages of a CI/CD pipeline
All the stages of a CI/CD pipeline‌‌

Best practices to make the most out of your CI/CD pipeline

As you might imagine, tending to the pipeline is essential to any software development project. The pipeline is a runnable specification that describes how an application will be built and released. So, you'll need to keep a few things in mind to make the most of your CI/CD pipeline.

1 - Choose a CI/CD platform that’s reliable, scalable, and easy to use

The platform you choose for running your CI/CD pipelines will be around for a long time, so it’s essential to consider all the alternatives and how they fit with your particular requirements. At a minimum, a CI/CD platform should provide:

  • Reliability: CI is the beating heart at the center of all your development efforts. If it’s down, you’re dead in the water. You can’t get any feedback or release new software.
  • Speed: you should see the results of your most recent build within 5 to 10 minutes.
  • Scalability: as the project grows, your pipeline also needs to grow. Your platform must be able to scale.
  • Easy to use: a CI pipeline should be easy to set up and modify. It also should be easy to troubleshoot build problems or test failures.

2 - Listen to the pipeline

The CI/CD pipeline is the vital sign monitor for your project: it will tell you if the build is broken and if your application is ready to be released. Consequently, a pipeline failure must not be ignored:

  • Never comment out failing tests, no matter how badly you need to deploy.
  • Never go home on a broken build. Either fix it or revert the change.
  • Do not check in a broken build. If the tests didn’t work on your machine, they will also fail on the CI server.
  • Try to fix problems as soon as they are introduced. At the very least, open an issue if you can’t take care of it immediately.
  • Wait for all tests to pass before opening a pull request.

3 - Adopt trunk-based development

Trunk-based development is a programming methodology that focuses on keeping branches as short as possible. With this practice, developers collaborate on a single main (or master) branch called the trunk. Branches can be created as long as they do not last for more than a few minutes or a couple of hours—never longer than a day. They must be merged into the trunk (or rejected) as fast as possible to keep the codebase in a constant releasable state.

Long-lived branches, e.g. feature branches, complicate development in many ways. The most problematic issues they cause are:

  • Information obscuring: other developers can’t see what you’re working on, impairing collaboration.
  • Merge conflicts: merge conflicts can create unforeseen delays, preventing you from releasing new features on schedule.
  • Extra work: the longer a branch lasts, the more effort will be needed to merge it.

Keeping the branches as short as possible gives us two benefits. First, we are spared from the problems long branches cause. Second, short branches naturally lead to working in small and safe increments.

4 - Build once, test many times

Every CI pipeline begins with a build stage in which the artifact is produced. In the build stage we:

  1. Download all dependencies, and compile or otherwise assemble an executable file.
  2. Apply linters and other quality checking tools to the code.
  3. Run unit, integration, end-to-end tests, and any other relevant types of tests.

The important thing is that the build process takes place only once. The resulting artifact is saved and is used in all later tests. Running all tests on the same artifact ensures results are consistent and accurate. Then, when all tests pass, the artifact is packaged for release.

Build once, test many times.

Build once, test many times.

Where should you store the artifact once it has been built? The answer depends on its nature. Compiled binaries and executables can be stored in an artifact store and published when ready, while container images typically go into registries like Docker Hub.

5 - Implement automated tests

The bulk of your testing should be completely automated—human intervention should not be not required. Ideally, tests should run each time you save a file on your development machine. Every language has many testing frameworks to choose from. Most will allow you to track file changes and re-run the tests when needed. This establishes a rapid feedback loop that adds consistency and will likely save hours of developers’ time every week.

Another instance in which tests must run is when you commit changes to the repository. Without automation, continuous integration is simply impossible.

While it can take some practice and may feel unnatural at first, an effective way to develop software is to write tests first and then the code. This is called Test-Driven Development, and, together with Behavior-Driven Development, are a powerful combo for writing code with fewer errors.

In TDD, we write the test first, set the objectives the code must meet a priori, and then write the minimal code needed for the test to pass. Finally, we refactor and improve the code until we have a modular and clean commit to push.

6 - Keep it fast

If it takes more than 10 minutes to see results after pushing new code, your CI/CD pipeline is holding you back. When results take a long time to arrive, they are less relevant and useful because developers must stop what they are doing to fix the problem. A rapid iteration cycle is what keeps engineers in a productive state.

Keeping a CI/CD pipeline fast involves optimizing the speed of your tests. Optimizing the testing suite can be done in many ways:

  • Breaking up large tests and parallelizing them.
  • Making tests independent from each other, and removing dependencies.
  • Removing sleep/waits from all your tests.
  • Analyzing and correcting flaky tests (tests that fail or succeed in a seemingly random fashion).

If you have a large and slow test suite, you will need to formulate a plan to improve it:

  1. Identify the worst performers.
  2. Start with the slowest tests.
  3. Analyze and profile the selected tests to know the root cause for their poor performance.
  4. Issue a fix, then repeat this process with other slow tests until things are running smoothly again.

7 - If you build it, you run it

The entire DevOps philosophy can be summed up in these seven words: “if you build it, you run it”. The custom of having separate development and operations silos does not work in a fast-paced, customer-oriented market. A DevOps culture means dev and ops are not separated in silos. The important thing is that developers' responsibility does not end when the test pass. They must be involved in the deployment and day-to-day maintenance in production.

Engaging developers in daily operations results in a better product. But developers are not operations experts, so how do we bridge the gap? By automating every step needed to release and deploy with CI/CD. To that end, a continuous deployment pipeline is responsible for:

  • Checking that the environment is ready to receive the deployment.
  • Deploying the application in production.
  • Running smoke tests to ensure successful deployment.
  • Optionally, with techniques such as canary deployments or blue-green deployments, the pipeline can also automatically roll back a failed deployment.

For example, the following pipeline builds a Docker image and deploys it to Kubernetes:

A CI/CD pipeline at work. In this instance, the pipeline builds, tests, and deploys the application to production automatically. This is awesome because it makes deployment routine and allows developers to release as soon as a new feature is ready.
Pipeline of a Docker image deployed on Kubernetes


A CI/CD pipeline at work. In this instance, the pipeline builds, tests, and deploys the application to production automatically. This is awesome because it makes deployment routine and allows developers to release as soon as a new feature is ready.

Conclusion

A pipeline may sound like overhead, but once it’s set up and running, it is an enabler for higher productivity, better developer experience, shorter development cycles, and more satisfied users. Without CI/CD, we are in the dark about the state of our project—we can neither maintain momentum nor produce software at a predictable rate.

We hope these tips will help you set up your first CI/CD pipeline and make the most of it. Happy building!