Automating CI/CD for a Java Application with GitHub Actions

Ntando Mvubu
9 min readJun 22, 2024

--

In today’s fast-paced software development environment, automating the Continuous Integration/Continuous Deployment (CI/CD) pipeline is crucial for ensuring efficiency, reliability, and speed. With prior experience in CI/CD automation with GitHub Actions, I delved deeper into the tool's capabilities for this project. Leveraging its robust features, I automated the CI/CD processes for a Java-based Petclinic application I found on GitHub, credit to Aditya Jaiswal.

Through this project, I aimed to delve into Tomcat as a server for deploying my Java application, given its widespread use and reputation as a reliable choice in real-world Java environments. Exploring Tomcat was crucial because it’s a go-to solution in the industry for deploying and managing Java applications.

Consequently, my project encompasses deploying the application to Tomcat, thus gaining practical insights into utilizing a widely recognized platform within the Java ecosystem.

Project Outline

Simple Architecture Diagram

Understanding the Architecture:

  1. Code Commit: Developers push code changes to GitHub.
  2. Code Retrieval: Jenkins fetches the latest code from the GitHub repository.
  3. Build Process: Maven compiles and builds the code into a deployable artifact.
  4. Deployment: The built code is deployed onto a Tomcat server.
  5. Access: Users can access the application through their web browser.

This flow ensures a seamless integration from code development to deployment, providing a streamlined and efficient process for delivering updates.

What is CI/CD?

In modern software development, CI/CD helps teams to rapidly and reliably release updates. Let’s break it down using a fitness app example:

  1. Continuous Integration (CI): Whenever a developer adds new code, CI ensures it integrates smoothly with the existing codebase. For our fitness app, this means every new workout routine or bug fix gets automatically tested as soon as it’s added to a shared repository like GitHub. Automated tests run to catch issues early, ensuring that new changes don’t break the app.
  2. Continuous Deployment (CD): Once the code passes all tests, CD automatically packages and deploys it to a staging environment for final checks, and then to the live app for users. This seamless process allows updates to be delivered quickly and reliably, minimizing manual effort and reducing errors.

By adopting CI/CD, the team can continually improve the fitness app, adding new features and fixing bugs efficiently, which leads to a better user experience. Moreover, CI/CD improves efficiency through automation by removing the manual work involved in traditional software development and deployment. Instead of relying on manual code integration, testing, and deployment steps, CI/CD automates these processes, leading to faster and more reliable releases. This automation minimizes human error, reduces deployment time, and ensures that new updates are consistently integrated and delivered with minimal downtime. As a result, teams can focus more on innovation and less on the repetitive tasks associated with manual deployment, ultimately speeding up the delivery of new features and improvements to users.

Why GitHub?

For this project, I chose GitHub as the platform to automate my CI/CD pipeline because of its seamless integration capabilities and widespread adoption in the industry. GitHub not only hosts code repositories but also provides GitHub Actions, a powerful tool for automating workflows. GitHub Actions enables easy setup of CI/CD processes directly within the GitHub repository, allowing for automated testing, building, and deployment with minimal configuration. Additionally, GitHub’s extensive community support, robust security features, and continuous updates make it an ideal choice for managing and automating projects.

Setting up the Server

Diving straight into the thick of things, I forked the Petclinic repo and cloned it onto my local machine. I then set up an EC2 instance hosted on AWS, SSH’d into the server and installed Java 11 (because Tomcat runs on Java runtime) and Apache Tomcat 9.

To make sure I could access the Tomcat server through my browser, I set up my EC2's security group inbound rules to allow all traffic 0.0.0.0/0 to port 8888.

Testing Connection to Tomcat

Once my server was set up as per my requirements, I moved on to GitHub Actions to configure the CI/CD pipeline.

Working with GitHub Actions

To kick things off, I focused on setting up the Continuous Integration (CI) pipeline. It is considered best practice to start with a CI (Continuous Integration) pipeline before expanding to a full CI/CD (Continuous Integration/Continuous Deployment) pipeline. This ensures that your code is automatically tested and integrated smoothly. Once that works well, you proceed to add the more complex part of automatic deployments. This step-by-step approach makes managing and improving your workflow easier.

Setting up the CI Pipeline

The heart of automation lies in the .github/workflows/main.yml file. This file defines the processes you want GitHub to run in response to various events in your repository and it automates these processes by executing them upon certain triggers.

Making use of the GitHub Actions documentation and some research online, I managed to configure the CI part of my pipeline. Below is a snippet of the YAML file illustrating the configuration:

name: Java CI pipeline
on:
push:
branches: [ "ci-cd-workflows" ]

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 package

- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: petclinic-war
path: "/petclinic.war"

In this configuration:

  • The workflow triggers on pushes made to the ci-cd-workflows branch.
  • It runs on the latest version of Ubuntu.
  • It checks out the repository code.
  • Sets up JDK 11 with Temurin distribution and caches Maven dependencies.
  • Builds the Java application using Maven. Maven was specified as a requirement in the pom.xml file governs the project's configuration, dependencies, and build processes, aligning seamlessly with best practices for Java-based applications.
  • Uploads the built artifact (petclinic.war) on Github Actions artifacts for later use (i.e. deployment stage).

Issues Encountered

During the execution of my CI pipeline, I encountered two issues:

  1. My CI pipeline was marked as passed, even though the Upload Artifact step failed.

To address the discrepancy where the CI pipeline passed despite the failure of the upload artifact step, I implemented error handling within the workflow configuration. By introducing conditional logic, the workflow now verifies the success of the preceding steps before proceeding with the upload artifact action. Below is the updated snippet of the YAML file illustrating the error-handling implementation:

- name: Upload Artifact
if: success() # Only execute if previous steps were successful
uses: actions/upload-artifact@v3
with:
name: petclinic-war
path: "/petclinic.war"
if-no-files-found: error # Fail the workflow if no files are found to upload

To test if it worked as expected, I intentionally failed the pipeline with the updated configurations on this step, and the error handling worked:

Tested Error Handling

2. The Upload Artifact step had failed, with the below error:

Artifact Not Found Error

To troubleshoot this, my initial step was to investigate the location of the built artifact. I reviewed the console output generated in the “Build with Maven” step during the CI pipeline execution. Typically, the build process logs include information about the file paths, which allowed me to ascertain the location of the built artifact.

Correct Artifact Location

After confirming its location, I made the necessary adjustment by changing the path from /petclinic.war to /target/petclinic.waron my maven.yml. This modification ensured that the CI pipeline successfully located and uploaded the artifact, resolving the issue encountered during the CI process.

Setting up the CD Pipeline

After successfully setting up the Continuous Integration (CI) pipeline to automate the build process of my Java application using Maven, the next crucial step in my project involved deploying the built artifact to an Amazon EC2 instance running Apache Tomcat. This section took me quite a while to figure out, and I encountered some challenges. Below, is the configuration and steps taken to deploy the application:

  deploy:
needs: build
runs-on: ubuntu-latest

steps:

- name: Download artifact
uses: actions/download-artifact@v3
with:
name: petclinic-war
path: "/home/runner/work/Petclinic/Petclinic/target/"


- name: Set up SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.EC2_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa



- name: Deploy to EC2
run: |
scp -i "~/.ssh/id_rsa" -o StrictHostKeyChecking=no /home/runner/work/Petclinic/Petclinic/target/petclinic.war ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/opt/apache-tomcat-9.0.65/webapps

In this configuration:

  • Download artifact: Downloads the petclinic.war artifact from the GitHub Actions artifacts associated with the petclinic-war name and place it in the specified path on the runner machine.
  • Set up SSH: Here, SSH setup is performed. The script creates a .ssh directory if it doesn't exist, writes the SSH private key (EC2_KEY stored in GitHub Secrets) to ~/.ssh/id_rsa, and sets appropriate permissions (chmod 600) to ensure secure access to the Tomcat server.
  • Deploy to EC2: This step attempts to copy (scp) the petclinic.war file to the webapps folder of Apache Tomcat. This allows Tomcat to automatically deploy and run the Java web application. This step uses the SSH private key (~/.ssh/id_rsa) and SSH options (StrictHostKeyChecking=no) to bypass strict host key checking during the SSH connection.

To ensure that my deployment steps run successfully, I have to make sure that the EC2_KEY, EC2_HOST, and EC2_USER secrets referenced in the pipeline are stored securely:

Secrets stored on GitHub Actions

Issues Encountered:

As mentioned, this part of the project was no smooth sailing either; my pipeline failed as shown below:

  1. The error message ssh: could not resolve hostname: name or service unknown
Deployment Failure Error

After some research, I figured out the issue. The GitHub Actions runner, as of 2023, does not have a .ssh directory, and thus, a known_hosts file using the ssh-key-scan command. This means that I need to create the file and add the SSH host to it. See the relevant resource here.

Modified SSH configurations:

 - name: Set up SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.EC2_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts

After these amendments, my CD pipeline ran successfully.

Testing Deployment on Server:

To test that my application was successfully deployed as reflected on the pipeline, I checked if the petclinic.war file was present in the specified /opt/apache-tomcat-9.0.65/webapps/ directory on my remote server.

.war File Present in Remote Server

My .war file was successfully copied into the /webapps directory. In the context of Tomcat, the webapps directory is where Tomcat looks for web applications to deploy. By placing the .war file in this directory, Tomcat recognizes it as a web application and initiates the deployment process. This process includes unpacking the .war file, setting up the necessary configurations, and making the application accessible via its specified context path (typically based on the filename of the .war file). Thus, copying the .war file to the webapps folder ensures that the application is deployed and ready for users to access without manual intervention.

And finally, I attempted to access the application using the server’s public IP address running on port 8888, with the “petclinic” added to the path.

The application was successfully deployed and accessible!

Conclusion

Through this project, I effectively automated the Continuous Integration and Continuous Deployment (CI/CD) pipeline for a Java-based Petclinic application, utilizing GitHub Actions and Maven. The automation ensured smooth integration and deployment of code changes, ultimately deploying the application seamlessly to a Tomcat server. This not only simplified the development process but also demonstrated the significant advantages of automated pipelines in speeding up and securing deployments.

Working with Tomcat, I gained valuable experience in configuring and managing it as a web server, deepening my understanding of deploying Java applications in a real-world environment. This hands-on exploration with Tomcat configurations was instrumental in ensuring the stability and scalability of the deployment process.

Overall, this project provided a comprehensive, practical understanding of CI/CD practices and their application in real-world scenarios. The successful deployment confirmed the efficiency of integrating GitHub Actions with a reliable deployment strategy, proving that automated workflows are key to handling updates efficiently and consistently.

--

--