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 in einem abgeriegelten Linux-Container, einem CI-Runner, einem Kubernetes-Pod oder einer AI-Agent-Sandbox arbeiten, in der root, sudo, User Namespaces (unshare(CLONE_NEWUSER)), mount(2) und /dev/fuse blockiert sind, ist gvx (rungv) die sichere Lösung mit mehrschichtiger Verteidigung, um eine vollständige docker:dind-Engine im User Space auszufü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 Sicherheitsrichtlinie des Hosts (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor oder die Beschränkungen für nicht-root UIDs). Stattdessen sperrt rungv seinen eigenen Host-Prozess ab (PR_SET_NO_NEW_PRIVS + strikte BPF-seccomp-Allowlist) und führt dockerd im speichersicheren User-Space-Kernel gVisor (Sentry) von Google aus. Alle Capabilities der Gast-UID 0, overlayfs-Mounts (ext4lite) und iptables-Regeln bleiben streng 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 Single-File-ext4lite-Volume starten
12#    Leitet ~/.gvx/docker.sock (Modus 0600) an Port 2375 des Gasts 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-Tools in eingeschränkten Sandboxes scheitern

Wenn ein Ingenieur oder ein autonomer Coding-Agent docker build, docker run oder docker compose innerhalb eines gehärteten Containers oder eines Multi-Tenant-Runners ausführen muss, scheitern übliche Workarounds an fünf unterschiedlichen Kernel-Barrieren.

1. Kein Host-Docker-Daemon-Socket

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:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

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 im 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)

Als „rootless“ vermarktete Werkzeuge (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+ kernel.apparmor_restrict_unprivileged_userns = 1 aktiviert.
  • 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 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) ist ohne Host-CAP_SYS_ADMIN blockiert

Selbst wenn eine Umgebung CLONE_NEWUSER erlaubt, schlägt das Mounten eines neuen /proc innerhalb eines verschachtelten Mount-Namespaces fehl, wenn der äußere Container /proc-Teilbäume maskiert hat (/proc/acpi, /proc/kcore, /proc/keys) oder mount(2) in seccomp verworfen hat:

1mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
2OCI 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
3bwrap: 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 /dev/fuse-Device-Passthrough 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 bittet, Namespaces zu erzeugen, 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. Speicherinterne 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 statt an den Host-Kernel weiter:

  • unshare(2), clone(2), pivot_root(2), chroot(2) und mount(2) operieren auf der speicherinternen Task-Hierarchie des Sentry und 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 Single-File-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 einzelnen Host-Datei (gvx-docker.ext4) implementiert, mit Unterstützung für trusted.overlay.*-erweiterte Attribute, Character-Whiteout-Devices (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 Explosion des Speicherplatzbedarfs, die mit dem vfs-Fallback-Treiber von Docker einhergeht.

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) und FILTER mit addrtype- und conntrack-Matches innerhalb des User-Space-netstack von gvx.
  • Bindet die Angabe von -p unix:~/.gvx/docker.sock:2375 einen Host-Unix-Domain-Socket (Modus 0600) und leitet Verbindungen direkt an Port 2375 des 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)N/ANeinBuildah: Nein / Kaniko: JaJa
Funktioniert ohne /dev/fuseJa (nutzt Kernel-overlay2)Nur über langsame vfs-KopieJaJa (overlayfs auf ext4lite)
Unterstützt docker build (BuildKit)JaJa (falls userns erlaubt)Nur Image-Build (keine Runtime)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 Kernel-Anforderungen des Hosts unter Linux

gvx benötigt keinerlei erhöhte Capabilities (CapEff: 0000000000000000) und keine User Namespaces. Auf einem Linux-Host stützt sich rungv nur auf drei standardmäßige unprivilegierte Kernel-Primitiven:

  1. ptrace(PTRACE_ATTACH) auf den eigenen Kindthread, einmalig während der Initialisierung: systrap startet beim Start einen lokalen Stub-Helferthread, hängt sich an ihn an, um den gemeinsam genutzten Speicherkontext zu initialisieren, und löst sich anschließend 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 von Elternprozess zu Kindprozess erlaubt ist.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Wird verwendet, um Gast-Systemaufrufe von Container-Stub-Threads 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 innerhalb 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: Erstellen 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# Image mit overlayfs auf ext4lite erstellen
20docker build -t demo-ping:latest .
21
22# 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 entfernen und die Sandbox beenden
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Weiterführende 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 AI-Agent-RL-Rollouts mit gvx: Skalierung paralleler SWE-bench- und RL-Rollout-Umgebungen mit 100-Millisekunden-Copy-on-Write-Resets von .ext4-Volumes.