Building a CI/CD Pipeline using GitHub Actions

Lakshan Madhuranga
7 min readJul 17, 2024

--

Hello Solvers..

In this blog post, I have discussed what CI/CD is, why we need CI/CD, and the concepts behind it. Finally, I have demonstrated how to create a CI/CD pipeline for a simple spring boot application using GitHub actions.

What is CI/CD

CI/CD refers to continuous integration and continuous delivery or deployment. This is a practice that is used in software development process. In this practice, we can automate manual processes such as code build, testing (including integration tests, unit tests, and regression tests), deployment, and infrastructure provisioning. Therefore, developers just need to commit their code changes into centralize repository. Then all the other processes like build, testing, deployment will automatically happen through an implemented CI/CD pipeline. This CI/CD pipeline triggered continuously when developers commit any code changes.

CI/CD is not a single process; it is completely two processes. So now we check what is the difference between these two.

Continues Integration (CI)

Sometimes, when developers working with a team, they are developing their features individually. Think what will happen, if they push their code into the master branch after they develop their feature. If there are any bugs in their code base, whole system will be down, making it harder to deliver updates to customers quickly.

So the continues integration (CI) come to the stage now. If we have configured a CI pipeline to our project, we are not facing this kind of problems. The CI pipeline can automatically run builds, store artifacts, run tests, and even conduct code reviews using tools like SonarQube. Therefore, developers can identify if there are any bugs or issues in their code, before push it into master branch or any other cloud platform. So we can configure the CI pipeline to be triggered every time there is a commit/merge in the codebase. You can get better understanding how CI pipeline workflow is working from below diagram.

CI Pipeline Workflow

Once developer commit code into a version control system (ex: git), then trigger the CI pipeline and automatically happen build, test, code scan and finally merge to master branch. If code has any errors or bugs, the build will fail, and the developer will be notified. Then developers need to fix issues and again commit to the changes. If there are no issues, successfully build, pass the test cases, and finally complete the CI pipeline.

This helps organizations deliver updates to their consumers frequently, finds and fixes errors faster, and increases team productivity by reducing the developers of manual work.

Continues Delivery/Deployment (CI)

In continues delivery stage in the CI/CD pipeline there might be different environments, such as developer , Testing , staging, production environment. So in this stage, developers and testers can test the software further in these environments and fix any issues that are there. Finally, we can deploy in to production.

if the production deployment is doing manually, that procedure is known as continuous delivery. however, continuous deployment is used when the deployment to the production environment is automated. You can get some understanding from the below diagram of how the CD pipeline workflow is working.

CD Pipeline Workflow

Build CI/CD pipeline with GitHub actions — Demo

In this section I have discussed how to create a simple CI/CD pipeline using GitHub actions. So I have already developed a simple spring boot application for this demo and then pushed it into a git repository. The following diagram shows what I have done in this demo.

Demo workflow

First, I developed an application and then pushed into GitHub. Then I created a CI/CD pipeline using GitHub actions. And then automatically pushed the application as an image into Docker Hub. This is the overall workflow of this demo. Now let's see how to create the CI/CD pipeline in GitHub actions.

When we navigate to our Git repository, where we need to create a CI/CD pipeline, we can see there is a tab called actions. We can select any workflow (based on our project) from the GitHub actions.

GitHub actions page

I configured my workflow in Java with Maven in this demo, because my application created in Java. When we select the desired workflow then generate a workflow.yml file. Now lets see what is this file and what we have in workflow file. You can see below my generated workflow file

# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: project cicd flow

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn clean install

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6

A workflow is a configurable automated process that can run one or more jobs. Workflows are defined by a YAML file that is checked into your repository. They can be manually triggered, scheduled, or triggered by an event that occurs in your repository. Now lets see what are in each line of the workflow file.

name: project cicd flow
# We can give any name for the name as a workflow name.
on: [push]
[pull_request]
# Here we specifies the trigger for this workflow. In this demo, use push and
# pull_request event so the workflow run is triggered every time someone pushes a change to the repository
# or merges a pull request to the main brach.
jobs:
# Here we group together all the jobs that run on my workflow
build:
# In my demo has one job, which is 'build'.
runs-on:
# We can configure the jobs to run on the latest version of an Ubuntu runner.
# That means the job will execute on a fresh virtual machine hosted by GitHub.
steps:
# This groups together all the steps that run in the "build" job.
uses: actions/checkout@v4
# This specifies that this step will run v4 of the actions/checkout actions.
uses: actions/setup-java@v3
with:
java-version: '17'
# This step uses actions/setup-java@v3 action to install specified java version.
run: mvn clean install
# The run keyword specified the job to excute the command on the runner.
# In this case, I used "mvn clean install" which removes all files generated by the previous build and commands to deploy the packaged JAR/ WAR files to the repository.

Now I need to change my yml file’s JDK version. To do that, I just change the JDK version to 22.0.1 and then commit to the repository. When I push the latest code to the repository, it automatically runs my workflow file and builds the project based on the workflow file.

Specifying the Docker image to build and push to Docker Hub

As our final step in creating the CI/CD pipeline, we specify the Docker image to build and push to the Docker Hub. We have to specify few steps in our workflow file to achieve this. I have listed those steps as follows:

  1. Login to my Docker Hub
  2. Build the Docker image
  3. Push the image to Docker Hub repository

First, we need to create a repository in our Docker Hub that need to push the final Docker image.

Now we can specify the commands in the workflow file as follows:

    - name: Build & push Docker image
uses: mr-smithers-excellent/docker-build-push@v6
with:
image: devlakshan/springboot-cicd-image
tags: latest
registry: docker.io
dockerfile: Dockerfile
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

I have defined the image name (In the image section) that I have created in Docker Hub. In here, we need to define the username and password of our Dokcer Hub as a secret. To do that, you can follow the following steps:

  1. Go to settings in your repository.
  2. Go to secrets and variables in the security section.
  3. Now you can click on the New repository secret. And define the username and password of your Docker Hub.

And then we have to create a Docker file in our project. Before creating it, I defined the jar name in my spring boot application using <finalName> tag. Because, we need the jar name to define the Dockerfile. You can add it to the pom.xml file in your spring application.

In Dockerfile we need to define followings:

FROM openjdk:22
EXPOSE 8080
ADD target/springboot-cicd-image.jar springboot-cicd-image.jar
ENTRYPOINT ["java", "-jar" "/springboot-cicd-image.jar"]

We need to provide our application’s jar name for the ADD and ENTRYPOINT sections.

Now we can commit our changes. After we push our changes, the pipeline will automatically run and build our application. Then create a Docker image for our application and deploy it to the Docker Hub.

Docker Image

I think you can get a better understanding of what CI/CD is, how it works, and how to create a simple CI/CD pipeline using Git Hub Actions from this blog.

Thank you very much for reading my post.

--

--

Lakshan Madhuranga
Lakshan Madhuranga

Written by Lakshan Madhuranga

Undergraduate at university of Kelaniya, and Studies ICT in faculty of computing and technology.

No responses yet