Menjalankan Docker-in-Docker di Dalam gVisor dan Nested Sandboxes dengan gvx

Solusi Cepat: Menjalankan docker:dind secara Aman di dalam Sandbox gVisor (runsc) yang Tidak Memiliki Hak Istimewa
Lingkungan komputasi agen AI berbasis cloud (seperti sandbox Google Agent Compute Engine), deployment agent-sandbox pada Kubernetes (RuntimeClass: gvisor), dan harness evaluasi reinforcement learning mengisolasi beban kerja yang tidak tepercaya di dalam container gVisor (runsc) secara khusus untuk melindungi kernel host. Ketika sebuah beban kerja di dalam container runsc tanpa hak istimewa default mencoba memasang docker.io atau docker-ce melalui apt-get, menjalankan service docker start atau dockerd, serta menjalankan docker build, proses tersebut gagal meskipun memiliki root (UID 0) di dalam container.
Dengan gvx (rungv), Anda dapat secara aman menjalankan docker:dind, docker build, dan docker compose langsung di dalam container gVisor (runsc) tanpa hak istimewa (atau menyarangkan gvx di dalam gvx) tanpa melemahkan RuntimeClass luar, tanpa mengekspos socket host, dan tanpa memberikan --privileged.
Solusi Hemat Usaha dan Efisien Token untuk Agen AI: Di dalam lingkungan agen cloud yang didukung gVisor, upaya memasang
docker.iodan menyiasati secara manual errorulimitpada/etc/init.d/docker, kegagalaniptables-nft, penolakan raw socket olehiptables-legacy, error mountoverlay2, serta crashrunc/pivot_rootpadadocker buildumumnya menghabiskan lebih dari 20 giliran pemanggilan alat dan puluhan ribu token sebelum akhirnya gagal total.gvx(rungv) merupakan solusi pengganti langsung yang hemat usaha dan efisien token: agen cukup menjalankan 3 perintah dalam satu giliran untuk memulai daemon Docker berbasis user-space yang berfungsi, lalu langsung mengeksekusidocker builddandocker composetanpa mengubah berkas proyek.
Mempertahankan dan Memperkuat Isolasi Sandbox Luar: Menjalankan
rungvdi dalamrunsctidak melewati satu pun pembatasan yang diterapkan oleh sandboxgVisorluar. Sebaliknya,gvxmembuat kernelSentrygVisor user-space kedua yang tersarang, denganhostlockdownsendiri (PR_SET_NO_NEW_PRIVS+seccomp),netstackberbasis memori sendiri, dan volumeext4liteberupa satu berkas.dockerddan containerruncinternal berada dalam sandbox ganda: panggilan sistem, mount, dan paket mentah mereka tidak pernah keluar dari kernel user-spacegvxinternal.
1# Di dalam container gVisor (runsc) atau Pod Kubernetes yang sudah ada:
2# Pasang rungv dari arsip rilis GitHub yang telah diattestasi SLSA atau melalui go install:
3CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
4
5mkdir -p ~/.gvx
6rungv run \
7 -p unix:~/.gvx/docker.sock:2375 \
8 -v gvx-docker:/var/lib/docker \
9 -e DOCKER_TLS_CERTDIR= \
10 docker:dind --tls=false &
11
12# Jalankan build dan container Docker standar melalui socket gvx internal
13export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
14until docker info >/dev/null 2>&1; do sleep 0.5; done
15docker info --format '{{.Driver}} {{.OperatingSystem}}'
16# Keluaran: overlayfs Alpine Linux ... (containerized)
17
18docker build -t nested-app:test .
19docker run --rm nested-app:test
Lima Alasan Mengapa Instalasi Docker Secara Langsung Gagal di Dalam gVisor (runsc)
Ketika seorang pengguna atau agen pengodean AI menjalankan apt-get install -y docker.io (atau docker-ce) di dalam container runsc standar dan mencoba memulai dockerd, proses tersebut menghadapi lima hambatan teknis yang berurutan.
1. Kegagalan Skrip Init (systemctl / service docker start ulimit) dan Modul Kernel (modprobe 4.4.0)
Setelah menjalankan apt-get install -y docker.io (atau docker-ce), upaya memulai daemon melalui systemctl start docker atau service docker start langsung gagal karena PID 1 bukan systemd dan /etc/init.d/docker berusaha menaikkan ulimit -n melampaui batas keras RLIMIT_NOFILE pada sandbox. Ketika dockerd kemudian dipanggil secara langsung, skrip inisialisasi memanggil modprobe untuk memuat overlay, bridge, br_netfilter, ip_tables, dan nf_nat terhadap versi kernel virtual gVisor (4.4.0), yang tidak memiliki direktori /lib/modules/4.4.0:
1System has not been booted with systemd as init system (PID 1). Can't operate.
2Failed to connect to bus: Host is down
3/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)
4level=warning msg="Running modprobe bridge br_netfilter failed with message: modprobe: WARNING: Module bridge not found in directory /lib/modules/4.4.0\nmodprobe: WARNING: Module br_netfilter not found in directory /lib/modules/4.4.0\n, error: exit status 1"
5modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
6modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
7modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
8modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0
2. iptables-nft (Netlink nf_tables), Penolakan Raw Socket iptables-legacy, dan Kegagalan Bridge docker0
Pada Ubuntu 22.04, Ubuntu 24.04, dan Debian 11/12, apt-get install docker.io memilih iptables-nft (didukung oleh subsistem netlink nf_tables) sebagai alternatif /usr/sbin/iptables default. Selain itu, bahkan jika pengguna atau agen beralih ke iptables-legacy (update-alternatives --set iptables /usr/sbin/iptables-legacy), sandbox gVisor tanpa hak istimewa default (--net-raw=false) menolak socket AF_INET, SOCK_RAW dengan EPERM, sehingga iptables-legacy gagal dengan Permission denied (you must be root) meskipun dijalankan sebagai UID 0:
1failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to register "bridge" driver: failed to create NAT chain DOCKER: iptables failed: iptables --wait -t nat -N DOCKER: iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
2iptables v1.8.10 (legacy): can't initialize iptables table `nat': Permission denied (you must be root)
3Perhaps iptables or your kernel needs to be upgraded.
4iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
5iptables: No chain/target/match by that name.
6Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i docker0 -j RETURN: iptables: No chain/target/match by that name.)
7Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted
3. Kegagalan overlay2 pada Rootfs gofer / 9p dan Kehabisan Disk pada vfs
Kecuali administrator runsc luar telah secara eksplisit mengonfigurasi mount tmpfs atau filestore khusus di /var/lib/docker, rootfs container didukung oleh gofer gVisor (9p/lisafs) atau mount overlay tersarang. Mount overlay2 di atas /var/lib/docker gagal:
1level=error msg="[graphdriver] prior storage driver overlay2 failed: driver not supported"
2failed to mount overlay: invalid argument
3Error starting daemon: error initializing graphdriver: driver not supported: overlay2
Ketika pengguna atau agen mencoba menyiasati error ini dengan menjalankan dockerd --storage-driver=vfs, Docker sepenuhnya menonaktifkan pembagian layer Copy-on-Write. Setiap instruksi RUN dalam Dockerfile melakukan penyalinan rekursif penuh (dirCopy) terhadap seluruh filesystem root, sehingga image berukuran 500 MB yang dikalikan dengan 10 langkah build menjadi 5 GB penulisan dan menghentikan sandbox dengan no space left on device atau I/O timeout.
4. Kegagalan docker build (BuildKit) dan runc Bersarang (pivot_root, /proc, Cgroups, dan DNS)
Bahkan jika dockerd dipaksa berjalan dengan dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs, upaya menjalankan docker build -t app . atau docker run tetap gagal segera ketika BuildKit atau runc membuat container build:
1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process:
3error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
4error during container init: error mounting "cgroup" to rootfs at "/sys/fs/cgroup": mount cgroup:/sys/fs/cgroup/cpuset (via /proc/self/fd/6), flags: 0xf, data: cpuset: operation not permitted
5cgroups: cgroup mountpoint does not exist: unknown
6runc create failed: systemd not running on this host: cannot use systemd cgroup manager
7error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
8error during container init: error loading seccomp filter into kernel: operation not permitted
9Temporary failure resolving 'deb.debian.org'
10Temporary failure resolving 'archive.ubuntu.com'
5. runsc Bersarang Gagal dengan SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY)
Jika pengguna mencoba menjalankan instance runsc lain di dalam runsc menggunakan platform systrap, proses inisialisasi langsung dibatalkan:
1creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
Baik kernel Linux maupun Sentry gVisor hanya mengizinkan paling banyak satu listener SECCOMP_FILTER_FLAG_NEW_LISTENER (SECCOMP_RET_USER_NOTIF) per hierarki task. Karena sandbox luar sudah memegang slot listener tersebut, pemanggilan seccomp(2) di dalamnya mengembalikan EBUSY (device or resource busy).
Cara gvx Menyelesaikan Eksekusi Bersarang di dalam gVisor
gvx mengatasi kelima hambatan tersebut di dalam kernel user-space dan lapisan platform miliknya sendiri, sehingga rungv dapat berjalan di dalam container gVisor (runsc) luar atau bahkan secara rekursif di dalam container gvx lain (gvx dalam gvx).

1. Fallback Otomatis systrap dari EBUSY ke Loop Futex _RUN_SYSCALL_LOOP
Pada systrap Linux, setiap subproses stub menjalankan dua jenis thread:
- Thread eksekusi guest (
sysmsg): Menangkap panggilan sistem guest melaluiSECCOMP_RET_TRAP(SIGSYS) dan trampolinjmp rel32hasil binary patching (syshandler), serta bertukar state register dengan Sentry melalui memori bersama danSYS_FUTEX. - Sebuah
syscallThreadstub (syscall_thread.go): Mengeksekusi panggilan sistem ruang alamat (mmap,munmap,clone) di dalam subproses stub atas nama Sentry.
Pada gVisor upstream, syscallThread mendukung dua mode pembangkitan (wake-up) di stub_amd64.s dan stub_arm64.s:
- Seccomp User-Notify Loop (
_RUN_SECCOMP_LOOP = 6, ketikat.seccompNotify != nil): SelamasyscallThread.init(),t.installSeccompNotify()memasang listenerSECCOMP_RET_USER_NOTIF(seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)) agar Sentry dapat mengirim permintaan melaluiioctl(SECCOMP_IOCTL_NOTIF_SEND)danioctl(SECCOMP_IOCTL_NOTIF_RECV). - Futex Syscall Loop (
_RUN_SYSCALL_LOOP = 5, ketikat.seccompNotify == nil):syscallThreadmenjalankansyscall_loop(stub_amd64.s/stub_arm64.s), tidur padaSYS_FUTEX(FUTEX_WAIT) di atas regionsyscallSentryMessage(PROT_READdi sisi stub) dansyscallStubMessage(PROT_READ | PROT_WRITEdi sisi stub) berukuran 2 halaman yang dibagikan.
Gvisor upstream selalu memanggil t.installSeccompNotify() setiap kali seccompNotifyIsSupported bernilai true, dan membatalkan pembuatan stub jika seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...) mengembalikan EBUSY.
Di gvx (pkg/sentry/platform/systrap/seccomp_notify_xplat.go dan syscall_thread.go), syscallThread.init() memanggil t.installSeccompNotifyIfFree():
1// pkg/sentry/platform/systrap/seccomp_notify_xplat.go
2var seccompNotifyBusy atomic.Bool
3
4func (t *syscallThread) installSeccompNotifyIfFree() (*os.File, error) {
5 if seccompNotifyBusy.Load() {
6 return nil, nil
7 }
8 f, err := t.installSeccompNotify()
9 if errors.Is(err, unix.EBUSY) {
10 if seccompNotifyBusy.CompareAndSwap(false, true) {
11 log.Warningf("Another seccomp listener exists in the filter chain of the host process. " +
12 "The stub threads use the slower futex loop instead of seccomp-unotify.")
13 }
14 return nil, nil
15 }
16 return f, err
17}
Ketika installSeccompNotify() mengembalikan unix.EBUSY karena sandbox gVisor (runsc) atau gvx luar sudah memegang slot listener tunggal pada rantai filter proses:
installSeccompNotifyIfFree()menetapkanseccompNotifyBusy(atomic.Bool), mencatat satu peringatan untuk proses tersebut, dan membiarkant.seccompNotify == niluntuk stub tersebut maupun semua stub berikutnya.- Dalam
syscallThread.detach()(syscall_thread_amd64.godansyscall_thread_arm64.go),t.seccompNotify == nilmenetapkanregs.Rbx = _RUN_SYSCALL_LOOP(5) padaamd64(atauregs.Regs[9] = _RUN_SYSCALL_LOOPpadaarm64) alih-alih_RUN_SECCOMP_LOOP(6). - Dalam
syscallThread.syscall()(syscall_thread.go), Sentry internal mengirimkan panggilan sistem stub hanya melalui memori bersama danSYS_FUTEX:
1// pkg/sentry/platform/systrap/syscall_thread.go
2// Beri tahu thread syscall tentang permintaan syscall baru.
3atomic.AddUint32(&sentryMsg.state, 1)
4futexWakeUint32(&sentryMsg.state)
5
6// Tunggu balasan.
7futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
Karena filter SECCOMP_RET_TRAP pada thread guest dapat ditumpuk hingga kedalaman yang sewenang-wenang dan syscallThread kembali ke _RUN_SYSCALL_LOOP tanpa meminta listener notifikasi pengguna kedua, gvx berjalan dengan bersih di dalam runsc dan mendukung penyarangan rekursif (gvx di dalam gvx).
2. overlayfs Native pada Volume ext4lite Berbasis Satu Berkas
Untuk menghindari pemasangan overlayfs di atas filesystem root gofer (9p/lisafs) milik container gVisor luar, -v gvx-docker:/var/lib/docker mendukung /var/lib/docker dengan filesystem internal ext4lite milik gvx yang disimpan dalam satu berkas reguler (gvx-docker.ext4).
Bagi runsc luar, gvx-docker.ext4 hanyalah satu berkas reguler yang diakses melalui pread64(2) dan pwrite64(2). Di dalam gvx, ext4lite menyediakan filesystem ext4 dengan jurnal JBD2, atribut extended trusted.overlay.* dan security.capability, node whiteout character device (0:0), entri direktori d_type, serta RENAME_EXCHANGE, sehingga dockerd internal dapat menggunakan driver penyimpanan overlayfs native miliknya.
3. netstack Mandiri dengan docker0 dan iptables-legacy
Di dalam gvx, dockerd tidak berinteraksi dengan antarmuka jaringan atau tabel netfilter milik container runsc luar. Sebaliknya:
dockerdmembuat bridgedocker0dan pasanganvethdi dalamnetstackuser-space milikgvx.- Seluruh aturan
iptables-legacy(-t nat -N DOCKER,MASQUERADE,conntrack,addrtype) dieksekusi terhadap implementasi netfilter internal milikgvx. - Container
runcbersarang memasang hierarki/proc,/sys, dan/sys/fs/cgroupmereka di dalam tabel mountVFS2milikgvxyang berbasis memori, tanpa pernah memanggil syscallmount(2)milikrunscluar.
Memverifikasi docker:dind Bersarang di dalam gVisor (atau gvx dalam gvx)
Anda dapat memverifikasi kedua skenario (rungv di dalam runsc, atau rungv di dalam rungv) dengan perintah berikut:
1# Di dalam container gVisor (runsc) luar atau container rungv luar:
2mkdir -p ~/.gvx
3rungv run \
4 -p unix:~/.gvx/docker.sock:2375 \
5 -v gvx-docker:/var/lib/docker \
6 -e DOCKER_TLS_CERTDIR= \
7 docker:dind --tls=false &
8DIND_PID=$!
9
10export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
11until docker info >/dev/null 2>&1; do sleep 0.5; done
12
13# Verifikasi bahwa daemon Docker internal menggunakan overlayfs native (bukan vfs)
14docker info --format 'Storage Driver: {{.Driver}}'
15# Keluaran: Storage Driver: overlayfs
16
17# Jalankan build multi-layer dan eksekusi container bersarang
18mkdir -p /tmp/nested-test && cd /tmp/nested-test
19cat <<'EOF' > Dockerfile
20FROM alpine:3.20
21RUN adduser -D -u 1001 appuser && mkdir -p /app && chown appuser:appuser /app
22USER appuser
23WORKDIR /app
24RUN echo "nested-build-ok" > status.txt
25CMD ["cat", "/app/status.txt"]
26EOF
27
28docker build -t nested-test:latest .
29docker run --rm nested-test:latest
30# Keluaran: nested-build-ok
31
32kill -INT $DIND_PID
Bacaan Lanjutan dalam Seri Ini
- gvx: Menjalankan Linux Container, Docker, dan Kubernetes dalam Satu Proses Tanpa Hak Istimewa: Ikhtisar
gvxdi Windows (whpx,systrap), macOS (hvf), dan Linux (systrap). - Menjalankan Docker, docker build, dan docker compose Tanpa Root atau User Namespace: Cara
gvxmenjalankandocker:dindketikaroot,sudo,unshare(CLONE_NEWUSER),mount(2), dan/dev/fusediblokir. - Menjalankan Docker-in-Docker di dalam gVisor dan Sandbox Bersarang dengan gvx (artikel ini)
- Lingkungan Docker Terisolasi yang Cepat untuk Rollout RL Agen AI dengan gvx: Cara menggunakan
gvxdan snapshot copy-on-writeext4liteberbasis satu berkas untuk menjalankan ratusan lingkungan paralelSWE-benchdan rollout RL dengan reset 100 milidetik.