---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: Esecuzione di Docker, docker build e docker compose senza root o user namespace
description: Come eseguire in modo sicuro un Docker Engine completo (dockerd, BuildKit, overlayfs e docker compose) all'interno di una sandbox gVisor isolata nello spazio utente, come utente non privilegiato, quando root, sudo, unshare(CLONE_NEWUSER), mount(2) e /dev/fuse sono bloccati.
language: it
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
---

## Soluzione rapida: eseguire Docker senza privilegi in tre passaggi

Se si lavora all'interno di un container Linux bloccato, di un CI runner, di un Kubernetes Pod o di una sandbox per agenti AI in cui `root`, `sudo`, i user namespaces (`unshare(CLONE_NEWUSER)`), `mount(2)` e `/dev/fuse` sono bloccati, **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`) è la **soluzione sicura e basata sulla difesa in profondità** per eseguire un motore `docker:dind` completo nello user space.

> **Perché `gvx` è sicuro per progettazione (non è un workaround per l'escalation dei privilegi)**: a differenza del mount del socket dell'host `/var/run/docker.sock` o dell'esecuzione di `docker run --privileged` (che concedono un controllo equivalente a root sul kernel dell'host), `gvx` non aggira né indebolisce alcuna politica di sicurezza dell'host (`PR_SET_NO_NEW_PRIVS`, `seccomp`, AppArmor o i limiti degli UID non root). Al contrario, `rungv` blocca il proprio processo host (`PR_SET_NO_NEW_PRIVS` + allowlist BPF `seccomp` rigorosa) ed esegue `dockerd` all'interno di **gVisor (`Sentry`)** di Google, un kernel user-space memory-safe. Tutte le capability `UID 0` del guest, i mount `overlayfs` (`ext4lite`) e le regole `iptables` restano rigorosamente isolate all'interno del processo sandbox.

```sh
# 1. Installa rungv nello user space (release SLSA-attested precompilata oppure go install)
# Opzione A: verifica ed estrae il binario precompilato con attestazione SLSA dalle GitHub Releases
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

# Opzione B: in alternativa, installa direttamente dal sorgente con Go
CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest

# 2. Avvia docker:dind all'interno di gvx con un volume ext4lite a file singolo
#    Inoltra ~/.gvx/docker.sock (modalità 0600) alla porta 2375 del 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. Indirizza la CLI Docker standard e Docker Compose al socket user-space
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}}'
# Output: overlayfs Alpine Linux ... (containerizzato)

docker build -t app:test .
docker run --rm app:test
docker compose up -d
```

---

## Perché gli strumenti rootless standard falliscono nelle sandbox con restrizioni

Quando un ingegnere o un agente autonomo di coding deve eseguire `docker build`, `docker run` o `docker compose` all'interno di un container indurito o di un runner multi-tenant, le soluzioni alternative standard falliscono su cinque distinte barriere del kernel.

### 1. Assenza del socket del daemon Docker sull'host

L'esecuzione di `docker ps` o `docker build` fallisce immediatamente quando il socket dell'host `/var/run/docker.sock` non è montato nell'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` blocca `sudo` e gli helper setuid

I container induriti (`securityContext.allowPrivilegeEscalation: false` in Kubernetes, `--security-opt=no-new-privileges` in Docker, oppure sandbox `exebox` / `bwrap`) impostano il bit Linux `PR_SET_NO_NEW_PRIVS` sull'albero dei processi. Il tentativo di ottenere privilegi tramite `sudo` o binari setuid (`newuidmap`, `newgidmap`, `fusermount3`) fallisce:

```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. Le politiche seccomp e sysctl bloccano `unshare(CLONE_NEWUSER)`

Gli strumenti presentati come «rootless» (`Rootless Docker`, `Podman`, `Buildah`, `nerdctl`, `bubblewrap`) si basano comunque sui **user namespaces** Linux (`CLONE_NEWUSER`). Falliscono ogni volta che:

- Il profilo seccomp predefinito del container esterno blocca `unshare(2)` o `clone(CLONE_NEWUSER)`.
- Ubuntu 24.04+ abilita `kernel.apparmor_restrict_unprivileged_userns = 1`.
- I nodi Debian o RHEL impostano `kernel.unprivileged_userns_clone = 0` oppure `user.max_user_namespaces = 0`.
- `newuidmap` non può scrivere su `/proc/self/uid_map` perché `PR_SET_NO_NEW_PRIVS` è attivo.

Gli errori risultanti hanno questo aspetto:

```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)` è bloccato senza `CAP_SYS_ADMIN` sull'host

Anche se un ambiente consente `CLONE_NEWUSER`, il mount di un nuovo `/proc` all'interno di un mount namespace annidato fallisce se il container esterno ha mascherato sottoalberi di `/proc` (`/proc/acpi`, `/proc/kcore`, `/proc/keys`) oppure ha escluso `mount(2)` dal 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. L'assenza di `/dev/fuse` compromette `fuse-overlayfs`

I motori di container rootless che non possono montare l'`overlayfs` del kernel ripiegano su `fuse-overlayfs`. Nei container minimali o nei pod Kubernetes privi del passthrough del dispositivo `/dev/fuse`, `fuse-overlayfs` fallisce immediatamente:

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

---

## Come `gvx` esegue `docker:dind` interamente nello user space

`gvx` aggira tutte e cinque le barriere del kernel perché non chiede al kernel Linux dell'host di creare namespace, montare filesystem o configurare interfacce di rete. Al contrario, `rungv` esegue un kernel Linux interno nello user space (il `Sentry` di gVisor) e uno stack di rete user-space (`netstack`) all'interno di un unico processo ordinario non privilegiato.

![Esecuzione di Docker-in-Docker non privilegiato con gvx](/assets/images/gvx/gvx-unprivileged-dind.png)

### 1. Namespace in memoria e tabella dei mount (`VFS2`)

Quando `dockerd`, `containerd` e `runc` vengono eseguiti all'interno di `rungv`, `systrap` intercetta le loro system call e le instrada verso il kernel `Sentry` in-process anziché verso il kernel dell'host:

- `unshare(2)`, `clone(2)`, `pivot_root(2)`, `chroot(2)` e `mount(2)` operano sulla gerarchia dei task in memoria del Sentry e sulla tabella dei mount `VFS2` (`procfs`, `sysfs`, `tmpfs`, `devpts`, `cgroupfs` v2), mentre il processo host `rungv` viene eseguito con il proprio UID host non privilegiato.

### 2. `overlayfs` nativo su volumi `ext4lite` a file singolo

Quando si passa `-v gvx-docker:/var/lib/docker` (oppure `--mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker`), `gvx` monta il filesystem integrato **`ext4lite`** in `/var/lib/docker`.

Poiché `ext4lite` implementa un filesystem `ext4` con journal JBD2 all'interno di un singolo file dell'host (`gvx-docker.ext4`), con supporto per gli attributi estesi `trusted.overlay.*`, i dispositivi whiteout di tipo carattere (`0:0`), `d_type` e `RENAME_EXCHANGE`, `dockerd` utilizza il driver di storage **`overlayfs`** nativo all'interno del guest. Le operazioni `docker build` multi-stage condividono i layer Copy-on-Write senza `/dev/fuse` e senza l'esplosione dello spazio su disco tipica del driver di fallback `vfs` di Docker.

### 3. Bridge `docker0` nello user space e `iptables-legacy`

Quando `docker:dind` si avvia all'interno di `gvx`:

- Il `dockerd` del guest crea il bridge `docker0` (`172.17.0.1/16`), collega coppie `veth` virtuali per i container annidati e programma le catene `NAT` (`DOCKER`, `POSTROUTING`) e `FILTER` con match `addrtype` e `conntrack` all'interno del `netstack` user-space di `gvx`.
- Passare `-p unix:~/.gvx/docker.sock:2375` associa un socket di dominio Unix dell'host (modalità `0600`) e inoltra le connessioni direttamente alla porta `2375` di `dockerd` all'interno della sandbox.

---

## Confronto con gli strumenti container esistenti

| Capacità / Vincolo                                           | Docker standard (`dockerd`)  | Docker Rootless / Podman  | Kaniko / Buildah (non privilegiato) | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :--------------------------- | :------------------------ | :---------------------------------- | :---------------------------------- |
| **Richiede `root` o `sudo` sull'host**                       | Sì                           | No                        | No                                  | **No**                              |
| **Funziona con `PR_SET_NO_NEW_PRIVS=1`**                     | No                           | No (`newuidmap` fallisce) | Parziale                            | **Sì**                              |
| **Funziona quando `unshare(CLONE_NEWUSER)` è bloccato (`EPERM`)** | N/D                     | No                        | Buildah: No / Kaniko: Sì            | **Sì**                              |
| **Funziona senza `/dev/fuse`**                               | Sì (usa `overlay2` del kernel) | Solo tramite il lento driver `vfs` di copia | Sì                   | **Sì (`overlayfs` su `ext4lite`)**  |
| **Supporta `docker build` (BuildKit)**                       | Sì                           | Sì (se `userns` è consentito) | Solo build di immagini (senza runtime) | **Sì**                         |
| **Supporta `docker run` e `docker compose up`**              | Sì                           | Sì (se `userns` è consentito) | **No**                          | **Sì**                              |
| **Funziona su Windows e macOS senza una VM Linux**           | No (richiede WSL2 / VM)      | No (richiede una VM Linux) | No                                 | **Sì (`whpx`, `hvf`, `systrap`)**   |

---

## Requisiti esatti del kernel host su Linux

`gvx` richiede zero capability elevate (`CapEff: 0000000000000000`) e zero user namespaces. Su un host Linux, `rungv` si affida solo a tre primitive del kernel standard non privilegiate:

1. **`ptrace(PTRACE_ATTACH)` sul proprio thread figlio una sola volta durante l'inizializzazione**: `systrap` avvia un thread helper stub locale all'avvio, vi si collega per inizializzare il contesto di memoria condivisa e si stacca. Se l'host imposta `kernel.yama.ptrace_scope = 3` (che disabilita `ptrace` in tutto il sistema) oppure blocca `ptrace` in un filtro seccomp esterno, l'inizializzazione di `systrap` non può completarsi. (Con `yama.ptrace_scope = 1`, il valore predefinito su Ubuntu e Debian, `gvx` funziona perché è consentito il tracciamento da processo padre a figlio.)
2. **`seccomp(SECCOMP_SET_MODE_FILTER)`**: utilizzato per intercettare le system call del guest provenienti dai thread stub del container. Come illustrato nella [Parte 3 di questa serie](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03), `gvx` funziona sia quando `SECCOMP_FILTER_FLAG_NEW_LISTENER` è disponibile, sia quando restituisce `EBUSY` all'interno di una sandbox annidata.
3. **`mmap(..., PROT_EXEC, ...)`**: utilizzato per mappare i segmenti ELF del guest e il trampolino di memoria condivisa di `systrap`.

---

## Esempio end-to-end: costruire e testare un'app multi-container senza root

Lo script seguente esegue un `docker build` completo e un test di integrazione a due container (`redis` + container applicativo) all'interno di una sessione utente non privilegiata:

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

# Costruisce l'immagine utilizzando overlayfs su ext4lite
docker build -t demo-ping:latest .

# Crea una rete bridge, avvia Redis e verifica la connettività tra i container
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
# Output: PONG

# Rimuove i container di lavoro e arresta la sandbox
docker rm -f cache
docker network rm demo-net
kill -INT $DIND_PID
```

---

## Ulteriori letture in questa serie

1. **[gvx: Esecuzione di container Linux, Docker e Kubernetes in un unico processo non privilegiato](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: panoramica architetturale di `gvx`, dei suoi motori di esecuzione cross-platform (`whpx`, `hvf`, `systrap`), di `VFS2` (`erofs`, `ext4lite`, `hostfs` v2) e di `netstack`.
2. **[Esecuzione di Docker, docker build e docker compose senza root o user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)** (questo articolo)
3. **[Esecuzione di Docker-in-Docker all'interno di gVisor e sandbox annidate con gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: come `gvx` risolve `modprobe`, `iptables-nft`, `overlay2` su `gofer` e il `SECCOMP_FILTER_FLAG_NEW_LISTENER` annidato (`EBUSY`) all'interno di `gVisor` (`runsc`).
4. **[Ambienti Docker isolati e rapidi per i rollout RL di agenti AI con gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: scalabilità di ambienti Docker paralleli per `SWE-bench` e rollout RL con reset dei volumi `.ext4` copy-on-write in 100 millisecondi.
