GoSuda

Köra Docker, docker build och docker compose utan root eller användarnamnrymder

By Lemon Mint views ...

Snabblösning: Säkra Docker utan privilegier i tre steg

Om du arbetar i en låst Linux-container, CI-runner, Kubernetes Pod eller AI-agentsandlåda där root, sudo, användarnamnrymder (unshare(CLONE_NEWUSER)), mount(2) och /dev/fuse är blockerade, är gvx (rungv) den säkra lösningen med försvar på flera nivåer för att köra en fullständig docker:dind-motor i användarutrymme.

Varför gvx är säkert by design (inte en lösning för privilegieeskalering): Till skillnad från att montera värdens /var/run/docker.sock eller köra docker run --privileged (vilket ger root-motsvarande kontroll över värdens kärna) kringgår eller försvagar inte gvx någon säkerhetspolicy på värden (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor eller begränsningar för icke-root-UID). I stället låser rungv sin egen värdprocess (PR_SET_NO_NEW_PRIVS + en strikt BPF-seccomp-tillåtelselista) och kör dockerd inuti Googles minnessäkra gVisor (Sentry)-kärna i användarutrymme. Alla gäst-UID 0-förmågor, overlayfs-monteringar (ext4lite) och iptables-regler förblir strikt isolerade inom sandlådeprocessen.

 1# 1. Installera rungv i användarutrymme (förkompilerad SLSA-attesterad release eller go install)
 2# Alternativ A: Verifiera och extrahera den SLSA-attesterade förkompilerade binären från GitHub Releases
 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# Alternativ B: Eller installera direkt från källkod med Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Starta docker:dind inuti gvx med en enfils-ext4lite-volym
12#    Vidarebefordrar ~/.gvx/docker.sock (läge 0600) till gästens port 2375
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. Peka den vanliga Docker CLI och Docker Compose mot användarutrymmets socket
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# Utdata: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Varför vanliga verktyg för rootless-körning misslyckas i begränsade sandlådor

När en ingenjör eller en autonom kodningsagent behöver köra docker build, docker run eller docker compose inuti en härdad container eller en multi-tenant-runner, misslyckas vanliga lösningar vid fem separata kärnbarriärer.

1. Ingen värd-socket för Docker-daemonen

Att köra docker ps eller docker build misslyckas omedelbart när värdens /var/run/docker.sock inte är monterad i miljön:

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

2. PR_SET_NO_NEW_PRIVS blockerar sudo och setuid-hjälpprogram

Härdade containrar (securityContext.allowPrivilegeEscalation: false i Kubernetes, --security-opt=no-new-privileges i Docker, eller exebox- / bwrap-sandlådor) sätter biten PR_SET_NO_NEW_PRIVS i Linux på processträdet. Försök att eskalera via sudo eller setuid-binärer (newuidmap, newgidmap, fusermount3) misslyckas:

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- och sysctl-policyer blockerar unshare(CLONE_NEWUSER)

Verktyg som marknadsförs som "rootless" (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) förlitar sig fortfarande på Linux användarnamnrymder (CLONE_NEWUSER). De misslyckas när:

  • Den yttre containerns standardprofil för seccomp blockerar unshare(2) eller clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ aktiverar kernel.apparmor_restrict_unprivileged_userns = 1.
  • Debian- eller RHEL-noder sätter kernel.unprivileged_userns_clone = 0 eller user.max_user_namespaces = 0.
  • newuidmap inte kan skriva till /proc/self/uid_map eftersom PR_SET_NO_NEW_PRIVS är aktivt.

De resulterande felen ser ut så här:

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) är blockerad utan värdens CAP_SYS_ADMIN

Även om en miljö tillåter CLONE_NEWUSER misslyckas montering av en ny /proc inuti en kapslad monteringsnamnrymd om den yttre containern har maskerat /proc-delträd (/proc/acpi, /proc/kcore, /proc/keys) eller tagit bort mount(2) från seccomp:

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. Saknad /dev/fuse bryter fuse-overlayfs

Rootless-containermotorer som inte kan montera kärnans overlayfs faller tillbaka till fuse-overlayfs. I minimala containrar eller Kubernetes-pods utan genomslussning av enheten /dev/fuse misslyckas fuse-overlayfs omedelbart:

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

Hur gvx kör docker:dind helt i användarutrymme

gvx kringgår alla fem kärnbarriärerna eftersom den inte ber värdens Linux-kärna att skapa namnrymder, montera filsystem eller konfigurera nätverksgränssnitt. I stället kör rungv en intern Linux-kärna i användarutrymme (gVisor Sentry) och en nätverksstack i användarutrymme (netstack) inuti en enda vanlig process utan privilegier.

Körning av Docker-in-Docker utan privilegier med gvx

1. Namnrymder och monteringstabell i minnet (VFS2)

När dockerd, containerd och runc körs inuti rungv fångar systrap upp deras systemanrop och dirigerar dem till den processinterna Sentry-kärnan i stället för till värdens kärna:

  • unshare(2), clone(2), pivot_root(2), chroot(2) och mount(2) arbetar mot Sentrys uppgiftshierarki i minnet och monteringstabellen VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), medan värdprocessen rungv körs under ditt UID på värden utan privilegier.

2. Native overlayfs på enfils-volymer av typen ext4lite

När du anger -v gvx-docker:/var/lib/docker (eller --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker) monterar gvx sitt inbyggda filsystem ext4lite på /var/lib/docker.

Eftersom ext4lite implementerar ett JBD2-journalfört ext4-filsystem inuti en enda fil på värden (gvx-docker.ext4) med stöd för utökade attribut trusted.overlay.*, teckenbaserade whiteout-enheter (0:0), d_type och RENAME_EXCHANGE, använder dockerd sin inbyggda lagringsdrivrutin overlayfs inuti gästen. Flerstegsoperationer för docker build delar Copy-on-Write-lager utan /dev/fuse och utan den diskutrymmesexplosion som följer av Dockers reservdrivrutin vfs.

3. Användarutrymmets docker0-brygga och iptables-legacy

När docker:dind startar inuti gvx:

  • Gästens dockerd skapar bryggan docker0 (172.17.0.1/16), kopplar virtuella veth-par för kapslade containrar och programmerar kedjorna NAT (DOCKER, POSTROUTING) och FILTER med matchningar för addrtype och conntrack inuti gvx:s netstack i användarutrymme.
  • Med -p unix:~/.gvx/docker.sock:2375 binds en Unix-domänsocket på värden (läge 0600) och anslutningar vidarebefordras direkt till port 2375 för dockerd inuti sandlådan.

Jämförelse med befintliga containerverktyg

Egenskap / BegränsningStandard Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (utan privilegier)gvx (rungv + docker:dind)
Värd-root eller sudo krävsJaNejNejNej
Fungerar med PR_SET_NO_NEW_PRIVS=1NejNej (newuidmap misslyckas)DelvisJa
Fungerar när unshare(CLONE_NEWUSER) är blockerad (EPERM)N/ANejBuildah: Nej / Kaniko: JaJa
Fungerar utan /dev/fuseJa (använder kärnans overlay2)Endast via långsam vfs-kopieringJaJa (overlayfs på ext4lite)
Stöder docker build (BuildKit)JaJa (om userns tillåts)Endast bildbygge (ingen körtid)Ja
Stöder docker run och docker compose upJaJa (om userns tillåts)NejJa
Fungerar på Windows och macOS utan Linux-VMNej (kräver WSL2 / VM)Nej (kräver Linux-VM)NejJa (whpx, hvf, systrap)

Exakta krav på värdens Linux-kärna

gvx kräver noll förhöjda förmågor (CapEff: 0000000000000000) och noll användarnamnrymder. På en Linux-värd förlitar sig rungv endast på tre standardprimitiver i kärnan som kan användas utan privilegier:

  1. ptrace(PTRACE_ATTACH) på sin egen underordnade tråd en gång under initieringen: systrap startar en lokal stubbhjälptråd vid uppstart, kopplar sig till den för att initiera det delade minneskontexten och kopplar sedan loss. Om värden sätter kernel.yama.ptrace_scope = 3 (vilket inaktiverar all ptrace systemomfattande) eller blockerar ptrace i ett yttre seccomp-filter kan inte initieringen av systrap slutföras. (Med yama.ptrace_scope = 1, standard på Ubuntu och Debian, lyckas gvx eftersom spårning från förälder till barn är tillåten.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Används för att fånga gästens systemanrop från containerstubbtrådar. Som beskrivs i del 3 av denna serie fungerar gvx både när SECCOMP_FILTER_FLAG_NEW_LISTENER är tillgänglig och när den returnerar EBUSY inuti en kapslad sandlåda.
  3. mmap(..., PROT_EXEC, ...): Används för att mappa gästens ELF-segment och systrap:s delade minnestrampolin.

Exempel från början till slut: Bygga och testa en multi-container-app utan root

Följande skript kör ett komplett docker build och ett integrationstest med två containrar (redis + applikationscontainer) inuti en användarsession utan privilegier:

 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# Bygg avbildningen med overlayfs på ext4lite
20docker build -t demo-ping:latest .
21
22# Skapa ett bryggnätverk, starta Redis och verifiera anslutningen mellan containrarna
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# Utdata: PONG
27
28# Rensa arbetscontainrarna och stoppa sandlådan
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Fortsatt läsning i den här serien

  1. gvx: Köra Linux-containrar, Docker och Kubernetes i en enda process utan privilegier: Arkitekturöversikt av gvx, dess plattformsoberoende exekveringsmotorer (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) och netstack.
  2. Köra Docker, docker build och docker compose utan root eller användarnamnrymder (detta inlägg)
  3. Köra Docker-in-Docker inuti gVisor och kapslade sandlådor med gvx: Hur gvx löser modprobe, iptables-nft, overlay2 på gofer och kapslad SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) inuti gVisor (runsc).
  4. Snabba isolerade Docker-miljöer för AI-agenters RL-rollouts med gvx: Skalning av parallella SWE-bench- och RL-rollout-miljöer med återställning av .ext4-volymer med Copy-on-Write på 100 millisekunder.