---
id: e62d78ef5850c982dbddc9d3ff9134ae
author: Lemon Mint
title: 'gvx: запуск Linux-контейнеров, Docker и Kubernetes в одном непривилегированном процессе'
description: Техническийобзор gvx, безопасной для работы с повышенной защитой кроссплатформенной среды выполнения контейнеров и SDK на Go, построенной на пользовательском ядре gVisor, которая безопасно запускает контейнеры Linux, Docker-in-Docker и многоузловые отказоустойчивые кластеры Kubernetes в едином непривилегированном процессе в Windows, macOS и Linux.
language: ru
date: 2026-11-06T12:02:56.008516536Z
path: /blog/posts/gvx-cross-platform-linux-containers-z48916d01
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: lowlevel
---

## Введение: защищенные контейнеры без root и хост-демонов

Запуск Linux-контейнеров, `docker build` или Kubernetes на машине разработчика, CI-раннере или многопользовательском облачном узле обычно вынуждает идти на неудобный компромисс в области безопасности:

- На **Linux** стандартные OCI-рантаймы (`runc`, `crun`, `runsc`) требуют либо привилегий `root`, либо доступа к `/var/run/docker.sock` на хосте, либо `--privileged` (`CAP_SYS_ADMIN`), либо пространств имен пользователя (`unshare(CLONE_NEWUSER)`), которые расширяют поверхность атаки ядра хоста.
- На **Windows** и **macOS** движки контейнеров (`Docker Desktop`, `Podman Machine`, `Lima`, `OrbStack`) загружают полноценное ядро Linux в виртуальной машине внутри WSL2, Hyper-V или `Virtualization.framework` и запускают постоянный фоновый демон внутри этой виртуальной машины.

**[gvx](https://github.com/gfire-sigs/gvx)** (CLI `rungv` и Go SDK `z.gfire.dev/gvx/pkg/gvx`) предоставляет **безопасную альтернативу без повышенных привилегий**. Созданный как форк проекта Google **gVisor**, отслеживаемый по upstream, с пользовательским ядром Linux (`Sentry`) и пользовательским сетевым стеком (`netstack`), `gvx` запускает немодифицированные бинарные файлы `linux/amd64` и `linux/arm64` ELF64 и контейнеры OCI непосредственно внутри **одного непривилегированного процесса хоста с самоблокировкой** на Windows, macOS и Linux.

> **Гарантия безопасности (трехкольцевая эшелонированная защита)**: `gvx` никогда не ослабляет границы безопасности хостовой ОС, никогда не устанавливает драйверы ядра и никогда не требует `root`, `sudo`, `--privileged` на хосте или `CLONE_NEWUSER`. Прежде чем выполнить хотя бы одну инструкцию гостевой системы, `gvx` блокирует собственный процесс хоста с помощью Windows Restricted Tokens и Job Objects, macOS Seatbelt (`deny default`) или Linux `PR_SET_NO_NEW_PRIVS` и BPF `seccomp`. Все системные вызовы гостя, виртуальные привилегии `UID 0`, монтирования (`VFS2`) и сетевые таблицы (`netstack`) строго ограничены внутри безопасного в отношении памяти ядра `Sentry` на Go.

| Среда выполнения           | ОС хоста                                              |         Холодный запуск | Объем памяти     | Требуются права администратора или `sudo` | Фоновый демон ВМ | Прямой запуск образа OCI |
| :------------------------- | :---------------------------------------------------- | ----------------------: | ---------------: | :---------------------------------------- | :--------------- | :----------------------- |
| **`gvx` (`whpx` / `hvf`)** | Windows (`amd64`, `arm64`) и macOS (`arm64`)          |              `~20 ms`   |        `~30 MiB` | Нет                                       | Нет              | Да                       |
| **`gvx` (`systrap`)**      | Windows (`amd64`, `arm64`) и Linux (`amd64`, `arm64`) |        `от 25 до 65 ms` |        `~31 MiB` | Нет                                       | Нет              | Да                       |
| **WSL2 / Docker Desktop**  | Windows и macOS                                       |      `от 800 до 2500 ms` |      `> 512 MiB` | Да (первоначальная настройка)             | Да               | Требуется демон          |
| **QEMU (Full System)**     | Windows и macOS                                       |     `от 1500 до 4000 ms` |      `> 256 MiB` | Нет                                       | Нет              | Требуется образ диска    |

Ядро Linux в виртуальной машине на Windows или macOS не загружается. Фоновый демон на хосте не запускается. Никакие `root`, `sudo`, пространства имен пользователя (`CLONE_NEWUSER`), `mount(2)`, драйверы `TUN`/`TAP` или привилегии `CAP_NET_ADMIN` на хостовой ОС не требуются. Внутри этого непривилегированного процесса `gvx` запускает **Docker-in-Docker (`docker:dind`)**, `docker build`, `docker compose` и **многоузловой высокодоступный Kubernetes (`k3s` со встроенным `etcd` и CNI `Calico`)**.

---

## Жизненный цикл контейнера и трехкольцевая самоблокировка хоста

Каждая песочница `gvx` работает внутри одного непривилегированного процесса хоста и реализует трехфазный жизненный цикл: предварительное открытие всех необходимых дескрипторов файлов и сокетов хоста, блокировку процесса хостовой ОС через `pkg/hostlockdown` и вход в цикл выполнения `Sentry` внутри процесса.

![gvx Container Lifecycle and Host Self-Lockdown](/assets/images/gvx/gvx-lifecycle-lockdown.png)

Перед передачей управления первой инструкции гостя `gvx` заранее открывает кэшированные дескрипторы слоев образа `.erofs`, дескрипторы файлов томов `.ext4`, дескриптор `*os.Root` для `/workspace` и сетевые сокеты хоста, затем вызывает `hostlockdown.Apply()`:

1. **Windows NT (`lockdown_windows.go`)**: вызывает `AdjustTokenPrivileges` с `SE_PRIVILEGE_REMOVED`, чтобы удалить привилегии на месте, привязывает ограниченный токен олицетворения `DISABLE_MAX_PRIVILEGE` с низким уровнем целостности (`S-1-16-4096`), помещает процесс в анонимный Job Object с `JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1` (блокируя создание любых дочерних процессов) и включает `DisallowWin32kSystemCalls = 1` через `SetProcessMitigationPolicy`.
2. **macOS XNU (`lockdown_darwin.go`)**: устанавливает `RLIMIT_NPROC = 0` и применяет профиль Seatbelt (`sandbox_init_with_parameters`), начинающийся с `(version 1)(deny default)`, который блокирует `process-fork`, `process-exec*` и все новые открытия файлов хоста.
3. **Linux (`lockdown_linux.go` и `pkg/gvx/syscallfilter_linux.go`)**: устанавливает `PR_SET_NO_NEW_PRIVS` и применяет разрешающий список BPF `seccomp` ядра Sentry во всех потоках (`SECCOMP_FILTER_FLAG_TSYNC`) с явными правилами `EPERM` для `execve`, `execveat`, `clone`, `openat` и `socket`.

---

## Перехват системных вызовов на разных платформах и страничная организация памяти по 4 KB (`pkg/sentry/platform`)

Бинарные файлы Linux ELF64 предполагают гранулярность страниц памяти 4 KB (`0x1000`) для сегментных отображений `mmap(MAP_FIXED)`. Однако Windows `VirtualAlloc` и `MapViewOfFile3` требуют выравнивания по 64 KB (`0x10000`), а macOS на Apple Silicon (`darwin/arm64`) использует страницы хоста размером 16 KB (`0x4000`). Кроме того, каждой ОС требуется свой механизм для перехвата инструкций Linux `SYSCALL` (`amd64`) и `SVC #0` (`arm64`) без загрузки виртуальной машины Linux.

`gvx` решает обе проблемы в четырех бэкендах выполнения, которые реализуют `pkg/sentry/platform.Context`:

![gvx Platform Syscall Interception and 4KB Paging Mechanics](/assets/images/gvx/gvx-platform-syscalls.png)

### 1. Аппаратный движок Windows: `platform/whpx` (`WinHvPlatform.dll`)

На `windows/amd64` и `windows/arm64` `platform/whpx` создает легковесный раздел гипервизора через `WHvCreatePartition`, не загружая ядро гостевой ОС:

- **Развязка страниц 64 KB и 4 KB (`64 KB-to-4 KB Paging Decoupler`)**: `whpx` регистрирует каждую арену `pgalloc.MemoryFile` размером 64 MiB в разделе один раз через `WHvMapGpaRange`. Последующие вызовы `MapFile` и `Unmap` изменяют только таблицы страниц гостя внутри раздела, обеспечивая точные права `R/W/X` на уровне 4 KB, несмотря на гранулярность выделения 64 KB в Windows.
- **Обмен регистрами без API через `CommPage`**:
  - На `amd64` гостевой `SYSCALL` из Ring-3 переходит в заглушку `MSR_LSTAR` в Ring-0 внутри раздела (`stubgen_x86.go`), которая сохраняет GPR быстрого пути системных вызовов (`RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP`) в общую страницу `CommPage` размером 4 KB и выполняет `HLT` для выхода из `WHvRunVirtualProcessor`.
  - На `arm64` гостевой `SVC #0` из EL0 переходит к векторам EL1 (`stubgen_a64.go`), сохраняет `X0..X8, SP_EL0, ELR_EL1, PSTATE` в `CommPage` и выполняет загрузку из непривязанного GPA с нулевым адресом, чтобы вызвать возврат к Sentry через обращение к памяти.

### 2. Аппаратный движок macOS: `platform/hvf` (`Hypervisor.framework`)

На Apple Silicon (`darwin/arm64`) `platform/hvf` запускает потоки контейнеров внутри vCPU `Hypervisor.framework` (`hv_vm_create`, `hv_vcpu_run`):

- **Двухступенчатая трансляция MMU: страницы 4 KB поверх страниц 16 KB**: Stage-2 (`hv_vm_map`) один раз отображает фрагменты файла памяти размером 1 GiB в пространство промежуточных физических адресов (IPA) с гранулярностью страниц хоста 16 KB и правами `HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC`. Внутри vCPU заглушка EL1 `bluepill` (`bluepill_arm64.go`) настраивает `TCR_EL1.TG0 = 0b00` (гранула 4 KB) в `TTBR0_EL1` и заполняет записи таблиц страниц Stage-1 размером 4 KB с точными правами `R-X` или `RW-` (`UXN=1`). Поскольку MMU ARM64 применяет пересечение прав Stage-1 и Stage-2, аппаратное обеспечение перехватывает ошибки прав доступа на точных границах 4 KB.
- **Перехват системного вызова**: гостевой `SVC #0` из EL0 переходит к `VBAR_EL1` в EL1, сохраняет регистры в общую страницу `CommPage` размером 4 KB и выполняет `HVC #0`, чтобы вернуться из `hv_vcpu_run` в поток Go Sentry.

### 3. Резервный пользовательский режим Windows: `platform/systrap` (`winsystrap`)

Когда аппаратная виртуализация недоступна (например, во вложенных облачных виртуальных машинах), `winsystrap` выполняет гостевой код в пользовательском режиме на `windows/amd64` и `windows/arm64`:

- **Аппаратная изоляция `CR3` / `TTBR0_EL1`**: гостевые потоки работают внутри изолированных подпроцессов-заглушек, загруженных из сгенерированного исполняемого файла размером 1 KB (`stubimage_xplat.go`), ограниченных токеном с низким уровнем целостности и Job Object.
- **Патчинг бинарного кода на месте и IPC `winSysmsg`**: на `amd64` патчер (`patcher_amd64_xplat.go`) заменяет инструкции `syscall` (`0f 05`) длиной 2 байта на переходы `jmp rel32` длиной 5 байт (или на ловушки-точки останова `CC 90`, когда окно в 5 байт недоступно) в «остров» быстрого пути. На `arm64` `svc #0` патчится переходом `B` к слоту острова. Остров сохраняет регистры в общую страницу `winSysmsg` и синхронизируется с Sentry через `WaitOnAddress` и `WakeByAddressSingle`, тогда как непатченные ловушки и ошибки страниц останавливаются на отладочном объекте Windows (`WaitForDebugEventEx`).

### 4. Движок пользовательского режима Linux: `platform/systrap`

На `linux/amd64` и `linux/arm64` `systrap` запускает гостевые потоки (`sysmsg`), которые перехватывают гостевые системные вызовы через `SECCOMP_RET_TRAP` (`SIGSYS`) и выполняют бинарный патчинг мест вызова `syscall` в общий трамплин в разделяемой памяти (`sysmsg`), синхронизированный через `SYS_FUTEX`. Тем временем заглушечный поток `syscallThread` (`syscall_thread.go`) выполняет операции с адресным пространством (`mmap`, `munmap`, `clone`) через `SECCOMP_RET_USER_NOTIF` (`_RUN_SECCOMP_LOOP`). Когда `gvx` работает внутри внешней песочницы `gVisor` (`runsc`), где единственный слот `SECCOMP_FILTER_FLAG_NEW_LISTENER` возвращает `EBUSY`, `gvx` автоматически переключает `syscallThread` на цикл futex в разделяемой памяти (`_RUN_SYSCALL_LOOP`), как подробно описано в [части 3 этой серии](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03).

---

## Как `gvx` сопоставляет системные вызовы гостевого Linux с виртуальными подсистемами и дескрипторами хоста

После того как платформенный движок перехватывает инструкцию гостевого `SYSCALL` (`amd64`) или `SVC #0` (`arm64`) и записывает регистры гостя в общую страницу размером 4 KB (`CommPage`, `sysmsg` или `winSysmsg`), управление возвращается к горутине задачи Go `Sentry` (`pkg/sentry/kernel/task_syscall.go`). Вместо передачи системных вызовов Linux в ядро ОС хоста `Task.doSyscall()` сохраняет исходное состояние регистров (`SyscallSaveOrig()`), проверяет фильтры BPF `seccomp` и остановки `ptrace` внутри гостя и направляет номер системного вызова через `linux.AMD64` или `linux.ARM64` (`pkg/sentry/syscalls/linux/linux64.go`, охватывающий около 370 системных вызовов Linux) в четыре внутренних домена:

![How gvx Maps Guest Linux Syscalls to Subsystems and Host OS](/assets/images/gvx/gvx-syscall-mapping.png)

| Домен системных вызовов | Типичные системные вызовы гостевого Linux | Внутренняя подсистема `gvx` (`pkg/sentry`) | Взаимодействие с границей ОС хоста (после `hostlockdown`) |
| :--- | :--- | :--- | :--- |
| **A. Процессы, пространства имен, монтирование и синхронизация** | `clone`, `execve`, `unshare`, `setns`, `mount`, `umount2`, `pivot_root`, `setuid`, `capset`, `futex`, `epoll_wait`, `pipe2` | `kernel.Task`, виртуальные учетные данные, таблица futex и `VFS2` в памяти (`procfs`, `sysfs`, `cgroupfs` v2, `tmpfs`, `overlayfs`) | **Ноль системных вызовов хоста** (100% состояния Go в памяти; полностью невидимо для ОС хоста) |
| **B. Виртуальная память (`MM`)** | `mmap`, `munmap`, `mremap`, `mprotect`, `brk`, `madvise`, `memfd_create` | `mm.MemoryManager` (дерево VMA размером 4 KB) и `pgalloc.MemoryFile` | Двунаправленные обновления таблиц страниц 4 KB через `WHvMapGpaRange`, `hv_vm_map` + `TTBR0_EL1` или заглушечные `mmap`/`mprotect` |
| **C. Файлы и хранилище (`VFS2`)** | `openat`, `pread64`, `pwrite64`, `getdents64`, `statx`, `renameat2`, `setxattr`, `mknod`, `inotify_add_watch` | Маршрутизатор таблицы монтирования `vfs.VirtualFilesystem` (`fsimpl/erofs`, `fsimpl/ext4lite`, `fsimpl/hostfs` v2) | Двунаправленные `pread`/`pwrite` для одного закрепленного FD `.erofs` или одного FD `.ext4`, либо ограниченные относительные обходы через `*os.Root` |
| **D. Сеть и Netfilter** | `socket`, `bind`, `listen`, `connect`, `sendmsg`, `recvmsg`, `netlink`, `setsockopt(IPT_SO_SET_REPLACE)` | Пользовательский `pkg/tcpip` (`lo`, `eth0`, `docker0`, `veth`, `vxlan`, `wireguard`, `ipset`, `iptables-legacy`) | Трафик внутри песочницы не использует **системных вызовов хоста**; внешний трафик отображается на `net.Dial`/`net.Listen` (`-net=nat`) или на один UDP-сокет (`-net=wg`) |

Когда обработчик возвращает `(rval, ctrl, err)`, `Task.doSyscallInvoke()` преобразует любую ошибку в отрицательный код `errno` Linux (`-ExtractErrno(err, sysno)`), записывает возвращаемое значение обратно в общую страницу регистров (`RAX` на `amd64`, `X0` на `arm64`) и возобновляет выполнение гостя.

---

## Хранилище (`VFS2`) и двухканальная пользовательская сеть (`netstack`)

Все файловые и сетевые операции гостя обслуживаются внутри Go `Sentry` через три файловые системы `VFS2` и два трека `pkg/netbackend`:

![gvx VFS2 Storage Engines and Dual-Track Netstack](/assets/images/gvx/gvx-storage-netstack.png)

| Движок хранилища | Роль монтирования в `gvx` | Базовый ресурс хоста | Вызовы `openat` / `NtCreateFile` на хосте во время выполнения | Посекторные проверки `Windows Defender` (`WdFilter.sys`) |
| :--- | :--- | :--- | ---: | ---: |
| **`fsimpl/erofs`** | Нижние слои образов OCI только для чтения | Один файл `<sha256>.erofs` на слой | `0` | `0` |
| **`fsimpl/ext4lite`** | Именованные тома (`-v name:/path`) | Один файл `<name>.ext4` на том | `0` | `0` |
| **`fsimpl/hostfs` v2** | Рабочая область хоста (`-workspace`, `-v /host:/guest`) | Закрепленный каталог хоста `*os.Root` | `O(depth)` на каждое некэшированное открытие | `1` на каждое закрытие файла хоста |

### Почему `ext4lite` важен для Docker, баз данных и менеджеров пакетов

Распаковка или запись десятков тысяч файлов контейнера непосредственно на Windows NTFS или macOS APFS вызывает сканирование антивирусными minifilter-драйверами (`WdFilter.sys`), коллизии из-за чувствительности к регистру и отсутствие расширенных атрибутов POSIX.

`pkg/sentry/fsimpl/ext4lite` реализует чисто Go-файловую систему `ext4` внутри одного заранее открытого файла хоста (`%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4` в Windows, `~/Library/Caches/gvx/volumes/<name>.ext4` в macOS или `~/.cache/gvx/volumes/<name>.ext4` в Linux) со следующими возможностями:

- **Блоки по 4 KiB и динамические группы блоков по 16 MiB (`meta_bg`)**: растет динамически без задержек на выделение разреженных файлов (`fallocate` в Linux, `fcntl(F_PREALLOCATE)` в macOS, `FileAllocationInfo` в Windows) и уплотняет неиспользуемые хвостовые группы при чистом размонтировании.
- **Журнал JBD2 в режиме ordered (inode 8)**: атомарная фиксация транзакций с контрольными суммами CRC-32 и блоками revoke, а также восстановление через `fsckLite`.
- **Поддержка верхнего слоя `overlayfs`**: реализует расширенные атрибуты `trusted.overlay.*` и `security.capability`, символьные устройства-заглушки (`0:0`), сообщение `d_type` для записей каталогов и `RENAME_EXCHANGE`.

---

## Практические рабочие нагрузки: Docker Engine, Kubernetes (`k3s` + `Calico`) и Go SDK

### Установка `rungv`: готовые релизы с аттестацией SLSA или `go install`

Вы можете загрузить готовый архив `rungv` для `linux/amd64`, `linux/arm64`, `darwin/arm64`, `windows/amd64` или `windows/arm64` со страницы [GitHub Releases](https://github.com/gfire-sigs/gvx/releases) (каждый собран конвейером выпуска SLSA с пакетом происхождения Sigstore и файлом `SHA256SUMS`) либо установить напрямую из исходного кода командой `go install`:

```sh
# Вариант A: скачать готовый релиз и проверить провенанс сборки SLSA
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
shasum -a 256 -c SHA256SUMS --ignore-missing
tar -xzf rungv-v0.2.0-linux-amd64.tar.gz

# Вариант B: установить напрямую через Go (Linux / Windows)
CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
```

> На macOS (`darwin/arm64`) готовый архив релиза уже подписан с правом `com.apple.security.hypervisor` (`xattr -d com.apple.quarantine rungv` снимает карантин браузера). Если вы собираете локально через `go install` или `go build` на macOS, подпишите бинарный файл командой `codesign --sign - --entitlements entitlements.plist --force $(which rungv)`.

### 1. Запуск непривилегированного Docker Engine (`docker:dind`)

Следуя инструкции [`docs/docker-engine.md`](https://github.com/gfire-sigs/gvx/blob/main/docs/docker-engine.md), вы можете запустить немодифицированный контейнер `docker:dind` внутри `rungv` в Windows, macOS или Linux и управлять им с помощью CLI `docker` на хосте:

```sh
# 1. Создать каталог на хосте для проброшенного Unix-сокета
mkdir -p ~/.gvx

# 2. Запустить docker:dind внутри песочницы gvx с томом ext4lite
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
```

Во втором терминале направьте CLI `docker` на хосте на сокет песочницы:

```sh
docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
docker context use gvx

docker info --format '{{.Driver}} {{.OperatingSystem}}'
# Вывод: overlayfs Alpine Linux ... (в контейнере)

docker build -t myapp ./app
docker compose up -d
```

### 2. Запуск Kubernetes (`k3s` + `Calico`) в непривилегированных песочницах

Поскольку `gvx` реализует `/dev/kmsg`, `cgroup` v2 (убийца OOM `memory.max` и ограничение пропускной способности `cpu.max`), `/proc/diskstats`, сетевые устройства netlink `wireguard` и `vxlan`, `ipset` и `iptables-legacy` (`addrtype`, `conntrack`, `statistic`, `recent`), немодифицированный образ `rancher/k3s` достигает состояния `Ready` внутри одного процесса `rungv`:

```sh
mkdir -p ~/.gvx/k3s

rungv run \
  -e IPTABLES_MODE=legacy \
  -p 127.0.0.1:6443:6443 \
  -p 127.0.0.1:30000-30019:30000-30019 \
  -v k3s:/var/lib/rancher \
  --mount type=bind,src=$HOME/.gvx/k3s,dst=/output \
  rancher/k3s:v1.31.4-k3s1 server \
  --disable=traefik,servicelb \
  --write-kubeconfig=/output/kubeconfig.yaml \
  --write-kubeconfig-mode=644
```

Вы также можете объединить несколько узлов `gvx` через WireGuard (`flannel-backend=wireguard-native` или ячеистая сеть `wg0`) со встроенной высокой доступностью `etcd` либо запустить **Calico** (`calico-vxlan.yaml` с `FELIX_IPTABLESBACKEND=Legacy`), чтобы полностью обеспечить соблюдение `NetworkPolicy` Kubernetes в пользовательском пространстве.

### 3. Встраивание Linux-контейнеров в Go через `z.gfire.dev/gvx/pkg/gvx`

Go-приложения могут встраивать Linux-контейнеры напрямую, без внешнего демона контейнеров:

```go
package main

import (
	"context"
	"fmt"
	"os"

	"z.gfire.dev/gvx/pkg/gvx"
)

func main() {
	ctx := context.Background()
	sb, err := gvx.New(ctx, gvx.Config{
		Platform:      gvx.PlatformAuto,
		NetworkMode:   gvx.NetModeNAT,
		OCIImage:      "alpine:3.20",
		WorkspacePath: "./repo",
		Argv:          []string{"/usr/bin/git", "status"},
		Stdout:        os.Stdout,
		Stderr:        os.Stderr,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "init sandbox: %v\n", err)
		os.Exit(1)
	}
	defer sb.Close()

	exitCode, err := sb.Run(ctx)
	if err != nil {
		fmt.Fprintf(os.Stderr, "run sandbox: %v\n", err)
		os.Exit(1)
	}
	os.Exit(exitCode)
}
```

---

## Дополнительная литература в этой серии

1. **[gvx: запуск Linux-контейнеров, Docker и Kubernetes в одном непривилегированном процессе](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)** (этот пост)
2. **[Запуск Docker, docker build и docker compose без root и пространств имен пользователя](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: как запустить `docker:dind` в ограниченных песочницах, где заблокированы `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)` и `/dev/fuse`.
3. **[Запуск Docker-in-Docker внутри gVisor и вложенных песочниц с gvx](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: почему `apt-get install docker.io` и вложенный `runsc` не работают внутри `gVisor` из-за пяти барьеров ядра и как `gvx` запускает вложенный `docker:dind` с помощью цикла futex в разделяемой памяти.
4. **[Быстрые изолированные окружения Docker для развертываний RL-агентов с gvx](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: масштабирование `SWE-bench` и развертываний обучения с подкреплением агентов на сотнях параллельных непривилегированных экземпляров `docker:dind` со сбросом томов `.ext4` по механизму copy-on-write за 100 миллисекунд.
