GoSuda

Ejecución de Docker, docker build y docker compose sin privilegios de root ni espacios de nombres de usuario

By Lemon Mint views ...

Solución rápida: Docker sin privilegios de forma segura en tres pasos

Si trabajas dentro de un contenedor Linux restringido, un runner de CI, un Pod de Kubernetes o un sandbox de agentes de IA donde root, sudo, los user namespaces (unshare(CLONE_NEWUSER)), mount(2) y /dev/fuse están bloqueados, gvx (rungv) es la solución segura y con defensa en profundidad para ejecutar un motor docker:dind completo en espacio de usuario.

Por qué gvx es seguro por diseño (no es una solución alternativa para la escalada de privilegios): A diferencia de montar el /var/run/docker.sock del host o de ejecutar docker run --privileged (que otorgan un control equivalente a root sobre el kernel del host), gvx no omite ni debilita ninguna política de seguridad del host (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor, ni los límites de UID no root). En su lugar, rungv bloquea su propio proceso del host (PR_SET_NO_NEW_PRIVS más una lista blanca estricta de BPF seccomp) y ejecuta dockerd dentro del kernel de espacio de usuario gVisor (Sentry) de Google, con seguridad de memoria. Todas las capacidades de UID 0 del invitado, los montajes overlayfs (ext4lite) y las reglas de iptables permanecen estrictamente aisladas dentro del proceso del sandbox.

 1# 1. Instalar rungv en espacio de usuario (binario precompilado con atestación SLSA o go install)
 2# Opción A: Verificar y extraer el binario precompilado con atestación SLSA desde 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# Opción B: O instalar directamente desde el código fuente con Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Iniciar docker:dind dentro de gvx con un volumen ext4lite de un solo archivo
12#    Reenvía ~/.gvx/docker.sock (modo 0600) al puerto 2375 del invitado
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. Apuntar la CLI estándar de Docker y Docker Compose al socket de espacio de usuario
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# Salida: overlayfs Alpine Linux ... (en contenedor)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Por qué fallan las herramientas estándar sin root en sandboxes restringidos

Cuando un ingeniero o un agente autónomo de programación necesita ejecutar docker build, docker run o docker compose dentro de un contenedor endurecido o de un runner multiinquilino, los workarounds estándar fallan en cinco barreras distintas del kernel.

1. No hay socket del daemon Docker del host

Ejecutar docker ps o docker build falla de inmediato cuando el /var/run/docker.sock del host no está montado en el entorno:

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

2. PR_SET_NO_NEW_PRIVS bloquea sudo y los helpers setuid

Los contenedores endurecidos (securityContext.allowPrivilegeEscalation: false en Kubernetes, --security-opt=no-new-privileges en Docker, o los sandboxes exebox / bwrap) activan el bit PR_SET_NO_NEW_PRIVS de Linux en el árbol de procesos. Intentar escalar privilegios mediante sudo o binarios setuid (newuidmap, newgidmap, fusermount3) falla:

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. Las políticas de seccomp y sysctl bloquean unshare(CLONE_NEWUSER)

Las herramientas comercializadas como "sin root" (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) siguen dependiendo de los user namespaces de Linux (CLONE_NEWUSER). Fallan siempre que:

  • El perfil seccomp predeterminado del contenedor externo bloquea unshare(2) o clone(CLONE_NEWUSER).
  • Ubuntu 24.04 o posterior activa kernel.apparmor_restrict_unprivileged_userns = 1.
  • Los nodos Debian o RHEL establecen kernel.unprivileged_userns_clone = 0 o user.max_user_namespaces = 0.
  • newuidmap no puede escribir en /proc/self/uid_map porque PR_SET_NO_NEW_PRIVS está activo.

Los errores resultantes tienen este aspecto:

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) está bloqueado sin CAP_SYS_ADMIN del host

Incluso si un entorno permite CLONE_NEWUSER, montar un /proc nuevo dentro de un mount namespace anidado falla si el contenedor externo ha enmascarado subárboles de /proc (/proc/acpi, /proc/kcore, /proc/keys) o ha eliminado mount(2) de 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. La falta de /dev/fuse rompe fuse-overlayfs

Los motores de contenedores sin privilegios que no pueden montar el overlayfs del kernel recurren a fuse-overlayfs. En contenedores mínimos o en Pods de Kubernetes sin paso del dispositivo /dev/fuse, fuse-overlayfs falla de inmediato:

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

Cómo gvx ejecuta docker:dind completamente en espacio de usuario

gvx evita las cinco barreras del kernel porque no pide al kernel Linux del host que cree namespaces, monte sistemas de archivos ni configure interfaces de red. En su lugar, rungv ejecuta un kernel Linux interno en espacio de usuario (el Sentry de gVisor) y una pila de red en espacio de usuario (netstack) dentro de un único proceso ordinario sin privilegios.

Ejecución de Docker-in-Docker sin privilegios con gvx

1. Namespaces en memoria y tabla de montajes (VFS2)

Cuando dockerd, containerd y runc se ejecutan dentro de rungv, systrap intercepta sus llamadas al sistema y las dirige al kernel Sentry en proceso, en lugar de al kernel del host:

  • unshare(2), clone(2), pivot_root(2), chroot(2) y mount(2) operan sobre la jerarquía de tareas en memoria del Sentry y sobre la tabla de montajes VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), mientras el proceso del host rungv se ejecuta bajo tu UID del host sin privilegios.

2. overlayfs nativo sobre volúmenes ext4lite de un solo archivo

Cuando pasas -v gvx-docker:/var/lib/docker (o --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx monta su sistema de archivos integrado ext4lite en /var/lib/docker.

Dado que ext4lite implementa un sistema de archivos ext4 con journal JBD2 dentro de un único archivo del host (gvx-docker.ext4), con soporte para atributos extendidos trusted.overlay.*, dispositivos whiteout de caracteres (0:0), d_type y RENAME_EXCHANGE, dockerd utiliza su controlador de almacenamiento nativo overlayfs dentro del invitado. Las operaciones docker build de múltiples etapas comparten capas Copy-on-Write sin /dev/fuse y sin la explosión de espacio en disco del controlador de respaldo vfs de Docker.

3. Bridge docker0 en espacio de usuario e iptables-legacy

Cuando docker:dind se inicia dentro de gvx:

  • El dockerd del invitado crea el bridge docker0 (172.17.0.1/16), conecta pares veth virtuales para los contenedores anidados y programa las cadenas NAT (DOCKER, POSTROUTING) y FILTER con coincidencias addrtype y conntrack dentro del netstack de espacio de usuario de gvx.
  • Pasar -p unix:~/.gvx/docker.sock:2375 vincula un socket de dominio Unix del host (modo 0600) y reenvía las conexiones directamente al puerto 2375 de dockerd dentro del sandbox.

Comparación con las herramientas de contenedores existentes

Capacidad / RestricciónDocker estándar (dockerd)Docker sin root / PodmanKaniko / Buildah (sin privilegios)gvx (rungv + docker:dind)
Requiere root o sudo en el hostSíNoNoNo
Funciona con PR_SET_NO_NEW_PRIVS=1NoNo (newuidmap falla)ParcialSí
Funciona cuando unshare(CLONE_NEWUSER) está bloqueado (EPERM)N/ANoBuildah: No / Kaniko: SíSí
Funciona sin /dev/fuseSí (usa overlay2 del kernel)Solo mediante copia vfs lentaSíSí (overlayfs sobre ext4lite)
Admite docker build (BuildKit)SíSí (si userns está permitido)Solo construcción de imágenes (sin runtime)Sí
Admite docker run y docker compose upSíSí (si userns está permitido)NoSí
Funciona en Windows y macOS sin una VM de LinuxNo (requiere WSL2 / VM)No (requiere VM de Linux)NoSí (whpx, hvf, systrap)

Requisitos exactos del kernel del host en Linux

gvx no requiere capacidades elevadas (CapEff: 0000000000000000) ni user namespaces. En un host Linux, rungv depende únicamente de tres primitivas estándar del kernel sin privilegios:

  1. ptrace(PTRACE_ATTACH) sobre su propio hilo hijo, una sola vez durante la inicialización: systrap crea un hilo auxiliar stub local al iniciarse, se adjunta a él para inicializar el contexto de memoria compartida y luego se desadjunta. Si el host establece kernel.yama.ptrace_scope = 3 (lo que deshabilita todo ptrace en el sistema) o bloquea ptrace en un filtro seccomp externo, la inicialización de systrap no puede completarse. (Con yama.ptrace_scope = 1, el valor predeterminado en Ubuntu y Debian, gvx funciona porque se permite el trazado de padre a hijo).
  2. seccomp(SECCOMP_SET_MODE_FILTER): se usa para interceptar las llamadas al sistema del invitado desde los hilos stub del contenedor. Como se explica en la parte 3 de esta serie, gvx funciona tanto cuando SECCOMP_FILTER_FLAG_NEW_LISTENER está disponible como cuando devuelve EBUSY dentro de un sandbox anidado.
  3. mmap(..., PROT_EXEC, ...): se usa para mapear los segmentos ELF del invitado y el trampolín de memoria compartida de systrap.

Ejemplo de extremo a extremo: construir y probar una aplicación multicontenedor sin root

El siguiente script ejecuta un docker build completo y una prueba de integración de dos contenedores (redis más el contenedor de la aplicación) dentro de una sesión de usuario sin privilegios:

 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# Construir la imagen usando overlayfs sobre ext4lite
20docker build -t demo-ping:latest .
21
22# Crear una red bridge, iniciar Redis y verificar la conectividad entre contenedores
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# Salida: PONG
27
28# Limpiar los contenedores de carga de trabajo y detener el sandbox
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Más lectura en esta serie

  1. gvx: Ejecutar contenedores Linux, Docker y Kubernetes en un único proceso sin privilegios: Visión general de la arquitectura de gvx, sus motores de ejecución multiplataforma (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) y netstack.
  2. Ejecutar Docker, docker build y docker compose sin root ni user namespaces (esta entrada)
  3. Ejecutar Docker-in-Docker dentro de gVisor y sandboxes anidados con gvx: Cómo gvx resuelve modprobe, iptables-nft, overlay2 sobre gofer y SECCOMP_FILTER_FLAG_NEW_LISTENER anidado (EBUSY) dentro de gVisor (runsc).
  4. Entornos Docker aislados y rápidos para rollouts de RL de agentes de IA con gvx: Escalado de entornos paralelos de SWE-bench y de rollouts de RL mediante reinicios de volúmenes .ext4 con copy-on-write en 100 milisegundos.