Deine erste CI/CD-Pipeline mit GitLab CI und Docker: Ein Schritt-für-Schritt-Guide

Julian | Jul 31, 2025 min read

Hallo zusammen,

als Entwickler wissen wir: Code schreiben ist die eine Sache, ihn zuverlässig zu bauen, zu testen und auszurollen, eine ganz andere. Manuell ist das mühsam und fehleranfällig. Hier kommen Continuous Integration (CI) und Continuous Deployment (CD) ins Spiel. Sie automatisieren genau diese Schritte und sorgen dafür, dass euer Code schneller, sicherer und konsistenter in Produktion landet.

In diesem Artikel tauchen wir in die Praxis ein und bauen eure erste eigene CI/CD-Pipeline mit GitLab CI und Docker. Keine Angst, es ist einfacher, als es klingt! Am Ende dieses Guides habt ihr eine funktionierende Pipeline, die euren Code baut, testet und ein Docker-Image erstellt.

Warum GitLab CI und Docker?

  • GitLab CI: Ist direkt in GitLab integriert, was kurze Wege und eine nahtlose Developer Experience bedeutet. Kein externes Tooling, keine zusätzlichen Accounts.
  • Docker: Ermöglicht es uns, unsere Anwendung und ihre Abhängigkeiten in einer isolierten, portablen Umgebung zu verpacken. “It works on my machine” gehört damit der Vergangenheit an!

Nehmen wir an, wir haben eine einfache Spring Boot Anwendung in Java, die wir bauen und als Docker-Image verpacken wollen.

Schritt 1: Das Projekt vorbereiten – Deine Dockerfile

Zuerst brauchen wir eine Dockerfile in unserem Projektverzeichnis. Diese Datei beschreibt, wie unser Docker-Image gebaut werden soll.

src/main/docker/Dockerfile (oder einfach im Root-Verzeichnis deines Projekts)

# Offizielles OpenJDK Image als Basis
FROM openjdk:17-jdk-slim

# Metadaten für das Image
LABEL authors="Julian Paul"
LABEL description="Spring Boot Anwendung fuer CI/CD Demo"

# Arbeitsverzeichnis im Container
WORKDIR /app

# Die kompilierte Spring Boot JAR-Datei in den Container kopieren
# Annahme: Deine Build-Pipeline erstellt eine JAR unter 'target/your-app-name.jar'
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

# Port, den die Anwendung im Container exponiert
EXPOSE 8080

# Befehl zum Starten der Anwendung, wenn der Container läuft
ENTRYPOINT ["java","-jar","/app/app.jar"]

Kurze Erklärung zur Dockerfile:

  • FROM openjdk:17-jdk-slim: Wir starten mit einem schlanken OpenJDK 17 Image.
  • WORKDIR /app: Setzt /app als unser Arbeitsverzeichnis im Container.
  • ARG JAR_FILE und COPY: Hier wird die nach dem Kompilieren erzeugte .jar-Datei in das Image kopiert. Der ARG Befehl ist praktisch, wenn der genaue Dateiname des JARs variiert (z.B. durch Versionen).
  • EXPOSE 8080: Informiert Docker, dass der Container auf Port 8080 lauschen wird.
  • ENTRYPOINT: Definiert den Befehl, der ausgeführt wird, wenn der Container gestartet wird.

Schritt 2: Deine GitLab CI Pipeline definieren – Die .gitlab-ci.yml

Das Herzstück unserer Pipeline ist die .gitlab-ci.yml-Datei. Diese Datei muss im Root-Verzeichnis deines GitLab-Projekts liegen. GitLab liest diese Datei bei jedem Push und führt die darin definierten Jobs aus.

.gitlab-ci.yml

# Definiert das Docker-Image, das fuer alle Jobs standardmaessig verwendet wird
# Hier ein Maven-Image, da wir eine Java/Spring Boot Anwendung bauen
# Hier koennt ihr natuerlich jedes andere Maven Image nutzen
image: maven:3.9.11-amazoncorretto-24-al2023

# Definiert verschiedene Stufen (Stages) in unserer Pipeline
# Jobs in spaeteren Stages werden erst ausgefuehrt, wenn alle Jobs der vorherigen Stage erfolgreich waren
stages:
  - build
  - test
  - package
  - deploy # Diese Stage wird spaeter fuer das Deployment genutzt

# === BUILD STAGE ===
build-job:
  stage: build
  script:
    - echo "Starte den Build-Prozess..."
    - mvn clean package -DskipTests
  artifacts: # Artefakte sind Dateien, die von diesem Job generiert und fuer spaetere Jobs benoetigt werden
    paths:
      - target/*.jar # Wir speichern unsere kompilierte JAR-Datei
    expire_in: 1 week # Wie lange die Artefakte gespeichert bleiben sollen
  only:
    - main # Dieser Job wird nur auf Aenderungen im 'main'-Branch ausgefuehrt

# === TEST STAGE ===
test-job:
  stage: test
  script:
    - echo "Starte die Tests..."
    - mvn test
  only:
    - main

# === PACKAGE STAGE (Docker Image Bau) ===
package-job:
  stage: package
  image: docker:latest # Fuer diesen Job benoetigen wir das Docker-Image
  services: # Ermoeglicht die Nutzung eines Docker-Daemons im Job (Docker-in-Docker)
    - docker:dind
  script:
    - echo "Baue das Docker-Image..."
    # Login in die GitLab Container Registry
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    # Build des Docker-Images. $CI_REGISTRY_IMAGE ist eine vordefinierte GitLab Variable
    # $CI_COMMIT_SHORT_SHA ist der kurze Hash des aktuellen Commits - gut fuer eindeutige Tags
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    # Push des Docker-Images in die GitLab Container Registry
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    # Zusaetzlich einen 'latest'-Tag pushen, wenn es der main-Branch ist
    - if [ "$CI_COMMIT_BRANCH" == "main" ]; then
        docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest;
        docker push $CI_REGISTRY_IMAGE:latest;
      fi
  dependencies: # Dieser Job ist abhaengig vom 'build-job', um die JAR-Datei zu bekommen
    - build-job
  only:
    - main

# === DEPLOYMENT STAGE (Optional, Beispiel fuer spaeter) ===
# Hier wuerde dein Deployment Code stehen, z.B. auf Kubernetes, einer VM etc.
# deploy-job:
#   stage: deploy
#   script:
#     - echo "Bereitstellung des Docker-Images..."
#     # Beispiel: kubectl apply -f kubernetes/deployment.yaml
#   environment:
#     name: production
#   only:
#     - main
#   when: manual # Manuelles Deployment zur Sicherheit

Kurze Erklärung zur .gitlab-ci.yml:

  • image: Definiert das Basis-Docker-Image, in dem eure Jobs laufen. Für unseren Java-Build ist ein maven Image perfekt.
  • stages: Hier definieren wir die Phasen unserer Pipeline: build (Kompilieren), test (Tests ausführen), package (Docker Image bauen), deploy (bereitstellen). Jobs in einer Stage starten erst, wenn alle vorherigen Stages erfolgreich waren.
  • build-job:
    • stage: build: Ordnet den Job der Build-Phase zu.
    • script: Die Befehle, die ausgeführt werden. mvn clean package -DskipTests kompiliert unser Projekt und packt es in eine JAR-Datei, wobei Tests übersprungen werden (die laufen im nächsten Job).
    • artifacts: Die erzeugte .jar-Datei wird als Artefakt gespeichert, damit sie in nachfolgenden Jobs (z.B. package-job) verwendet werden kann.
    • only: - main: Dieser Job wird nur ausgeführt, wenn Änderungen auf den main-Branch gepusht werden.
  • test-job:
    • stage: test: Ordnet den Job der Test-Phase zu.
    • script: Führt die Unit-Tests mit mvn test aus.
  • package-job:
    • stage: package: Ordnet den Job der Packaging-Phase zu.
    • image: docker:latest und services: - docker:dind: Ganz wichtig! Um Docker-Befehle innerhalb eines GitLab CI Jobs ausführen zu können (z.B. docker build, docker push), benötigen wir das docker Image als Basis und den docker:dind (Docker-in-Docker) Service.
    • script: Loggt sich bei der GitLab Container Registry ein (die Variablen $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD, $CI_REGISTRY sind von GitLab vordefiniert und enthalten die Anmeldedaten und URL deiner Registry). Anschließend wird das Docker-Image gebaut und mit einem eindeutigen Tag ($CI_COMMIT_SHORT_SHA) sowie einem optionalen latest-Tag gepusht.
    • dependencies: - build-job: Stellt sicher, dass die Artefakte (unsere JAR-Datei) aus dem build-job für diesen Job verfügbar sind.

Schritt 3: GitLab Projekt-Setup

  1. Repository pushen: Lege ein neues Projekt in GitLab an (oder nutze ein bestehendes) und pushe deinen Code samt Dockerfile und .gitlab-ci.yml in den main-Branch.
  2. Runner prüfen: Stelle sicher, dass für dein Projekt Shared Runners (die Standard-Runner von GitLab) verfügbar sind oder richte eigene Runner ein.
  3. Pipeline beobachten: Gehe in deinem GitLab-Projekt auf CI/CD -> Pipelines. Du solltest sehen, wie deine Pipeline startet und die einzelnen Jobs durchläuft.

Fazit

Glückwunsch! Du hast soeben deine erste CI/CD-Pipeline mit GitLab CI und Docker erfolgreich implementiert. Dein Code wird nun automatisch gebaut, getestet und als Docker-Image in der GitLab Registry bereitgestellt. Dies ist der Grundstein für eine automatisierte und effiziente Softwareentwicklung.

Von hier aus kannst du die Pipeline erweitern: Füge statische Code-Analysen hinzu, implementiere weitere Teststufen (Integrationstests, End-to-End-Tests) und natürlich das eigentliche Deployment in deine Zielumgebung (Kubernetes, Cloud-VMs, etc.).

CI/CD ist kein Luxus, sondern ein Muss für moderne Entwicklungsteams. Es spart Zeit, reduziert Fehler und ermöglicht es euch, euch auf das zu konzentrieren, was wirklich zählt: großartigen Code zu schreiben.

Habt ihr Fragen oder eigene Tipps für CI/CD-Pipelines? Teilt sie in den Kommentaren!

Beispiel Code

Den Code für diesen Beitrag findet ihr hier: GitLab Repository