---
id: 83fb2c108243a7df80db3a6e78290daf
author: Lemon Mint
title: Spúšťanie Docker-in-Docker vnútri gVisor a Nested Sandboxes s gvx
description: Prečo inštalácia docker.io alebo vnorenie runsc do kontajnera gVisor zlyháva pri modprobe, iptables-nft, overlay2, cgroups a seccomp EBUSY, a ako gvx bezpečne spúšťa vnorený docker:dind vo vnútri izolovaného jadra v user-space bez --privileged.
language: sk
date: 2026-11-06T12:02:55.931762327Z
path: /blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: lowlevel
---

## Rýchle riešenie: Bezpečné spúšťanie `docker:dind` vnútri neprivilegovaného sandboxu `gVisor` (`runsc`)

Cloudové platformy pre AI agentov, nasadenia `agent-sandbox` v Kubernetes (`RuntimeClass: gvisor`) a evaluačné rámce pre reinforcement learning izolujú nedôveryhodné úlohy v kontajneroch **gVisor (`runsc`)**, konkrétne na ochranu jadra hostiteľa. Keď sa úloha v predvolenom neprivilegovanom kontajneri `runsc` pokúsi nainštalovať `docker.io` alebo `docker-ce` pomocou `apt-get` a spustiť `dockerd`, zlyhá aj vtedy, ak má v kontajneri oprávnenia `root` (`UID 0`).

Pomocou **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`) môžete **bezpečne** spúšťať `docker:dind`, `docker build` a `docker compose` priamo vnútri neprivilegovaného kontajnera `gVisor` (`runsc`) (alebo vnoriť `gvx` do `gvx`) **bez oslabenia vonkajšej `RuntimeClass`, bez sprístupnenia hostiteľských socketov a bez udelenia `--privileged`**.

> **Zachovanie a posilnenie izolácie vonkajšieho sandboxu**: Spustenie `rungv` vnútri `runsc` neobchádza ani jedno obmedzenie vynucované vonkajším sandboxom `gVisor`. Namiesto toho `gvx` vytvorí druhé, vnorené jadro `gVisor Sentry` v používateľskom priestore s vlastným `hostlockdown` (`PR_SET_NO_NEW_PRIVS` + `seccomp`), vlastným `netstack` v pamäti a jedným súborovým zväzkom `ext4lite`. Vnútorné kontajnery `dockerd` a `runc` sú dvojito sandboxované: ich systémové volania, pripojenia a surové pakety nikdy neopustia vnútorné jadro `gvx` v používateľskom priestore.

```sh
# Vnútri existujúceho kontajnera gVisor (runsc) alebo Kubernetes Pod:
# Nainštalujte rungv z archívu vydania na GitHube s SLSA atestáciou alebo pomocou go install:
CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest

mkdir -p ~/.gvx
rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &

# Spúšťajte štandardné Docker buildy a kontajnery voči vnútornému socketu gvx
export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done
docker info --format '{{.Driver}} {{.OperatingSystem}}'
# Výstup: overlayfs Alpine Linux ... (v kontajneri)

docker build -t nested-app:test .
docker run --rm nested-app:test
```

---

## Päť dôvodov, prečo priama inštalácia Dockeru zlyhá vnútri `gVisor` (`runsc`)

Keď používateľ alebo AI agent pre programovanie spustí `apt-get install -y docker.io` (alebo `docker-ce`) vnútri štandardného kontajnera `runsc` a pokúsi sa spustiť `dockerd`, narazí na päť za sebou nasledujúcich technických prekážok.

### 1. Načítanie kernel modulov (`modprobe`) voči virtuálnemu jadru `4.4.0`

Počas inštalácie balíka a spúšťania `dockerd` inicializačné skripty spúšťajú `modprobe` na načítanie modulov `overlay`, `br_netfilter`, `ip_tables`, `nf_nat` a `xt_conntrack`. Vonkajší Sentry gVisor blokuje `init_module(2)` a `finit_module(2)` a hlási virtuálnu verziu jadra (`4.4.0`), pre ktorú neexistuje adresár `/lib/modules/4.4.0`:

```text
modprobe: FATAL: Module overlay not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module br_netfilter not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module ip_tables not found in directory /lib/modules/4.4.0
modprobe: FATAL: Module nf_nat not found in directory /lib/modules/4.4.0
```

### 2. Zlyhania `iptables-nft` (netlink `nf_tables`) a mostu `docker0`

Na Ubuntu 22.04, Ubuntu 24.04 a Debian 11/12 príkaz `apt-get install docker.io` vyberie `iptables-nft` (založený na netlink podsystéme `nf_tables`) ako predvolenú alternatívu `/usr/sbin/iptables`. V štandardnom kontajneri gVisor bez sieťových administrátorských oprávnení alebo podpory netlink `nf_tables` `dockerd` zlyhá pri inicializácii reťazca NAT `DOCKER` a mostu `docker0`:

```text
failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER
iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Invalid argument
iptables v1.8.10 (legacy): can't initialize iptables table 'nat': Table does not exist (do you need to insmod?)
iptables: No chain/target/match by that name.
Unable 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.)
Error initializing network controller: error obtaining controller instance: failed to create bridge "docker0": operation not permitted
```

### 3. Zlyhanie `overlay2` na rootfs `gofer` / `9p` a vyčerpanie disku pri `vfs`

Pokiaľ vonkajší administrátor `runsc` výslovne nepredkonfiguroval vyhradené pripojenie `tmpfs` alebo filestore na `/var/lib/docker`, koreňový súborový systém kontajnera je podložený `gofer` (`9p`/`lisafs`) od gVisor alebo vnoreným overlay pripojením. Pripojenie `overlay2` na `/var/lib/docker` zlyhá:

```text
level=error msg="[graphdriver] prior storage driver overlay2 failed: driver not supported"
failed to mount overlay: invalid argument
Error starting daemon: error initializing graphdriver: driver not supported: overlay2
```

Keď sa používatelia alebo agenti pokúsia túto chybu obísť spustením `dockerd --storage-driver=vfs`, Docker úplne vypne zdieľanie vrstiev Copy-on-Write. Každá inštrukcia `RUN` v `Dockerfile` vykoná úplnú rekurzívnu kópiu (`dirCopy`) celého koreňového súborového systému, takže obraz s veľkosťou 500 MB v 10 krokoch zostavenia vytvorí 5 GB zápisov a ukončí sandbox chybou `no space left on device` alebo časovým limitom I/O.

### 4. Odmietnutie vnorených cgroup a pripojení `/proc` v `runc`

Aj keď je `dockerd` vynútene spustený s `dockerd --iptables=false --bridge=none --storage-driver=vfs`, spustenie `docker run` alebo `docker build` vyvolá `runc`, ktorý vytvorí vnútorný kontajner. V predvolenom neprivilegovanom kontajneri gVisor `runc` zlyhá pri pripájaní cgroup, opätovnom pripájaní `/proc` alebo načítaní vnútorných filtrov seccomp:

```text
docker: 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:
error 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
cgroups: cgroup mountpoint does not exist: unknown
runc create failed: systemd not running on this host: cannot use systemd cgroup manager
error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
error during container init: error loading seccomp filter into kernel: operation not permitted
```

### 5. Vnorený `runsc` zlyhá s `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`EBUSY`)

Ak sa používateľ pokúsi spustiť ďalšiu inštanciu `runsc` vnútri `runsc` s platformou `systrap`, inicializácia sa okamžite preruší:

```text
creating subprocess: sysmsg: failed to initialize thread: failed to install seccomp notify rules: device or resource busy
```

Linuxové jadro aj Sentry gVisor povoľujú **najviac jeden** listener `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`SECCOMP_RET_USER_NOTIF`) na hierarchiu úloh. Keďže vonkajší sandbox už drží tento slot listenera, vnútorné volanie `seccomp(2)` vráti `EBUSY` (`device or resource busy`).

---

## Ako `gvx` rieši vnorené spúšťanie vnútri `gVisor`

`gvx` rieši všetkých päť prekážok vo vlastnom jadre v používateľskom priestore a vrstve platformy, čo umožňuje `rungv` bežať vnútri vonkajšieho kontajnera `gVisor` (`runsc`) alebo dokonca rekurzívne vnútri ďalšieho kontajnera `gvx` (`gvx` v `gvx`).

![Vnorené vykonávanie gvx vnútri vonkajšieho gVisor](/assets/images/gvx/gvx-nested-seccomp-futex.png)

### 1. Automatický záložný režim `systrap` z `EBUSY` na futex slučku `_RUN_SYSCALL_LOOP`

V Linux `systrap` každý podproces stub spúšťa dva druhy vlákien:

- **Vlákna vykonávania hosťa (`sysmsg`)**: zachytávajú systémové volania hosťa cez `SECCOMP_RET_TRAP` (`SIGSYS`) a binárne upravené skoky `jmp rel32` (`syshandler`), pričom si so Sentry vymieňajú stav registrov cez zdieľanú pamäť a `SYS_FUTEX`.
- **Stub `syscallThread` (`syscall_thread.go`)**: vykonáva systémové volania adresného priestoru (`mmap`, `munmap`, `clone`) vnútri podprocesu stub v mene Sentry.

V upstream gVisor `syscallThread` podporuje dva režimy prebudenia v `stub_amd64.s` a `stub_arm64.s`:

1. **Slučka Seccomp User-Notify (`_RUN_SECCOMP_LOOP = 6`, keď `t.seccompNotify != nil`)**: Počas `syscallThread.init()` volanie `t.installSeccompNotify()` nainštaluje listener `SECCOMP_RET_USER_NOTIF` (`seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, stubSyscallRules)`), aby Sentry mohla odosielať požiadavky cez `ioctl(SECCOMP_IOCTL_NOTIF_SEND)` a `ioctl(SECCOMP_IOCTL_NOTIF_RECV)`.
2. **Slučka systémových volaní Futex (`_RUN_SYSCALL_LOOP = 5`, keď `t.seccompNotify == nil`)**: `syscallThread` spúšťa `syscall_loop` (`stub_amd64.s` / `stub_arm64.s`) a spí na `SYS_FUTEX` (`FUTEX_WAIT`) nad zdieľanou oblasťou dvoch stránok `syscallSentryMessage` (`PROT_READ` v stub) a `syscallStubMessage` (`PROT_READ | PROT_WRITE` v stub).

Upstream gVisor bezpodmienečne volá `t.installSeccompNotify()` vždy, keď je `seccompNotifyIsSupported` pravdivé, a zruší vytvorenie stubu, ak `seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, ...)` vráti `EBUSY`.

V `gvx` (`pkg/sentry/platform/systrap/seccomp_notify_xplat.go` a `syscall_thread.go`) volá `syscallThread.init()` funkciu `t.installSeccompNotifyIfFree()`:

```go
// pkg/sentry/platform/systrap/seccomp_notify_xplat.go
var seccompNotifyBusy atomic.Bool

func (t *syscallThread) installSeccompNotifyIfFree() (*os.File, error) {
	if seccompNotifyBusy.Load() {
		return nil, nil
	}
	f, err := t.installSeccompNotify()
	if errors.Is(err, unix.EBUSY) {
		if seccompNotifyBusy.CompareAndSwap(false, true) {
			log.Warningf("Another seccomp listener exists in the filter chain of the host process. " +
				"The stub threads use the slower futex loop instead of seccomp-unotify.")
		}
		return nil, nil
	}
	return f, err
}
```

Keď `installSeccompNotify()` vráti `unix.EBUSY`, pretože vonkajší `gVisor` (`runsc`) alebo vonkajší sandbox `gvx` už drží jediný slot listenera v reťazci filtrov procesu:

- `installSeccompNotifyIfFree()` nastaví `seccompNotifyBusy` (`atomic.Bool`), zaznamená jedno varovanie pre proces a ponechá `t.seccompNotify == nil` pre tento stub a všetky nasledujúce stuby.
- V `syscallThread.detach()` (`syscall_thread_amd64.go` a `syscall_thread_arm64.go`) podmienka `t.seccompNotify == nil` nastaví `regs.Rbx = _RUN_SYSCALL_LOOP` (`5`) na `amd64` (alebo `regs.Regs[9] = _RUN_SYSCALL_LOOP` na `arm64`) namiesto `_RUN_SECCOMP_LOOP` (`6`).
- V `syscallThread.syscall()` (`syscall_thread.go`) vnútorný Sentry odosiela systémové volania stubu výhradne cez zdieľanú pamäť a `SYS_FUTEX`:

```go
// pkg/sentry/platform/systrap/syscall_thread.go
// Oznámte vláknu syscall stubu novú požiadavku na systémové volanie.
atomic.AddUint32(&sentryMsg.state, 1)
futexWakeUint32(&sentryMsg.state)

// Počkajte na odpoveď.
futexWaitWake(&sentryMsg.state, atomic.LoadUint32(&sentryMsg.state))
```

Keďže filtre `SECCOMP_RET_TRAP` na vláknach hosťa sa vrstvia ľubovoľne hlboko a `syscallThread` sa vracia k `_RUN_SYSCALL_LOOP` bez žiadania druhého používateľského listenera notifikácií, `gvx` beží čisto vnútri `runsc` a podporuje rekurzívne vnorenie (`gvx` vnútri `gvx`).

### 2. Natívny `overlayfs` na jednosúborových zväzkoch `ext4lite`

Aby sa zabránilo pripojeniu `overlayfs` na koreňový súborový systém `gofer` (`9p`/`lisafs`) vonkajšieho kontajnera gVisor, `-v gvx-docker:/var/lib/docker` podkladá `/var/lib/docker` interným súborovým systémom **`ext4lite`** od `gvx`, uloženým v jedinom bežnom súbore (`gvx-docker.ext4`).

Pre vonkajší `runsc` je `gvx-docker.ext4` len jeden bežný súbor, ku ktorému sa pristupuje cez `pread64(2)` a `pwrite64(2)`. Vnútri `gvx` poskytuje `ext4lite` súborový systém `ext4` s žurnálom JBD2, s rozšírenými atribútmi `trusted.overlay.*` a `security.capability`, uzlami whiteout znakových zariadení (`0:0`), položkami adresárov `d_type` a `RENAME_EXCHANGE`, čo umožňuje vnútornému `dockerd` použiť natívny ovládač úložiska `overlayfs`.

### 3. Samostatný `netstack` s `docker0` a `iptables-legacy`

Vnútri `gvx` `dockerd` neinteraguje so sieťovými rozhraniami vonkajšieho kontajnera `runsc` ani s tabuľkami netfilter. Namiesto toho:

- `dockerd` vytvára svoj most `docker0` a páry `veth` vnútri vlastného `netstack` od `gvx` v používateľskom priestore.
- Všetky pravidlá `iptables-legacy` (`-t nat -N DOCKER`, `MASQUERADE`, `conntrack`, `addrtype`) sa vykonávajú voči internej implementácii netfilter od `gvx`.
- Vnorené kontajnery `runc` pripájajú svoje hierarchie `/proc`, `/sys` a `/sys/fs/cgroup` vnútri pamäťovej tabuľky pripojení `VFS2` od `gvx` a nikdy nevolajú systémové volanie `mount(2)` vonkajšieho `runsc`.

---

## Overenie vnoreného `docker:dind` vnútri `gVisor` (alebo `gvx` v `gvx`)

Obidva scenáre (`rungv` vnútri `runsc` alebo `rungv` vnútri `rungv`) môžete overiť pomocou nasledujúcich príkazov:

```sh
# Vnútri vonkajšieho kontajnera gVisor (runsc) alebo vonkajšieho kontajnera rungv:
mkdir -p ~/.gvx
rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &
DIND_PID=$!

export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
until docker info >/dev/null 2>&1; do sleep 0.5; done

# Overte, že vnútorný Docker daemon používa natívny overlayfs (nie vfs)
docker info --format 'Storage Driver: {{.Driver}}'
# Výstup: Storage Driver: overlayfs

# Spustite viacvrstvový build a vykonajte vnorený kontajner
mkdir -p /tmp/nested-test && cd /tmp/nested-test
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN adduser -D -u 1001 appuser && mkdir -p /app && chown appuser:appuser /app
USER appuser
WORKDIR /app
RUN echo "nested-build-ok" > status.txt
CMD ["cat", "/app/status.txt"]
EOF

docker build -t nested-test:latest .
docker run --rm nested-test:latest
# Výstup: nested-build-ok

kill -INT $DIND_PID
```

---

## Ďalšie čítanie v tejto sérii

1. **[gvx: Spúšťanie Linux kontajnerov, Dockeru a Kubernetes v jednom neprivilegovanom procese](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Prehľad `gvx` naprieč systémami Windows (`whpx`, `systrap`), macOS (`hvf`) a Linux (`systrap`).
2. **[Spúšťanie Dockeru, docker build a docker compose bez root oprávnení alebo používateľských namespace](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: Ako `gvx` spúšťa `docker:dind`, keď sú zablokované `root`, `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` a `/dev/fuse`.
3. **[Docker-in-Docker vnútri gVisor a vnorených sandboxov s gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)** (tento príspevok)
4. **[Rýchle izolované Docker prostredia pre RL rollouty AI agentov s gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: Ako použiť `gvx` a jednosúborové copy-on-write snapshoty `ext4lite` na spustenie stoviek paralelných prostredí SWE-bench a RL rolloutov s resetmi za 100 milisekúnd.
