ルートユーザーやユーザー名前空間を使わずに Docker、docker build、docker compose を実行する

クイックソリューション: 3つのステップで特権なしの Docker を安全に実行する
root、sudo、ユーザー名前空間(unshare(CLONE_NEWUSER))、mount(2)、/dev/fuse が禁止されている、ロックダウンされた Linux コンテナ、CI ランナー、Kubernetes Pod、AI エージェントのサンドボックス内で作業している場合、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と厳格な BPFseccomp許可リスト)、Google のメモリ安全な gVisor (Sentry) ユーザー空間カーネル内でdockerdを実行します。ゲスト内のUID 0の権限、overlayfsマウント(ext4lite)、iptablesルールはすべて、サンドボックスプロセス内に厳密に隔離されたままです。
1# 1. ユーザー空間に rungv をインストールする(ビルド済みの SLSA 証明付きリリースまたは go install)
2# オプション A: GitHub Releases から SLSA 証明付きのビルド済みバイナリを検証して展開する
3gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
4 --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
5 --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv
7
8# オプション B: Go でソースから直接インストールする
9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. 単一ファイルの ext4lite ボリュームを使用して gvx 内で docker:dind を起動する
12# ~/.gvx/docker.sock (モード 0600) をゲストのポート 2375 に転送する
13mkdir -p ~/.gvx
14~/.local/bin/rungv run \
15 -p unix:~/.gvx/docker.sock:2375 \
16 -p 127.0.0.1:9000-9019:9000-9019 \
17 -v gvx-docker:/var/lib/docker \
18 -e DOCKER_TLS_CERTDIR= \
19 docker:dind --tls=false &
20
21# 3. 標準の Docker CLI と Docker Compose をユーザー空間のソケットに向ける
22export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
23until docker info >/dev/null 2>&1; do sleep 0.5; done
24docker info --format '{{.Driver}} {{.OperatingSystem}}'
25# 出力: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d
制限された環境で標準のロootレスツールが失敗する理由
エンジニアや自律型コーディングエージェントが、ハードニングされたコンテナやマルチテナントランナー内で docker build、docker run、docker compose を実行する必要がある場合、一般的な回避策は5つの異なるカーネルの障壁で失敗します。
1. ホストの Docker デーモンソケットが存在しない
ホストの /var/run/docker.sock が環境にマウントされていない場合、docker ps や docker build は即座に失敗します:
1Cannot 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)による権限昇格を試みると失敗します:
1sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
2sudo: 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に書き込めない。
発生するエラーは次のようになります:
1unshare: unshare failed: Operation not permitted
2cannot clone: Operation not permitted
3Error: cannot re-exec process to join the existing user namespace
4newuidmap: write to uid_map failed: Operation not permitted
5RootlessKit: 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 をマウントすることは失敗します:
1mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
2OCI 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
3bwrap: Creating new namespace failed: Operation not permitted
5. /dev/fuse が存在しないと fuse-overlayfs が壊れる
カーネルの overlayfs をマウントできない rootless コンテナエンジンは、fuse-overlayfs にフォールバックします。最小構成のコンテナや、/dev/fuse デバイスがパススルーされていない Kubernetes Pod では、fuse-overlayfs は即座に失敗します:
1fuse: device not found, try 'modprobe fuse' first
2fusermount3: failed to open /dev/fuse: No such file or directory
3error initializing graphdriver: driver not supported: fuse-overlayfs
gvx が docker:dind を完全にユーザー空間で実行する仕組み
gvx は、ホストの Linux カーネルに名前空間の作成、ファイルシステムのマウント、ネットワークインターフェースの設定を要求しないため、5つのカーネルの障壁すべてを回避します。代わりに、rungv は1つの通常の非特権プロセス内で、内部のユーザー空間 Linux カーネル(gVisor の Sentry)とユーザー空間のネットワークスタック(netstack)を実行します。

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、cgroupfsv2)に対して動作し、ホストの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つの標準的な非特権カーネルプリミティブのみに依存します:
- 初期化時に一度だけ、自身の子スレッドに対して行う
ptrace(PTRACE_ATTACH):systrapは起動時にローカルのスタブヘルパースレッドを生成し、そこにアタッチして共有メモリコンテキストを初期化した後、デタッチします。ホストがkernel.yama.ptrace_scope = 3(システム全体でptraceを無効化する)を設定している場合、または外側の seccomp フィルタでptraceをブロックしている場合、systrapの初期化は完了できません。(Ubuntu および Debian のデフォルトであるyama.ptrace_scope = 1の場合、親から子へのトレースが許可されるため、gvxは成功します。) seccomp(SECCOMP_SET_MODE_FILTER): コンテナのスタブスレッドからのゲストシステムコールを捕捉するために使用されます。このシリーズの第3部で説明されているとおり、gvxはSECCOMP_FILTER_FLAG_NEW_LISTENERが利用可能な場合と、入れ子のサンドボックス内でEBUSYを返す場合の両方で動作します。mmap(..., PROT_EXEC, ...): ゲストの ELF セグメントとsystrapの共有メモリトランポリンをマップするために使用されます。
エンドツーエンドの例: root なしでのマルチコンテナアプリのビルドとテスト
次のスクリプトは、非特権のユーザーセッション内で、完全な docker build と2コンテナの統合テスト(redis + アプリケーションコンテナ)を実行します:
1mkdir -p ~/.gvx
2rungv run \
3 -p unix:~/.gvx/docker.sock:2375 \
4 -v gvx-docker:/var/lib/docker \
5 -e DOCKER_TLS_CERTDIR= \
6 docker:dind --tls=false &
7DIND_PID=$!
8
9export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
10until docker info >/dev/null 2>&1; do sleep 0.5; done
11
12mkdir -p /tmp/demo-app && cd /tmp/demo-app
13cat <<'EOF' > Dockerfile
14FROM alpine:3.20
15RUN apk add --no-cache redis
16CMD ["redis-cli", "-h", "cache", "ping"]
17EOF
18
19# ext4lite 上の overlayfs を使用してイメージをビルドする
20docker build -t demo-ping:latest .
21
22# ブリッジネットワークを作成し、Redis を起動して、コンテナ間の接続を検証する
23docker network create demo-net
24docker run -d --name cache --network demo-net redis:7-alpine
25docker run --rm --network demo-net demo-ping:latest
26# 出力: PONG
27
28# ワークロードコンテナをクリーンアップし、サンドボックスを停止する
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID
本シリーズの関連記事
- gvx: 1つの特権なしプロセスで Linux コンテナ、Docker、Kubernetes を実行する:
gvxのアーキテクチャの概要、そのクロスプラットフォーム実行エンジン(whpx、hvf、systrap)、VFS2(erofs、ext4lite、hostfsv2)、およびnetstackについて。 - root またはユーザー名前空間なしで Docker、docker build、docker compose を実行する(本記事)
- gvx による gVisor および入れ子サンドボックス内での Docker-in-Docker の実行:
gvxがmodprobe、iptables-nft、gofer上のoverlay2、およびgVisor(runsc)内の入れ子のSECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY)をどのように解決するか。 - gvx による AI エージェント RL ロールアウト向けの高速な隔離 Docker 環境: 100 ミリ秒の Copy-on-Write
.ext4ボリュームリセットを用いて、並列のSWE-benchおよび RL ロールアウト環境をスケールさせる方法。