Runner, DinD, Caching und Artifacts
GitLab CI mit Docker ist mächtig, aber die Konfiguration hat viele Fallstricke: falsche Runner-Typen, unsicheres Docker-in-Docker, Build-Caches die nicht treffen und Artifacts, die unnötige Megabytes transferieren. Wer die Mechanismen versteht, baut Pipelines, die in drei Minuten fertig sind statt in dreißig.
Inhaltsverzeichnis
- 1. GitLab Runner Typen: Shell, Docker und Kubernetes
- 2. Docker-in-Docker: wann es nötig ist und wann nicht
- 3. Socket-Mounting als DinD-Alternative
- 4. BuildKit in GitLab CI aktivieren
- 5. Registry-based Build-Cache für schnelle Rebuilds
- 6. Pipeline-Struktur für Docker-Builds
- 7. Artifacts richtig einsetzen
- 8. DinD vs. Socket-Mounting im Vergleich
- 9. Sicherheit: was nie in die CI-Pipeline gehört
- 10. Zusammenfassung
- 11. FAQ
1. GitLab Runner Typen: Shell, Docker und Kubernetes
Der GitLab CI-Runner ist die Komponente, die Pipeline-Jobs ausführt. Es gibt drei wichtige Executor-Typen. Der Shell-Executor führt Jobs direkt auf dem Runner-Host aus – kein Container, keine Isolation. Das ist schnell, aber jeder Job kann den Zustand des nächsten beeinflussen. Der Docker-Executor erstellt für jeden Job einen Container aus dem in image: definierten Docker-Image und wirft ihn nach dem Job weg. Das gibt vollständige Isolation: jeder Job startet in einem sauberen Zustand. Der Kubernetes-Executor startet Jobs als Pods im Cluster – ideal für bereits vorhandene K8s-Infrastruktur.
Für Docker in GitLab CI ist der Docker-Executor die häufigste Wahl. Die Runner-Konfiguration in /etc/gitlab-runner/config.toml definiert, welches privileged-Flag der Docker-Executor erhält und welche Volumes gemountet werden. Privileged-Mode ist für Docker-in-Docker nötig, aber ein Sicherheitsrisiko – ein Container mit privileged-Flag kann auf dem Host eskalieren. Self-hosted Runner sollten auf dedizierten Hosts laufen, nicht auf denselben Systemen, auf denen Produktions-Workloads laufen. GitLab.com-SaaS-Runner nutzen isolierte VMs für jeden Job und haben kein privileged-Problem.
2. Docker-in-Docker: wann es nötig ist und wann nicht
Docker-in-Docker (DinD) bedeutet, einen Docker-Daemon innerhalb eines Docker-Containers zu betreiben. Das ist notwendig, wenn ein GitLab CI-Job Docker-Commands ausführen muss – zum Beispiel, um Images zu bauen – und der Runner-Container selbst keinen Zugriff auf den Host-Docker-Daemon hat. DinD benötigt den docker:dind-Service-Container, der den Docker-Daemon bereitstellt, und das privileged: true-Flag im Runner, damit der Service-Container auf den nötigen Kernel-Funktionen aufbauen kann. Ohne privileged läuft DinD nicht.
Die Sicherheitsimplikation von DinD in GitLab CI: ein privilegierter Container hat volle Zugriffsrechte auf den Host-Kernel. Jeder CI-Job, der in einem privilegierten Container läuft, kann im Prinzip aus dem Container ausbrechen. Das ist auf dedizierten CI-Runner-Hosts akzeptabel – das Risiko ist auf diesen Host begrenzt. Auf Hosts, die andere Workloads laufen, ist es inakzeptabel. Eine echte Alternative ist der kaniko-Build-Container von Google, der Docker-Images ohne Docker-Daemon und ohne privileged-Flag baut – allerdings langsamer und ohne vollständigen BuildKit-Support.
# .gitlab-ci.yml — Docker image build with Docker-in-Docker
# Requires privileged Runner; use on dedicated CI hosts only
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_BUILDKIT: "1" # enable BuildKit for parallel stages and cache
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build:
stage: build
image: docker:26
services:
- name: docker:26-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
# Login to GitLab Container Registry using built-in CI variables
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
# Build with registry cache — speeds up rebuilds significantly
- |
docker buildx build \
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache:main \
--cache-to type=registry,ref=$CI_REGISTRY_IMAGE/cache:main,mode=max \
--tag $IMAGE_TAG \
--push \
.
after_script:
- docker logout "$CI_REGISTRY"
3. Socket-Mounting als DinD-Alternative
Socket-Mounting ist die häufigste Alternative zu DinD in Docker GitLab CI-Setups. Dabei wird der Docker-Socket des Hosts (/var/run/docker.sock) als Volume in den Job-Container gemountet. Der Container kann dann Docker-Befehle ausführen, die direkt auf dem Host-Docker-Daemon laufen – ohne einen eigenen privilegierten Daemon. Das ist einfacher als DinD, aber hat einen wichtigen Unterschied: alle Jobs teilen denselben Docker-Daemon, was zu Race Conditions bei gleichnamigen Images oder Container-Namen führen kann.
Das Sicherheitsmodell von Socket-Mounting in GitLab CI ist problematisch: ein Job mit Zugriff auf /var/run/docker.sock kann beliebige Container auf dem Host starten, Volumes mounten und damit effektiv Root-Zugriff auf den Host erlangen. Das ist mindestens genauso problematisch wie DinD mit privileged-Flag. Der einzige wirklich sichere Ansatz für Docker in GitLab CI auf geteilter Infrastruktur ist kaniko oder Buildah, die keine Daemon-Verbindung benötigen. Auf dedizierten CI-Hosts ist Socket-Mounting pragmatisch und weit verbreitet.
# GitLab Runner config.toml — Socket mounting configuration
# Use on dedicated CI hosts only — socket access grants host root equivalence
[[runners]]
name = "docker-runner-socket"
executor = "docker"
[runners.docker]
image = "docker:26"
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
# No privileged = true needed for socket mounting
# But: any job can start arbitrary containers on the host
---
# .gitlab-ci.yml — Build job using socket-mounted Docker daemon
build-socket:
stage: build
image: docker:26
variables:
DOCKER_HOST: "unix:///var/run/docker.sock" # use host daemon via socket
DOCKER_BUILDKIT: "1"
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
# Use unique tag based on commit SHA to avoid naming conflicts between parallel jobs
- docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
# Tag as latest only on default branch
- |
if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
docker tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" "$CI_REGISTRY_IMAGE:latest"
docker push "$CI_REGISTRY_IMAGE:latest"
fi
4. BuildKit in GitLab CI aktivieren
BuildKit ist seit Docker Engine 23 Standard, muss aber in GitLab CI-Jobs explizit aktiviert werden, wenn der Job-Container eine ältere Docker-Version nutzt oder die Umgebungsvariable nicht gesetzt ist. Die Aktivierung erfolgt über die Variable DOCKER_BUILDKIT=1 im variables-Block. Mit BuildKit aktiviert, werden Multi-Stage-Builds parallelisiert, der Registry-Cache funktioniert mit --cache-from type=registry und Mount-Types für Secrets und Caches stehen zur Verfügung. BuildKit verbessert die Build-Performance von Docker in GitLab CI bei Multi-Stage-Builds erheblich.
Eine wichtige BuildKit-Funktion für GitLab CI ist das Inline-Caching. Mit --build-arg BUILDKIT_INLINE_CACHE=1 werden Cache-Metadaten direkt ins Image eingebettet. Das ermöglicht es, ein bestehendes Image als Cache-Quelle zu nutzen, ohne einen separaten Cache-Export: --cache-from $CI_REGISTRY_IMAGE:main nutzt dann das bereits gepushte Main-Branch-Image als Cache-Basis für Feature-Branch-Builds. Das ist weniger effizient als der vollständige Registry-Cache-Export, aber einfacher zu konfigurieren und ausreichend für viele Teams.
5. Registry-based Build-Cache für schnelle Rebuilds
Der Registry-based Build-Cache ist die effektivste Methode, um Docker in GitLab CI-Builds zu beschleunigen. Ohne Cache werden alle Dockerfile-Schritte bei jedem Pipeline-Run von Null ausgeführt: Package-Installation, Dependency-Download, Compiler-Läufe. Mit Registry-Cache werden gecachte Layer aus der Registry geladen und wiederverwendet – nur die tatsächlich geänderten Schritte laufen neu. Die Cache-Hit-Rate hängt davon ab, wie gut das Dockerfile für maximales Caching optimiert ist.
Die empfohlene Cache-Strategie für GitLab CI mit Docker: ein separates Cache-Repository in der GitLab-Registry ($CI_REGISTRY_IMAGE/cache), das unabhängig von den eigentlichen Images befüllt wird. Cache wird mit mode=max exportiert – das speichert alle Layers aller Stages, nicht nur die finale Stage. Feature-Branch-Builds nutzen den Main-Branch-Cache als Ausgangspunkt. Der Cache wird nach jedem Main-Branch-Build aktualisiert. Dieses Muster stellt sicher, dass Feature-Branches von einem warmen Cache profitieren, ohne dass parallele Branches sich gegenseitig überschreiben.
6. Pipeline-Struktur für Docker-Builds
Eine gut strukturierte GitLab CI-Pipeline für Docker-Builds hat klare Stages mit definierten Verantwortlichkeiten: build für den Image-Build und Push in die Registry, test für Tests die den gebauten Container nutzen, scan für Sicherheits-Scanning, und deploy für Deployments auf Zielumgebungen. Jeder Stage-Job nutzt das Image des vorherigen Stages – nicht den Source-Code neu auschecken und neu bauen. Das stellt sicher, dass Test, Scan und Deploy dasselbe Image verwenden, das in Production landet.
Dependency-Jobs in GitLab CI steuern, welcher Job auf welchen anderen warten muss. Mit needs: statt dependencies: werden direkte Job-Abhängigkeiten definiert, unabhängig von der Stage-Reihenfolge. Das ermöglicht, unabhängige Jobs parallel laufen zu lassen: Image-Build und Static-Code-Analysis können gleichzeitig laufen, der Test-Job wartet auf beide. Diese Parallelisierung reduziert die Gesamtlaufzeit der Pipeline erheblich – wichtiger als die Build-Zeit des einzelnen Docker-Jobs.
# .gitlab-ci.yml — Complete Docker CI pipeline with caching, scanning and deployment
stages:
- build
- test
- scan
- deploy
variables:
DOCKER_BUILDKIT: "1"
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
CACHE_IMAGE: $CI_REGISTRY_IMAGE/cache:$CI_COMMIT_REF_SLUG
build-image:
stage: build
image: docker:26
services: [docker:26-dind]
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
script:
# Use branch-specific cache, fallback to main branch cache
- |
docker buildx build \
--cache-from type=registry,ref=$CI_REGISTRY_IMAGE/cache:main \
--cache-from type=registry,ref=$CACHE_IMAGE \
--cache-to type=registry,ref=$CACHE_IMAGE,mode=max \
--tag $IMAGE \
--push .
trivy-scan:
stage: scan
image: aquasec/trivy:latest
needs: [build-image]
script:
# Fail pipeline on critical CVEs; generate GitLab Security Dashboard report
- trivy image --exit-code 1 --severity CRITICAL --format gitlab $IMAGE > gl-container-scanning-report.json || true
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
deploy-staging:
stage: deploy
image: alpine/k8s:1.30
needs: [build-image, trivy-scan]
script:
- kubectl set image deployment/app app=$IMAGE -n staging
- kubectl rollout status deployment/app -n staging
environment:
name: staging
only:
- main
7. Artifacts richtig einsetzen
Artifacts in GitLab CI sind Dateien, die ein Job erzeugt und die für nachfolgende Jobs oder den Download verfügbar gemacht werden. Der häufigste Fehler: zu viele oder zu große Artifacts. Jedes Artifact wird komprimiert, in die GitLab-Datenbank geschrieben und für jeden nachfolgenden Job übertragen. Wenn ein Build-Job den gesamten Build-Output als Artifact speichert, summiert sich das schnell auf hunderte Megabytes pro Pipeline-Run. Das verlangsamt die Pipeline und belastet den GitLab-Speicher.
Die richtige Strategie für Docker in GitLab CI: Images in die Registry pushen statt als Artifacts speichern. Der gebaute Docker-Container ist das Artifact – er lebt in der Registry mit dem Commit-SHA-Tag. Nachfolgende Jobs ziehen das Image aus der Registry statt es als Datei-Artifact zu empfangen. Wirkliche Datei-Artifacts in der Pipeline sollten klein und gezielt sein: Test-Reports, Coverage-Dateien, Security-Scan-Reports und Deployment-Logs. Diese sind typischerweise unter einem Megabyte und wertvoll für den GitLab-Berichts-Mechanismus.
8. DinD vs. Socket-Mounting im Vergleich
Die Wahl zwischen DinD und Socket-Mounting in Docker GitLab CI ist eine der häufigsten Konfigurationsentscheidungen. Beide Ansätze haben klare Vor- und Nachteile.
| Kriterium | Docker-in-Docker (DinD) | Socket-Mounting | Empfehlung |
|---|---|---|---|
| Isolation | Eigener Daemon pro Job | Geteilter Host-Daemon | DinD für Isolation |
| Sicherheit | privileged: true nötig | Docker-Socket = root-Äquivalenz | Beide nur auf dedizierten Hosts |
| Performance | Langsamer Start (Daemon) | Schnellerer Start | Socket für schnelle Builds |
| TLS | TLS zwischen Job und Daemon | Kein TLS, lokaler Socket | DinD für TLS-Sicherheit |
| BuildKit Cache | Vollständig unterstützt | Vollständig unterstützt | Beide gleich |
Die Praxisempfehlung für Docker in GitLab CI: DinD auf dedizierten Runner-Hosts mit aktiviertem TLS. Socket-Mounting ist schneller, aber der geteilte Daemon erzeugt bei parallelen Jobs mit ähnlichen Image-Namen gelegentlich Konflikte. Mit der DinD-TLS-Konfiguration (DOCKER_TLS_CERTDIR: "/certs") ist DinD seit Docker 20.10 erheblich sicherer als früher.
9. Sicherheit: was nie in die CI-Pipeline gehört
Sicherheitsfehler in GitLab CI-Pipelines mit Docker sind oft trivial zu vermeiden, kommen aber regelmäßig vor. Credentials gehören niemals in die .gitlab-ci.yml oder in das Repository selbst. GitLab CI Variables (unter Settings → CI/CD → Variables) speichern Secrets verschlüsselt und maskieren sie in Logs. Das $CI_REGISTRY_PASSWORD-Variable ist immer automatisch verfügbar und gehört nie in eine Datei im Repository. API-Keys, Deploy-Tokens und Zugangsdaten für externe Services werden als Masked und Protected Variable angelegt.
Ein weiterer kritischer Sicherheitspunkt in Docker GitLab CI: gepushte Images müssen auf bekannte Schwachstellen gescannt werden, bevor sie deployed werden. Ein Trivy-Scan-Job, der nach dem Build-Job läuft und den Deploy-Job bei kritischen CVEs blockiert, ist eine wichtige Sicherheitsschicht. Das GitLab Security Dashboard zeigt die Scan-Ergebnisse strukturiert an, wenn der Job einen Report im GitLab-Format erzeugt. Das erfordert keine externe Scanning-Infrastruktur – Trivy läuft als Container im CI-Job und benötigt nur den Registry-Zugriff auf das gebaute Image.
10. Zusammenfassung
Docker in GitLab CI effizient zu nutzen erfordert die richtige Runner-Konfiguration, eine klare Entscheidung zwischen DinD und Socket-Mounting und einen gut konfigurierten Build-Cache. Der Docker-Executor mit DinD auf dedizierten Hosts ist die sicherste Option für isolierte CI-Jobs. BuildKit mit Registry-based Cache reduziert Build-Zeiten erheblich: nur veränderte Dockerfile-Schritte laufen neu. Images in die Registry pushen statt als Artifacts – nachfolgende Jobs ziehen das Image aus der Registry.
Eine gute GitLab CI-Pipeline für Docker hat klare Stages: Build, Test, Scan, Deploy. Jeder Stage-Job nutzt das gepushte Image aus der vorherigen Stage. Trivy-Scanning nach dem Build blockiert Deployments bei kritischen CVEs. Credentials nie in der Pipeline-Definition, sondern ausschließlich in GitLab CI Variables. Mit diesen Grundsätzen läuft eine Docker GitLab CI-Pipeline schnell, sicher und reproduzierbar – ohne Überraschungen in Produktion.
Mironsoft
GitLab CI/CD, Docker-Pipelines und Deployment-Automatisierung
Langsame oder fragile GitLab CI Pipeline?
Wir analysieren bestehende GitLab CI Pipelines, konfigurieren BuildKit mit Registry-Cache, richten Image-Scanning ein und bringen Docker-Builds von 20 Minuten auf unter 5 Minuten.
Pipeline-Audit
Analyse langsamer Jobs, ineffizienter Caching-Strategien und Sicherheitslücken in GitLab CI
BuildKit-Optimierung
Registry-Cache, parallele Stages und Dockerfile-Reihenfolge für maximale Cache-Hit-Rate
Security-Setup
Trivy-Scanning, Security Dashboard Integration und sichere Variable-Verwaltung
Docker in GitLab CI — Das Wichtigste auf einen Blick
DinD vs. Socket
DinD: eigener Daemon pro Job, bessere Isolation, TLS-Sicherheit, braucht privileged. Socket: schneller, aber geteilter Daemon und keine Job-Isolation.
Registry-Cache
--cache-from type=registry + --cache-to mode=max. Feature-Branches nutzen Main-Branch-Cache. Spart 80% der Build-Zeit bei Cache-Hits.
Artifacts
Images in Registry pushen, nicht als Artifacts. Artifacts nur für Test-Reports, Coverage und Security-Scan-Ergebnisse – typisch unter 1 MB.
Sicherheit
Credentials nur als GitLab CI Variables. Trivy-Scanning nach Build. Kritische CVEs blockieren Deploy. Runner nur auf dedizierten Hosts.
11. FAQ: Docker in GitLab CI
1DinD vs. Socket-Mounting: Hauptunterschied?
2Warum DinD privileged: true braucht?
3BuildKit in GitLab CI aktivieren?
DOCKER_BUILDKIT: "1" im variables-Block. Standard seit Docker Engine 23, Variable sichert ältere Images ab.4Registry-Cache in GitLab CI?
--cache-from type=registry lädt Layer. --cache-to mode=max schreibt alle Layers zurück. Feature-Branches nutzen Main-Cache.5Images als Artifacts speichern?
6Credentials aus Logs heraushalten?
$CI_REGISTRY_* Variablen.7Was ist Inline Caching?
BUILDKIT_INLINE_CACHE=1 bettet Cache-Metadaten ins Image ein. Gepushtes Image als --cache-from nutzbar. Einfacher, aber weniger effizient als mode=max.8Parallele Jobs ohne Naming-Konflikte?
$CI_COMMIT_SHORT_SHA oder $CI_JOB_ID versehen. Verhindert Kollisionen paralleler Jobs.9Trivy in GitLab CI integrieren?
--format gitlab für Security Dashboard. --exit-code 1 blockiert Deploy bei kritischen CVEs.