CI/CD is a combination of coding practices, work culture, and technological innovations that allow us to make more frequent and reliable software releases. Companies use CI/CD to achieve milestones predictably, ship better products and increase productivity.
CI/CD stands for Continuous Integration and Continuous Delivery. It’s a methodology that encompasses every step in the life of software development, from building to testing, then deployment, and maintenance. Continuous integration is the central component in DevOps and Agile practices.
The ultimate goal of CI/CD is to make releases with less effort and without stress. Continuous integration places emphasis on extensive testing to remove the uncertainty of changing code, while continuous delivery and deployment streamline the publishing process and reduce the possibility of human error. Continuous Integration.
About the author
Marko Anastasov is co-founder of Semaphore
In the construction world, architects rely on models to test buildings. The smallest change can make the difference between a bridge that tolerates strong winds and one that collapses. Continuous integration is to developers what models and simulations are to architects, a way of making sure our work works.
One of CI’s core principles is that the master branch must always be ready to release. To achieve this, we use CI/CD pipelines. Pipelines automate the software delivery process. A pipeline builds code, runs tests, and safely deploys the application. Pipelines come in all kinds of shapes; some demand dedicated hardware and a lot of maintenance.
Continuous integration is followed by Continuous Delivery (CD), which handles all the many details needed to build a deployable package. Depending on the nature of the project, its output can range from a compiled binary, a Java Jar, a module, or a minified package, to a container image.
Continuous delivery allows us to have a working, up-to-date version of the project at all times, which makes QA teams and managers happy. Also, customers get to sample new features earlier—a win-win situation since they get an early taste of the product, and we get invaluable feedback. In short, delivery pipelines let us sync up requirements more frequently.
If we go beyond delivery territory, we reach Continuous Deployment. Continuous deployment goes through the sequence of steps that deploys the code into the production environment, and it does it with minimal human intervention.
How do you know if you’re practicing CI/CD? We can discuss methodologies all day, but numbers don’t lie, so ask yourself this: Can I stop everything I’m doing right now and make a release in under 20 minutes? Can I deploy my application by pushing a button? If you answered “yes” to both questions, congrats! You’re doing CI/CD. If not, read on!
What can CI/CD give us?
CI/CD can give us enormous gains in agility and speed—we can go from releasing on a monthly schedule to releasing weekly, then daily, and ultimately many times in a day.
There’s a reason why all competitive software companies are using continuous integration, and it’s because it helps them reach their goals predictably. A CI/CD pipeline removes all manual tasks, which results in fewer errors, less time wasted on fixing stuff, and more free time to focus on features.
The greatest challenge when starting a new project is figuring out what it will look like and how it will work. A good design is half of the battle won. A bad design will drag development on, cause frustration, and end up with disappointing results for everyone involved.
CI/CD not only shortens the development cycle but also lets us progress in smaller steps, iterate and re-evaluate progress more often. In consequence, we have a better shot at meeting our users’ precise needs.
Higher quality, fewer failures
The pipeline will always be better at running tests than us. Pipelines are relentless and uncompromising—pipelines keep bad code at bay. We can use them to enforce quality checks in pull requests. Linters, style checkers, coverage reports, copy-paste detectors they all check for anti-patterns, convoluted, or otherwise hard-to-read submissions. Unit and integration tests verify that new code is reliable and meets specifications.
On the final account, the level of innovation depends heavily on how many iterations we can fit in before release. A shorter development cycle gives us the chance to make more iterations, try new things, take more chances. CI/CD gives us the freedom to experiment more and to react to changing requirements quickly.
An open culture
The CI/CD pipelines are transparent—they show the current state of the project, its history, and its progress to everyone. This openness fosters a culture of communication, ownership, and accountability. Everyone sees contributions, and issues can be assigned more fairly.
When we systematize releases, we take away many of the risks inherent to patching or updating. Patch days can be like any other day when we have confidence in the process. And if, despite everything, something goes wrong, rolling back is a strategic retreat.
How to start practicing CI/CD
You don’t have to change all your workflow at once. In fact, it’s best to make changes here and there in small increments, see what works, what doesn’t, and adjust as you go.
The only rule is that you have to have all your codebase in a central repository like GitHub, where everyone in the team can view and change it. You are probably using something like this already, so you’re well on the road to CI/CD.
Make it your first challenge to move the build process to the CI environment. This includes things like downloading dependencies, compiling, or building Docker images. If it’s the first time you do this outside your laptop, you’ll find unexpected problems preventing the build—this is good, you’re systematizing the process and making your code more resilient.
Once you get the build going, make it a priority never to let it fail. If at any point it does, drop everything else you’re doing and fix it. Remember the prime directive: always be ready to release.
While coding, prefer to use short-lived branches; they will be easier to merge later. So, split big changes and merge them separately. By using laser-focused branches, you’ll be able to develop faster and explore more ideas. And, speaking of branches, it’s not a good idea to use them to hide features. Instead, use feature flags to disable features that aren’t yet ready for prime time.
Your second challenge is to make tests an integral part of your development. Tests are the first line of defense. Tests are the main tool you have to keep quality high and confirm that the code is correct.
Two software development techniques will help you here: Test-Driven Development (TDD) and Behavior-Driven Development (BDD). The first encourages you to switch the usual order, write tests first, and follow with the code. The second technique brings this write-test-first strategy to the macro level, giving us a view from the end user’s side.
Pay attention to how long the pipeline takes to run; if it takes too long, it’s easy to get distracted and lose the train of thoughts. You’ll find that solving problems is much easier if you can keep your flow going. As a rule of thumb, if you have to wait longer than the time it takes to get a cup of coffee, you need to optimize your pipeline. So, aim for it to run in 10 minutes or less. That way, you’ll keep the feedback loop short and the number of iterations high.
Here are some tips for keeping the test stage fast:
- Classify testing by level: unit tests, style checks, and coverage analysis are low-level. Integration tests are in the middle, and user interface tests are high-level.
- Order tests by complexity: start with low-level tests. Often, these take the least time to run, and their failure marks fundamental problems. There is no point in running a complex test suite when we have code not even passing basic quality checks.
- Integrate: follow the middle-level tests like integration, functional, end-to-end tests, and then continue with high-level UI tests if applicable.
- Audit: complete the test stage with security and vulnerability scans.
Tempting as it may be, never comment out failing tests. It’s best to fix problems while they are still fresh in mind. On the same note, do yourself a favor and never go home with a failing commit. Either fix it before leaving or revert the change and try again tomorrow. This may sound harsh, but you and only you are responsible for the errors you introduce.
Continuous delivery lets us frequently expose beta testers to intermediate builds. That way, we can confirm and adjust the specifications along the way. The end product will thus be much better aligned with expectations.
Delivery pipelines build deployable artifacts. Typically, the delivery stage deploys the application in a production-like environment. If everything goes according to plan, changes are approved, and the new branch can be merged into the mainline.
Who is responsible for deploying the application? In the old days, it used to be up to the operations team. But since DevOps became mainstream, it’s up to you. If you build it, you also have to deploy and maintain it.
The deployment stage publishes the application. This pipeline must also set up all the related services, accounts, and other infrastructure that the application depends on.
Deployment may be initiated manually, but the process itself should be automated. If you have to go through a checklist or do any manual steps, you’re not there yet.
We have at our disposal many DevOps tools to automate deployments:
- Serverless: also known as Lambdas or Functions-as-a-Service (Faas), these platforms allow us to run applications in the cloud without needing to worry about infrastructure.
- Configuration management: we can use software like Ansible, Puppet or Chef to configure servers, install services and deploy applications.
- Infrastructure as code: tools like Terraform or CloudFormation allow us to creation infrastructure on-demand.
- Containers: we can use platforms like Docker Swarm or Kubernetes to run containerized applications at scale
Adopting DevOps and CI/CD practices involves more than just using new technologies; it’s about promoting a culture based on transparency, ownership, and communication.
It means changing the way we think about software development and how team members interact. In the end, it is a transformation that leads to better products and a better experience for everyone involved.