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

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é
gvxes seguro por diseño (no es una solución alternativa para la escalada de privilegios): A diferencia de montar el/var/run/docker.sockdel host o de ejecutardocker run --privileged(que otorgan un control equivalente a root sobre el kernel del host),gvxno 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,rungvbloquea su propio proceso del host (PR_SET_NO_NEW_PRIVSmás una lista blanca estricta de BPFseccomp) y ejecutadockerddentro del kernel de espacio de usuario gVisor (Sentry) de Google, con seguridad de memoria. Todas las capacidades deUID 0del invitado, los montajesoverlayfs(ext4lite) y las reglas deiptablespermanecen 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)oclone(CLONE_NEWUSER). - Ubuntu 24.04 o posterior activa
kernel.apparmor_restrict_unprivileged_userns = 1. - Los nodos Debian o RHEL establecen
kernel.unprivileged_userns_clone = 0ouser.max_user_namespaces = 0. newuidmapno puede escribir en/proc/self/uid_mapporquePR_SET_NO_NEW_PRIVSestá 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.

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)ymount(2)operan sobre la jerarquía de tareas en memoria del Sentry y sobre la tabla de montajesVFS2(procfs,sysfs,tmpfs,devpts,cgroupfsv2), mientras el proceso del hostrungvse 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
dockerddel invitado crea el bridgedocker0(172.17.0.1/16), conecta paresvethvirtuales para los contenedores anidados y programa las cadenasNAT(DOCKER,POSTROUTING) yFILTERcon coincidenciasaddrtypeyconntrackdentro delnetstackde espacio de usuario degvx. - Pasar
-p unix:~/.gvx/docker.sock:2375vincula un socket de dominio Unix del host (modo0600) y reenvía las conexiones directamente al puerto2375dedockerddentro del sandbox.
Comparación con las herramientas de contenedores existentes
| Capacidad / Restricción | Docker estándar (dockerd) | Docker sin root / Podman | Kaniko / Buildah (sin privilegios) | gvx (rungv + docker:dind) |
|---|---|---|---|---|
Requiere root o sudo en el host | Sí | No | No | No |
Funciona con PR_SET_NO_NEW_PRIVS=1 | No | No (newuidmap falla) | Parcial | Sí |
Funciona cuando unshare(CLONE_NEWUSER) está bloqueado (EPERM) | N/A | No | Buildah: No / Kaniko: Sí | Sí |
Funciona sin /dev/fuse | Sí (usa overlay2 del kernel) | Solo mediante copia vfs lenta | Sí | 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 up | Sí | Sí (si userns está permitido) | No | Sí |
| Funciona en Windows y macOS sin una VM de Linux | No (requiere WSL2 / VM) | No (requiere VM de Linux) | No | Sí (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:
ptrace(PTRACE_ATTACH)sobre su propio hilo hijo, una sola vez durante la inicialización:systrapcrea 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 establecekernel.yama.ptrace_scope = 3(lo que deshabilita todoptraceen el sistema) o bloqueaptraceen un filtro seccomp externo, la inicialización desystrapno puede completarse. (Conyama.ptrace_scope = 1, el valor predeterminado en Ubuntu y Debian,gvxfunciona porque se permite el trazado de padre a hijo).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,gvxfunciona tanto cuandoSECCOMP_FILTER_FLAG_NEW_LISTENERestá disponible como cuando devuelveEBUSYdentro de un sandbox anidado.mmap(..., PROT_EXEC, ...): se usa para mapear los segmentos ELF del invitado y el trampolín de memoria compartida desystrap.
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
- 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,hostfsv2) ynetstack. - Ejecutar Docker, docker build y docker compose sin root ni user namespaces (esta entrada)
- Ejecutar Docker-in-Docker dentro de gVisor y sandboxes anidados con gvx: Cómo
gvxresuelvemodprobe,iptables-nft,overlay2sobregoferySECCOMP_FILTER_FLAG_NEW_LISTENERanidado (EBUSY) dentro degVisor(runsc). - Entornos Docker aislados y rápidos para rollouts de RL de agentes de IA con gvx: Escalado de entornos paralelos de
SWE-benchy de rollouts de RL mediante reinicios de volúmenes.ext4con copy-on-write en 100 milisegundos.