Rootless Container Builds with Buildah¶
Einordnung ins Overtime-Projekt
Für die CI-Pipeline von Overtime (z. B. Django-Backend) müssen OCI-konforme Container-Images gebaut werden. Um die Sicherheit des Gesamtsystems zu gewährleisten, sollten die dafür genutzten CI-Runner keinesfalls mit erweiterten Host-Rechten (--privileged) betrieben werden. Diese Dokumentation beschreibt die Evaluierung und die endgültige Entscheidung für Buildah als primäres Build-Werkzeug.
1. Kontext & Problemstellung (Deutsch)¶
Im modernen Software-Lebenszyklus ist der automatisierte Bau von Container-Images ein kritischer Schritt innerhalb der CI/CD-Pipeline. Der historisch etablierte Standard-Ansatz dafür ist Docker-in-Docker (DinD). Dieser erfordert jedoch, dass der ausführende GitLab-Runner im sogenannten Privileged Mode gestartet wird.
Aus Sicht der IT-Sicherheit stellt dies ein Risiko dar: Ein priviligierter Container hebt die Isolationsebene des Kernels nahezu vollständig auf und gewährt dem Container direkten Zugriff auf die Hardware und Kernel-Funktionen des zugrundeliegenden Host-Systems. Sollte ein Angreifer in die Pipeline eindringen (z. B. durch manipulierte Abhängigkeiten im Django-Backend), ermöglicht der Privileged Mode einen trivialen Ausbruch aus dem Container direkt auf den Build-Server.
Um dem gerecht zu werden, wurde nach einer rootless und daemonless Alternative gesucht, die OCI-Images ohne erhöhte Kernel-Privilegien und ohne die Freigabe des Host-Docker-Sockets erzeugen kann.
2. Architecture Decision Record (English)¶
ADR-001: Rootless Container Builds with Buildah¶
Status¶
Accepted
Context and Problem Statement¶
CI pipelines need to build and push OCI container images. The most common approach — Docker-in-Docker (DinD) — requires the GitLab runner container to run with --privileged, which grants near-root access to the host kernel. This violates the principle of least privilege and introduces critical infrastructure vulnerabilities.
Decision Drivers¶
- No
--privilegedcontainers allowed on shared or self-hosted runners. - Minimal Linux capabilities granted to the build environment.
- Remote build layer caching to maintain fast pipeline execution.
- Native OCI/Docker format compatibility with the GitLab Container Registry.
Considered Options¶
- Docker-in-Docker (DinD) / Socket Binding — Mounts the host Docker socket (DooD) or runs a privileged sidecar container (DinD); permits standard Docker CLI/Buildx commands but introduces critical host compromise risks.
- Kaniko — Rootless and daemonless builder. The original Google repository was archived in June 2025, though an active fork is maintained by Chainguard. Supports remote registry caching.
- Moby BuildKit — Standalone rootless mode execution using
buildctl-daemonless.sh; bypasses the sidecar but requires complex namespace configurations on the host runner. - Buildah — Rootless and daemonless OCI builder from the
containers/buildahecosystem; handles unprivileged image compilation natively via user namespaces.
Decision Outcome¶
Chosen: Buildah, because it builds completely rootless via fuse-overlayfs (utilizing /dev/fuse without needing host kernel privileges), natively supports layer caching against a remote registry, and produces standard OCI/Docker-format images fully compatible with all downstream environments.
Kaniko was ruled out due to Google abandoning the primary project repository. Moby BuildKit in rootless mode was left untested as Buildah successfully met all pipeline criteria.
Consequences & Trade-offs¶
Positive Consequences¶
- Enhanced Security: Runners only require localized access to
/dev/fuse— the dangerous--privilegedflag is strictly forbidden. - Performance: Layer cache is securely offloaded to
${CI_REGISTRY_IMAGE}/cache. - Reproducibility: The exact same OCI image configuration can be built locally via Buildah/Podman and within the CI pipeline.
Negative Consequences / Trade-offs¶
- Runner Configuration: Build hosts must mount
/dev/fuseinto the unprivileged runner container (this is a minor, one-time admin setup). - Storage Driver Nuance: The driver configuration (
STORAGE_DRIVER: overlay) forces the use offuse-overlayfsinside the container. This requires an image that explicitly ships the binary (such as the officialquay.io/buildah/stable). - Caching Engine: Buildah's
--cache-fromand--cache-topull and push actual intermediate image layers directly to the registry rather than utilizing optimized inline BuildKit cache manifests. This functionality works but can feel slightly slower compared to local native Docker builds.
3. Pipeline Implementation & Code Snippet (English)¶
The following job template is implemented in overtime-ci-templates/build-docker.yml and can be extended by any microservice (e.g., the Django backend) requiring safe image creation:
.build-docker:
stage: build
image: quay.io/buildah/stable:v1.43.1@sha256:ca4edbaabf71ca330ec9b2714bf8e06c726a6e85ae215cfb1474d2e2c832aacb
variables:
# overlay uses fuse-overlayfs (bundled in the image) via /dev/fuse — much less disk than vfs.
STORAGE_DRIVER: overlay
# Produce Docker-format images for full registry compatibility.
BUILDAH_FORMAT: docker
DOCKERFILE_PATH: "Dockerfile"
BUILD_CONTEXT: "."
IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
before_script:
- echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- |
EFFECTIVE_TAG="${IMAGE_TAG:-$CI_COMMIT_SHORT_SHA}"
buildah bud \
--layers \
--cache-from "${CI_REGISTRY_IMAGE}/cache" \
--cache-to "${CI_REGISTRY_IMAGE}/cache" \
--file "${DOCKERFILE_PATH}" \
--tag "${CI_REGISTRY_IMAGE}:${EFFECTIVE_TAG}" \
--tag "${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}" \
"${BUILD_CONTEXT}"
buildah push "${CI_REGISTRY_IMAGE}:${EFFECTIVE_TAG}"
buildah push "${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}"
4. Compliance Einordnung¶
BSI IT-Grundschutz Compliance¶
| Standard | Kriterium / Inhalt | Wie im Projekt adressiert |
|---|---|---|
| BSI IT-Grundschutz | SYS.1.6.A17 (S): Ausführung von Containern ohne Privilegien | Buildah läuft innerhalb des GitLab-Runners vollständig rootless. Es werden keinerlei Root-Rechte auf dem Host-Betriebssystem des Runners benötigt oder erlangt. |
| BSI IT-Grundschutz | SYS.1.6.A4 (B): Planung der Bereitstellung und Verteilung von Images | Das zugehörige Architektur-Dokument (ADR-001) dient als Teil-Nachweis über die gegenwärtige und methodische Planung des Image-Bereitstellungsprozesses. |
| BSI IT-Grundschutz | APP.4.4.A10 (S): Absicherung von Prozessen der Automatisierung | Durch den strikten Verzicht auf den --privileged-Flag bei den dedizierten Build-Runnern werden die Rechte und die Angriffsfläche der CI/CD-Pipeline auf ein Minimum reduziert |
| BSI IT-Grundschutz | APP.4.4.A2 (B): Planung der Automatisierung mit CI/CD | Die Build-Phase des Django-Backends wurde architektonisch isoliert geplant und dokumentiert, um das Einbringen unautorisierter Änderungen in den späteren Kubernetes-Cluster zu verhindern. Dieses ist Teil der Dokumentation. |
CIS Docker Benchmark Compliance (v1.8.0) and CIS Kubernetes Benchmark (v2.0.0)¶
| Standard | Kriterium / Inhalt | Wie im Projekt adressiert |
|---|---|---|
| CIS Docker Benchmark | Sektionen 1, 2, 3 & 5: Daemon & Host Configuration | Out of Scope: Der CIS-Benchmark bezieht sich auf die Härtung eines permanent laufenden Docker-Daemons. Da Buildah daemonless arbeitet, existiert diese Angriffsfläche in der Pipeline nicht. |
| CIS Docker Benchmark | Sektion 4: Container Images and Build File Configuration | Die inhaltlichen Anforderungen an die Images (Base Images, Scanning, Non-Root-Runtime) werden dediziert in den Folge-Dokumenten 'SUSE BCI', 'Trivy' und 'Multi-Stage Dockerfile' behandelt. |
| CIS Kubernetes Benchmark | Sektionen 1 bis 4: Control Plane & Node Configuration | Out of Scope: Diese Sektionen regeln die Härtung von physischer/virtueller Cluster-Infrastruktur und sind für den isolierten Image-Build-Prozess nicht relevant. |
| CIS Kubernetes Benchmark | Sektion 5.2.2: Minimize the admission of privileged containers | Indem Buildah auf privilegierte Container verzichtet, erfüllt der Build-Job nativ die strengen Pod Security Standards (Baseline/Restricted) für Cluster, auf denen die CI-Runner betrieben werden. |