You’re browsing job listings, trying to get your first role as a Junior Developer when you see it, “Experience with CI/CD.” It’s enough to make your stomach drop, especially if you’re a bootcamp graduate.
See, most bootcamps don’t teach CI/CD, or if they do, they teach it poorly, trying to cram it in during project week.
And yet, CI/CD is essential for most enterprise level companies.
In this series, I’m going to try and give you the basics of CI/CD in hopes that you’ll at least feel comfortable discussing it, maybe even building your own pipeline!
What is CI/CD?
CI/CD is an acronym for Continuous Integration & Continuous Delivery. Alternatively, you’ll see some people use Continuous Integration & Continuous Deployment.
Why is Continuous Integration Important?
Continuous Integration is super meta. In most cases, you’re writing code to check the code you wrote. For me, it typically boils down to one question: “Is this code good?”
Of course, that leads to other questions like, “What is good code?”
As well as, “When should I be checking the code?”
Quickly followed by, “If the code is bad, what should I do?”
And its corollary, “What should I do if the code is good?”
Team Code Standards
Continuous Integration is important because it allows us to enforce team code standards. If you’ve written code for any length of time, you’ve probably already discovered that people everywhere have their own thoughts and opinions on the proper way to write code. From single-quotes vs double quotes, spacing, to line length, every bit of formatting is up for grabs.
It turns out, good code is what your team agrees upon. The best teams I have been a part of dedicate a small portion of the sprint to code standard meetings. Prior to these meetings, team members submit “rules” they would like to enforce for the team. Where do these rules come from? Often times, they revolve around industry options called ESLint.
During the meeting, you have the opportunity to “sell” your teammates on your opinion that, “We should use a line length of 80.” Or, “We should restrict unused imports.”
If you’re starting as a Junior Developer, you likely won’t have a lot of input in this, especially if you’re joining an established team that already has code standards in place. But if you get a chance to attend these meetings, I recommend them.
All of these stylistic “preferences” or code standards, can be enforced by code. That happens as part of Continuous Integration.
I’m going to write entire articles about unit tests, integration tests, and end-to-end tests. But for now, understand that team code standards are more about how the code looks than how it functions.
Imagine you’ve just written some code. Maybe you’ve “tested” it. If it’s an API endpoint, you’ve loaded up postman and made an API call a couple times. And it works! Is that enough confidence to deploy to production?
What if that code will be used by millions of people? Would you still feel confident with a couple of test calls?
What’s more, can you be sure that in the process of adding your new code, you didn’t mess up any existing code?
You begin to see the issue. By running automated tests as part of Continuous Integration, we can work to ensure that 1) No existing functionality was lost. and 2) New code should function as expected.
Code standards and tests help us understand what makes good code, but when should we be checking this?
Most enterprise level branching strategies assume that the Main branch (legacy systems still sometimes refer to this as the Master branch) is production-ready. That means that it should be able to be deployed at any time.
We want to lint and test our code BEFORE it gets to the production branch.
Enter the Development branch.
This branch is often used as a temporary place to store code before deploying to production. Especially if you have multiple features going out on a single release, the Development branch can allow your team to push code changes, test, and integrate them together before going into Main.
This means, we also need Feature branches.
A feature branch is a specific branch that you are working on to add your feature. This keeps you from pushing directly to the Development branch, as this could affect others who are working on the same project.
Now, it doesn’t have to just be for features. It could be for bugs, or tech debt, or any other naming system your team has in place. But whatever it is, you want to be consistent. Teams that use Jira often use the standard:
This system prefixes the branch name with the type of story that it is, the story number, and the story title. Anyone can tell at a glance exactly what’s supposed to be happening on this branch.
So when should we be checking for good code?
You definitely want to run your checks before code goes from the Development branch into Main. You can hook into this with a Pull-Request check.
Most teams will also run a check before a Feature branch gets merged into Development. This is also done via Pull-Request.
And some teams will even run checks when you push your Feature branch up to GitHub or similar cloud-based repository managers. This can be accomplished with a git push hook.
At any step of the process, if your code fails linting, or your tests fail, the process stops. Your Pull Request is rejected. You cannot merge your code.
The nice thing about this is that it’s no longer left up to a person for enforce code standards, or reject someone’s “bad” code. The computer handles it for us. You can argue with me. But it’s hard to argue with a computer.
Once your code passes all the continuous integration checks at each stage, it moves from your Feature branch into Development. And once Development has been merged into Main, what happens then? It gets deployed.
Why is Continuous Deployment Important?
Back in the day, software development followed a system known as the waterfall method. Teams would work on an application or feature for 6–12 months, sometimes longer, and then (metaphorically), push it over the waterfall and release it. This came with several problems.
First, in those six to twelve months, the scope may have changed significantly. Maybe we need to add a new feature. Or remove a no longer necessary feature? Too bad. We’re building this!
Additionally, it posed problems for maintenance and upkeep post-release. How am I supposed to attack bugs in a live production app if I have to wait for the six-month release cycle? Hotfixes were a definite thing.
Now, most teams use what is known as the Agile Methodology. It allows teams to work in 1–2 week sprints, moving rapidly between features and bugs. It gets software out into production as quickly as possible, giving teams valuable user feedback.
But this means, teams needed a way to deploy much more quickly.
Enter Continuous Deployment
Now, as part of a CI/CD pipeline, anytime code is successfully merged into the Main branch, it is deployed, automatically!
Remember, as part of the CI/CD paradigm, the only code that should be in Main is production-ready code. And to ensure that it is production ready, it’s run through numerous checks and tests.
And so, the pipeline itself continuously deploys any code changes from the master branch. All major platforms have APIs that allow these automated systems to deploy: AWS, Google Cloud, Azure, Heroku, and more.
You will often see as part of the deployment step, some sort of containerization, especially Kubernetes has risen as a popular platform. This is typically accomplished with Docker.
But there is a problem with this type of automated deployment. What if we merge code to Main, and despite all our checks something breaks?
You’ll need a plan in place to “roll-back” to a previous version. Sometimes this is done manually, but you will also see some companies that build custom deployment jobs for this so that it can also be handled automatically.
If you’re interested in learning more about CI/CD, I’m hosting a special summer session absolutely free. You can sign up here!