CI/CD for SpringBoot applications using Travis-CI
In this article we are going to learn how we can useĀ TravisĀ CIĀ for Continuous Integration and Continuous Deployment (CI/CD) of aĀ SpringBootĀ application. We will learn how to run maven build goals, perform test coverage validation using JaCoCo plugin, Code Quality checks usingĀ SonarCloud, buildĀ Dockerimage and push it toĀ DockerHubĀ and finally deploy it toĀ Heroku.
The source code for this article is atĀ https://github.com/sivaprasadreddy/freelancer-kit
Last week I was talking to my friend about how easy it became to build a Java application and deploy it using SpringBoot. During the discussion one point came out about how much it cost to build a Java application and deploy it somewhere (cloud). So, I thought of exploring more about the free services that we can use to automate all the project development activities with minimal or no cost at all.
Few years ago I usedĀ CloudBeesĀ andĀ CloudFoundryĀ to build and deploy my pet projects which were offering free hosting service, but they are not providing free services anymore.
In the past I have usedĀ Travis CIĀ for my java projects just for preliminary testing purpose, but looking at their documentation I realised they provide a lot more features.
So I thought of checking can I use Travis CI for my projects to do all the usual tasks such as:
- Checkout the latest code
- Compile and run Unit and Integration Tests
- Run JaCoCo code coverage and fail the build if desired percentage is not met
- Run SonarQube code quality checks
- Optionally, built Docker image and publish it to Docker Hub
- Deploy application on some free cloud hosting service likeĀ HerokuĀ orĀ OpenShift
After going through their documentation I realised that we can do all these tasks by using some of the free online services and Travis-CI integration with them.
- GitHubĀ for code repository
- SonarCloudĀ for free SonarQube service
- Docker HubĀ for publishing Docker images
- HerokuĀ for deploying the application
Let us see how we can do all the above mentioned tasks usingĀ Travis-CIĀ for aĀ SpringBootĀ project.
Step 1: Create SpringBoot project
Create a SpringBoot project either usingĀ http://start.spring.ioĀ or from your IDE. I am using Maven build tool, you can use Gradle also if you prefer. Now commit the project into your github repository.
Step 2: Create .travis.yml file
In order to enable Travis-CI integration we need to createĀ .travis.ymlĀ file in project root folder.
As we are creating the Maven based java project create .travis.yml file with following content:
.travis.yml
language: java jdk: oraclejdk8
This minimal configuration is sufficient for Travis-CI to recognize our Maven based Java project and build it. If there is a build.gradle file in our project root folder Travis will treat it as Gradle project, or if there is pom.xml it will treat it as Maven project.Ā If both build.gradle and pom.xml are there then Gradle build script will take priority.
By default Travis will runĀ mvn test -BĀ for building the project. If Travis findsĀ mvnwĀ wrapper then it will be used likeĀ ./mvnw test -B.
But if you want to run a different command or want to run multiple commands we can use script block to customize it.
Now commit and push the .travis.yml file to GitHub.
Step 3: Enable Travis-CI for GitHub repository
Go toĀ https://travis-ci.org/Ā andĀ Signin with GitHub.
Now click onĀ Add New RepositoryĀ (+ symbol).
Enable Travis for the repository. After enabling Travis click on that repository and you can trigger build by selectingĀ More Options -> Trigger build.
Now you can see that build is running and tests are executed and an email notification will be sent to your email regarding the build status.
Step 4: Add JaCoCo Code Coverage check
Add the Maven JaCoCo plugin toĀ pom.xmlĀ with options like what is the desired code coverage percentage, packages/classes to ignore etc.
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.9</version>
<configuration>
<excludes>
<exclude>in/sivalabs/freelancerkit/entities/*</exclude>
<exclude>in/sivalabs/freelancerkit/*Application</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-prepare-agent-integration</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>default-report-integration</id>
<goals>
<goal>report-integration</goal>
</goals>
</execution>
<execution>
<id>default-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<!-- implementation is needed only for Maven 2 -->
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLE</element>
<limits>
<!-- implementation is needed only for Maven 2 -->
<limit implementation="org.jacoco.report.check.Limit">
<counter>COMPLEXITY</counter>
<value>COVEREDRATIO</value>
<minimum>0.60</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>Step 5: Run Unit and Integration Tests
As I mentioned earlier, by default Travis runĀ mvn test -BĀ which willĀ only run Unit tests.
We want to run Unit tests and Integration tests separately by using maven-failsafe-plugin. We will follow the convention by namingĀ Unit testsĀ asĀ *Test.java/*Tests.javaĀ andĀ Integration testsĀ asĀ *IT.java.
AddĀ maven-failsafe-pluginĀ as mentioned below:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
</configuration>
<executions>
<execution>
<id>failsafe-integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>While configuring the maven-failsafe-plugin for SpringBoot project I hit this issueĀ https://github.com/spring-projects/spring-boot/issues/6254Ā .
To fix this issue I have added theĀ classifierĀ configuration to spring-boot-maven-plugin as follows:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>Now we are going to useĀ scriptĀ block to specify our custom maven goal to run instead of default goal.
.travis.yml
language: java jdk: oraclejdk8 script: - ./mvnw clean install -B
Step 6: SonarQube code quality checks using SonarCloud
SonarCloudĀ , which built onĀ SonarQube, offers free code quality checks for Open Source projects.
Login with GitHubĀ and go toĀ My Account -> SecurityĀ and generate a new token for your project and save it somewhere. Now click onĀ OrganizationsĀ tab and create an Organization with some unique key.
Travis-CI provides ability to encrypt sensitive data (https://docs.travis-ci.com/user/encryption-keys/) so that we can encrypt any keys, password and configure in .travis.yml file.
> sudo gem install travis
From project root folder run the following command to encrypt data:
travis encrypt SOMEVAR=āsecretvalueā
This will generate output like
secure: āā¦. encrypted data ā¦.ā
We can add all the secrets as global environment variables as follows:
env: global: - secure: "....encrypted data....."
Now let us encrypt SonarCloud Token as follows:
travis encrypt SONAR_TOKEN=āmy-sonar-token-hereā
Finally, let us add SonarCloud support as an AddOn (https://docs.travis-ci.com/user/sonarcloud/) as follows:
language: java
jdk: oraclejdk8
env:
global:
- secure: "....encrypted sonar token here....."
addons:
sonarcloud:
organization: "sivaprasadreddy-github"
token:
secure: $SONAR_TOKEN
script:
- ./mvnw clean install -B
- ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonarNote that we usedĀ $SONAR_TOKENĀ to refer to encrypted token variable and added one more command to run inĀ scriptĀ block to runĀ sonar:sonarĀ goal.
Step 7: Build Docker image and publish to DockerHub
Travis CI builds can run and build Docker images, and can also push images to Docker repositories. For more information readĀ https://docs.travis-ci.com/user/docker/
CreateĀ DockerfileĀ in project root folder for our SpringBoot application as follows:
FROM frolvlad/alpine-oraclejdk8:slim VOLUME /tmp ADD target/freelancer-kit-0.0.1-SNAPSHOT.jar app.jar RUN sh -c 'touch /app.jar' ENV JAVA_OPTS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8787,suspend=n" EXPOSE 8080 8787 ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.profiles.active=docker -jar /app.jar" ]
To use Docker add the following settings to .travis.yml:
sudo: required services: - docker
Now we can run Docker commands in our build.
Once the build is successful we may want to build the Docker image and push it to Docker Hub. We can leverageĀ after_successĀ section to perform this action.
We need to login intoĀ DockerHubĀ before pushing the image, we are going to configure DockerHub credentials by encrypting them.
travis encrypt DOCKER_USER=ādockerhub-usernameā
travis encrypt DOCKER_PASS=ādockerhub-passwordā
Add these 2 secrets toĀ env.globalĀ section of .travis.yml.
Now we can add our docker commands to build image and publish to dockerhub inĀ after_successsection as follows:
after_success: - docker login -u $DOCKER_USER -p $DOCKER_PASS - export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH; fi` - export IMAGE_NAME=sivaprasadreddy/freelancerkit - docker build -t $IMAGE_NAME:$COMMIT . - docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG - docker push $IMAGE_NAME
Step 8: Deploy to Heroku
Travis CI provides options to deploy on a wide range of platforms including Heroku, OpenShift, AWS, Azure etc.Ā Travis CI can automatically deploy your Heroku application after a successful build.
We are going to deploy our SpringBoot application on Heroku using TravisĀ https://docs.travis-ci.com/user/deployment/heroku/. Before deploying our application to Heroku first we need to login toĀ https://www.heroku.com/Ā and create an application from Dashboard.
Now createĀ ProcfileĀ in root folder of the project as follows:
web java -Dserver.port=$PORT -Dspring.profiles.active=heroku $JAVA_OPTS -jar target/freelancer-kit-0.0.1-SNAPSHOT-exec.jar
First we need to get the Heroku API Key and add it as encrypted secret.
travis encrypt HEROKU_API_KEY=āyour-heroku-api-key-hereā
We can deploy to Heroku from Travis by addingĀ deployĀ section as follows:
deploy: provider: heroku api_key: $HEROKU_API_KEY app: freelancerkit
Now the completeĀ .travis.ymlĀ file will look like follows:
sudo: required
language: java
jdk: oraclejdk8
services:
- docker
env:
global:
- secure: "encrypted-sonar-token"
- secure: "encrypted-dockerhub-username"
- secure: "encrypted-dockerhub-password"
- secure: "encrypted-heroku-api-key"
- COMMIT=${TRAVIS_COMMIT::7}
addons:
sonarcloud:
organization: "sivaprasadreddy-github"
token:
secure: $SONAR_TOKEN
script:
- ./mvnw clean install -B
- ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH&amp;amp;amp;amp;amp;lt;span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;/span&amp;amp;amp;amp;amp;gt;; fi`
- export IMAGE_NAME=sivaprasadreddy/freelancer-kit
- docker build -t $IMAGE_NAME:$COMMIT .
- docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG
- docker push $IMAGE_NAME
deploy:
provider: heroku
api_key: $HEROKU_API_KEY
app: freelancer-kitOnce the build is successful and deployed on Heroku you should be able to access the application at https://<app>.herokuapp.com/.
I just covered the most commonly performed tasks in Java applications only, but Travis-CI can perform lot more. Checkout the TravisCI documentation atĀ https://docs.travis-ci.com/.
| Published on Java Code Geeks with permission by Siva Reddy, partner at our JCG program. See the original article here: CI/CD for SpringBoot applications using Travis-CI Opinions expressed by Java Code Geeks contributors are their own. |




