GitLab CI/CD - 1 : Building a Java Project using Maven and Docker within the GitLab CI pipeline.

Cumhur Akkaya
11 min readJun 24, 2023

In this article, we will create a GitLab CI (continuous integration) pipeline, using Maven and Docker. We’ll use the Java application as an artifact.

Whenever someone pushes on a branch of your repository, a pipeline will be triggered on a dedicated virtual machine running jobs described in .gitlab-ci.yml file. We will observe the pipeline’s output. We will do it all step by step.

Also, we will talk about GitLab CI/CD concept and term, Maven Life Cycle, and Maven commands.

Topics we will cover:

1. Introduction to GitLab CI/CD concept

1. a. The benefits of using

1. b. Basic concepts and key terms used in it

1. c. GitLab CI/CD workflow

2. Creating a GitLab Repository and Prerequisites

3. Creating .gitlab-ci.yml (Configuration File) and explanation of it

3. a. Creating .gitlab-ci.yml

3. b. Explanation of .gitlab-ci.ymlfile

3. c. What are the Maven Life Cycle?

3. d. Explanation of Maven’s commands in the .gitlab-ci.ymlfile

4. Creating pom.xml file and explanation of it

5. Observing the pipeline’s triggering that is described in .gitlab-ci.yml file

6. As a result

7. Next post

8. References

If you like the article, I will be happy if you click on the Medium Following button to encourage me to write more, and not miss future articles.

Your clap, follow, or subscribe, they help my articles to reach the broader audience. Thank you in advance for them.

1. Introduction to GitLab CI/CD concept.

1. a. The benefits of using

Use GitLab CI/CD to catch bugs and errors early in the development cycle. Ensure that all the code deployed to production complies with the code standards you established for your app. GitLab CI/CD can automatically build, test, deploy, and monitor your applications by using Auto DevOps.(1)

1. b. Basic concepts and key terms used in it

GitLab CI/CD uses a number of concepts to describe and run your build and deployment. The most important of these basic concepts are explained below.

.gitlab-ci.yml : (Configuration File). We configure our pipeline with the .gitlab-ci.yml file. We must create it in your repository. When we push on a branch of our repository, a pipeline will be triggered on a dedicated virtual machine running jobs described in the .gitlab-ci.yml file.

Job : Pipeline configuration begins with jobs. Jobs are the most fundamental element of a .gitlab-ci.yml file. We can consider jobs as separate steps (3). Jobs are picked up by runners and executed in the environment of the runner. What is important is that each job is run independently from each other.

job1:
script: "execute-script-for-job1"

job2:
script: "execute-script-for-job2"

Runner : Every job is run on a different machine every time. We call this machine a “Runner”. These machines (Runners) are provisioned by GitLab for free and shared with all other GitLab users. They are relatively slower than having an own machine. If you want a faster runner, you can configure your own runner.

Stages : It defines when to run the jobs. For example, after one stage compiles the code, other stages run tests, see Figure 1.

Figure 1.

1. c. GitLab CI/CD workflow

Figure 2.

This workflow shows the major steps in the GitLab process, as shown in Figure 2. (2). When we can push your commits to a feature branch in a remote repository that’s hosted in GitLab, the push triggers the CI/CD pipeline for our project. Then, GitLab CI/CD (4);

  • Runs automated scripts (sequentially or in parallel).
  • Build and test your application.
  • Preview the changes in a Review App, the same as you would see on your localhost.

After the implementation works as expected:

  • Get your code reviewed and approved.
  • Merge the feature branch into the default branch.
  • GitLab CI/CD deploys your changes automatically to a production environment.

If something goes wrong, you can roll back your changes.

2. Creating a GitLab Repository and Prerequisites

Prerequisites

Before we continue, you need to have a GitLab account.

Creating a GitLab Repository

Create a simple repository in Gitlab with a readme file, as shown in Figure 3.

Figure 3.

Upload the source files in the link to your local host or cloud provider by using “git clone” command.

Then send them your own repository by using “git add / git commit / git push” commands, as shown in Figure 4.

Figure 4.

3. Creating .gitlab-ci.yml (Configuration File) and explanation of it

3. a. Creating .gitlab-ci.yml

Create a yaml file by name .gitlab-ci.yml in the same repo with the following content, and click on the “commit changes” button, as shown in Figures 5–6 (If you have included .gitlab-ci.ymlin your project repository within the source files, there is no need to do this step.).

Please note that the name of this file should be .gitlab-ci.yml and should not be anything else otherwise, the pipeline won’t trigger.

Figures 5.
Figures 6.

the content of the .gitlab-ci.yml file:

variables:
MAVEN_OPTS: -Dmaven.repo.local=.m2/repository

image: maven:latest

stages:
- build
- test
- package
- deploy


cache:
paths:
- .m2/repository
- target

build_job:
stage: build
tags:
- docker

script:
- echo "Maven compile started"
- "mvn compile"


test_job:
stage: test
tags:
- docker

script:
- echo "Maven test started"
- "mvn test"

package_job:
stage: package
tags:
- docker

script:
- echo "Maven packaging started"
- "mvn package"


Deploy_job:
stage: deploy
tags:
- docker

script:
- echo "Maven deploy started"

3. b. Explanation of .gitlab-ci.ymlfile:

variables : We will set a variable called MAVEN_OPTS, this will be the local repository which will be “.m2/repository”. Note: The .m2 folder is created by the maven when you run any maven command. By default, the maven local repository is %USER_HOME%/. m2 directory (5).

image maven:latest: We are choosing a docker image for Gitlab runner here. We will use the maven image for this hands-on because this is a Java project and we will use the maven container. Maven is available in the docker image “maven:latest”. latest means the latest approved image of the maven docker image, we will use it. If we don’t choose an image, The default image ruby:2.5 is used by the GitLab runner. Also, if you want to use all the jobs in your pipeline to use the same image, the location of the image should be at the top level as shown above.

stage: The names of our 4 jobs are here.

cache: The path that Maven will copy the files like the .jar file when it runs the commands in “the Build Life cycle”.

3. c. What is the Maven Life Cycle?

It is useful to briefly mention the Maven Build Life cycle here, in Figure 7. Apache Maven executions are tied to lifecycles. A lifecycle groups a sequence of activities. Maven provides three basic lifecycles for its standard build management(6). There are three standard lifecycles provided by Maven;

  • clean — Intended for clean-up of any prior build-managed outputs and artifacts.
  • default (build) — Intended for project build, test and deployment of artifacts.
  • site — Intended for project site documentation.

We have implemented the 7th step of the default life cycle with the “mvn compile” command in the.gitlab-ci.yml file above. Invoking a phase means that all previous stages in that lifecycle have been executed. That is, with the “mvn compile” command, we have executed the previous 6 steps before the compile (validate, initialize, etc.).

To see more information about the Build Lifecycle, the Maven website.

Figure 7 - The source of the image (7).

3. d. Explanation of Maven’s commands in the .gitlab-ci.ymlfile

build_job: The name of the first job in this case. Prints “Maven compile started” on the screen with the echo command.

The script section in the second line tells the GitLab to compile the code. With themvn compilecommand, Maven takes the source code and converts it to byte-code and creates the target folder, and saves it as a file with a class extension in the target folder.

test_job: With “mvn test” command, Maven runs “unit tests”, and saves outputs in the src/test folder. It is the 15th step of the Default Life cycle.

package_job: With “mvn package” command, Maven creates the jarextension package and saves outputs in the “target” folder. It is the 17th step of the Default Life cycle.

deploy_job: With “mvn deploy” command, Maven creates the jarextension artifact file, and saves it in the “.m2 folder”. Also, If we set it in “pom.xml” files, it is also saved to a remote artifact repository like Nexus with this command. It is the 23rd step and final step of the Default Life cycle.

4. Creating pom.xml file and explanation of it

Maven first reads the settings file (config file - pom.xml). So we need to create this file before the .gitlab-ci.yml file. Otherwise, when we click on the “commit changes” button in Figure 6, the pipeline will give an error.

It is an XML file that contains information about the project and configuration details used by Maven to build the project. It contains default values for most projects. Examples: the build directory, which is target; the source directory, which is src/main/java; the test source directory, which is src/test/java; and so on. When executing a task or goal, Maven looks for the POM in the current directory. It reads the POM, gets the needed configuration information, then executes the goal. (8).

pom.xmlIt searches the local repository (local cache) for required dependencies;
For Windows in the %homepath%\.m2 folder.
For Linux, in the $HOME/.m2 folder

Create a XML file by name pom.xml in the same repo with the following content, and click on the “commit changes” button, as we did in item 3 in Figures 5–6 (If you have included pom.xmlin your project repository within the source files, there is no need to do this step.). For more information about the pom.xml file see this link.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>my-app</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<!-- Build an executable JAR -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.mycompany.app.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>[3.5.4,)</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

<groupId>com.mycompany.app</groupId> : The name of the app.

<artifactId>my-app</artifactId> : The name of the project and folder.

Note: <groupId> + <artifactId> + <version> = “g.a.v.” creates our folder path.

<packaging>jar</packaging> : The extension of artifact will be Jar , when the “mvn package” command runs.

<version>1.0-SNAPSHOT</version> : It sends our Jar file to snapshot repository.

Note: If it is “<version>1.0</version>” , It sends our Jar file to release repository. Also, we use this “<version>” in a 3rd party repository like Nexus.

<dependency>
<groupId>junit</groupId> :
It will use JUnit to test java codes.

<plugins> : Plugins that need to be downloaded for Maven to work are specified here

5. Observing the pipeline’s triggering that is described in .gitlab-ci.yml file.

After completing the files above;

When we click on the “commit changes” button in Figure 6,

or,

When we send them your own repository by using “git push” commands in Figure 4,

Our pipeline will be triggered on a virtual machine that runs jobs described in .gitlab-ci.yml file.

After this, whenever someone pushes on a branch of your repository, a pipeline will be triggered and run jobs.

To see this, for example, we made changes in the “.gitlab-ci.yml” file, using the Gitlab pipeline editor, and clicked on the commit changes button, as shown in Figure 8.

Figure 8.

We can see that our pipeline is triggered and running jobs in order, as shown in Figures 9–10.

Figures 9.

All jobs passed, as shown in Figure 10.

Figures 10.

If we click on the “Update .gitlab-ci.yml file” pipeline, we can see all jobs, as shown in Figures 11.

Figures 11.

If we click on any job, we can see what operations are done inside the job, as shown in Figures 12–13.

Figures 12.

We can see that the deployment is successful, as shown in Figure 13.

6. As a result

We created a pipeline easily with GitLab. When the developer commits and pushes changes in their code, Gitlab executes the CI pipeline automatically. Thus, we could easily complete the compile, test, and deploy operations of the written Java code.

Also, when we compare the Jenkins pipeline with the GitLab pipeline, we can say that the operations here are much easier. You can refer to my article on the link to see the processes related to creating a Jenkins pipeline.

We have created a CI pipeline that can be useful for the development stage.

Note: You can access the necessary files from my GitLab account in the link.

If you liked the article, I would be happy if you click on the Medium Following button to encourage me to write and not miss future articles.

Your clap, follow or subscribe, they help my articles to reach the broader audience. Thank you in advance for them.

For more info and questions, please contact me on Linkedin or Medium.

7. Next post

In the next post, “GitLab CI/CD - 2 : Building a Java Spring Boot API using Maven and Docker in GitLab CI/CD pipeline by running GitLab Runner in a container”.

We will create a GitLab CI/CD (continuous integration/continuous delivery and deployment) pipeline, using Maven and Docker. GitLab Runner will run in a container. We’ll use the Java Spring Boot API as an artifact.

Whenever someone pushes on a branch of your repository, a pipeline will be triggered on a dedicated virtual machine that runs jobs described in .gitlab-ci.yml file. And we will see the results on localhost:8080.

We will do it all step by step.

Happy Clouding…

I hope you enjoyed reading this article. Don’t forget to follow my Medium or LinkedIn account to be informed about new articles.

--

--

Cumhur Akkaya

✦ DevOps/Cloud Engineer, ✦ Believes in learning by doing, ✦ Dedication To Lifelong Learning, ✦ Tea and Coffee Drinker. ✦ Linkedin: linkedin.com/in/cumhurakkaya