Am I right in guessing you found this page because you're looking for guidance on how to start a migration from Jenkins to GitLab CI? If the answer’s yes, you’ve come to the right place! But before diving in, let’s start with a quick introduction to what we’re dealing with when we talk about software pipelines.
According to Wikipedia:
"A software pipeline is an automated process for executing defined stages in software development—build, test, deploy, and release—in a systematic, repeatable way."
By understanding what a pipeline is, you’ll see how the choice of a CI/CD tool impacts your workflow's efficiency and reliability. Since GitLab CI is your new tool of choice, this marks a new chapter in your software lifecycle and will also change your team’s ways of working.
Before attempting a 1:1 migration (which likely won’t be possible), I recommend taking a moment to pause and discuss with your team whether you’re satisfied with how your pipeline has been functioning so far. You can use various tools to facilitate this conversation, like our pipeline game!
Getting started with your migration
Now that you’ve defined and made your plans, what is the goal of the pipeline, and how does it bring value for you and the development team using the pipeline (the best outcome would be if you belong directly to the development team)? Let’s check what we need
To get started with a Jenkins to GitLab CI migration, I recommend having the following skills:
- YAML
- YAML
- YAML
- Domain understanding of what your team is doing
So why focus so much on YAML? The answer is simple: Everything in GitLab CI uses YAML syntax. The next part of the course is to understand what is needed from Jenkins in GitLab CI.
The Terminology of CI
Since GitLab CI is a completely different tool from Jenkins (technically, both are glorified cronjob runners), it’s important to agree on terminology.
Jenkins |
GitLab |
Difference |
Agent/Node |
GitLab runner |
In Jenkins, an "agent" or "node" is a machine where jobs run. In GitLab, the equivalent is a "runner," which executes jobs defined in .gitlab-ci.yml. GitLab offers shared runners (hosted by GitLab) or self-managed runners. |
Jenkinsfile |
.gitlab-ci.yml |
The Jenkinsfile defines the pipeline in Jenkins, typically using Groovy-based syntax. In GitLab CI, the pipeline is defined using the .gitlab-ci.yml file, which uses YAML syntax. |
Stages and steps |
Stages and jobs |
A pipeline is divided into stages in both Jenkins and GitLab CI. In Jenkins, each stage contains multiple steps, while in GitLab CI, a stage contains jobs (which can run sequentially or in parallel). |
Trigger / Build trigger |
Pipeline triggers |
Jenkins triggers jobs via SCM polling, webhook, or manual triggers. GitLab has similar options, such as webhooks, schedules, and manual pipeline runs, but GitLab also integrates with GitLab-specific events (e.g., push events). |
Workspace |
CI/CD Runner directory |
In Jenkins, the workspace is a directory on the agent machine where jobs run. The runner's working directory functions similarly in GitLab, but the GitLab Runner manages it, and artifacts are stored differently (using GitLab's artifact storage). |
Jenkins plugin |
GitLab CI templates /components |
GitLab CI minimizes dependency on external extensions, simplifying management. Components provide modularity, while templates enable reuse. Explore more at GitLab CI Catalog. |
Artifacts |
Artifacts |
Both Jenkins and GitLab CI allow the storage of build artifacts (e.g., logs and reports). GitLab CI has a built-in feature for managing and downloading artifacts stored for a specific duration or pipeline job. |
Jobs |
Jobs |
In Jenkins, a job represents a single unit of work (e.g., a build). In GitLab, a job is a single task within a pipeline stage. The key difference is how jobs are defined and executed in .gitlab-ci.yml vs. in Jenkins' job configuration. |
The necessary environments
As we can see from the terminology, GitLab CI doesn’t have "built-in" or "nodes" or “agents” like Jenkins does. Instead, it uses runners. This is where your domain expertise comes into play, as you’ll need to consider your team’s specific requirements.
What environments do you need? You must set up your own runners if you're running GitLab on-premises. These can run directly on Linux, Windows, or macOS. You can also run them via Docker on these operating systems or leverage Kubernetes to orchestrate on-demand Docker runners in the background. If you've chosen GitLab.com (the SaaS approach), you can use public runners, depending on your company’s policies.
Now that you've defined your requirements, it’s time to evaluate your existing Jenkins nodes/agents. Check if they can be reused by installing the GitLab Runner or if it’s time to decommission them and send them to the great Server Valhalla, where all retired hardware finds peace.
Tips when converting from Jenkinsfile to GitLab CI yaml file
Before starting to .gitlab-ci.yaml file, take a look at your existing Jenkinsfiles, or freestyle jobs configurations.
Do you really understand what those do? If the answer is no, then I strongly recommend paying the technical debt first and using the tasker tool.
sh '''
if ! command -v node &> /dev/null; then
curl -fsSL https://deb.nodesource.com/setup_${LTS}.x -o nodesource_setup.sh
bash nodesource_setup.sh
rm nodesource_setup.sh
apt-get install -y nodejs
else
node -v
fi
npm install -g grunt-cli
rm -rf node_modules && ||
npm cache clean --force && npm install -g npm@latest
npm -v
npm outdated --long
npm install
npm audit fix
npm ls --depth=0
npm cache verify
node -v
npm install --no-save eslint prettier jest
which node
which npm
npm ls -g --depth=0
'''
So this could be converted to e.g., sh 'npm run install-global-dependencies'
Now for the actual tips and tricks. There can be valid reasons for having multiline shell/bash scripts that just don’t justify being in a tasker or separate script file.
Multiline example from Jenkins
sh '''
# Create a Python virtual environment
python3 -m venv venv
# Activate the virtual environment
source venv/bin/activate
# Install dependencies from requirements.txt
pip install -r requirements.txt
# Run the Python script
python my_script.py
# Deactivate the virtual environment
deactivate
'''
How to use artifacts from different stages
In Jenkinsfile, there is a step called stash and unstash to store build binaries and other artifacts for the pipeline run. In GitLab there is no such concept, but there is “artifacts” as a concept.
Jenkinsfile example:
stage('Build Python Project') {
steps {
script {
// Build the Python project using pdm
sh '''
pdm build
'''
// Stash the build artifacts (e.g., .whl or .tar.gz files)
stash name: 'build-artifacts', includes: 'dist/*'
}
}
}
stage('Deploy Python Project') {
steps {
script {
// Unstash the build artifacts from the previous stage
unstash 'build-artifacts'
// Install the built package from the dist folder
sh '''
pip install dist/*.whl
python -m my_module
'''
}
}
}
GitLab CI yaml example:
build_python_project:
stage: build
script:
- pdm build # Build the Python project
artifacts:
paths:
- dist/* # Store the built artifacts (e.g., .whl, .tar.gz)
expire_in: 1 hour # Set expiry time for the artifacts
deploy_python_project:
stage: deploy
dependencies:
- build_python_project # This tells GitLab CI to use artifacts from the build stage
script:
- pip install dist/*.whl # Install the built wheel package
- python -m my_module # Run the Python module
Post-build actions
In Jenkins, post-build actions are powerful. You can define them with additional steps, e.g., always, failure, cleanup, etc. In GitLab, you have only after_script, which can have conditionals, of course. One of the biggest things that I noticed in GitLab CI is that you can’t fail the pipeline with the after_script block. So, if you want your pipeline to fail, e.g., if your unit tests didn’t pass a high enough pass rate, then you need to do it in a script block instead of after_script.
Jenkinsfile example:
stage('Run JUnit Tests') {
steps {
script {
sh 'mvn test'
}
}
post {
always {
junit '**/target/test-classes/*.xml'
}
}
}
GitLab CI yaml example:
test:
stage: test
script:
- mvn test
after_script:
- |
if [ -f "target/test-classes/*.xml" ]; then
echo "JUnit test results found. Archiving..."
# Use GitLab's artifact feature to store the test results
mv target/test-classes/*.xml /tmp/junit-test-results/
else
echo "No JUnit test results found."
exit 1 # doesn’t affect the pipeline status
fi
artifacts:
paths:
- /tmp/junit-test-results/*.xml
expire_in: 1 hour
There have been a couple of concrete examples of what you might encounter when doing a 1:1 migration from Jenkinsfiles to GitLab CI YAML. I highly recommend running YAMLLint locally against the .gitlab-ci.yml file before pushing it to the remote repository. Additionally, AI tools like GitHub Copilot can be useful for doing the heavy lifting once a basic understanding is established.
Jenkins plugins
“What about Jenkins plugins?” you may ask. Previous examples didn’t mention or use them because GitLab CI doesn’t rely on plugins. No more plugin management or issues with Maven/Gradle versions or Java dependencies!
Instead of plugins and shared libraries, GitLab CI uses templates! GitLab provides many predefined templates, which you can create and share across your organization.
Another alternative to plugins is to look for CLI tools for different use cases. For example, JFrog offers both an Artifactory Jenkins plugin and a CLI tool.
So, instead of creating your own custom tool, I recommend first checking if there is already a suitable tool available out of the box.
Summary
Before starting the actual work, I recommend taking the time to plan. Ask yourself and your team using the pipeline the following questions:
- Is the current pipeline producing the output we need?
- Is the current pipeline delivering value to us?
- What is the most annoying thing about the current pipeline?
A change of tooling is like starting with an empty canvas, and that’s a situation that should be approached carefully.
Once the goals and gatekeepers of the pipeline are defined, compare what is needed from the old system. From there, begin the actual work. Also, take the time to familiarize yourself with GitLab CI/CD—understand what can be found and where. The web UI is quite different from Jenkins, so I recommend getting trained or, at the very least, reviewing some example videos.
And if you need help with the task, don’t hesitate to contact us!
Published: Dec 6, 2024