Deploy Quarkus Application with GitLab-CI to Heroku PaaS
Build a full pipeline and deploy a Quarkus application to different environments with GitLab-CI
Introduction
The main purpose of this tutorial is to create and deploy step by step a simple application with Quarkus using GitLab-CI Pipeline and Heroku fully managed data services.
We will initiate three environment applications on Heroku: Staging, Pre-production and Production.
Staging environment will be attached to a staging branch, Pre-production to the master branch and production environment will be updated each time we add a protected tag to the master branch.
Source code could be found at https://gitlab.com/elie29/quarkus-gitlab-ci-Heroku
Prerequisites
We will not cover GraalVM nor docker through this article in order to focus on the pipeline and its different stages. However, we need at least Java 11 for code compilation and optionally maven 3.6.x if we want to create the Quarkus project via the command line.
Quarkus Application
First things first, let us create our application by using the following command (maven 3.6.x required):
We can use “https://code.quarkus.io/” if we don’t want to install Apache maven locally.
For more information on tooling: https://quarkus.io/guides/maven-tooling
The command will generate a ready to use simple application with one endpoint (api/users).
If we check the pom.xml, we can find the following dependencies:
- Quarkus JsonB: Standard binding layer for converting Java objects to/from JSON messages
- Quarkus arc: build-time oriented dependency injection
- Quarkus resteasy: JAX-RS implementation for Restful services in Quarkus
- Junit 5: For unit testing purpose
- Rest-assured: For Rest endpoints testing
I did some changes manually to the pom.xml by adding the following property and extensions:
- Application name
- Quarkus rest-client: Extension for the REST client support
- Lombok: Logging variables, Getter/Setter and other useful method automations
Starting the application
The UsersRessource.java has been generated automatically by the command line and has one method:
The maven wrapper is shipped with the application, and the only prerequisites to run the application is JAVA 11:
- ./mvnw quarkus:dev Launches the application on port 8080 in dev mode
- curl http://localhost:8080/api/users would return “Hello RESTEasy”
- Visiting localhost:8080, a default homepage created by Quarkus will be shown.
- ./mwnw test, launches the test with rest-assured
Rest Endpoint Modification (Optional)
Instead of returning “Hello RESTEasy”, let us implement rest client to use jsonplaceholder users API. To do that, we need to add the following dependencies:
- Quarkus rest-client: to call rest services
- Quarkus resteasy-mutiny: for reactive JAX-RS endpoints
N.B.: to add these quarkus extensions, we can use ./mvnw quarkus:add-extension or directly modifying the pom.xml
Adding the model
We are interested only in some attributes: id, name, username, email and company. The model creation is facilitated by Lombok:
Setting the interface
UsersService interface is needed with the proper JAX-RS and MicroProfile annotations to call jsonplaceholder API:
When returning a Uni
, every subscription invokes the remote service asynchronously.
Configure the Client URL
In the application.properties, we configure the client URL as follows:
/users is appended in the UsersService Path
Update the controller and the test
UsersService is ready, let us inject it in the controller and call the getUsers:
The test should be updated as well:
Once the application is launched (./mvnw quarkus:dev), open the browser and call: http://localhost:8080/api/users
GitLab Project Creation
Before making any changes to our Quarkus project, I will create first a GitLab repository in order to push my first commit:
Once created, we grab the remote repository address: git@gitlab.com:elie29/quarkus-gitlab-ci-heroku.git
In our Quarkus project, we open a terminal, we initiate a local git repository, and we commit all files (.gitignore is shipped with Quarkus) as follows:
N.B.: On windows, we should not forget to add mvnw as executable before pushing the code:
We create an empty staging branch and push it to the remote repository — it is required later in the configuration step:
Heroku Application Creation
As I mentioned above, We need to create a pipeline on GitLab to deploy our application on three environments: so I will create three applications on Heroku.
In order to create an application, we need to have a Heroku account. The account sign up is straightforward and needs only a valid email and a password (no credit card needed for the first 5 applications)
On Heroku Dashboard, select new → Create New App → enter a name (should be unique) and click on Create app — (Repeat the procedure 3 times):
- For production environment, I chose: play-with-quarkus
- For staging: play-with-quarkus-staging
- For pre-production: play-with-quarkus-preprod
That’s it for Heroku✨
GitLab Repository Configuration
Let’s move to GitLab in order to configure a few settings and protect our branches.
Protect the merge request
Settings → General → Merge requests → check Pipeline must succeed and All discussions must be resolved then save changes
Protect branches and tags
Settings → Repository → Protected branches → add staging as protected branch:
We do the same for Protected tags but by using a wildcard → v*:
That means, a tag starting with v (e.g. v1.2.4) would be considered as protected tag. It prevents anyone from updating or removing the tag. Moreover, we can rely on protected tags to deploy to production.
Inject Variables
Last but not least, we need to inject Heroku variables as follows:
- HEROKU_API_KEY: should contain Api Key found in Heroku account page
- HEROKU_APP_PREPROD, HEROKU_APP_PRODUCTION and HEROKU_APP_STAGING: should contain Heroku app name. In my case:
We check Protected option for all variables and Masked option for at least HEROKU_API_KEY
Protected variables would be exposed only to protected branches and protected tags otherwise they will not be injected and available in the pipeline.
GitLab-CI Pipeline Initialization
Pipelines are the top-level artifact for CI/CD — continuous integration, delivery and deployment.
With GitLab, creating a pipeline is simple as adding a ‘.gitlab-ci.yml’ in the project’s root directory:
N.B.: file name and extension are case sensitive.
GitLab pipelines are composed of jobs and stages:
- Jobs are tasks which define what to do: compile, test, package the code
- Stages regroup jobs and define when to run them.
To understand better the concept, let’s start with a typical pipeline:
This pipeline consists of 3 stages: Build, Test and Deploy, and in each stage we have declared one or two jobs.
Pipeline Syntax
To declare stages, we start with the following block:
stages:
- build
- test
- deploy
Then, we create each job and attach it to a specific stage as follows:
build-job:
stage: build
script:
- echo "Project compilation"
test-job:
stage: test
script:
- echo "Project test execution"
package-job:
stage: deploy
script:
- echo "Project packaging"
deploy-job:
stage: deploy
script:
- echo "Project deployment"
By default, a job with an unspecific stage is attached to the test stage.
Validate GitLab CI Configuration
In order to validate our pipeline before pushing it, we can use the linter editor found at CI/CD → Jobs → CI Lint:
https://gitlab.com/elie29/quarkus-gitlab-ci-heroku/-/ci/lint
Building Quarkus Application
Now, let’s build our Quarkus application through the pipeline.
Define the stages
Our application needs to be compiled, tested, packaged and deployed.
Actually, we don’t need to package the application as Heroku would do the job perfectly. So, we need 3 stages:
stages:
- build
- test
- deploy
build-job:
stage: build
script:
- ./mvnw compile -B
# keep compilation for test stage
artifacts:
paths:
- target
test-job:
stage: test
script:
- ./mvnw test -B
deploy-job:
stage: deploy
script:
- echo "deploying to heroku..."
But in order to launch maven wrapper, we need Java compiler. For that, we add the following:
image: openjdk:11
The pipeline could be find at: https://gitlab.com/elie29/quarkus-gitlab-ci-heroku/-/blob/02-quarkus-build/.gitlab-ci.yml
Deploying Quarkus Application
Heroku variables are created in GitLab, however we need to tweak Heroku to identify our quarkus project and uses Java:11
For that, we need to create two files:
- system.properties: to define java version “java.runtime.version=11”
- Procfile: case-sensitive file to define the web process responsible of launching our application “web: java -Dquarkus.http.port=$PORT $JAVA_OPTS -jar target/*-runner.jar”
To deploy to Heroku, we will use ruby deploy tool “DPL”:
In order to use the “dpl” command, we add a before_script to install ruby:
Changes pushed to remote repository could be found at: https://gitlab.com/elie29/quarkus-gitlab-ci-heroku/-/blob/03-quarkus-deploy/.gitlab-ci.yml
Staging Deployment
We deploy to the staging environment only when a code is merged to the staging protected branch. Two keywords could be useful to specify this condition — only or rules:
deploy-staging:
stage: deploy
before_script:
- apt-get update -qy
- apt-get install -y ruby-dev
- gem install dpl
script:
- dpl --provider=heroku --app=$HEROKU_APP_STAGING --api-key=$HEROKU_API_KEY --skip-cleanup=true
only:
- staging
In order to launch the deploy stage, we create a merge request to staging branch as follows:
Once the merge request is accepted, the pipeline is launched. The application is now deployed on staging:
We test the application by visiting Heroku staging app: https://play-with-quarkus-staging.herokuapp.com/api/users
Pre-production and Production Deployment
Same as staging, but for pre-production environment, only changes on master would trigger the deployment. However, for production environment, only tags would trigger the deployment.
With some refactoring, and the using of hidden jobs, we completed the pipeline:
deploy-staging:
extends: .deploy
variables:
HEROKU_APP: $HEROKU_APP_STAGING
only:
- staging
deploy-preprod:
extends: .deploy
variables:
HEROKU_APP: $HEROKU_APP_PREPROD
only:
- master
deploy-production:
extends: .deploy
variables:
HEROKU_APP: $HEROKU_APP_PRODUCTION
only:
- tags
After merging to master and creating the first protected tag v1.0.0, the application is now deployed to pre-production and production environments.
URLs are as follows:
- Staging: https://play-with-quarkus-staging.herokuapp.com/api/users
- PreProd: https://play-with-quarkus-preprod.herokuapp.com/api/users
- Production: https://play-with-quarkus.herokuapp.com/api/users
Caching dependencies
In order to preserve maven dependencies in subsequent pipelines, GitLab-CI proposes cache keyword, so dependencies once downloaded, they don’t have to be fetched from the internet again.
N.B.: artifacts are used to pass build results between stages(cf. cache vs artifact)
For maven, we define first the local repo in MAVEN_OPTS then we specify the cache paths and key. We may choose different strategy for caching objects (by branch, by commit, …)
Through this configuration, all subsequent pipelines for this project would retrieve maven dependencies from the cache.
Clearing the cache
Sometimes, we need to start the pipeline with a fresh copy of the cache, so to do that, we clear the cache via GitLab Pipeline interface:
We can clear the cache by changing the key as well.
Security Checks
GitLab-CI offers many templates to check for security vulnerabilities. However, most of them are only available with Ultimate Version. Here, we will use Secret Detection and Static Application Security Testing (SAST) as they are available for all tiers.
The simplest way to use GitLab security scanning tools is by including their templates as follows:
Once templates are included, we need to configure SAST execution to avoid job compilation twice :
Now, pushing the modification — branch 06-security — to the remote repository launches the pipeline as follows:
Wrapping Up
This article shows how to create and deploy an application using different strategies with GitLab-ci pipeline.
A step could be added in order to store project artifacts in GitLab Package Registry, Nexus or JFrog Artifactory. We can also improve the pipeline to create and store a container image.
The complete source code is available at: https://gitlab.com/elie29/quarkus-gitlab-ci-heroku, each step has its own branch:
Don’t hesitate to give me your feedback, or contact me on Twitter @elie_nehme