Dockerコンテナの中で docker run を実行する環境(CI/CDパイプライン、DevContainer、Jenkinsなど)で、誰もが一度は遭遇する「謎の現象」があります。
- 現象: ホストのファイルをマウントしたはずなのに、コンテナ内には空っぽのディレクトリしか生成されない。
-
謎: 相対パス (
-v ./src:/app) だと失敗し、絶対パスで書くと動く(場合がある)。
「バグかな?」と思われがちですが、これは Dockerのアーキテクチャ(CLIとDaemonの分離) に起因する仕様通りの動作です。
なぜこの現象が起きるのか、どうすれば正しく動くのか。
「DooD (Docker outside of Docker)」 特有の落とし穴を、図解で徹底解説します。
結論:そこは「コンテナの中」ではなく「ホスト」です
結論から言うと、Dockerコンテナの中からホストのソケット (/var/run/docker.sock) を共有して Docker コマンドを叩くとき、ファイルパスはすべて「ホスト側の絶対パス」として解釈されます。
あなたが「コンテナの中」にいると思っていても、命令を受け取る本体(Daemon)は「ホスト」にいるからです。
発生する現象の再現
例えば、以下のような構成で作業しているとします。
- 開発コンテナ (Dev Container) 内で作業中。
- このコンテナには
/var/run/docker.sockがマウントされている。 - 現在地は
/app/my-project。ここにsrcディレクトリがある。
# 開発コンテナ内での操作
$ ls -F
src/ Dockerfile
# "src" を新しいコンテナにマウントしたい(相対パス指定)
$ docker run -v ./src:/target alpine ls -F /target
期待する結果: src ディレクトリの中身が表示される。
実際の結果: 何も表示されない(空っぽ)、あるいはディレクトリが見つからないエラー。
【図解】なぜ失敗するのか?(CLIとDaemonのすれ違い)
この失敗の裏側では、CLI(あなた)とDaemon(実行者)の間で、致命的な**「場所の認識ズレ」**が起きています。
詳細解説:何が起きているのか
-
CLIの親切心 (Path Expansion)
dockerコマンド(CLI)は、ユーザーが入力した相対パス./srcを、実行された場所(コンテナ内)のカレントディレクトリ を元に絶対パス/app/my-project/srcに変換します。これはCLIの標準的な仕様です。 -
APIリクエスト
CLIは変換後の絶対パスを使って、Docker Daemonに命令を送ります。「ボリューム/app/my-project/srcをマウントしてね」 -
Daemonの実直さ
命令を受け取った Docker Daemon は、ホストOS上 で動作しています。そのため、ホストのファイルシステム上 で/app/my-project/srcを探します。 -
不一致 (Mismatch)
ホスト上には/app/my-project/srcというパスが存在しない(あるいは、あっても中身が開発コンテナ内とは違う)ため、Dockerは仕様に従い「空のディレクトリ」をホスト上に作成し、それをマウントします。
これが「空っぽ問題」の正体です。
コラム:「兄弟コンテナ (Sibling Containers)」という構造
Dind (Docker in Docker) という言葉から、多くの人は「マトリョーシカ」のような入れ子構造をイメージします。
- ホスト > 親コンテナ > 子コンテナ
しかし、ソケット共有(DooD)の場合、実際は 「兄弟」 関係になります。
- ホスト > 兄(開発コンテナ:ここにCLIがある)
- ホスト > 弟(新規コンテナ:兄がCLIで作った)
弟コンテナはホストOS上に直接作られるため、兄(開発コンテナ)の中にあるファイルシステムは見えません。
兄が「僕のお腹の中にある ./src を使ってよ」と言っても、弟は「そんなの知らない、ホストのお父さんに聞いて」となるわけです。
解決策
この問題を解決するには、「コンテナ内のパス」ではなく「ホスト側のパス」を正しくDaemonに伝える必要があります。
1. 【推奨】環境変数でホストのパスを渡す
最も汎用的でスマートな方法は、親コンテナ(開発コンテナ)を起動する際に、「ホスト側でのカレントディレクトリ」を環境変数として教えてあげることです。
Step 1: 親コンテナ起動時
docker-compose.yml などで、PWD を環境変数 HOST_PWD(名前は任意)として渡します。
# docker-compose.yml
services:
dev-container:
image: my-dev-image
environment:
- HOST_PWD=${PWD} # ホストの現在のパスを注入!
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .:/app/my-project
Step 2: コンテナ内での実行時
コンテナ内で docker run をするときは、自身のカレントディレクトリではなく、注入された HOST_PWD を使います。
# コンテナ内での実行
# ❌ ./src (コンテナ内パス) はダメ
# ⭕ ${HOST_PWD}/src (ホスト側パス) を使う
docker run -v ${HOST_PWD}/src:/target alpine ls /target
これなら、CLIが展開するパスが「ホスト上に実在するパス」になるため、正しくマウントされます。
2. 【Workaround】ホストとコンテナのパスを一致させる
「力技」ですが、シンプルで分かりやすい方法です。
ホスト側でプロジェクトが /home/user/project にあるなら、コンテナ側も全く同じ /home/user/project にマウントして作業します。
# 親コンテナ起動時
docker run -v /home/user/project:/home/user/project ...
こうすれば、「コンテナ内で変換された絶対パス」と「ホスト側の絶対パス」が偶然一致するため、何も考えずに相対パスを使っても動作します。
まとめ
- Docker CLI は、自分がいる場所(コンテナ)を基準にパスを解釈する。
- Docker Daemon は、自分がいる場所(ホスト)を基準にファイルを探す。
- DooD (ソケット共有) では、この「視点のズレ」を意識して、常に 「ホスト側のパス」 を指定する必要がある。
「相対パスで書くとダメ、絶対パスならOK」という挙動に悩まされたら、「今動かそうとしているコンテナは、実は私の隣(ホスト上)にいる兄弟なんだ」 と思い出してください。