GoSuda

gvx: Ausführung von Linux-Containern, Docker und Kubernetes in einem einzigen unprivilegierten Prozess

By Lemon Mint views ...

Einführung: Sicherheitsgehärtete Container ohne Root oder Host-Daemons

Das Ausführen von Linux-Containern, docker build oder Kubernetes innerhalb eines Entwicklungsrechners, eines CI-Runners oder eines Multi-Tenant-Cloud-Knotens erzwingt üblicherweise einen unangenehmen Sicherheitskompromiss:

  • Unter Linux erfordern Standard-OCI-Runtimes (runc, crun, runsc) entweder root-Rechte, die Exposition des Host-Sockets /var/run/docker.sock, --privileged (CAP_SYS_ADMIN) oder User Namespaces (unshare(CLONE_NEWUSER)), die die Angriffsfläche des Host-Kernels vergrößern.
  • Unter Windows und macOS starten Container-Engines (Docker Desktop, Podman Machine, Lima, OrbStack) einen vollständigen Linux-Virtual-Machine-Kernel innerhalb von WSL2, Hyper-V oder Virtualization.framework und betreiben in dieser VM einen persistenten Hintergrunddaemon.

gvx (rungv CLI und das z.gfire.dev/gvx/pkg/gvx Go SDK) bietet eine sichere Alternative ohne Privilegien. Als upstream-nachverfolgbarer Fork des User-Space-Linux-Kernels Sentry von Googles gVisor und des User-Space-Netzwerkstacks netstack konzipiert, führt gvx unveränderte linux/amd64- und linux/arm64-ELF64-Binärdateien sowie OCI-Container direkt innerhalb eines einzigen unprivilegierten, selbst gesperrten Host-Prozesses unter Windows, macOS und Linux aus.

Sicherheitsgarantie (Drei-Ring-Defense-in-Depth): gvx schwächt niemals die Sicherheitsgrenzen des Host-Betriebssystems, installiert niemals Kernel-Treiber und erfordert niemals root, sudo, --privileged auf dem Host oder CLONE_NEWUSER. Bevor auch nur eine einzige Gast-Instruktion ausgeführt wird, sperrt gvx seinen eigenen Host-Prozess mithilfe von Windows Restricted Tokens und Job Objects, macOS Seatbelt (deny default) oder Linux PR_SET_NO_NEW_PRIVS + BPF-seccomp. Alle Gast-Systemaufrufe, virtuellen UID 0-Privilegien, Mounts (VFS2) und Netzwerktabellen (netstack) sind strikt innerhalb des speichersicheren Go-Sentry-Kernels eingeschlossen.

RuntimeHost-BetriebssystemKaltstartSpeicherbedarfAdministrator- oder sudo-Rechte nötigHintergrund-VM-DaemonDirekte Ausführung von OCI-Images
gvx (whpx / hvf)Windows (amd64, arm64) & macOS (arm64)~20 ms~30 MiBNeinNeinJa
gvx (systrap)Windows (amd64, arm64) & Linux (amd64, arm64)~25 bis 65 ms~31 MiBNeinNeinJa
WSL2 / Docker DesktopWindows & macOS800 bis 2500 ms> 512 MiBJa (Ersteinrichtung)JaErfordert Daemon
QEMU (Full System)Windows & macOS1500 bis 4000 ms> 256 MiBNeinNeinErfordert Disk Image

Auf Windows oder macOS wird kein Linux-VM-Kernel gestartet. Auf dem Host läuft kein Hintergrunddaemon. Für das Host-Betriebssystem sind keine root-, sudo-, User-Namespace- (CLONE_NEWUSER), mount(2)-, TUN/TAP-Treiber- oder CAP_NET_ADMIN-Privilegien erforderlich. Innerhalb dieses unprivilegierten Prozesses führt gvx Docker-in-Docker (docker:dind), docker build, docker compose sowie hochverfügbares Multi-Node-Kubernetes (k3s mit eingebettetem etcd und Calico CNI) aus.


Container-Lebenszyklus und Drei-Ring-Host-Sperrung

Jede gvx-Sandbox läuft innerhalb eines unprivilegierten Host-Prozesses und durchläuft einen dreiphasigen Lebenszyklus: Vorab-Öffnung aller erforderlichen Host-File-Deskriptoren und Sockets, Sperrung des Host-Betriebssystemprozesses über pkg/hostlockdown und Eintritt in die prozessinterne Sentry-Ausführungsschleife.

gvx Container Lifecycle and Host Self-Lockdown

Bevor die Kontrolle an die erste Gast-Instruktion übergeben wird, öffnet gvx vorab seine zwischengespeicherten .erofs-Image-Layer-Deskriptoren, .ext4-Volume-Deskriptoren, das /workspace-*os.Root-Handle und Host-Netzwerk-Sockets und ruft anschließend hostlockdown.Apply() auf:

  1. Windows NT (lockdown_windows.go): Ruft AdjustTokenPrivileges mit SE_PRIVILEGE_REMOVED auf, um Privilegien direkt zu entfernen, bindet ein DISABLE_MAX_PRIVILEGE-eingeschränktes Impersonierungstoken mit Low-Integrität (S-1-16-4096), weist den Prozess einem anonymen Job Object mit JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1 zu (wodurch die Erstellung jedes Kindprozesses blockiert wird) und aktiviert DisallowWin32kSystemCalls = 1 über SetProcessMitigationPolicy.
  2. macOS XNU (lockdown_darwin.go): Setzt RLIMIT_NPROC = 0 und wendet ein Seatbelt-Profil (sandbox_init_with_parameters) an, das mit (version 1)(deny default) beginnt und process-fork, process-exec* sowie alle neuen Host-Dateiöffnungen blockiert.
  3. Linux (lockdown_linux.go + pkg/gvx/syscallfilter_linux.go): Setzt PR_SET_NO_NEW_PRIVS und installiert die Sentry-BPF-seccomp-Allowlist über alle Threads (SECCOMP_FILTER_FLAG_TSYNC) mit expliziten EPERM-Regeln für execve, execveat, clone, openat und socket.

Plattformübergreifendes Abfangen von Systemaufrufen und 4-KB-Paging (pkg/sentry/platform)

Linux-ELF64-Binärdateien setzen für mmap(MAP_FIXED)-Segmentzuordnungen eine Speicherseitengranularität von 4 KB (0x1000) voraus. Windows VirtualAlloc und MapViewOfFile3 erzwingen jedoch eine Ausrichtung auf 64 KB (0x10000), und Apple-Silicon-macOS (darwin/arm64) verwendet 16-KB-Host-Seiten (0x4000). Darüber hinaus benötigt jedes Betriebssystem einen anderen Mechanismus, um Linux-SYSCALL- (amd64) und SVC #0-Instruktionen (arm64) abzufangen, ohne eine Linux-VM zu starten.

gvx löst beide Probleme über vier Ausführungs-Backends, die pkg/sentry/platform.Context implementieren:

gvx Platform Syscall Interception and 4KB Paging Mechanics

1. Windows-Hardware-Engine: platform/whpx (WinHvPlatform.dll)

Auf windows/amd64 und windows/arm64 erstellt platform/whpx über WHvCreatePartition eine leichtgewichtige Hypervisor-Partition, ohne einen Gast-Betriebssystemkernel zu booten:

  • 64-KB-zu-4-KB-Paging-Entkoppler: whpx registriert jede 64-MiB-Arena von pgalloc.MemoryFile einmalig über WHvMapGpaRange bei der Partition. Nachfolgende MapFile- und Unmap-Aufrufe ändern lediglich die Gastseitentabellen innerhalb der Partition und erzwingen exakte 4-KB-R/W/X-Berechtigungen trotz der 64-KB-Allokationsgranularität von Windows.
  • Registeraustausch über CommPage ohne API-Aufrufe:
    • Auf amd64 gelangt ein SYSCALL aus Ring 3 des Gastes in einen innerhalb der Partition liegenden Ring-0-MSR_LSTAR-Stub (stubgen_x86.go), der die GPRs für den Fast-Path-Systemaufruf (RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP) in eine gemeinsame 4-KB-CommPage schreibt und HLT ausführt, um WHvRunVirtualProcessor zu verlassen.
    • Auf arm64 gelangt ein SVC #0 aus EL0 des Gastes in die EL1-Vektoren (stubgen_a64.go), schreibt X0..X8, SP_EL0, ELR_EL1, PSTATE in die CommPage und lädt von einer nicht hinterlegten GPA-Null, um einen Speicherexit zurück zum Sentry auszulösen.

2. macOS-Hardware-Engine: platform/hvf (Hypervisor.framework)

Auf Apple Silicon (darwin/arm64) führt platform/hvf Container-Threads innerhalb von Hypervisor.framework-vCPUs aus (hv_vm_create, hv_vcpu_run):

  • Zweistufige 4-KB-über-16-KB-MMU-Übersetzung: Stage-2 (hv_vm_map) bildet 1-GiB-Chunks der Memory-File einmalig mit 16-KB-Host-Seitengranularität in den Intermediate-Physical-Address-(IPA)-Raum ab, mit HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC. Innerhalb der vCPU konfiguriert der EL1-bluepill-Stub (bluepill_arm64.go) TCR_EL1.TG0 = 0b00 (4-KB-Granule) in TTBR0_EL1 und befüllt 4-KB-Stage-1-Seitentabelleneinträge mit exakten R-X- oder RW- (UXN=1)-Berechtigungen. Da die ARM64-MMU die Schnittmenge der Stage-1- und Stage-2-Berechtigungen durchsetzt, fängt die Hardware Berechtigungsfehler exakt an 4-KB-Grenzen ab.
  • Systemaufruf-Trap: Ein SVC #0 aus EL0 des Gastes trifft auf EL1 VBAR_EL1, schreibt Register in die gemeinsame 4-KB-CommPage und führt HVC #0 aus, um aus hv_vcpu_run in den Go-Sentry-Thread zurückzukehren.

3. Windows-User-Mode-Fallback: platform/systrap (winsystrap)

Wenn Hardwarevirtualisierung nicht verfügbar ist (etwa innerhalb verschachtelter Cloud-VMs), führt winsystrap Gastcode im User Mode auf windows/amd64 und windows/arm64 aus:

  • Hardware-CR3- / TTBR0_EL1-Isolation: Gast-Threads laufen innerhalb isolierter Stub-Subprozesse, die aus einer generierten 1-KB-ausführbaren Datei (stubimage_xplat.go) gestartet werden und durch ein Low-Integrity-Token und ein Job Object eingeschränkt sind.
  • Binärpatching an Ort und Stelle & winSysmsg-IPC: Auf amd64 ersetzt der Patcher (patcher_amd64_xplat.go) 2-Byte-syscall-Instruktionen (0f 05) durch 5-Byte-jmp rel32-Sprünge (oder durch CC 90-Breakpoint-Traps, wenn kein 5-Byte-Fenster verfügbar ist) in eine Fast-Path-Insel. Auf arm64 wird svc #0 durch einen B-Branch zu einem Inselslot ersetzt. Die Insel schreibt Register in die gemeinsame winSysmsg-Seite und synchronisiert sich mit dem Sentry über WaitOnAddress und WakeByAddressSingle, während nicht gepatchte Traps und Seitenfehler an einem Windows-Debug-Object (WaitForDebugEventEx) anhalten.

4. Linux-User-Mode-Engine: platform/systrap

Auf linux/amd64 und linux/arm64 führt systrap Gast-Threads (sysmsg) aus, die Gastsystemaufrufe über SECCOMP_RET_TRAP (SIGSYS) abfangen und syscall-Stellen binär in einen gemeinsam genutzten Speicher-Trampolin (sysmsg) patchen, der über SYS_FUTEX synchronisiert wird. Gleichzeitig führt ein Stub-syscallThread (syscall_thread.go) Adressraumoperationen (mmap, munmap, clone) über SECCOMP_RET_USER_NOTIF (_RUN_SECCOMP_LOOP) aus. Wenn gvx innerhalb einer äußeren gVisor-(runsc)-Sandbox läuft, in der der einzelne SECCOMP_FILTER_FLAG_NEW_LISTENER-Slot EBUSY zurückgibt, schaltet gvx syscallThread automatisch auf eine Futex-Schleife mit gemeinsamem Speicher (_RUN_SYSCALL_LOOP) um, wie in Teil 3 dieser Serie beschrieben.


Wie gvx Linux-Systemaufrufe des Gastes auf virtuelle Subsysteme und Host-Handles abbildet

Sobald eine Plattform-Engine eine SYSCALL- (amd64) oder SVC #0-Instruktion (arm64) des Gastes abfängt und die Gastregister in die 4-KB-gemeinsame Seite (CommPage, sysmsg oder winSysmsg) schreibt, kehrt die Kontrolle zur Go-Sentry-Task-Goroutine zurück (pkg/sentry/kernel/task_syscall.go). Statt Linux-Systemaufrufe an den zugrunde liegenden Host-Betriebssystemkernel weiterzuleiten, speichert Task.doSyscall() den ursprünglichen Registerzustand (SyscallSaveOrig()), wertet alle im Gast eingebetteten BPF-seccomp-Filter und ptrace-Stopps aus und leitet die Systemaufrufnummer über linux.AMD64 oder linux.ARM64 (pkg/sentry/syscalls/linux/linux64.go, das etwa 370 Linux-Systemaufrufe abdeckt) in vier interne Domänen weiter:

How gvx Maps Guest Linux Syscalls to Subsystems and Host OS

Systemaufruf-DomäneRepräsentative Linux-Systemaufrufe des GastesInterne gvx-Subsystem (pkg/sentry)Interaktion mit der Host-OS-Grenze (nach hostlockdown)
A. Prozess, Namespace, Mount & Synchronisationclone, execve, unshare, setns, mount, umount2, pivot_root, setuid, capset, futex, epoll_wait, pipe2kernel.Task, virtuelle Credentials, Futex-Tabelle und im Speicher liegendes VFS2 (procfs, sysfs, cgroupfs v2, tmpfs, overlayfs)Null Host-Systemaufrufe (100 % im Go-Zustand im Speicher; für das Host-Betriebssystem vollständig unsichtbar)
B. Virtueller Speicher (MM)mmap, munmap, mremap, mprotect, brk, madvise, memfd_createmm.MemoryManager (4-KB-VMA-Baum) & pgalloc.MemoryFileBidirektionale 4-KB-Seitentabellenaktualisierungen über WHvMapGpaRange, hv_vm_map + TTBR0_EL1 oder Stub-mmap/mprotect
C. Dateien & Speicher (VFS2)openat, pread64, pwrite64, getdents64, statx, renameat2, setxattr, mknod, inotify_add_watchMount-Tabellen-Router vfs.VirtualFilesystem (fsimpl/erofs, fsimpl/ext4lite, fsimpl/hostfs v2)Bidirektionales pread/pwrite auf 1 fixierten .erofs-FD oder 1 .ext4-FD oder eingeschränkte relative Pfadauflösungen auf *os.Root
D. Netzwerk & Netfiltersocket, bind, listen, connect, sendmsg, recvmsg, netlink, setsockopt(IPT_SO_SET_REPLACE)Benutzerraum-pkg/tcpip (lo, eth0, docker0, veth, vxlan, wireguard, ipset, iptables-legacy)Datenverkehr innerhalb der Sandbox verwendet null Host-Systemaufrufe; externer Datenverkehr wird auf net.Dial/net.Listen (-net=nat) oder 1 UDP-Socket (-net=wg) abgebildet

Wenn der Handler (rval, ctrl, err) zurückgibt, wandelt Task.doSyscallInvoke() jeden Fehler in einen negativen Linux-errno um (-ExtractErrno(err, sysno)), schreibt den Rückgabewert zurück auf die gemeinsame Registerseite (RAX auf amd64, X0 auf arm64) und setzt die Ausführung des Gastes fort.


Speicher (VFS2) und zweigleisiger User-Space-Netzwerkbetrieb (netstack)

Alle Datei- und Netzwerkoperationen des Gastes werden innerhalb des Go-Sentry über drei VFS2-Dateisysteme und zwei pkg/netbackend-Stränge bedient:

gvx VFS2 Storage Engines and Dual-Track Netstack

Speicher-EngineEinhängerolle in gvxZugrunde liegende Host-RessourceHost-openat- / NtCreateFile-Aufrufe zur LaufzeitWindows-Defender-(WdFilter.sys)-Scans pro Datei
fsimpl/erofsSchreibgeschützte untere OCI-Image-LayerEine einzelne <sha256>.erofs-Datei pro Layer00
fsimpl/ext4liteBenannte Volumes (-v name:/path)Eine einzelne <name>.ext4-Datei pro Volume00
fsimpl/hostfs v2Host-Workspace (-workspace, -v /host:/guest)Fixiertes *os.Root-Host-VerzeichnisO(depth) pro uncachierter Öffnung1 pro Schließen einer Host-Datei

Warum ext4lite für Docker, Datenbanken und Paketmanager wichtig ist

Das Entpacken oder Schreiben von Zehntausenden Container-Dateien direkt auf Windows NTFS oder macOS APFS löst Antivirus-Minifilter-Scans (WdFilter.sys), Kollisionen bei der Groß-/Kleinschreibung und fehlende erweiterte POSIX-Attribute aus.

pkg/sentry/fsimpl/ext4lite implementiert ein rein in Go geschriebenes ext4-Dateisystem innerhalb einer einzelnen vorab geöffneten Host-Datei (%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4 unter Windows, ~/Library/Caches/gvx/volumes/<name>.ext4 unter macOS oder ~/.cache/gvx/volumes/<name>.ext4 unter Linux) mit:

  • 4-KiB-Blöcke und dynamische 16-MiB-Blockgruppen (meta_bg): Wächst dynamisch, ohne Stalls durch Sparse-File-Allokation (fallocate unter Linux, fcntl(F_PREALLOCATE) unter macOS, FileAllocationInfo unter Windows), und verdichtet nicht genutzte nachlaufende Gruppen beim sauberen Aushängen.
  • JBD2-Journal im Ordered-Modus (Inode 8): Atomare Transaktionsbestätigungen mit CRC-32-Prüfsummen und Revoke-Blöcken, zusammen mit einer fsckLite-Wiederherstellung.
  • Unterstützung der overlayfs-Upper-Layer: Implementiert die erweiterten Attribute trusted.overlay.* und security.capability, Character-Device-Whiteouts (0:0), d_type-Meldung für Verzeichniseinträge und RENAME_EXCHANGE.

Praktische Workloads: Docker Engine, Kubernetes (k3s + Calico) und Go SDK

Installation von rungv: SLSA-attestierte vorgefertigte Releases oder go install

Sie können ein vorgefertigtes rungv-Archiv für linux/amd64, linux/arm64, darwin/arm64, windows/amd64 oder windows/arm64 von GitHub Releases herunterladen (jeweils erstellt durch die SLSA-Release-Pipeline mit einem Sigstore-Provenance-Bundle und SHA256SUMS) oder direkt aus dem Quellcode mit go install installieren:

1# Option A: Vorgefertigtes Release herunterladen und SLSA-Build-Provenance verifizieren
2gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
3  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
4  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
5shasum -a 256 -c SHA256SUMS --ignore-missing
6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz
7
8# Option B: Direkte Installation über Go (Linux / Windows)
9CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

Auf macOS (darwin/arm64) ist das vorgefertigte Release-Archiv bereits mit dem Berechtigungsanspruch com.apple.security.hypervisor signiert (xattr -d com.apple.quarantine rungv entfernt die Browser-Quarantäne). Wenn Sie lokal mit go install oder go build unter macOS bauen, signieren Sie die Binärdatei mit codesign --sign - --entitlements entitlements.plist --force $(which rungv).

1. Ausführen einer unprivilegierten Docker Engine (docker:dind)

Gemäß docs/docker-engine.md können Sie einen unveränderten docker:dind-Container innerhalb von rungv unter Windows, macOS oder Linux ausführen und mit Ihrer Host-docker-CLI steuern:

 1# 1. Host-Verzeichnis für den weitergeleiteten Unix-Socket erstellen
 2mkdir -p ~/.gvx
 3
 4# 2. docker:dind innerhalb einer gvx-Sandbox mit einem ext4lite-Volume starten
 5rungv run \
 6  -p unix:~/.gvx/docker.sock:2375 \
 7  -p 127.0.0.1:9000-9019:9000-9019 \
 8  -v gvx-docker:/var/lib/docker \
 9  -e DOCKER_TLS_CERTDIR= \
10  docker:dind --tls=false

In einem zweiten Terminal richten Sie Ihre Host-docker-CLI auf den Sandbox-Socket:

1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# Ausgabe: overlayfs Alpine Linux ... (containerized)
6
7docker build -t myapp ./app
8docker compose up -d

2. Ausführen von Kubernetes (k3s + Calico) in unprivilegierten Sandboxes

Da gvx /dev/kmsg, cgroup v2 (memory.max-OOM-Killer und cpu.max-Bandbreitendrosselung), /proc/diskstats, wireguard- und vxlan-Netlink-Geräte, ipset sowie iptables-legacy (addrtype, conntrack, statistic, recent) implementiert, erreicht ein unverändertes rancher/k3s-Image innerhalb eines einzigen rungv-Prozesses den Status Ready:

 1mkdir -p ~/.gvx/k3s
 2
 3rungv run \
 4  -e IPTABLES_MODE=legacy \
 5  -p 127.0.0.1:6443:6443 \
 6  -p 127.0.0.1:30000-30019:30000-30019 \
 7  -v k3s:/var/lib/rancher \
 8  --mount type=bind,src=$HOME/.gvx/k3s,dst=/output \
 9  rancher/k3s:v1.31.4-k3s1 server \
10  --disable=traefik,servicelb \
11  --write-kubeconfig=/output/kubeconfig.yaml \
12  --write-kubeconfig-mode=644

Sie können außerdem mehrere gvx-Knoten über WireGuard verbinden (flannel-backend=wireguard-native oder ein wg0-Mesh) mit eingebettetem etcd für Hochverfügbarkeit oder Calico (calico-vxlan.yaml mit FELIX_IPTABLESBACKEND=Legacy) ausführen, um Kubernetes-NetworkPolicy vollständig im Benutzerraum durchzusetzen.

3. Einbetten von Linux-Containern in Go über z.gfire.dev/gvx/pkg/gvx

Go-Anwendungen können Linux-Container direkt ohne externen Container-Daemon einbetten:

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"os"
 7
 8	"z.gfire.dev/gvx/pkg/gvx"
 9)
10
11func main() {
12	ctx := context.Background()
13	sb, err := gvx.New(ctx, gvx.Config{
14		Platform:      gvx.PlatformAuto,
15		NetworkMode:   gvx.NetModeNAT,
16		OCIImage:      "alpine:3.20",
17		WorkspacePath: "./repo",
18		Argv:          []string{"/usr/bin/git", "status"},
19		Stdout:        os.Stdout,
20		Stderr:        os.Stderr,
21	})
22	if err != nil {
23		fmt.Fprintf(os.Stderr, "init sandbox: %v\n", err)
24		os.Exit(1)
25	}
26	defer sb.Close()
27
28	exitCode, err := sb.Run(ctx)
29	if err != nil {
30		fmt.Fprintf(os.Stderr, "run sandbox: %v\n", err)
31		os.Exit(1)
32	}
33	os.Exit(exitCode)
34}

Weiterführende Literatur in dieser Serie

  1. gvx: Ausführen von Linux-Containern, Docker und Kubernetes in einem einzigen unprivilegierten Prozess (dieser Beitrag)
  2. Ausführen von Docker, docker build und docker compose ohne Root oder User Namespaces: Wie Sie docker:dind in abgesicherten Sandboxes booten, in denen sudo, unshare(CLONE_NEWUSER), mount(2) und /dev/fuse blockiert sind.
  3. Ausführen von Docker-in-Docker innerhalb von gVisor und verschachtelten Sandboxes mit gvx: Warum apt-get install docker.io und verschachteltes runsc innerhalb von gVisor an fünf Kernel-Barrieren scheitern und wie gvx verschachteltes docker:dind über eine Futex-Schleife mit gemeinsamem Speicher ausführt.
  4. Schnelle isolierte Docker-Umgebungen für RL-Rollouts von KI-Agenten mit gvx: Skalierung von SWE-bench und Reinforcement-Learning-Rollouts von Agenten mit Hunderten paralleler unprivilegierter docker:dind-Instanzen und 100-Millisekunden-Copy-on-Write-.ext4-Volume-Resets.