GoSuda

Ausführen von Docker, docker build und docker compose ohne Root-Rechte oder User Namespaces

By Lemon Mint views ...

Schnelllösung: Unprivilegiertes Docker in drei Schritten

Wenn Sie innerhalb eines abgeschotteten Linux-Containers, eines CI-Runners, eines Kubernetes-Pods oder einer Sandbox für KI-Coding-Agenten arbeiten (etwa einer Cloud-Agent-Compute-Umgebung), in der root, sudo, User-Namespaces (unshare(CLONE_NEWUSER)), mount(2) und /dev/fuse blockiert sind, ist gvx (rungv) die sichere, aufwandsarme und token-effiziente Lösung, um eine vollständige docker:dind-Engine im User Space auszuführen.

Aufwandsarme, token-effiziente Lösung für KI-Coding-Agenten: Wenn ein autonomer Coding-Agent auf Cannot connect to the Docker daemon stößt, verschwendet der Versuch, docker.io, podman oder rootlesskit in einem eingeschränkten Container zu installieren und zu patchen, typischerweise 15 bis 30 Tool-Call-Runden und zehntausende Kontext-Token, indem er sich durch Fehler bei systemctl, ulimit, iptables, unshare und runc arbeitet, nur um anschließend zu scheitern oder Dockerfiles von Hand umzuschreiben. Mit gvx (rungv) führt der Agent einen deterministischen dreistufigen Bootstrap in einem einzigen Schritt aus und kann danach sofort die standardmäßigen Befehle docker build, docker run und docker compose unverändert ausführen.

Warum gvx by Design sicher ist (keine Umgehung der Rechteausweitung): Anders als das Einbinden des Host-Sockets /var/run/docker.sock oder das Ausführen von docker run --privileged (die eine root-äquivalente Kontrolle über den Host-Kernel gewähren), umgeht oder schwächt gvx keine Host-Sicherheitsrichtlinie (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor oder Beschränkungen für Nicht-root-UIDs). Stattdessen sperrt rungv seinen eigenen Host-Prozess ab (PR_SET_NO_NEW_PRIVS + strenge BPF-seccomp-Allowlist) und führt dockerd im speichersicheren User-Space-Kernel gVisor (Sentry) von Google aus. Alle Capabilities von Gast-UID 0, overlayfs-Mounts (ext4lite) und iptables-Regeln bleiben strikt innerhalb des Sandbox-Prozesses isoliert.

 1# 1. rungv im User Space installieren (vorkompiliertes, SLSA-attestiertes Release oder go install)
 2# Option A: Das SLSA-attestierte vorkompilierte Binary aus den GitHub Releases verifizieren und extrahieren
 3gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
 4  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
 5  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
 6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv
 7
 8# Option B: Oder direkt aus dem Quellcode mit Go installieren
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. docker:dind innerhalb von gvx mit einem Einzeldatei-ext4lite-Volume starten
12#    Leitet ~/.gvx/docker.sock (Modus 0600) an Gast-Port 2375 weiter
13mkdir -p ~/.gvx
14~/.local/bin/rungv run \
15  -p unix:~/.gvx/docker.sock:2375 \
16  -p 127.0.0.1:9000-9019:9000-9019 \
17  -v gvx-docker:/var/lib/docker \
18  -e DOCKER_TLS_CERTDIR= \
19  docker:dind --tls=false &
20
21# 3. Die Standard-Docker-CLI und Docker Compose auf den User-Space-Socket ausrichten
22export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
23until docker info >/dev/null 2>&1; do sleep 0.5; done
24docker info --format '{{.Driver}} {{.OperatingSystem}}'
25# Ausgabe: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Warum Standard-Rootless-Werkzeuge in eingeschränkten Sandboxes scheitern

Wenn ein Ingenieur oder autonomer Coding-Agent docker build, docker run oder docker compose in einem gehärteten Container oder einem Mandanten-Runner ausführen muss, scheitern übliche Workarounds an fünf unterschiedlichen Kernel-Barrieren.

1. Kein Host-Docker-Daemon-Socket (auch systemctl und service docker start schlagen fehl)

Der Befehl docker ps oder docker build schlägt sofort fehl, wenn der Host-Socket /var/run/docker.sock nicht in die Umgebung eingebunden ist. Der Versuch, docker.io über systemctl oder service docker start zu starten, scheitert, weil PID 1 nicht systemd ist und /etc/init.d/docker an die harte ulimit -n-Grenze des Containers stößt:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
3System has not been booted with systemd as init system (PID 1). Can't operate.
4Failed to connect to bus: Host is down
5/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)

2. PR_SET_NO_NEW_PRIVS blockiert sudo und Setuid-Helfer

Gehärtete Container (securityContext.allowPrivilegeEscalation: false in Kubernetes, --security-opt=no-new-privileges in Docker oder exebox- bzw. bwrap-Sandboxes) setzen das Linux-Bit PR_SET_NO_NEW_PRIVS auf dem gesamten Prozessbaum. Der Versuch, über sudo oder Setuid-Binaries (newuidmap, newgidmap, fusermount3) Rechte zu erweitern, schlägt fehl:

1sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
2sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

3. Seccomp- und Sysctl-Richtlinien blockieren unshare(CLONE_NEWUSER)

Werkzeuge, die als „rootless“ vermarktet werden (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap), setzen weiterhin auf Linux-User-Namespaces (CLONE_NEWUSER). Sie schlagen immer dann fehl, wenn:

  • das Standard-seccomp-Profil des äußeren Containers unshare(2) oder clone(CLONE_NEWUSER) blockiert.
  • Ubuntu 24.04 und neuer kernel.apparmor_restrict_unprivileged_userns = 1 aktivieren.
  • Debian- oder RHEL-Knoten kernel.unprivileged_userns_clone = 0 oder user.max_user_namespaces = 0 setzen.
  • newuidmap nicht in /proc/self/uid_map schreiben kann, weil PR_SET_NO_NEW_PRIVS aktiv ist.

Die daraus resultierenden Fehler sehen so aus:

1unshare: unshare failed: Operation not permitted
2cannot clone: Operation not permitted
3Error: cannot re-exec process to join the existing user namespace
4newuidmap: write to uid_map failed: Operation not permitted
5RootlessKit: parent: failed to start the child: fork/exec /proc/self/exe: operation not permitted

4. mount(2) und pivot_root(2) sind während docker build ohne Host-CAP_SYS_ADMIN blockiert

Selbst wenn eine Umgebung CLONE_NEWUSER erlaubt oder dockerd mit --iptables=false --bridge=none --storage-driver=vfs gestartet wird, schlägt docker build bereits bei der ersten RUN-Anweisung fehl, weil das Mounten eines frischen /proc oder der Aufruf von pivot_root(2) innerhalb eines verschachtelten Mount-Namespaces blockiert ist:

1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
3OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
4OCI runtime create failed: runc create failed: unable to start container process: error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
5bwrap: Creating new namespace failed: Operation not permitted

5. Fehlendes /dev/fuse bricht fuse-overlayfs

Rootless-Container-Engines, die kein Kernel-overlayfs mounten können, weichen auf fuse-overlayfs aus. In minimalen Containern oder Kubernetes-Pods ohne Durchreichen des Geräts /dev/fuse schlägt fuse-overlayfs sofort fehl:

1fuse: device not found, try 'modprobe fuse' first
2fusermount3: failed to open /dev/fuse: No such file or directory
3error initializing graphdriver: driver not supported: fuse-overlayfs

Wie gvx docker:dind vollständig im User Space ausführt

gvx umgeht alle fünf Kernel-Barrieren, weil es den Host-Linux-Kernel nicht darum bittet, Namespaces zu erstellen, Dateisysteme zu mounten oder Netzwerkschnittstellen zu konfigurieren. Stattdessen führt rungv einen internen Linux-Kernel im User Space (den gVisor-Sentry) und einen Netzwerkstack im User Space (netstack) innerhalb eines gewöhnlichen, unprivilegierten Prozesses aus.

Ausführung von unprivilegiertem Docker-in-Docker mit gvx

1. In-Memory-Namespaces und Mount-Tabelle (VFS2)

Wenn dockerd, containerd und runc innerhalb von rungv ausgeführt werden, fängt systrap deren Systemaufrufe ab und leitet sie an den prozessinternen Sentry-Kernel weiter, nicht an den Host-Kernel:

  • unshare(2), clone(2), pivot_root(2), chroot(2) und mount(2) arbeiten auf der In-Memory-Task-Hierarchie des Sentry und auf der VFS2-Mount-Tabelle (procfs, sysfs, tmpfs, devpts, cgroupfs v2), während der Host-Prozess rungv unter Ihrer unprivilegierten Host-UID läuft.

2. Natives overlayfs auf Einzeldatei-ext4lite-Volumes

Wenn Sie -v gvx-docker:/var/lib/docker (oder --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker) übergeben, mountet gvx sein integriertes Dateisystem ext4lite unter /var/lib/docker.

Da ext4lite ein JBD2-journalisiertes ext4-Dateisystem innerhalb einer einzigen Host-Datei (gvx-docker.ext4) implementiert, mit Unterstützung für die erweiterten Attribute trusted.overlay.*, Whiteout-Zeichengeräte (0:0), d_type und RENAME_EXCHANGE, verwendet dockerd im Gast seinen nativen Storage-Treiber overlayfs. Mehrstufige docker build-Vorgänge teilen sich Copy-on-Write-Layer, ohne /dev/fuse und ohne die Speicherplatzexplosion des vfs-Fallback-Treibers von Docker.

3. User-Space-Bridge docker0 und iptables-legacy

Wenn docker:dind innerhalb von gvx startet:

  • Erstellt der Gast-dockerd die Bridge docker0 (172.17.0.1/16), hängt virtuelle veth-Paare für verschachtelte Container an und programmiert die Ketten NAT (DOCKER, POSTROUTING) sowie FILTER mit addrtype- und conntrack-Matches innerhalb des User-Space-netstack von gvx.
  • Bindet die Angabe -p unix:~/.gvx/docker.sock:2375 einen Unix-Domain-Socket des Hosts (Modus 0600) und leitet Verbindungen direkt an Port 2375 von dockerd innerhalb der Sandbox weiter.

Vergleich mit bestehenden Container-Werkzeugen

Fähigkeit / EinschränkungStandard-Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (unprivilegiert)gvx (rungv + docker:dind)
Host-root oder sudo erforderlichJaNeinNeinNein
Funktioniert mit PR_SET_NO_NEW_PRIVS=1NeinNein (newuidmap schlägt fehl)TeilweiseJa
Funktioniert, wenn unshare(CLONE_NEWUSER) blockiert ist (EPERM)Nicht zutreffendNeinBuildah: Nein / Kaniko: JaJa
Funktioniert ohne /dev/fuseJa (nutzt Kernel-overlay2)Nur über langsamen vfs-KopiervorgangJaJa (overlayfs auf ext4lite)
Unterstützt docker build (BuildKit)JaJa (falls userns erlaubt)Nur Image-Build (ohne Laufzeit)Ja
Unterstützt docker run und docker compose upJaJa (falls userns erlaubt)NeinJa
Läuft unter Windows und macOS ohne Linux-VMNein (benötigt WSL2 / VM)Nein (benötigt Linux-VM)NeinJa (whpx, hvf, systrap)

Genaue Anforderungen an den Host-Kernel unter Linux

gvx benötigt keine erhöhten Capabilities (CapEff: 0000000000000000) und keine User-Namespaces. Unter Linux stützt sich rungv nur auf drei standardmäßige unprivilegierte Kernel-Primitive:

  1. ptrace(PTRACE_ATTACH) auf den eigenen Kindthread, einmalig bei der Initialisierung: systrap startet bei der Initialisierung einen lokalen Stub-Hilfsthread, hängt sich an diesen an, um den gemeinsam genutzten Speicherkontext zu initialisieren, und löst die Verbindung wieder. Wenn der Host kernel.yama.ptrace_scope = 3 setzt (wodurch ptrace systemweit deaktiviert wird) oder ptrace in einem äußeren seccomp-Filter blockiert, kann die Initialisierung von systrap nicht abgeschlossen werden. (Unter yama.ptrace_scope = 1, dem Standard auf Ubuntu und Debian, funktioniert gvx, da das Tracing vom Eltern- zum Kindprozess erlaubt ist.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Wird verwendet, um Systemaufrufe des Gasts aus den Stub-Threads des Containers abzufangen. Wie in Teil 3 dieser Serie beschrieben, funktioniert gvx sowohl dann, wenn SECCOMP_FILTER_FLAG_NEW_LISTENER verfügbar ist, als auch dann, wenn es in einer verschachtelten Sandbox EBUSY zurückgibt.
  3. mmap(..., PROT_EXEC, ...): Wird verwendet, um ELF-Segmente des Gasts und das gemeinsam genutzte Speicher-Trampolin von systrap abzubilden.

End-to-End-Beispiel: Bauen und Testen einer Multi-Container-App ohne Root

Das folgende Skript führt einen vollständigen docker build und einen Integrationstest mit zwei Containern (redis + Anwendungscontainer) in einer unprivilegierten Benutzersitzung aus:

 1mkdir -p ~/.gvx
 2rungv run \
 3  -p unix:~/.gvx/docker.sock:2375 \
 4  -v gvx-docker:/var/lib/docker \
 5  -e DOCKER_TLS_CERTDIR= \
 6  docker:dind --tls=false &
 7DIND_PID=$!
 8
 9export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
10until docker info >/dev/null 2>&1; do sleep 0.5; done
11
12mkdir -p /tmp/demo-app && cd /tmp/demo-app
13cat <<'EOF' > Dockerfile
14FROM alpine:3.20
15RUN apk add --no-cache redis
16CMD ["redis-cli", "-h", "cache", "ping"]
17EOF
18
19# Das Image mit overlayfs auf ext4lite bauen
20docker build -t demo-ping:latest .
21
22# Ein Bridge-Netzwerk erstellen, Redis starten und die Konnektivität zwischen den Containern prüfen
23docker network create demo-net
24docker run -d --name cache --network demo-net redis:7-alpine
25docker run --rm --network demo-net demo-ping:latest
26# Ausgabe: PONG
27
28# Workload-Container aufräumen und die Sandbox beenden
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Weitere Lektüre in dieser Serie

  1. gvx: Linux-Container, Docker und Kubernetes in einem unprivilegierten Prozess ausführen: Architekturüberblick über gvx, seine plattformübergreifenden Ausführungs-Engines (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) und netstack.
  2. Docker, docker build und docker compose ohne Root oder User-Namespaces ausführen (dieser Beitrag)
  3. Docker-in-Docker innerhalb von gVisor und verschachtelten Sandboxes mit gvx ausführen: Wie gvx modprobe, iptables-nft, overlay2 auf gofer und das verschachtelte SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) innerhalb von gVisor (runsc) löst.
  4. Schnelle isolierte Docker-Umgebungen für RL-Rollouts von KI-Agenten mit gvx: Skalierung paralleler SWE-bench- und RL-Rollout-Umgebungen mit 100-Millisekunden-Copy-on-Write-Resets von .ext4-Volumes.