5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【図解】なぜコンテナ内からマウントすると空っぽになるのか?「兄弟コンテナ」の仕組み

Posted at

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)は「ホスト」にいるからです。


発生する現象の再現

例えば、以下のような構成で作業しているとします。

  1. 開発コンテナ (Dev Container) 内で作業中。
  2. このコンテナには /var/run/docker.sock がマウントされている。
  3. 現在地は /app/my-project。ここに src ディレクトリがある。
# 開発コンテナ内での操作
$ ls -F
src/  Dockerfile

# "src" を新しいコンテナにマウントしたい(相対パス指定)
$ docker run -v ./src:/target alpine ls -F /target

期待する結果: src ディレクトリの中身が表示される。
実際の結果: 何も表示されない(空っぽ)、あるいはディレクトリが見つからないエラー。


【図解】なぜ失敗するのか?(CLIとDaemonのすれ違い)

この失敗の裏側では、CLI(あなた)とDaemon(実行者)の間で、致命的な**「場所の認識ズレ」**が起きています。

詳細解説:何が起きているのか

  1. CLIの親切心 (Path Expansion)
    docker コマンド(CLI)は、ユーザーが入力した相対パス ./src を、実行された場所(コンテナ内)のカレントディレクトリ を元に絶対パス /app/my-project/src に変換します。これはCLIの標準的な仕様です。

  2. APIリクエスト
    CLIは変換後の絶対パスを使って、Docker Daemonに命令を送ります。「ボリューム /app/my-project/src をマウントしてね」

  3. Daemonの実直さ
    命令を受け取った Docker Daemon は、ホストOS上 で動作しています。そのため、ホストのファイルシステム上/app/my-project/src を探します。

  4. 不一致 (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」という挙動に悩まされたら、「今動かそうとしているコンテナは、実は私の隣(ホスト上)にいる兄弟なんだ」 と思い出してください。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?