---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: Executando Docker, docker build e docker compose sem Root ou User Namespaces
description: Como executar de forma segura um Docker Engine completo (dockerd, BuildKit, overlayfs e docker compose) dentro de um sandbox isolado do gVisor em espaço de usuário, como usuário não privilegiado, quando root, sudo, unshare(CLONE_NEWUSER), mount(2) e /dev/fuse estão bloqueados.
language: pt
date: 2026-11-06T12:02:56.050897958Z
path: /blog/posts/run-docker-without-root-or-userns-gvx-z48916d02
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: ops
---

## 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](https://github.com/gfire-sigs/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.

```sh
# 1. Instale o rungv no espaço do usuário (binário pré-compilado com atestação SLSA ou go install)
# Opção A: Verifique e extraia o binário pré-compilado com atestação SLSA das Releases do GitHub
gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv

# Opção B: Ou instale diretamente a partir do código-fonte com Go
CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest

# 2. Inicie o docker:dind dentro do gvx com um volume ext4lite de arquivo único
#    Encaminha ~/.gvx/docker.sock (modo 0600) para a porta 2375 do guest
mkdir -p ~/.gvx
~/.local/bin/rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -p 127.0.0.1:9000-9019:9000-9019 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false &

# 3. Direcione a CLI padrão do Docker e o Docker Compose para o socket do espaço do usuário
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}}'
# Saída: overlayfs Alpine Linux ... (em contêiner)

docker build -t app:test .
docker run --rm app:test
docker 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:

```text
Cannot 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:

```text
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
sudo: 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:

```text
unshare: unshare failed: Operation not permitted
cannot clone: Operation not permitted
Error: cannot re-exec process to join the existing user namespace
newuidmap: write to uid_map failed: Operation not permitted
RootlessKit: 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:

```text
mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
OCI 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
bwrap: 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:

```text
fuse: device not found, try 'modprobe fuse' first
fusermount3: failed to open /dev/fuse: No such file or directory
error 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](/assets/images/gvx/gvx-unprivileged-dind.png)

### 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ção                                       | Docker Padrão (`dockerd`)    | Docker Rootless / Podman  | Kaniko / Buildah (Sem Privilégios) | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :--------------------------- | :------------------------ | :--------------------------------- | :---------------------------------- |
| **Requer `root` ou `sudo` no host**                          | Sim                          | Não                       | Não                                | **Não**                             |
| **Funciona com `PR_SET_NO_NEW_PRIVS=1`**                     | Não                          | Não (`newuidmap` falha)   | Parcial                            | **Sim**                             |
| **Funciona quando `unshare(CLONE_NEWUSER)` é bloqueado (`EPERM`)** | N/A                    | Não                       | Buildah: Não / Kaniko: Sim         | **Sim**                             |
| **Funciona sem `/dev/fuse`**                                 | Sim (usa `overlay2` do kernel) | Somente via `vfs` lento | Sim                                | **Sim (`overlayfs` sobre `ext4lite`)** |
| **Oferece suporte a `docker build` (BuildKit)**              | Sim                          | Sim (se `userns` permitido) | Somente build de imagem (sem runtime) | **Sim**                        |
| **Oferece suporte a `docker run` e `docker compose up`**     | Sim                          | Sim (se `userns` permitido) | **Não**                          | **Sim**                             |
| **Executa em Windows e macOS sem uma VM Linux**              | Não (requer WSL2 / VM)       | Não (requer VM Linux)     | Não                                | **Sim (`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](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03), 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:

```sh
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

mkdir -p /tmp/demo-app && cd /tmp/demo-app
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN apk add --no-cache redis
CMD ["redis-cli", "-h", "cache", "ping"]
EOF

# Construa a imagem usando overlayfs sobre ext4lite
docker build -t demo-ping:latest .

# Crie uma rede bridge, inicie o Redis e verifique a conectividade entre os contêineres
docker network create demo-net
docker run -d --name cache --network demo-net redis:7-alpine
docker run --rm --network demo-net demo-ping:latest
# Saída: PONG

# Remova os contêineres de carga de trabalho e encerre o sandbox
docker rm -f cache
docker network rm demo-net
kill -INT $DIND_PID
```

---

## Leitura Complementar Nesta Série

1. **[gvx: Executando Contêineres Linux, Docker e Kubernetes em Um Único Processo Sem Privilégios](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: 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](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)** (esta postagem)
3. **[Executando Docker-in-Docker Dentro do gVisor e de Sandboxes Aninhados com o gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: 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](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: escalando ambientes paralelos de rollout de `SWE-bench` e RL com resets de volumes `.ext4` Copy-on-Write em 100 milissegundos.
