The file GitLab actually looks for is .gitlab-ci.yml. It’s easy to mistype, so let’s get the name right before we get into the content.

This post walks through the basics of that file, using a simple Java Spring Boot (Maven) project as the example.

What is .gitlab-ci.yml?

It’s a YAML file you put in the root of your repository. GitLab reads it automatically every time you push code, and uses it to define a pipeline — a sequence of automated steps like „build the app,“ „run tests,“ and „deploy it.“

You don’t install or configure anything special to enable this. If GitLab finds .gitlab-ci.yml in your repo, it runs it.

The Core Building Blocks

Before writing any YAML, it helps to know three terms:

  • Pipeline — the whole run, triggered by a commit or merge request.
  • Stage — a phase of the pipeline (e.g., build, test, deploy). Stages run in order, one after another.
  • Job — an actual task GitLab executes (e.g., „run mvn test„). Jobs within the same stage run in parallel.

Think of it like a factory line: stages are the stations on the line, and jobs are the workers at each station.

A Minimal Example

Here’s the simplest possible pipeline for a Spring Boot project — it just builds the app:

yaml

build-job:
script:
- mvn compile

That’s a complete, valid .gitlab-ci.yml. GitLab will spin up a runner (a machine/container that executes the job), check out your code, and run mvn compile. No stages defined means GitLab uses sensible defaults.

But a real project usually wants more structure.

Defining Stages

yaml

stages:
- build
- test
- package

This tells GitLab: „run all build jobs first, then all test jobs, then all package jobs.“ If a stage fails, later stages don’t run — the pipeline stops, so you find out about a broken build before it gets any further.

Adding Jobs to Each Stage

yaml

stages:
- build
- test
- package
compile:
stage: build
script:
- mvn compile
unit-tests:
stage: test
script:
- mvn test
package-jar:
stage: package
script:
- mvn package
artifacts:
paths:
- target/*.jar

A few things to point out here:

  • Each job (compile, unit-tests, package-jar) is just a YAML key with a script block under it. The job name itself can be anything — it’s a label, not a keyword.
  • stage: assigns the job to one of the stages we declared above.
  • artifacts: tells GitLab to save files after the job finishes so later stages (or a human downloading them from the GitLab UI) can use them. Here we’re keeping the built .jar file.

Specifying a Docker Image

By default, GitLab runners might not have Java or Maven installed. You tell GitLab what environment to run jobs in using image:

yaml

default:
image: maven:3.9-eclipse-temurin-17
stages:
- build
- test
- package
compile:
stage: build
script:
- mvn compile

Putting image under default: applies it to every job in the file, so you don’t have to repeat it. You can still override it per-job if one job needs something different.

Caching Maven Dependencies

Without caching, every single job re-downloads all your Maven dependencies from scratch — slow and wasteful. Caching keeps the .m2 repository between pipeline runs:

yaml

default:
image: maven:3.9-eclipse-temurin-17
stages:
- build
- test
- package
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
cache:
paths:
- .m2/repository
compile:
stage: build
script:
- mvn compile
unit-tests:
stage: test
script:
- mvn test
package-jar:
stage: package
script:
- mvn package
artifacts:
paths:
- target/*.jar

What’s happening here:

  • variables: sets an environment variable available to all jobs — we’re telling Maven to use a local folder (.m2/repository) inside the project directory instead of the default home-directory location.
  • cache: tells GitLab to save that folder after the pipeline and restore it at the start of the next one, so dependencies aren’t re-downloaded every time.

This single change is one of the highest-impact tweaks for speeding up Java pipelines.

Running Jobs Only on Certain Branches

You often don’t want every job running on every branch. For example, maybe package-jar should only run on main:

yaml

package-jar:
stage: package
script:
- mvn package
artifacts:
paths:
- target/*.jar
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

rules: lets you control when a job runs. $CI_COMMIT_BRANCH is a built-in variable GitLab provides automatically — there are many of these (commit SHA, merge request ID, pipeline trigger source, etc.) and they’re how you make pipelines conditional without writing custom logic.

Putting It All Together

yaml

default:
image: maven:3.9-eclipse-temurin-17
stages:
- build
- test
- package
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
cache:
paths:
- .m2/repository
compile:
stage: build
script:
- mvn compile
unit-tests:
stage: test
script:
- mvn test
package-jar:
stage: package
script:
- mvn package
artifacts:
paths:
- target/*.jar
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

Hinterlasse einen Kommentar

I’m Iman

Mein Name ist Iman Dabbaghi. Ich arbeite als Senior Software Engineer in der Schweiz. Außerdem interessiere ich mich sehr für gewaltfreie Kommunikation, Bachata-Tanz und Musik sowie fürs die Persönlichkeitsentwicklung.

Ich habe einen Masterabschluss in Informatik von der Universität Freiburg in Deutschland, bin Spring/Java Certified Professional (OCP), Certified Professional for Software Architecture (CPSA-F) und ein lebenslanger Lernender 🎓.

EN:

My name is Iman Dabbaghi. I work as a Senior Software Engineer in Switzerland. I am also very interessted in nonviolent communication, Bachata dance and music and also for personal development.

I hold a masters degree in computer science from the university of Freiburg in Germany, am a Spring / Java Certified Professional (OCP), Certified Software Architecture (CPSA-F) and Life Long Learner🎓

Let’s connect