---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: Docker, docker build en docker compose uitvoeren zonder root of user namespaces
description: How to securely run a complete Docker Engine (dockerd, BuildKit, overlayfs, and docker compose) inside an isolated gVisor user-space sandbox as an unprivileged user when root, sudo, unshare(CLONE_NEWUSER), mount(2), and /dev/fuse are blocked.
language: nl
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
---

## Snelle oplossing: veilig onbevoorrecht Docker in drie stappen

Als u werkt binnen een afgeschermde Linux-container, CI-runner, Kubernetes Pod of AI-agentsandbox waarin `root`, `sudo`, user namespaces (`unshare(CLONE_NEWUSER)`), `mount(2)` en `/dev/fuse` zijn geblokkeerd, is **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`) de **veilige oplossing met defense-in-depth** om een volledige `docker:dind`-engine in user space uit te voeren.

> **Waarom `gvx` veilig is by design (geen omweg voor privilege-escalatie)**: In tegenstelling tot het koppelen van de host-socket `/var/run/docker.sock` of het uitvoeren van `docker run --privileged` (die root-equivalente controle over de host-kernel verlenen), omzeilt of verzwakt `gvx` geen enkel beveiligingsbeleid van de host (`PR_SET_NO_NEW_PRIVS`, `seccomp`, AppArmor, of beperkingen van niet-root UID). In plaats daarvan vergrendelt `rungv` zijn eigen hostproces (`PR_SET_NO_NEW_PRIVS` + strikte BPF `seccomp`-allowlist) en draait `dockerd` binnen Google's geheugenveilige **gVisor (`Sentry`)** user-space kernel. Alle `UID 0`-capabilities van de gast, `overlayfs`-mounts (`ext4lite`) en `iptables`-regels blijven strikt geïsoleerd binnen het sandboxproces.

```sh
# 1. Installeer rungv in user space (vooraf gebouwde SLSA-geattesteerde release of go install)
# Optie A: verifieer en pak de vooraf gebouwde SLSA-geattesteerde binary uit 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

# Optie B: of installeer rechtstreeks vanuit de broncode met Go
CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest

# 2. Start docker:dind binnen gvx met een ext4lite-volume van één bestand
#    Stuurt ~/.gvx/docker.sock (modus 0600) door naar gastpoort 2375
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. Richt de standaard Docker CLI en Docker Compose op de user-space socket
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}}'
# Uitvoer: overlayfs Alpine Linux ... (gecontaineriseerd)

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

---

## Waarom standaard rootless-tools falen in beperkte sandboxes

Wanneer een engineer of autonome codeeragent `docker build`, `docker run` of `docker compose` moet uitvoeren binnen een geharde container of multi-tenant runner, falen standaardomwegen bij vijf afzonderlijke kernelbarrières.

### 1. Geen host-Docker-daemonsocket

`docker ps` of `docker build` faalt onmiddellijk wanneer de host-socket `/var/run/docker.sock` niet in de omgeving is gekoppeld:

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

### 2. `PR_SET_NO_NEW_PRIVS` blokkeert `sudo` en setuid-helpers

Geharde containers (`securityContext.allowPrivilegeEscalation: false` in Kubernetes, `--security-opt=no-new-privileges` in Docker, of `exebox`- of `bwrap`-sandboxes) zetten de Linux-bit `PR_SET_NO_NEW_PRIVS` in de procesboom. Een poging tot escalatie via `sudo` of setuid-binaries (`newuidmap`, `newgidmap`, `fusermount3`) mislukt:

```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. Seccomp- en sysctl-beleid blokkeert `unshare(CLONE_NEWUSER)`

Tools die als "rootless" worden aangeprezen (`Rootless Docker`, `Podman`, `Buildah`, `nerdctl`, `bubblewrap`) vertrouwen nog steeds op Linux **user namespaces** (`CLONE_NEWUSER`). Ze falen wanneer:

- Het standaard seccomp-profiel van de buitenste container `unshare(2)` of `clone(CLONE_NEWUSER)` blokkeert.
- Ubuntu 24.04+ `kernel.apparmor_restrict_unprivileged_userns = 1` inschakelt.
- Debian- of RHEL-nodes `kernel.unprivileged_userns_clone = 0` of `user.max_user_namespaces = 0` instellen.
- `newuidmap` niet naar `/proc/self/uid_map` kan schrijven omdat `PR_SET_NO_NEW_PRIVS` actief is.

De resulterende fouten zien er als volgt uit:

```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)` is geblokkeerd zonder host-`CAP_SYS_ADMIN`

Ook als een omgeving `CLONE_NEWUSER` toestaat, mislukt het koppelen van een nieuwe `/proc` binnen een geneste mount-namespace wanneer de buitenste container `/proc`-subbomen heeft gemaskeerd (`/proc/acpi`, `/proc/kcore`, `/proc/keys`) of `mount(2)` in seccomp heeft laten vallen:

```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. Ontbrekende `/dev/fuse` breekt `fuse-overlayfs`

Rootless container-engines die geen kernel-`overlayfs` kunnen koppelen, vallen terug op `fuse-overlayfs`. In minimale containers of Kubernetes-pods zonder doorgifte van het `/dev/fuse`-apparaat faalt `fuse-overlayfs` onmiddellijk:

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

---

## Hoe `gvx` `docker:dind` volledig in user space uitvoert

`gvx` omzeilt alle vijf kernelbarrières omdat het de host-Linux-kernel niet vraagt om namespaces aan te maken, bestandssystemen te koppelen of netwerkinterfaces te configureren. In plaats daarvan draait `rungv` een interne Linux-kernel in user space (de gVisor `Sentry`) en een netwerkstack in user space (`netstack`) binnen één gewoon onbevoorrecht proces.

![Onbevoorrecht Docker-in-Docker uitvoeren met gvx](/assets/images/gvx/gvx-unprivileged-dind.png)

### 1. Namespaces en mounttabel in geheugen (`VFS2`)

Wanneer `dockerd`, `containerd` en `runc` binnen `rungv` worden uitgevoerd, onderschept `systrap` hun systeemaanroepen en leidt deze om naar de in-process `Sentry`-kernel in plaats van naar de host-kernel:

- `unshare(2)`, `clone(2)`, `pivot_root(2)`, `chroot(2)` en `mount(2)` werken op de in-memory taakhiërarchie en de `VFS2`-mounttabel van de Sentry (`procfs`, `sysfs`, `tmpfs`, `devpts`, `cgroupfs` v2), terwijl het host-`rungv`-proces draait onder uw onbevoorrechte host-UID.

### 2. Native `overlayfs` op `ext4lite`-volumes van één bestand

Wanneer u `-v gvx-docker:/var/lib/docker` (of `--mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker`) doorgeeft, koppelt `gvx` het ingebouwde **`ext4lite`**-bestandssysteem aan `/var/lib/docker`.

Omdat `ext4lite` een JBD2-journaled `ext4`-bestandssysteem implementeert binnen één host-bestand (`gvx-docker.ext4`) met ondersteuning voor `trusted.overlay.*`-extended attributes, character whiteout-apparaten (`0:0`), `d_type` en `RENAME_EXCHANGE`, gebruikt `dockerd` in de gast zijn eigen **`overlayfs`**-opslagdriver. Meertraps-`docker build`-bewerkingen delen Copy-on-Write-lagen zonder `/dev/fuse` en zonder de explosie van de schijfruimte die gepaard gaat met de `vfs`-terugvaldriver van Docker.

### 3. `docker0`-bridge in user space en `iptables-legacy`

Wanneer `docker:dind` binnen `gvx` start:

- De `dockerd` in de gast maakt de `docker0`-bridge aan (`172.17.0.1/16`), koppelt virtuele `veth`-paren voor geneste containers en programmeert de `NAT`- (`DOCKER`, `POSTROUTING`) en `FILTER`-chains met `addrtype`- en `conntrack`-matches binnen de `netstack` van `gvx` in user space.
- Het doorgeven van `-p unix:~/.gvx/docker.sock:2375` bindt een host-Unix-domeinsocket (modus `0600`) en stuurt verbindingen rechtstreeks door naar poort `2375` van `dockerd` binnen de sandbox.

---

## Vergelijking met bestaande containertools

| Capaciteit / beperking                                       | Standaard Docker (`dockerd`) | Rootless Docker / Podman  | Kaniko / Buildah (onbevoorrecht) | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :--------------------------- | :------------------------ | :------------------------------- | :---------------------------------- |
| **Host-`root` of `sudo` vereist**                            | Ja                           | Nee                       | Nee                              | **Nee**                             |
| **Werkt met `PR_SET_NO_NEW_PRIVS=1`**                        | Nee                          | Nee (`newuidmap` faalt)   | Gedeeltelijk                     | **Ja**                              |
| **Werkt wanneer `unshare(CLONE_NEWUSER)` is geblokkeerd (`EPERM`)** | N.v.t.                | Nee                       | Buildah: Nee / Kaniko: Ja        | **Ja**                              |
| **Werkt zonder `/dev/fuse`**                                 | Ja (gebruikt kernel-`overlay2`) | Alleen via trage `vfs`-kopie | Ja                          | **Ja (`overlayfs` op `ext4lite`)**  |
| **Ondersteunt `docker build` (BuildKit)**                    | Ja                           | Ja (als `userns` is toegestaan) | Alleen imagebouw (geen runtime) | **Ja**                        |
| **Ondersteunt `docker run` en `docker compose up`**          | Ja                           | Ja (als `userns` is toegestaan) | **Nee**                      | **Ja**                              |
| **Draait op Windows en macOS zonder Linux-VM**               | Nee (vereist WSL2 / VM)      | Nee (vereist Linux-VM)    | Nee                              | **Ja (`whpx`, `hvf`, `systrap`)**   |

---

## Exacte host-kernelvereisten op Linux

`gvx` vereist nul verhoogde capabilities (`CapEff: 0000000000000000`) en nul user namespaces. Op een Linux-host vertrouwt `rungv` op slechts drie standaard onbevoorrechte kernelprimitieven:

1. **`ptrace(PTRACE_ATTACH)` op de eigen childthread, eenmalig tijdens de initialisatie**: `systrap` start bij het opstarten een lokale stub-helperthread, koppelt eraan vast om de gedeelde-geheugencontext te initialiseren, en maakt de koppeling weer ongedaan. Als de host `kernel.yama.ptrace_scope = 3` instelt (wat alle `ptrace` systeembreed uitschakelt) of `ptrace` blokkeert in een buitenste seccomp-filter, kan de initialisatie van `systrap` niet worden voltooid. (Onder `yama.ptrace_scope = 1`, de standaard op Ubuntu en Debian, slaagt `gvx` omdat tracering van ouder naar kind is toegestaan.)
2. **`seccomp(SECCOMP_SET_MODE_FILTER)`**: wordt gebruikt om systeemaanroepen van de gast te onderscheppen vanuit containerstub-threads. Zoals behandeld in [deel 3 van deze reeks](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03), werkt `gvx` zowel wanneer `SECCOMP_FILTER_FLAG_NEW_LISTENER` beschikbaar is als wanneer het `EBUSY` teruggeeft binnen een geneste sandbox.
3. **`mmap(..., PROT_EXEC, ...)`**: wordt gebruikt om ELF-segmenten van de gast en de `systrap` shared-memory-trampoline in kaart te brengen.

---

## End-to-end voorbeeld: een multi-container-app bouwen en testen zonder root

Het volgende script voert een volledige `docker build` en een integratietest met twee containers (`redis` + applicatiecontainer) uit binnen een onbevoorrechte gebruikerssessie:

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

# Bouw de image met overlayfs op ext4lite
docker build -t demo-ping:latest .

# Maak een bridge-netwerk aan, start Redis en controleer de connectiviteit tussen de containers
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
# Uitvoer: PONG

# Ruim de workloadcontainers op en stop de sandbox
docker rm -f cache
docker network rm demo-net
kill -INT $DIND_PID
```

---

## Verder lezen in deze reeks

1. **[gvx: Linux-containers, Docker en Kubernetes uitvoeren in één onbevoorrecht proces](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Architectuuroverzicht van `gvx`, de platformoverschrijdende uitvoeringsengines (`whpx`, `hvf`, `systrap`), `VFS2` (`erofs`, `ext4lite`, `hostfs` v2) en `netstack`.
2. **[Docker, docker build en docker compose uitvoeren zonder root of user namespaces](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)** (dit bericht)
3. **[Docker-in-Docker uitvoeren binnen gVisor en geneste sandboxes met gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Hoe `gvx` `modprobe`, `iptables-nft`, `overlay2` op `gofer` en geneste `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`EBUSY`) binnen `gVisor` (`runsc`) oplost.
4. **[Snelle geïsoleerde Docker-omgevingen voor AI-agent RL-rollouts met gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: Schalen van parallelle `SWE-bench`- en RL-rolloutomgevingen met copy-on-write `.ext4`-volumereset van 100 milliseconden.
