Migrating Jenkins Maven Workflows to GitLab CI/CD

The transition from legacy Jenkins automation to GitLab CI/CD represents a fundamental shift in how continuous integration and continuous delivery are architected. While Jenkins often relies on a combination of graphical user interface configurations, plugin ecosystems, and Jenkinsfiles, GitLab CI/CD centralizes the entire pipeline definition within a single YAML configuration file located at the root of the repository. For organizations utilizing Maven for Java project management, this migration involves translating the conceptual stages of building, testing, and installing into a structured GitLab pipeline. The process is not merely a syntax change but an evolution in how runners execute commands and how dependencies are managed across the lifecycle of a software project.

The essence of a Maven-based pipeline in GitLab is the orchestration of jobs grouped into sequential stages. In a traditional Jenkins environment, a developer might use a declarative pipeline or a freestyle project to trigger Maven goals. In GitLab, these are translated into specific jobs such as build-JAR, test-code, and install-JAR. These jobs are mapped to stages that ensure a logical progression: the build must successfully complete before tests are executed, and tests must pass before the artifact is installed. This structured approach ensures that defective code is identified early in the pipeline, preventing the installation of broken artifacts into the local or remote repository.

Jenkins Configuration Paradigms

Before executing a migration, it is critical to understand the various ways Maven is typically implemented within Jenkins, as each approach influences the migration strategy.

The first paradigm is the Freestyle project using shell execution. In this scenario, Jenkins is configured to call mvn commands directly from the shell on the agent. This method is the closest analog to a GitLab Runner using the shell executor, as both rely on the underlying operating system's environment to provide the necessary binaries.

Another common approach is the use of the Maven task plugin. This plugin allows users to declare specific goals within the Maven build lifecycle. Unlike raw shell scripts, the plugin acts as a wrapper, requiring Maven to be pre-installed on the Jenkins agent. This abstraction layer handles the execution of Maven goals but introduces a dependency on the plugin's version and compatibility.

Finally, the declarative pipeline represents the modern Jenkins standard. A typical declarative pipeline for Maven might look like the following:

groovy pipeline { agent any tools { maven 'maven-3.6.3' jdk 'jdk11' } stages { stage('Build') { steps { sh "mvn package -DskipTests" } } stage('Test') { steps { sh "mvn test" } } stage('Install') { steps { sh "mvn install -DskipTests" } } } }

In this configuration, the tools block specifies the exact versions of Maven and the JDK, ensuring environment parity across different build nodes. The transition to GitLab requires moving these tool specifications from the Jenkinsfile into the GitLab Runner configuration or a Docker image.

GitLab CI/CD Pipeline Architecture for Maven

A migrated GitLab CI/CD configuration consists of global keywords and job-specific definitions. The global keywords establish the environment and the sequence of events, while the jobs define the actual work to be performed.

The stages keyword is the primary mechanism for defining the execution order. In the Maven example, three stages are established:

  • build
  • test
  • install

These stages ensure that jobs are run in a specific sequence. A job assigned to the build stage must finish before any job in the test stage begins. This prevents the pipeline from attempting to test a JAR file that has not yet been packaged.

The variables keyword allows for the definition of environment variables that are accessible to every job in the pipeline. This eliminates the need to redefine common Maven flags in every script block.

Maven Variable Configurations

The specific configuration of variables is crucial for the stability and security of the build process.

The MAVEN_OPTS variable is used to pass environment variables to the Maven JVM. Two critical settings are typically defined here:

  • -Dhttps.protocols=TLSv1.2: This setting forces the use of TLS version 1.2 for all HTTP requests. This is a security requirement for interacting with modern remote repositories and ensures that the pipeline does not fail due to outdated protocol negotiations.
  • -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository: This redirects the local Maven repository to a folder within the GitLab project directory. By default, Maven stores dependencies in the user's home directory, which is often inaccessible or wiped between runs on a clean runner. Moving the repository to the project directory allows GitLab CI/CD to track and cache these dependencies.

Additionally, the MAVEN_CLI_OPTS variable is used for arguments passed directly to the Maven command line:

  • -DskipTests: This flag is used during the package and install phases to prevent the execution of tests. This is strategic because testing is handled as a dedicated, separate stage in the GitLab pipeline (test-code), avoiding redundant test execution.

Implementation via Shell Executor

For environments where a GitLab Runner is configured with a Shell executor, the pipeline relies on the software installed directly on the host machine.

Prerequisites for this setup include:

  • A GitLab Runner configured with a Shell executor.
  • Maven 3.6.3 installed on the shell runner.
  • Java 11 JDK installed on the shell runner.

The .gitlab-ci.yml configuration for a shell-based execution is as follows:

```yaml
stages:
- build
- test
- install

variables:
MAVENOPTS: >-
-Dhttps.protocols=TLSv1.2
-Dmaven.repo.local=$CI
PROJECTDIR/.m2/repository
MAVEN
CLI_OPTS: >-
-DskipTests

build-JAR:
stage: build
script:
- mvn $MAVENCLIOPTS package

test-code:
stage: test
script:
- mvn test

install-JAR:
stage: install
script:
- mvn $MAVENCLIOPTS install
```

In this model, the build-JAR job executes the package goal, the test-code job executes the test goal, and the install-JAR job handles the final installation. The use of the shell executor means the runner executes these commands exactly as a user would in a terminal, making it highly compatible with legacy Jenkins setups that relied on local shell scripts.

Advanced Optimization with Docker and Caching

To improve upon the basic shell migration, GitLab CI/CD offers advanced features such as containerization and caching. Instead of relying on the host's installed software, the pipeline can utilize a Docker image, providing a consistent, immutable environment for every build.

When using GitLab.com, users can leverage public instance runners, which support Docker-based jobs. This eliminates the need to manually install Maven and Java on a physical server.

The optimized configuration introduces the default keyword and a cache mechanism:

```yaml
stages:
- build
- test
- install

default:
image: maven:3.6.3-openjdk-11
cache:
key: $CICOMMITREF_SLUG
paths:
- .m2/

variables:
MAVENOPTS: >-
-Dhttps.protocols=TLSv1.2
-Dmaven.repo.local=$CI
PROJECTDIR/.m2/repository
MAVEN
CLI_OPTS: >-
-DskipTests

build-JAR:
stage: build
script:
- mvn $MAVENCLIOPTS package

test-code:
stage: test
script:
- mvn test

install-JAR:
stage: install
script:
- mvn $MAVENCLIOPTS install
```

Analysis of the Caching Mechanism

The inclusion of the cache keyword is a critical optimization. In a standard Maven build, the tool downloads a vast array of dependencies from central repositories. Without caching, every single job in every single pipeline run would need to re-download these dependencies, leading to significantly slower build times and increased network congestion.

The cache configuration in this example uses the following parameters:

  • key: $CI_COMMIT_REF_SLUG: This ensures that the cache is unique to the specific branch or tag being built. This prevents conflicts between different versions of dependencies across different branches.
  • paths: .m2/: This tells GitLab to preserve the .m2/ directory between jobs. Because MAVEN_OPTS was configured to place the local repository inside the project directory, GitLab can easily zip this folder and upload it to the cache server, then download it for subsequent jobs.

This caching strategy transforms the pipeline from a series of isolated executions into a continuous flow where the test-code job can reuse the dependencies already downloaded by the build-JAR job.

Comparative Analysis of Pipeline Components

The following table provides a detailed comparison between the Jenkins declarative approach and the migrated GitLab CI/CD approach.

Feature Jenkins Declarative GitLab CI/CD (Migrated) Impact of Change
Configuration File Jenkinsfile .gitlab-ci.yml Centralized version control of the pipeline.
Tool Management tools { ... } block image: ... or Host install Shift from plugin-based tools to containerized environments.
Execution Unit stage blocks jobs assigned to stages Greater flexibility in job parallelism and dependency mapping.
Environment Variables Global or Stage level variables keyword Standardized environment across all pipeline stages.
Dependency Storage Host-based / Workspace Project-based with cache Faster execution via distributed caching of .m2/ directories.
Runtime Environment Agent nodes GitLab Runners (Shell/Docker) Decoupling of the build logic from the physical hardware.

Broader GitLab CI/CD Ecosystem and Use Cases

Beyond the Maven migration, GitLab provides a wide array of CI/CD examples tailored to different tiers (Free, Premium, Ultimate) and offerings (GitLab.com, Self-Managed, Dedicated). The versatility of the platform allows for integration with various tools and languages.

The following table outlines common use cases and the resources available for implementation:

Use Case Resource/Tool Description
Application Deployment Dpl tool Deploying applications using the Dpl tool.
Static Website Hosting GitLab Pages Publishing static sites with automatic deployment.
Complex Orchestration Multi-project pipeline Building, testing, and deploying across multiple linked projects.
Package Management npm with semantic-release Publishing npm packages directly to the GitLab package registry.
Scripted Deployment Composer and npm with SCP Deploying scripts via Secure Copy Protocol (SCP).
PHP Testing PHPUnit and atoum Comprehensive testing for PHP-based projects.
Security and Secrets HashiCorp Vault Authenticating and reading sensitive secrets via Vault.

These examples demonstrate that while the Maven migration is a specific technical hurdle, the underlying logic of GitLab CI/CD—defined by stages, jobs, and variables—is a universal framework applicable to almost any software delivery lifecycle.

Conclusion

The migration of Maven workflows from Jenkins to GitLab CI/CD is a strategic transition that replaces plugin-heavy configurations with a transparent, code-driven approach. By utilizing the .gitlab-ci.yml file, organizations can achieve a higher degree of reproducibility and scalability. The transition involves three primary phases: the definition of stages (build, test, install), the configuration of environment variables to handle security (TLS 1.2) and dependency locations (.m2/repository), and the selection of an execution environment (Shell or Docker).

The most significant improvement in the GitLab model is the move toward containerization and intelligent caching. By utilizing a specific Docker image such as maven:3.6.3-openjdk-11 and caching the .m2/ directory using the $CI_COMMIT_REF_SLUG key, the pipeline eliminates the "cold start" problem where every job begins with an empty dependency cache. This results in a dramatic reduction in cycle time and a more resilient build process.

Ultimately, the migration allows developers to move away from the manual maintenance of Jenkins agents and instead treat their infrastructure as code. The alignment of the build-JAR, test-code, and install-JAR jobs into a sequential stage-based pipeline ensures that only verified, tested code ever reaches the installation phase, thereby upholding the integrity of the software supply chain.

Sources

  1. GitLab CI/CD - Jenkins Maven Migration
  2. GitLab CI/CD Examples

Related Posts