GoSuda

Executando Docker, docker build e docker compose sem Root ou User Namespaces

By Lemon Mint views ...

Solução Rápida: Docker Seguro e Sem Privilégios em Três Etapas

Se você estiver trabalhando dentro de um contêiner Linux restrito, de um runner de CI, de um Pod do Kubernetes ou de um sandbox de agente de IA em que root, sudo, user namespaces (unshare(CLONE_NEWUSER)), mount(2) e /dev/fuse estão bloqueados, o gvx (rungv) é a solução segura e com defesa em profundidade para executar um mecanismo docker:dind completo no espaço do usuário.

Por que o gvx É Seguro por Design (Não É um Contorno de Escalonamento de Privilégios): Diferentemente de montar o /var/run/docker.sock do host ou de executar docker run --privileged (que concedem controle equivalente ao de root sobre o kernel do host), o gvx não contorna nem enfraquece nenhuma política de segurança do host (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor ou os limites de UID não root). Em vez disso, o rungv restringe seu próprio processo hospedeiro (PR_SET_NO_NEW_PRIVS + allowlist estrita de BPF seccomp) e executa o dockerd dentro do kernel de espaço do usuário gVisor (Sentry), da Google, que é seguro em relação à memória. Todas as capacidades do UID 0 do guest, as montagens overlayfs (ext4lite) e as regras de iptables permanecem estritamente isoladas dentro do processo do sandbox.

 1# 1. Instale o rungv no espaço do usuário (binário pré-compilado com atestação SLSA ou go install)
 2# Opção A: Verifique e extraia o binário pré-compilado com atestação SLSA das Releases do GitHub
 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# Opção B: Ou instale diretamente a partir do código-fonte com Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Inicie o docker:dind dentro do gvx com um volume ext4lite de arquivo único
12#    Encaminha ~/.gvx/docker.sock (modo 0600) para a porta 2375 do guest
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. Direcione a CLI padrão do Docker e o Docker Compose para o socket do espaço do usuário
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# Saída: overlayfs Alpine Linux ... (em contêiner)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Por Que as Ferramentas Rootless Padrão Falham em Sandboxes Restritos

Quando um engenheiro ou um agente autônomo de programação precisa executar docker build, docker run ou docker compose dentro de um contêiner reforçado ou de um runner multilocatário, as soluções alternativas padrão falham em cinco barreiras distintas do kernel.

1. Ausência do Socket do Daemon Docker do Host

Executar docker ps ou docker build falha imediatamente quando o /var/run/docker.sock do host não está montado no ambiente:

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

2. PR_SET_NO_NEW_PRIVS Bloqueia sudo e Auxiliares Setuid

Contêineres reforçados (securityContext.allowPrivilegeEscalation: false no Kubernetes, --security-opt=no-new-privileges no Docker, ou sandboxes exebox / bwrap) definem o bit PR_SET_NO_NEW_PRIVS do Linux na árvore de processos. A tentativa de escalonamento por meio de sudo ou de binários setuid (newuidmap, newgidmap, fusermount3) falha:

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. Políticas de Seccomp e Sysctl Bloqueiam unshare(CLONE_NEWUSER)

Ferramentas apresentadas como "rootless" (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) ainda dependem de user namespaces do Linux (CLONE_NEWUSER). Elas falham sempre que:

  • O perfil padrão de seccomp do contêiner externo bloqueia unshare(2) ou clone(CLONE_NEWUSER).
  • O Ubuntu 24.04+ habilita kernel.apparmor_restrict_unprivileged_userns = 1.
  • Nós Debian ou RHEL definem kernel.unprivileged_userns_clone = 0 ou user.max_user_namespaces = 0.
  • O newuidmap não consegue escrever em /proc/self/uid_map porque PR_SET_NO_NEW_PRIVS está ativo.

Os erros resultantes têm a seguinte aparência:

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) É Bloqueado Sem CAP_SYS_ADMIN no Host

Mesmo que um ambiente permita CLONE_NEWUSER, montar um /proc novo dentro de um mount namespace aninhado falha se o contêiner externo tiver mascarado subárvores de /proc (/proc/acpi, /proc/kcore, /proc/keys) ou removido mount(2) do 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. A Ausência de /dev/fuse Quebra o fuse-overlayfs

Mecanismos de contêiner rootless que não conseguem montar o overlayfs do kernel recorrem ao fuse-overlayfs. Em contêineres mínimos ou em Pods do Kubernetes sem repasse do dispositivo /dev/fuse, o fuse-overlayfs falha imediatamente:

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

Como o gvx Executa o docker:dind Inteiramente no Espaço do Usuário

O gvx contorna as cinco barreiras do kernel porque não solicita ao kernel Linux do host que crie namespaces, monte sistemas de arquivos ou configure interfaces de rede. Em vez disso, o rungv executa um kernel Linux interno de espaço do usuário (o Sentry do gVisor) e uma pilha de rede de espaço do usuário (netstack) dentro de um único processo comum e sem privilégios.

Executando Docker-in-Docker sem privilégios com gvx

1. Namespaces em Memória e Tabela de Montagem (VFS2)

Quando dockerd, containerd e runc são executados dentro do rungv, o systrap intercepta as suas chamadas de sistema e as encaminha para o kernel Sentry em processo, e não para o kernel do host:

  • unshare(2), clone(2), pivot_root(2), chroot(2) e mount(2) operam sobre a hierarquia de tarefas em memória do Sentry e sobre a tabela de montagem VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), enquanto o processo hospedeiro rungv é executado sob o seu UID não privilegiado no host.

2. overlayfs Nativo em Volumes ext4lite de Arquivo Único

Quando você passa -v gvx-docker:/var/lib/docker (ou --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), o gvx monta seu sistema de arquivos integrado ext4lite em /var/lib/docker.

Como o ext4lite implementa um sistema de arquivos ext4 com journal JBD2 dentro de um único arquivo do host (gvx-docker.ext4), com suporte aos atributos estendidos trusted.overlay.*, a dispositivos de whiteout de caracteres (0:0), a d_type e a RENAME_EXCHANGE, o dockerd utiliza seu driver de armazenamento nativo overlayfs dentro do guest. Operações de docker build em múltiplos estágios compartilham camadas Copy-on-Write sem /dev/fuse e sem a explosão no consumo de disco do driver de fallback vfs do Docker.

3. Bridge docker0 no Espaço do Usuário e iptables-legacy

Quando o docker:dind é iniciado dentro do gvx:

  • O dockerd do guest cria a bridge docker0 (172.17.0.1/16), anexa pares veth virtuais para os contêineres aninhados e programa as cadeias NAT (DOCKER, POSTROUTING) e FILTER com correspondências addrtype e conntrack dentro do netstack do gvx, no espaço do usuário.
  • Passar -p unix:~/.gvx/docker.sock:2375 vincula um socket de domínio Unix do host (modo 0600) e encaminha as conexões diretamente para a porta 2375 do dockerd dentro do sandbox.

Comparação com Ferramentas Existentes de Contêineres

Capacidade / RestriçãoDocker Padrão (dockerd)Docker Rootless / PodmanKaniko / Buildah (Sem Privilégios)gvx (rungv + docker:dind)
Requer root ou sudo no hostSimNãoNãoNão
Funciona com PR_SET_NO_NEW_PRIVS=1NãoNão (newuidmap falha)ParcialSim
Funciona quando unshare(CLONE_NEWUSER) é bloqueado (EPERM)N/ANãoBuildah: Não / Kaniko: SimSim
Funciona sem /dev/fuseSim (usa overlay2 do kernel)Somente via vfs lentoSimSim (overlayfs sobre ext4lite)
Oferece suporte a docker build (BuildKit)SimSim (se userns permitido)Somente build de imagem (sem runtime)Sim
Oferece suporte a docker run e docker compose upSimSim (se userns permitido)NãoSim
Executa em Windows e macOS sem uma VM LinuxNão (requer WSL2 / VM)Não (requer VM Linux)NãoSim (whpx, hvf, systrap)

Requisitos Exatos do Kernel do Host no Linux

O gvx não requer capacidades elevadas (CapEff: 0000000000000000) nem user namespaces. Em um host Linux, o rungv depende de apenas três primitivas padrão e sem privilégios do kernel:

  1. ptrace(PTRACE_ATTACH) na sua própria thread filha, uma única vez durante a inicialização: o systrap cria uma thread auxiliar de stub local na inicialização, anexa-se a ela para inicializar o contexto de memória compartilhada e se desanexa. Se o host definir kernel.yama.ptrace_scope = 3 (o que desativa todo o ptrace em todo o sistema) ou bloquear ptrace em um filtro seccomp externo, a inicialização do systrap não pode ser concluída. (Com yama.ptrace_scope = 1, o padrão no Ubuntu e no Debian, o gvx funciona porque o rastreamento de pai para filho é permitido.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): usado para interceptar chamadas de sistema do guest originadas de threads stub do contêiner. Conforme abordado na Parte 3 desta série, o gvx funciona tanto quando SECCOMP_FILTER_FLAG_NEW_LISTENER está disponível quanto quando retorna EBUSY dentro de um sandbox aninhado.
  3. mmap(..., PROT_EXEC, ...): usado para mapear segmentos ELF do guest e o trampolim de memória compartilhada do systrap.

Exemplo Completo: Construção e Teste de um Aplicativo Multicontêiner Sem Root

O script a seguir executa um docker build completo e um teste de integração com dois contêineres (redis + contêiner da aplicação) dentro de uma sessão de usuário sem privilégios:

 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# Construa a imagem usando overlayfs sobre ext4lite
20docker build -t demo-ping:latest .
21
22# Crie uma rede bridge, inicie o Redis e verifique a conectividade entre os contêineres
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# Saída: PONG
27
28# Remova os contêineres de carga de trabalho e encerre o sandbox
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Leitura Complementar Nesta Série

  1. gvx: Executando Contêineres Linux, Docker e Kubernetes em Um Único Processo Sem Privilégios: visão geral da arquitetura do gvx, seus mecanismos de execução multiplataforma (whpx, hvf, systrap), o VFS2 (erofs, ext4lite, hostfs v2) e o netstack.
  2. Executando Docker, docker build e docker compose Sem Root ou User Namespaces (esta postagem)
  3. Executando Docker-in-Docker Dentro do gVisor e de Sandboxes Aninhados com o gvx: como o gvx resolve modprobe, iptables-nft, overlay2 sobre gofer e o SECCOMP_FILTER_FLAG_NEW_LISTENER aninhado (EBUSY) dentro do gVisor (runsc).
  4. Ambientes Docker Isolados e Rápidos para Rollouts de RL de Agentes de IA com o gvx: escalando ambientes paralelos de rollout de SWE-bench e RL com resets de volumes .ext4 Copy-on-Write em 100 milissegundos.