---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: Запуск Docker, docker build и docker compose без прав root и пространств имён пользователей
description: Как безопасно запустить полноценный Docker Engine (dockerd, BuildKit, overlayfs и docker compose) внутри изолированной пользовательской песочницы gVisor от имени непривилегированного пользователя, когда root, sudo, unshare(CLONE_NEWUSER), mount(2) и /dev/fuse заблокированы.
language: ru
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
---

## Быстрое решение: безопасный непривилегированный Docker в три шага

Если вы работаете внутри ограниченного Linux-контейнера, CI-раннера, Kubernetes Pod или песочницы AI-агента, где заблокированы `root`, `sudo`, пользовательские пространства имён (`unshare(CLONE_NEWUSER)`), `mount(2)` и `/dev/fuse`, **[gvx](https://github.com/gfire-sigs/gvx)** (`rungv`) является **безопасным решением с эшелонированной защитой** для запуска полноценного движка `docker:dind` в пользовательском пространстве.

> **Почему `gvx` безопасен по замыслу (это не обходной путь для повышения привилегий)**: в отличие от монтирования хостового `/var/run/docker.sock` или запуска `docker run --privileged` (которые предоставляют управление ядром хоста с правами, эквивалентными root), `gvx` не обходит и не ослабляет никакие политики безопасности хоста (`PR_SET_NO_NEW_PRIVS`, `seccomp`, AppArmor или ограничения непривилегированного UID). Вместо этого `rungv` блокирует собственный процесс на хосте (`PR_SET_NO_NEW_PRIVS` + строгий список разрешённых системных вызовов BPF `seccomp`) и запускает `dockerd` внутри пользовательского ядра **gVisor (`Sentry`)** от Google, написанного на безопасном по отношению к памяти языке. Все возможности гостевого `UID 0`, монтирования `overlayfs` (`ext4lite`) и правила `iptables` остаются строго изолированными внутри процесса песочницы.

```sh
# 1. Установка rungv в пользовательское пространство (предсобранный релиз с SLSA-аттестацией или go install)
# Вариант A: проверка и распаковка предсобранного бинарного файла с SLSA-аттестацией из 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

# Вариант B: или установка непосредственно из исходного кода с помощью Go
CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest

# 2. Запуск docker:dind внутри gvx с томом ext4lite в виде одного файла
#    Пробрасывает ~/.gvx/docker.sock (режим 0600) на гостевой порт 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. Направление стандартного Docker CLI и Docker Compose на сокет пользовательского пространства
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}}'
# Вывод: overlayfs Alpine Linux ... (containerized)

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

---

## Почему стандартные инструменты для работы без root не работают в ограниченных песочницах

Когда инженер или автономный агент для написания кода должен выполнить `docker build`, `docker run` или `docker compose` внутри усиленного контейнера или многопользовательского раннера, стандартные обходные пути выходят из строя на пяти различных барьерах ядра.

### 1. Отсутствие сокета демона Docker на хосте

Команды `docker ps` или `docker build` немедленно завершаются ошибкой, если хостовый `/var/run/docker.sock` не подключён к окружению:

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

### 2. `PR_SET_NO_NEW_PRIVS` блокирует `sudo` и вспомогательные программы с setuid

Усиленные контейнеры (`securityContext.allowPrivilegeEscalation: false` в Kubernetes, `--security-opt=no-new-privileges` в Docker или песочницы `exebox` / `bwrap`) устанавливают бит Linux `PR_SET_NO_NEW_PRIVS` для дерева процессов. Попытка повысить привилегии через `sudo` или исполняемые файлы с setuid (`newuidmap`, `newgidmap`, `fusermount3`) завершается ошибкой:

```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 и sysctl блокируют `unshare(CLONE_NEWUSER)`

Инструменты, позиционируемые как «rootless» (`Rootless Docker`, `Podman`, `Buildah`, `nerdctl`, `bubblewrap`), по-прежнему опираются на пользовательские пространства имён Linux (**user namespaces**, `CLONE_NEWUSER`). Они завершаются ошибкой, когда:

- профиль seccomp по умолчанию внешнего контейнера блокирует `unshare(2)` или `clone(CLONE_NEWUSER)`;
- в Ubuntu 24.04 и новее включён параметр `kernel.apparmor_restrict_unprivileged_userns = 1`;
- на узлах Debian или RHEL установлены `kernel.unprivileged_userns_clone = 0` или `user.max_user_namespaces = 0`;
- `newuidmap` не может выполнить запись в `/proc/self/uid_map`, поскольку активен `PR_SET_NO_NEW_PRIVS`.

Возникающие ошибки выглядят так:

```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)` заблокирован без `CAP_SYS_ADMIN` на хосте

Даже если среда разрешает `CLONE_NEWUSER`, монтирование нового `/proc` внутри вложенного пространства монтирования завершается ошибкой, если внешний контейнер скрыл поддеревья `/proc` (`/proc/acpi`, `/proc/kcore`, `/proc/keys`) или исключил `mount(2)` из 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. Отсутствие `/dev/fuse` ломает `fuse-overlayfs`

Rootless-движки контейнеров, которые не могут смонтировать ядерный `overlayfs`, переходят на `fuse-overlayfs`. В минимальных контейнерах или Kubernetes Pod без проброса устройства `/dev/fuse` `fuse-overlayfs` немедленно завершается ошибкой:

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

---

## Как `gvx` полностью запускает `docker:dind` в пользовательском пространстве

`gvx` обходит все пять барьеров ядра, поскольку не просит ядро Linux хоста создавать пространства имён, монтировать файловые системы или настраивать сетевые интерфейсы. Вместо этого `rungv` запускает внутреннее пользовательское ядро Linux (gVisor `Sentry`) и пользовательский сетевой стек (`netstack`) внутри одного обычного непривилегированного процесса.

![Запуск непривилегированного Docker-in-Docker с gvx](/assets/images/gvx/gvx-unprivileged-dind.png)

### 1. Пространства имён и таблица монтирования в памяти (`VFS2`)

Когда `dockerd`, `containerd` и `runc` выполняются внутри `rungv`, `systrap` перехватывает их системные вызовы и направляет их во внутрипроцессное ядро `Sentry`, а не в ядро хоста:

- `unshare(2)`, `clone(2)`, `pivot_root(2)`, `chroot(2)` и `mount(2)` работают с иерархией задач `Sentry` в памяти и таблицей монтирования `VFS2` (`procfs`, `sysfs`, `tmpfs`, `devpts`, `cgroupfs` v2), тогда как хостовый процесс `rungv` выполняется под вашим непривилегированным UID на хосте.

### 2. Нативный `overlayfs` на томах `ext4lite` в виде одного файла

Когда вы передаёте `-v gvx-docker:/var/lib/docker` (или `--mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker`), `gvx` монтирует встроенную файловую систему **`ext4lite`** в `/var/lib/docker`.

Поскольку `ext4lite` реализует файловую систему `ext4` с журналом JBD2 внутри одного файла хоста (`gvx-docker.ext4`) с поддержкой расширенных атрибутов `trusted.overlay.*`, символьных устройств-вайтаутов (`0:0`), `d_type` и `RENAME_EXCHANGE`, `dockerd` использует собственный драйвер хранения **`overlayfs`** внутри гостевой системы. Многоэтапные операции `docker build` используют общие слои Copy-on-Write без `/dev/fuse` и без чрезмерного расхода дискового пространства, характерного для резервного драйвера Docker `vfs`.

### 3. Пользовательский мост `docker0` и `iptables-legacy`

Когда `docker:dind` запускается внутри `gvx`:

- гостевой `dockerd` создаёт мост `docker0` (`172.17.0.1/16`), подключает виртуальные пары `veth` для вложенных контейнеров и настраивает цепочки `NAT` (`DOCKER`, `POSTROUTING`) и `FILTER` с сопоставлениями `addrtype` и `conntrack` внутри пользовательского `netstack` `gvx`;
- параметр `-p unix:~/.gvx/docker.sock:2375` привязывает хостовый сокет домена Unix (режим `0600`) и перенаправляет соединения напрямую на порт `2375` `dockerd` внутри песочницы.

---

## Сравнение с существующими инструментами для контейнеров

| Возможность / Ограничение                                    | Standard Docker (`dockerd`)  | Rootless Docker / Podman  | Kaniko / Buildah (Unprivileged) | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :--------------------------- | :------------------------ | :------------------------------ | :---------------------------------- |
| **Требуется `root` или `sudo` на хосте**                     | Да                           | Нет                       | Нет                             | **Нет**                             |
| **Работает с `PR_SET_NO_NEW_PRIVS=1`**                       | Нет                          | Нет (`newuidmap` завершается ошибкой) | Частично            | **Да**                              |
| **Работает, когда `unshare(CLONE_NEWUSER)` заблокирован (`EPERM`)** | Неприменимо           | Нет                       | Buildah: нет / Kaniko: да       | **Да**                              |
| **Работает без `/dev/fuse`**                                 | Да (использует ядерный `overlay2`) | Только через медленный копирующий `vfs` | Да                | **Да (`overlayfs` на `ext4lite`)**  |
| **Поддерживает `docker build` (BuildKit)**                   | Да                           | Да (если разрешён `userns`) | Только сборка образов (без runtime) | **Да**                        |
| **Поддерживает `docker run` и `docker compose up`**          | Да                           | Да (если разрешён `userns`) | **Нет**                       | **Да**                              |
| **Работает на Windows и macOS без виртуальной машины Linux** | Нет (требуется WSL2 / ВМ)    | Нет (требуется ВМ Linux)  | Нет                             | **Да (`whpx`, `hvf`, `systrap`)**   |

---

## Точные требования к ядру хоста Linux

`gvx` не требует повышенных возможностей (`CapEff: 0000000000000000`) и пользовательских пространств имён. На хосте Linux `rungv` опирается только на три стандартных непривилегированных примитива ядра:

1. **`ptrace(PTRACE_ATTACH)` на собственный дочерний поток один раз при инициализации**: `systrap` запускает локальный вспомогательный поток-заглушку при старте, подключается к нему для инициализации контекста разделяемой памяти и отключается. Если хост устанавливает `kernel.yama.ptrace_scope = 3` (что отключает `ptrace` во всей системе) или блокирует `ptrace` во внешнем фильтре seccomp, инициализация `systrap` не может завершиться. (При `yama.ptrace_scope = 1`, значении по умолчанию в Ubuntu и Debian, `gvx` работает, поскольку трассировка от родительского процесса к дочернему разрешена.)
2. **`seccomp(SECCOMP_SET_MODE_FILTER)`**: используется для перехвата системных вызовов гостевой системы из потоков-заглушек контейнера. Как описано в [части 3 этой серии](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03), `gvx` работает как тогда, когда доступен `SECCOMP_FILTER_FLAG_NEW_LISTENER`, так и тогда, когда он возвращает `EBUSY` во вложенной песочнице.
3. **`mmap(..., PROT_EXEC, ...)`**: используется для отображения сегментов ELF гостевой системы и трамплина разделяемой памяти `systrap`.

---

## Сквозной пример: сборка и тестирование многоконтейнерного приложения без root

Следующий скрипт выполняет полную `docker build` и интеграционный тест из двух контейнеров (`redis` + контейнер приложения) внутри непривилегированной пользовательской сессии:

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

# Сборка образа с использованием overlayfs на ext4lite
docker build -t demo-ping:latest .

# Создание сетевого моста, запуск Redis и проверка связи между контейнерами
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
# Вывод: PONG

# Очистка рабочих контейнеров и остановка песочницы
docker rm -f cache
docker network rm demo-net
kill -INT $DIND_PID
```

---

## Дополнительные материалы по этой серии

1. **[gvx: запуск контейнеров Linux, Docker и Kubernetes в одном непривилегированном процессе](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: архитектурный обзор `gvx`, его кроссплатформенных движков выполнения (`whpx`, `hvf`, `systrap`), `VFS2` (`erofs`, `ext4lite`, `hostfs` v2) и `netstack`.
2. **[Запуск Docker, docker build и docker compose без root и пользовательских пространств имён](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)** (этот пост)
3. **[Запуск Docker-in-Docker внутри gVisor и вложенных песочниц с gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: как `gvx` решает задачи `modprobe`, `iptables-nft`, `overlay2` на `gofer` и вложенного `SECCOMP_FILTER_FLAG_NEW_LISTENER` (`EBUSY`) внутри `gVisor` (`runsc`).
4. **[Быстрые изолированные среды Docker для развёртываний RL-агентов ИИ с gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: масштабирование параллельных сред `SWE-bench` и RL-развёртываний со сбросом томов `.ext4` с copy-on-write за 100 миллисекунд.
