---
id: 40e9d44f33fa847eb498f09bac5bf144
author: Lemon Mint
title: ルートユーザーやユーザー名前空間を使わずに Docker、docker build、docker compose を実行する
description: root、sudo、unshare(CLONE_NEWUSER)、mount(2)、および /dev/fuse がブロックされている場合に、非特権ユーザーとして、隔離された gVisor のユーザー空間サンドボックス内で完全な Docker Engine（dockerd、BuildKit、overlayfs、docker compose を含む）を安全に実行する方法。
language: ja
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
---

## クイックソリューション: 3つのステップで特権なしの Docker を安全に実行する

`root`、`sudo`、ユーザー名前空間(`unshare(CLONE_NEWUSER)`)、`mount(2)`、`/dev/fuse` が禁止されている、ロックダウンされた Linux コンテナ、CI ランナー、Kubernetes Pod、AI エージェントのサンドボックス内で作業している場合、**[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、非 root の UID 制限)も回避または弱体化させません。代わりに、`rungv` は自身のホストプロセスをロックダウンし(`PR_SET_NO_NEW_PRIVS` と厳格な BPF `seccomp` 許可リスト)、Google のメモリ安全な **gVisor (`Sentry`)** ユーザー空間カーネル内で `dockerd` を実行します。ゲスト内の `UID 0` の権限、`overlayfs` マウント(`ext4lite`)、`iptables` ルールはすべて、サンドボックスプロセス内に厳密に隔離されたままです。

```sh
# 1. ユーザー空間に rungv をインストールする(ビルド済みの SLSA 証明付きリリースまたは go install)
# オプション A: GitHub Releases から 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
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. 単一ファイルの ext4lite ボリュームを使用して gvx 内で docker:dind を起動する
#    ~/.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
```

---

## 制限された環境で標準のロootレスツールが失敗する理由

エンジニアや自律型コーディングエージェントが、ハードニングされたコンテナやマルチテナントランナー内で `docker build`、`docker run`、`docker compose` を実行する必要がある場合、一般的な回避策は5つの異なるカーネルの障壁で失敗します。

### 1. ホストの Docker デーモンソケットが存在しない

ホストの `/var/run/docker.sock` が環境にマウントされていない場合、`docker ps` や `docker build` は即座に失敗します:

```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 ヘルパーをブロックする

ハードニングされたコンテナ(Kubernetes の `securityContext.allowPrivilegeEscalation: false`、Docker の `--security-opt=no-new-privileges`、または `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 の**ユーザー名前空間** (`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` が設定されている。
- `PR_SET_NO_NEW_PRIVS` が有効なため、`newuidmap` が `/proc/self/uid_map` に書き込めない。

発生するエラーは次のようになります:

```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. host の `CAP_SYS_ADMIN` がない場合、`mount(2)` がブロックされる

環境が `CLONE_NEWUSER` を許可していても、外側のコンテナが `/proc` のサブツリー(`/proc/acpi`、`/proc/kcore`、`/proc/keys`)をマスクしているか、seccomp で `mount(2)` を削除している場合、入れ子になったマウント名前空間内で新しい `/proc` をマウントすることは失敗します:

```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` が壊れる

カーネルの `overlayfs` をマウントできない rootless コンテナエンジンは、`fuse-overlayfs` にフォールバックします。最小構成のコンテナや、`/dev/fuse` デバイスがパススルーされていない Kubernetes Pod では、`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 カーネルに名前空間の作成、ファイルシステムのマウント、ネットワークインターフェースの設定を要求しないため、5つのカーネルの障壁すべてを回避します。代わりに、`rungv` は1つの通常の非特権プロセス内で、内部のユーザー空間 Linux カーネル(gVisor の `Sentry`)とユーザー空間のネットワークスタック(`netstack`)を実行します。

![gvx による特権なし Docker-in-Docker の実行](/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. 単一ファイルの `ext4lite` ボリュームにおけるネイティブな `overlayfs`

`-v gvx-docker:/var/lib/docker`(または `--mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker`)を渡すと、`gvx` は組み込みの **`ext4lite`** ファイルシステムを `/var/lib/docker` にマウントします。

`ext4lite` は、単一のホストファイル(`gvx-docker.ext4`)内に JBD2 ジャーナリングされた `ext4` ファイルシステムを実装しており、`trusted.overlay.*` 拡張属性、文字型ホワイトアウトデバイス(`0:0`)、`d_type`、`RENAME_EXCHANGE` をサポートしているため、`dockerd` はゲスト内でネイティブの **`overlayfs`** ストレージドライバを使用します。マルチステージの `docker build` 処理は、`/dev/fuse` を必要とせず、Docker の `vfs` フォールバックドライバによるディスク容量の爆発的増加も起こさずに、Copy-on-Write レイヤーを共有します。

### 3. ユーザー空間の `docker0` ブリッジと `iptables-legacy`

`docker:dind` が `gvx` 内で起動すると:

- ゲストの `dockerd` は `docker0` ブリッジ(`172.17.0.1/16`)を作成し、入れ子コンテナ用の仮想 `veth` ペアを接続して、`gvx` のユーザー空間 `netstack` 内で `addrtype` および `conntrack` の一致条件を持つ `NAT`(`DOCKER`、`POSTROUTING`)および `FILTER` チェーンを設定します。
- `-p unix:~/.gvx/docker.sock:2375` を渡すと、ホストの Unix ドメインソケット(モード `0600`)がバインドされ、接続はサンドボックス内の `dockerd` のポート `2375` に直接転送されます。

---

## 既存のコンテナツールとの比較

| 機能 / 制約                                                  | 標準 Docker (`dockerd`)      | Rootless Docker / Podman    | Kaniko / Buildah (非特権)       | `gvx` (`rungv` + `docker:dind`)     |
| :----------------------------------------------------------- | :--------------------------- | :-------------------------- | :------------------------------ | :---------------------------------- |
| **ホストの `root` または `sudo` が必要**                     | はい                         | いいえ                      | いいえ                          | **いいえ**                          |
| **`PR_SET_NO_NEW_PRIVS=1` で動作する**                       | いいえ                       | いいえ(`newuidmap` が失敗) | 部分的                          | **はい**                            |
| **`unshare(CLONE_NEWUSER)` がブロックされている場合(`EPERM`)で動作する** | 該当なし                     | いいえ                      | Buildah: いいえ / Kaniko: はい  | **はい**                            |
| **`/dev/fuse` なしで動作する**                               | はい(カーネルの `overlay2` を使用) | 遅い `vfs` コピーのみ       | はい                            | **はい(`ext4lite` 上の `overlayfs`)** |
| **`docker build` (BuildKit) をサポートする**                 | はい                         | はい(`userns` が許可されている場合) | イメージビルドのみ(ランタイムなし) | **はい**                            |
| **`docker run` と `docker compose up` をサポートする**       | はい                         | はい(`userns` が許可されている場合) | **いいえ**                      | **はい**                            |
| **Linux VM なしで Windows および macOS で動作する**          | いいえ(WSL2 / VM が必要)     | いいえ(Linux VM が必要)     | いいえ                          | **はい(`whpx`、`hvf`、`systrap`)**  |

---

## Linux ホストにおける正確なカーネル要件

`gvx` は、特権機能をまったく必要とせず(`CapEff: 0000000000000000`)、ユーザー名前空間も必要としません。Linux ホストでは、`rungv` は次の3つの標準的な非特権カーネルプリミティブのみに依存します:

1. **初期化時に一度だけ、自身の子スレッドに対して行う `ptrace(PTRACE_ATTACH)`**: `systrap` は起動時にローカルのスタブヘルパースレッドを生成し、そこにアタッチして共有メモリコンテキストを初期化した後、デタッチします。ホストが `kernel.yama.ptrace_scope = 3`(システム全体で `ptrace` を無効化する)を設定している場合、または外側の seccomp フィルタで `ptrace` をブロックしている場合、`systrap` の初期化は完了できません。(Ubuntu および Debian のデフォルトである `yama.ptrace_scope = 1` の場合、親から子へのトレースが許可されるため、`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` と2コンテナの統合テスト(`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

# ext4lite 上の overlayfs を使用してイメージをビルドする
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: 1つの特権なしプロセスで Linux コンテナ、Docker、Kubernetes を実行する](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)**: `gvx` のアーキテクチャの概要、そのクロスプラットフォーム実行エンジン(`whpx`、`hvf`、`systrap`)、`VFS2`(`erofs`、`ext4lite`、`hostfs` v2)、および `netstack` について。
2. **[root またはユーザー名前空間なしで Docker、docker build、docker compose を実行する](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**(本記事)
3. **[gvx による gVisor および入れ子サンドボックス内での Docker-in-Docker の実行](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: `gvx` が `modprobe`、`iptables-nft`、`gofer` 上の `overlay2`、および `gVisor`(`runsc`)内の入れ子の `SECCOMP_FILTER_FLAG_NEW_LISTENER`(`EBUSY`)をどのように解決するか。
4. **[gvx による AI エージェント RL ロールアウト向けの高速な隔離 Docker 環境](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: 100 ミリ秒の Copy-on-Write `.ext4` ボリュームリセットを用いて、並列の `SWE-bench` および RL ロールアウト環境をスケールさせる方法。
