Изпълнение на Docker, docker build и docker compose без root права или user namespaces

Бързо решение: Сигурен непривилегирован Docker в три стъпки
Ако работите в заключен Linux контейнер, CI runner, Kubernetes Pod или среда за AI агент, където root, sudo, user namespaces (unshare(CLONE_NEWUSER)), mount(2) и /dev/fuse са блокирани, gvx (rungv) е сигурното решение с дълбока защита (defense-in-depth) за стартиране на пълен docker:dind engine в потребителското пространство.
Защо
gvxе безопасен по дизайн (не е заобиколно решение за ескалация на привилегии): За разлика от монтирането на хостовия/var/run/docker.sockили изпълнението наdocker run --privileged(които дават контрол, еквивалентен на root, върху ядрото на хоста),gvxне заобикаля и не отслабва никоя политика за сигурност на хоста (PR_SET_NO_NEW_PRIVS,seccomp, AppArmor или ограниченията за non-root UID). Вместо товаrungvзаключва собствения си хост процес (PR_SET_NO_NEW_PRIVS+ строг BPFseccompallowlist) и стартираdockerdв рамките на безопасното по отношение на паметта gVisor (Sentry) потребителско ядро на Google. Всички възможности на гостUID 0, монтираниятаoverlayfs(ext4lite) и правилата наiptablesостават строго изолирани в рамките на sandbox процеса.
1# 1. Инсталиране на rungv в потребителското пространство (предварително компилирано SLSA-атестирано издание или go install)
2# Опция A: Проверка и извличане на предварително компилирания SLSA-атестиран двоичен файл от 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# Опция Б: Или инсталиране директно от изходния код с Go
9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Стартиране на docker:dind в gvx с единичен ext4lite volume във файл
12# Препраща ~/.gvx/docker.sock (режим 0600) към порт 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. Насочване на стандартния Docker CLI и Docker Compose към потребителския 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# Изход: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d
Защо стандартните rootless инструменти се провалят в ограничени среди
Когато инженер или автономен агент за писане на код има нужда да изпълни docker build, docker run или docker compose в укрепен контейнер или многопотребителски runner, стандартните заобиколни решения се провалят на пет отделни бариери на ниво ядро.
1. Няма сокет на хостовия Docker демон
Изпълнението на docker ps или docker build се проваля незабавно, когато хостовият /var/run/docker.sock не е монтиран в средата:
1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2. PR_SET_NO_NEW_PRIVS блокира sudo и setuid помощните програми
Укрепените контейнери (securityContext.allowPrivilegeEscalation: false в Kubernetes, --security-opt=no-new-privileges в Docker, или exebox / bwrap sandboxes) задават битовете Linux PR_SET_NO_NEW_PRIVS в дървото на процесите. Опитът за ескалация чрез sudo или setuid двоични файлове (newuidmap, newgidmap, fusermount3) се проваля:
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 и sysctl блокират unshare(CLONE_NEWUSER)
Инструментите, рекламирани като "rootless" (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap), все още разчитат на Linux user namespaces (CLONE_NEWUSER). Те се провалят, когато:
- Профилът по подразбиране на seccomp на външния контейнер блокира
unshare(2)илиclone(CLONE_NEWUSER). - Ubuntu 24.04+ активира
kernel.apparmor_restrict_unprivileged_userns = 1. - Debian или RHEL възли задават
kernel.unprivileged_userns_clone = 0илиuser.max_user_namespaces = 0. newuidmapне може да записва в/proc/self/uid_map, защотоPR_SET_NO_NEW_PRIVSе активен.
Получените грешки изглеждат така:
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) е блокиран без CAP_SYS_ADMIN на хоста
Дори ако средата позволява CLONE_NEWUSER, монтирането на нов /proc във вложено mount namespace се проваля, ако външният контейнер е маскирал поддървета на /proc (/proc/acpi, /proc/kcore, /proc/keys) или е премахнал mount(2) от 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. Липсата на /dev/fuse нарушава fuse-overlayfs
Rootless контейнерните engines, които не могат да монтират ядрен overlayfs, преминават към fuse-overlayfs. В минимални контейнери или Kubernetes pods без проброс на устройството /dev/fuse, fuse-overlayfs се проваля незабавно:
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
Как gvx изпълнява docker:dind изцяло в потребителското пространство
gvx преодолява и петте бариери на ниво ядро, защото не изисква от ядрото на Linux на хоста да създава namespaces, да монтира файлови системи или да конфигурира мрежови интерфейси. Вместо това rungv стартира вътрешно потребителско Linux ядро (gVisor Sentry) и потребителски мрежов стек (netstack) в рамките на един обикновен непривилегирован процес.

1. Namespaces в паметта и таблица на монтиранията (VFS2)
Когато dockerd, containerd и runc се изпълняват в rungv, systrap прихваща техните системни извиквания и ги насочва към вътрешното ядро Sentry, а не към ядрото на хоста:
unshare(2),clone(2),pivot_root(2),chroot(2)иmount(2)работят върху йерархията на задачите в паметта наSentryи върху таблицата на монтираниятаVFS2(procfs,sysfs,tmpfs,devpts,cgroupfsv2), докато хост процесътrungvработи под вашия непривилегирован хост UID.
2. Нативен overlayfs върху ext4lite томове от единичен файл
Когато подадете -v gvx-docker:/var/lib/docker (или --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx монтира вградената си файлова система ext4lite в /var/lib/docker.
Тъй като ext4lite реализира ext4 файлова система с JBD2 журналиране в рамките на единствен файл на хоста (gvx-docker.ext4), с поддръжка на разширените атрибути trusted.overlay.*, символните whiteout устройства (0:0), d_type и RENAME_EXCHANGE, dockerd използва собствения си драйвер за съхранение overlayfs в госта. Многоетапните операции docker build споделят Copy-on-Write слоеве без /dev/fuse и без експлозията на дисковото пространство, характерна за резервния драйвер vfs на Docker.
3. Потребителски мост docker0 и iptables-legacy
Когато docker:dind стартира в gvx:
dockerdна госта създава мостаdocker0(172.17.0.1/16), свързва виртуалниvethдвойки за вложените контейнери и програмира веригитеNAT(DOCKER,POSTROUTING) иFILTERс мачовеaddrtypeиconntrackв потребителскияnetstackнаgvx.- Подаването на
-p unix:~/.gvx/docker.sock:2375свързва хостов Unix domain socket (режим0600) и препраща връзките директно към порт2375наdockerdв sandbox средата.
Сравнение с съществуващи инструменти за контейнери
| Възможност / Ограничение | Стандартен Docker (dockerd) | Rootless Docker / Podman | Kaniko / Buildah (непривилегирован) | gvx (rungv + docker:dind) |
|---|---|---|---|---|
Изисква хостов root или sudo | Да | Не | Не | Не |
Работи с PR_SET_NO_NEW_PRIVS=1 | Не | Не (newuidmap се проваля) | Частично | Да |
Работи, когато unshare(CLONE_NEWUSER) е блокиран (EPERM) | Не се прилага | Не | Buildah: Не / Kaniko: Да | Да |
Работи без /dev/fuse | Да (използва ядрен overlay2) | Само чрез бавен драйвер vfs | Да | Да (overlayfs върху ext4lite) |
Поддържа docker build (BuildKit) | Да | Да (ако userns е разрешен) | Само изграждане на образи (без runtime) | Да |
Поддържа docker run и docker compose up | Да | Да (ако userns е разрешен) | Не | Да |
| Работи на Windows и macOS без Linux VM | Не (изисква WSL2 / VM) | Не (изисква Linux VM) | Не | Да (whpx, hvf, systrap) |
Точни изисквания към ядрото на Linux хост
gvx не изисква никакви повишени възможности (CapEff: 0000000000000000) и никакви user namespaces. На Linux хост rungv разчита единствено на три стандартни непривилегировани примитива на ядрото:
ptrace(PTRACE_ATTACH)върху собствената му дъщерна нишка еднократно при инициализация:systrapстартира локална помощна нишка-стъб при старт, прикрепя се към нея, за да инициализира споделения контекст на паметта, и се отделя. Ако хостът зададеkernel.yama.ptrace_scope = 3(което деактивира всичкиptraceв системата) или блокираptraceвъв външен seccomp филтър, инициализацията наsystrapне може да завърши. (Приyama.ptrace_scope = 1, стойността по подразбиране на Ubuntu и Debian,gvxработи успешно, тъй като проследяването от родител към дете е разрешено.)seccomp(SECCOMP_SET_MODE_FILTER): Използва се за прихващане на системни извиквания на госта от нишките-стъбове на контейнерите. Както е описано в Част 3 от тази серия,gvxработи както когатоSECCOMP_FILTER_FLAG_NEW_LISTENERе наличен, така и когато връщаEBUSYвъв вложена sandbox среда.mmap(..., PROT_EXEC, ...): Използва се за картиране на ELF сегментите на госта и на споделения паметов трамплин наsystrap.
Пример от край до край: Изграждане и тестване на многоконтейнерно приложение без root
Следният скрипт изпълнява пълен docker build и интеграционен тест с два контейнера (redis + контейнер с приложението) в непривилегирована потребителска сесия:
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# Изграждане на образа с overlayfs върху ext4lite
20docker build -t demo-ping:latest .
21
22# Създаване на мрежа с мост, стартиране на Redis и проверка на връзката между контейнерите
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# Изход: PONG
27
28# Почистване на работните контейнери и спиране на sandbox средата
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID
Допълнително четене в тази серия
- gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Архитектурен преглед на
gvx, неговите междуплатформени изпълнителни engines (whpx,hvf,systrap),VFS2(erofs,ext4lite,hostfsv2) иnetstack. - Изпълнение на Docker, docker build и docker compose без root или User Namespaces (тази публикация)
- Изпълнение на Docker-in-Docker в gVisor и вложени sandbox среди с gvx: Как
gvxрешаваmodprobe,iptables-nft,overlay2върхуgoferи вложенияSECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY) вgVisor(runsc). - Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx: Мащабиране на паралелни
SWE-benchи RL rollout среди с 100-милисекундни копирай-при-запис (copy-on-write).ext4томове с нулиране.