GoSuda

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

By Lemon Mint views ...

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

root、sudo、ユーザー名前空間(unshare(CLONE_NEWUSER))、mount(2)、/dev/fuse がブロックされている、ロックダウンされた Linux コンテナ、CI ランナー、Kubernetes Pod、または AI コーディングエージェントのサンドボックス(クラウドエージェントのコンピュート環境など)内で作業している場合、gvx(rungv)は、ユーザー空間で完全な docker:dind エンジンを実行するための安全で、低負荷かつトークン効率の良いソリューションです。

AI コーディングエージェント向けの低負荷かつトークン効率の良いソリューション: 自律型コーディングエージェントが Cannot connect to the Docker daemon に遭遇した際、制限されたコンテナ内で docker.io、podman、rootlesskit のインストールやパッチ適用を試みると、通常、systemctl、ulimit、iptables、unshare、runc のエラーを繰り返すことで 15〜30 回のツール呼び出しターンと数万のコンテキストトークンを浪費します。その結果、失敗するか、Dockerfile を手作業で書き直すことになります。gvx(rungv)を使用すると、エージェントは単一のターンで決定的な 3 ステップのブートストラップを実行し、標準の docker build、docker run、docker compose コマンドをそのまま即座に実行できます。

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 ルールはすべて、サンドボックスプロセス内で厳密に隔離されたままです。

 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

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

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

1. ホストの Docker デーモンソケットが存在しない(systemctl および service docker start も失敗する)

ホストの /var/run/docker.sock が環境にマウントされていない場合、docker ps や docker build は即座に失敗します。また、systemctl や service docker start で docker.io を起動しようとすると、PID 1 が systemd ではないこと、および /etc/init.d/docker がコンテナのハードな ulimit -n の上限に達することにより失敗します。

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
3System has not been booted with systemd as init system (PID 1). Can't operate.
4Failed to connect to bus: Host is down
5/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)

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 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. ホストの CAP_SYS_ADMIN がない場合、docker build 中に mount(2) と pivot_root(2) がブロックされる

環境が CLONE_NEWUSER を許可している場合、または dockerd が --iptables=false --bridge=none --storage-driver=vfs を指定して強制的に起動された場合でも、ネストされたマウント名前空間内で新しい /proc をマウントしたり pivot_root(2) を呼び出したりすることがブロックされるため、docker build は最初の RUN 命令で失敗します。

1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
3OCI 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
4OCI runtime create failed: runc create failed: unable to start container process: error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
5bwrap: Creating new namespace failed: Operation not permitted

5. /dev/fuse が存在しないと fuse-overlayfs が機能しない

カーネルの overlayfs をマウントできないルートレスのコンテナエンジンは、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 は一つの通常の非特権プロセス内で、内部のユーザー空間 Linux カーネル(gVisor の Sentry)とユーザー空間ネットワークスタック(netstack)を実行します。

gvx による非特権の Docker-in-Docker の実行

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)ルートレス Docker / PodmanKaniko / 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 部で説明しているとおり、gvx は SECCOMP_FILTER_FLAG_NEW_LISTENER が利用可能な場合と、ネストされたサンドボックス内で EBUSY を返す場合の両方で動作します。
  3. 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

このシリーズのさらなる参考資料

  1. gvx: 1 つの非特権プロセスで Linux コンテナ、Docker、Kubernetes を実行する: gvx のアーキテクチャの概要、そのクロスプラットフォームの実行エンジン(whpx、hvf、systrap)、VFS2(erofs、ext4lite、hostfs v2)、および netstack についての解説。
  2. root やユーザー名前空間なしで Docker、docker build、docker compose を実行する(本記事)
  3. gVisor とネストされたサンドボックス内で Docker-in-Docker を gvx により実行する: gvx が gVisor(runsc)内で modprobe、iptables-nft、gofer 上の overlay2、およびネストされた SECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY)をどのように解決するか。
  4. gvx による AI エージェント RL ロールアウトのための高速な隔離 Docker 環境: 100 ミリ秒の Copy-on-Write .ext4 ボリュームリセットにより、並列の SWE-bench および RL ロールアウト環境をスケールさせる方法。