Using Github actions to add CI/CD to a project

brunooliveira profile image Bruno Oliveira ・5 min read


CI/CD has been gaining traction in recent times, with more and more teams aiming to become devOps-centric, and with more and more business seeing the importance of having good, meaningful and stable pipelines supporting the code that such businesses depend on.
As such, it's relevant to know how you can apply it to your next project and also to get familiar with the multiple different platforms and ways to setup CI/CD.

A case for CI/CD

The importance of CI/CD becomes more apparent the larger a project becomes and the more developers are working on it. The chances of breaking things or introducing regressions are higher the more developers are touching the same codebase, and, if no good test harness is in place, things will go by and slip into production unnoticed, potentially causing problems.

For e.g.: a developer works on a new feature that requires extensions of two different code areas and a bridge to a newly created service. The feature is developed, the developer tests it locally, runs it against all possible test cases, a colleague reviews the code and gives the green light and the feature is rolled out.
However, before the roll out, as a good practice dictates, a merge with the master branch has been done into the feature branch, and then that new "HEAD" is deployed.
Since there was no testing in place, and the code had been reviewed, nobody noticed that the interface for one of the dependent services had changed without knowledge of this new feature, and, as a result, code was broken in production.

CI/CD can help prevent and mitigate this type of scenarios, by:

  • Ensure correct code compilation (yes, seems obvious, but, it's a check that is wise to have in place, if anything, as a basic smoke test);

  • Automate the running of unit and integration tests if available. With every code push, a suite of automated tests must be ran automatically to ensure that the latest changes do not break anything;

  • If appropriate, a tagged release should be created and, for example, deployment actions shall be taken. These can range from re-tagging a docker image, to upload code to external registries to create docker images, send code to platforms upstream that handle the deployment, run jenkins jobs, orchestrate puppet deployments, etc.

If all of these (crucial) steps are automated along the way, developers can focus on writing code (and unit tests) while "forgetting" the rest of the steps, meaning: if you do your job correctly and well enough, the upstream handling of compiling, testing and deployment can all be automated (of course, automation is a lie and you need to write code for it, as we shall see :D ) for you.

Introducing Github Actions

There are many ways to setup and get started with CI/CD and some depend on the platform you're using (Gitlab vs Github for example). We will be using Github which recently introduced Github Actions for free for private repositories.

GitHub Actions help you automate your software development workflows in the same place you store code and collaborate on pull requests and issues. You can write individual tasks, called actions, and combine them to create a custom workflow. Workflows are custom automated processes that you can set up in your repository to build, test, package, release, or deploy any code project on GitHub.

With GitHub Actions you can build end-to-end continuous integration (CI) and continuous deployment (CD) capabilities directly in your repository. GitHub Actions powers GitHub's built-in continuous integration service.

In order to get started, we need to create a folder, named .github, place it in the root of our repository and there, we can define our workflows.

A workflow is composed of individual actions that can be tied together to create custom, more complex workflows.

Let's see how we can setup two steps in a pipeline, to start our journey towards CI/CD: we will want to compile the code, and, after, if that job succeeds, we want to run our unit tests in an automatic way.

First, as discussed, we create a .github folder in the root of our repository, and, inside, a new folder, called workflows that can contain our multiple workflows and inside of it, the name of our new workflow, for example, first-ci.yml. Our repo looks like:

repo structure

The base idea of Github Actions is that they are, partially, shareable, and customizable, so, from the github UI, we can view the currently available actions, and, from the list, we choose the Java CI with Maven. The UI should look like:


Now, let's write our first workflow, where we will compile the code, skipping Unit Testing and afterwards, run the tests only:

name: Java CI with Maven

    branches: [ master ]
    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
          java-version: 1.8
      - name: Build with Maven
        run: mvn clean compile install -DskipTests --file pom.xml
    runs-on: ubuntu-latest
      - uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
          java-version: 1.8
      - name: Run unit tests
        run: mvn test --file pom.xml

Let's look at our file in detail.

We are defining one single Github workflow called "Java CI with Maven", that will be triggered on every push to the master branch, as stated on lines 3-5.

Then, we define two jobs: build and test.

We see that these jobs are made of a series of steps and both of them run on a VM provisioned by github, in our case ubuntu-latest.

For each job, we first setup JDK with java 1.8 on the VM where the jobs will run, then, we compose our file with two custom actions:

  • actions/checkout@v2 : used to checkout our code into the VM so we will know what to run

  • actions/setup-java@v1 : this is required since this is a Java project and we need Java installed to run maven and compile and run our code and unit tests

We can give our jobs descriptive names, and in the run: ... piece of the configuration, we specify exactly what we want to run, the core of the job, tailored to our particular project.

Once this is in place, with every push to master branch, we can check the actions tab and we will now see our job, up and running:


Note how the names of the jobs, of the workflow and of the steps at each job exactly match what we defined in our first-ci.yml.

When the workflows follow this directory structure, we can get them up and running very very easily!


CI/CD is very important, and, while a few years back it could have been seen or even dismissed as a nice to have for many organizations and teams, in 2020, where complexity of projects is higher, teams are larger and cost of regressions keeps growing, there is no excuse to not have CI/CD setup.
It's very useful, it allows us, developers, to delve into the devOps world, and, it's becoming the new standard, so, not doing it means higher risks to businesses, customers and higher cost to rollout fixes and patches. Hope this post shed some light on it, and now... go and setup your Github Actions!


markdown guide