---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: Uruchamianie Dockera, docker build i docker compose bez uprawnień roota i bez przestrzeni nazw użytkowników
description: Jak bezpiecznie uruchomić kompletny Docker Engine (dockerd, BuildKit, overlayfs oraz docker compose) wewnątrz izolowanej piaskownicy gVisor działającej w przestrzeni użytkownika, jako użytkownik nieuprzywilejowany, gdy root, sudo, unshare(CLONE_NEWUSER), mount(2) oraz /dev/fuse są zablokowane.
language: pl
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
---

## Szybkie rozwiązanie: bezpieczny nieuprzywilejowany Docker w trzech krokach

Jeśli pracujesz w zablokowanym kontenerze Linux, na runnerze CI, w Podzie Kubernetes lub w piaskownicy agenta AI, gdzie `root`, `sudo`, przestrzenie nazw użytkownika (`unshare(CLONE_NEWUSER)`), `mount(2)` oraz `/dev/fuse` są zablokowane, **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`) stanowi **bezpieczne, wielowarstwowe rozwiązanie** umożliwiające uruchomienie pełnego silnika `docker:dind` w przestrzeni użytkownika.

> **Dlaczego `gvx` jest bezpieczny z założenia (nie jest obejściem eskalacji uprawnień)**: W przeciwieństwie do montowania hostowego `/var/run/docker.sock` lub uruchamiania `docker run --privileged` (które dają kontrolę równoważną rootowi nad jądrem hosta), `gvx` nie omija ani nie osłabia żadnej polityki bezpieczeństwa hosta (`PR_SET_NO_NEW_PRIVS`, `seccomp`, AppArmor ani ograniczeń UID innych niż root). Zamiast tego `rungv` zabezpiecza własny proces hosta (`PR_SET_NO_NEW_PRIVS` oraz ścisłą listę dozwolonych wywołań BPF `seccomp`) i uruchamia `dockerd` wewnątrz bezpiecznego jądra przestrzeni użytkownika **gVisor (`Sentry`)** firmy Google. Wszystkie możliwości `UID 0` działające w gościu, montowania `overlayfs` (`ext4lite`) oraz reguły `iptables` pozostają ściśle odizolowane wewnątrz procesu piaskownicy.

```sh
# 1. Zainstaluj rungv w przestrzeni użytkownika (prekompilowane wydanie z atestacją SLSA lub go install)
# Opcja A: Zweryfikuj i rozpakuj prekompilowany plik binarny z atestacją SLSA z 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

# Opcja B: Lub zainstaluj bezpośrednio ze źródeł za pomocą Go
CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest

# 2. Uruchom docker:dind wewnątrz gvx z jednoplikowym woluminem ext4lite
#    Przekierowuje ~/.gvx/docker.sock (tryb 0600) na port 2375 gościa
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. Skieruj standardowy CLI Dockera oraz Docker Compose na gniazdo w przestrzeni użytkownika
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}}'
# Wynik: overlayfs Alpine Linux ... (containerized)

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

---

## Dlaczego standardowe narzędzia rootless zawodzą w ograniczonych piaskownicach

Gdy inżynier lub autonomiczny agent programistyczny potrzebuje uruchomić `docker build`, `docker run` lub `docker compose` wewnątrz utwardzonego kontenera lub wielodostępowego runnera, standardowe obejścia zawodzą na pięciu odrębnych barierach jądra.

### 1. Brak gniazda demona Dockera na hoście

Polecenie `docker ps` lub `docker build` kończy się natychmiast niepowodzeniem, gdy hostowe `/var/run/docker.sock` nie jest zamontowane w środowisku:

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

### 2. `PR_SET_NO_NEW_PRIVS` blokuje `sudo` i pomocników setuid

Utwardzone kontenery (`securityContext.allowPrivilegeEscalation: false` w Kubernetes, `--security-opt=no-new-privileges` w Dockerze lub piaskownice `exebox` / `bwrap`) ustawiają bit Linuksa `PR_SET_NO_NEW_PRIVS` w drzewie procesów. Próba eskalacji przez `sudo` lub binaria setuid (`newuidmap`, `newgidmap`, `fusermount3`) kończy się niepowodzeniem:

```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. Polityki seccomp i sysctl blokują `unshare(CLONE_NEWUSER)`

Narzędzia reklamowane jako „rootless” (`Rootless Docker`, `Podman`, `Buildah`, `nerdctl`, `bubblewrap`) nadal opierają się na przestrzeniach nazw użytkownika Linuksa (`CLONE_NEWUSER`). Zawodzą one zawsze, gdy:

- Domyślny profil seccomp zewnętrznego kontenera blokuje `unshare(2)` lub `clone(CLONE_NEWUSER)`.
- Ubuntu 24.04+ włącza `kernel.apparmor_restrict_unprivileged_userns = 1`.
- Węzły Debian lub RHEL ustawiają `kernel.unprivileged_userns_clone = 0` lub `user.max_user_namespaces = 0`.
- `newuidmap` nie może zapisać `/proc/self/uid_map`, ponieważ aktywne jest `PR_SET_NO_NEW_PRIVS`.

Powstające błędy wyglądają następująco:

```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)` jest zablokowane bez hostowego `CAP_SYS_ADMIN`

Nawet jeśli środowisko zezwala na `CLONE_NEWUSER`, zamontowanie świeżego `/proc` wewnątrz zagnieżdżonej przestrzeni nazw montowania kończy się niepowodzeniem, gdy zewnętrzny kontener ukrył poddrzewa `/proc` (`/proc/acpi`, `/proc/kcore`, `/proc/keys`) lub usunął `mount(2)` z 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. Brak `/dev/fuse` uniemożliwia działanie `fuse-overlayfs`

Nieuprzywilejowane silniki kontenerów, które nie mogą zamontować jądrowego `overlayfs`, przechodzą na `fuse-overlayfs`. W minimalnych kontenerach lub podach Kubernetes bez przekazania urządzenia `/dev/fuse` `fuse-overlayfs` kończy się natychmiast niepowodzeniem:

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

---

## Jak `gvx` uruchamia `docker:dind` w całości w przestrzeni użytkownika

`gvx` omija wszystkie pięć barier jądra, ponieważ nie prosi jądra Linuksa hosta o tworzenie przestrzeni nazw, montowanie systemów plików ani konfigurowanie interfejsów sieciowych. Zamiast tego `rungv` uruchamia wewnętrzne jądro Linuksa działające w przestrzeni użytkownika (gVisor `Sentry`) oraz stos sieciowy działający w przestrzeni użytkownika (`netstack`) wewnątrz jednego zwykłego, nieuprzywilejowanego procesu.

![Uruchamianie nieuprzywilejowanego Docker-in-Docker za pomocą gvx](/assets/images/gvx/gvx-unprivileged-dind.png)

### 1. Przestrzenie nazw w pamięci oraz tabela montowań (`VFS2`)

Gdy `dockerd`, `containerd` i `runc` działają wewnątrz `rungv`, `systrap` przechwytuje ich wywołania systemowe i kieruje je do wewnątrzprocesowego jądra `Sentry`, a nie do jądra hosta:

- `unshare(2)`, `clone(2)`, `pivot_root(2)`, `chroot(2)` oraz `mount(2)` operują na hierarchii zadań `Sentry` w pamięci oraz tabeli montowań `VFS2` (`procfs`, `sysfs`, `tmpfs`, `devpts`, `cgroupfs` v2), podczas gdy hostowy proces `rungv` działa pod Twoim nieuprzywilejowanym UID hosta.

### 2. Natywny `overlayfs` na jednoplikowych woluminach `ext4lite`

Gdy przekażesz `-v gvx-docker:/var/lib/docker` (lub `--mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker`), `gvx` montuje wbudowany system plików **`ext4lite`** w katalogu `/var/lib/docker`.

Ponieważ `ext4lite` implementuje system plików `ext4` z dziennikiem JBD2, umieszczony w jednym pliku hosta (`gvx-docker.ext4`), i obsługuje atrybuty rozszerzone `trusted.overlay.*`, urządzenia whiteout znakowe (`0:0`), `d_type` oraz `RENAME_EXCHANGE`, `dockerd` używa w gościu natywnego sterownika pamięci masowej **`overlayfs`**. Wieloetapowe operacje `docker build` współdzielą warstwy Copy-on-Write bez `/dev/fuse` i bez gwałtownego wzrostu zużycia miejsca na dysku, typowego dla zapasowego sterownika `vfs` Dockera.

### 3. Mostek `docker0` w przestrzeni użytkownika oraz `iptables-legacy`

Gdy `docker:dind` startuje wewnątrz `gvx`:

- `dockerd` w gościu tworzy most `docker0` (`172.17.0.1/16`), podłącza pary wirtualnych `veth` dla zagnieżdżonych kontenerów i programuje łańcuchy `NAT` (`DOCKER`, `POSTROUTING`) oraz `FILTER` z dopasowaniami `addrtype` i `conntrack` wewnątrz stosu `netstack` działającego w przestrzeni użytkownika w `gvx`.
- Podanie `-p unix:~/.gvx/docker.sock:2375` wiąże hostowe gniazdo domeny Unix (tryb `0600`) i przekierowuje połączenia bezpośrednio do portu `2375` `dockerd` działającego w piaskownicy.

---

## Porównanie z istniejącymi narzędziami kontenerowymi

| Możliwość / Ograniczenie                                     | Standardowy Docker (`dockerd`) | Rootless Docker / Podman  | Kaniko / Buildah (nieuprzywilejowane) | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :----------------------------- | :------------------------ | :------------------------------------ | :---------------------------------- |
| **Wymagany `root` lub `sudo` na hoście**                     | Tak                            | Nie                       | Nie                                   | **Nie**                             |
| **Działa z `PR_SET_NO_NEW_PRIVS=1`**                         | Nie                            | Nie (`newuidmap` zawodzi) | Częściowo                             | **Tak**                             |
| **Działa, gdy `unshare(CLONE_NEWUSER)` jest zablokowane (`EPERM`)** | Nie dotyczy              | Nie                       | Buildah: Nie / Kaniko: Tak            | **Tak**                             |
| **Działa bez `/dev/fuse`**                                   | Tak (używa jądrowego `overlay2`) | Tylko przez wolny sterownik kopiowania `vfs` | Tak          | **Tak (`overlayfs` na `ext4lite`)** |
| **Obsługuje `docker build` (BuildKit)**                      | Tak                            | Tak (jeśli `userns` jest dozwolone) | Tylko budowanie obrazów (bez środowiska uruchomieniowego) | **Tak**          |
| **Obsługuje `docker run` i `docker compose up`**             | Tak                            | Tak (jeśli `userns` jest dozwolone) | **Nie**                     | **Tak**                             |
| **Działa na Windows i macOS bez maszyny wirtualnej z Linuksem** | Nie (wymaga WSL2 / maszyny wirtualnej) | Nie (wymaga maszyny wirtualnej z Linuksem) | Nie              | **Tak (`whpx`, `hvf`, `systrap`)**  |

---

## Dokładne wymagania jądra hosta Linux

`gvx` nie wymaga żadnych podwyższonych uprawnień (`CapEff: 0000000000000000`) ani przestrzeni nazw użytkownika. Na hoście Linux `rungv` opiera się wyłącznie na trzech standardowych, nieuprzywilejowanych prymitywach jądra:

1. **`ptrace(PTRACE_ATTACH)` na własnym wątku potomnym, jednorazowo podczas inicjalizacji**: `systrap` uruchamia lokalny wątek pomocniczy (stub) przy starcie, dołącza się do niego, aby zainicjować wspólny kontekst pamięci, a następnie odłącza się. Jeśli host ustawi `kernel.yama.ptrace_scope = 3` (co wyłącza `ptrace` w całym systemie) lub zablokuje `ptrace` w zewnętrznym filtrze seccomp, inicjalizacja `systrap` nie może się zakończyć. (Przy `yama.ptrace_scope = 1`, czyli domyślnym ustawieniu w Ubuntu i Debian, `gvx` działa poprawnie, ponieważ śledzenie od procesu nadrzędnego do potomnego jest dozwolone.)
2. **`seccomp(SECCOMP_SET_MODE_FILTER)`**: Używane do przechwytywania wywołań systemowych gościa z wątków stub kontenera. Jak opisano w [części 3 tej serii](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03), `gvx` działa zarówno wtedy, gdy dostępne jest `SECCOMP_FILTER_FLAG_NEW_LISTENER`, jak i wtedy, gdy zwraca `EBUSY` wewnątrz zagnieżdżonej piaskownicy.
3. **`mmap(..., PROT_EXEC, ...)`**: Używane do mapowania segmentów ELF gościa oraz trampoliny pamięci współdzielonej `systrap`.

---

## Przykład end-to-end: budowanie i testowanie aplikacji wielokontenerowej bez roota

Poniższy skrypt uruchamia kompletny `docker build` oraz dwukontenerowy test integracyjny (`redis` + kontener aplikacji) wewnątrz nieuprzywilejowanej sesji użytkownika:

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

# Zbuduj obraz, używając overlayfs na ext4lite
docker build -t demo-ping:latest .

# Utwórz sieć mostkową, uruchom Redis i sprawdź łączność między kontenerami
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
# Wynik: PONG

# Posprzątaj kontenery robocze i zatrzymaj piaskownicę
docker rm -f cache
docker network rm demo-net
kill -INT $DIND_PID
```

---

## Dalsza lektura w tej serii

1. **[gvx: Uruchamianie kontenerów Linux, Dockera i Kubernetes w jednym nieuprzywilejowanym procesie](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: Przegląd architektury `gvx`, jego wieloplatformowych silników wykonawczych (`whpx`, `hvf`, `systrap`), `VFS2` (`erofs`, `ext4lite`, `hostfs` v2) oraz `netstack`.
2. **[Uruchamianie Dockera, docker build i docker compose bez roota i przestrzeni nazw użytkownika](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)** (niniejszy wpis)
3. **[Uruchamianie Docker-in-Docker wewnątrz gVisor i zagnieżdżonych piaskownic za pomocą gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: Jak `gvx` rozwiązuje problemy z `modprobe`, `iptables-nft`, `overlay2` na `gofer` oraz zagnieżdżonym `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`EBUSY`) wewnątrz `gVisor` (`runsc`).
4. **[Szybkie izolowane środowiska Dockera dla rollouts RL agentów AI z gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: Skalowanie równoległych środowisk `SWE-bench` oraz rollouts RL dzięki resetom woluminów `.ext4` typu copy-on-write trwającym 100 milisekund.
