docker-composeを使った開発では以下の2つのディレクトリ構成になっていることが多いです。
.
├ app
│ ├ Dockerfile
│ └ ...その他ファイル群
├ api
│ ├ Dockerfile
│ └ ...その他ファイル群
├ nginx
│ ├ Dockerfile
│ └ ...その他ファイル群
└ docker-compose.yml
.
├ app/
├ api/
├ nginx/
├ docker
│ ├ app
│ │ └ Dockerfile
│ ├ api
│ │ └ Dockerfile
│ └ nginx
│ └ Dockerfile
└ docker-compose.yml
Dockerの コンテキスト という概念を知っていないと、ディレクトリ構成が違うだけで何度も__コンテキスト__周りのエラーで悩まされることがあります(1敗)。
なので自分的結論を出してみました。
TL;DR
docker-compose を使った開発では
version: "3"
services:
nginx:
build: ./docker/nginx/
のようにDockerfileがあるディレクトリを指定するだけでなく
services:
nginx:
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
のようにコンテキストをルートディレクトリに指定して、Dockerfileの場所も直接指定しておけばおk
Dockerの「コンテキスト」とは?
docker build コマンドを実行したときの、カレントなワーキングディレクトリのことを ビルドコンテキスト(build context)と呼びます。 デフォルトで Dockerfile は、カレントなワーキングディレクトリにあるものとみなされます。 ただしファイルフラグ(-f)を使って別のディレクトリとすることもできます。 Dockerfile が実際にどこにあったとしても、カレントディレクトリ配下にあるファイルやディレクトリの内容がすべて、ビルドコンテキストとして Docker デーモンに送られることになります。
これをさらに要約すれば 「docker buildコマンドを実行した場所」ってことですね。
docker build
コマンドを実行した場所ってことなので、docker build
コマンドはDockerfileがあるディレクトリで実行すれば問題なさそうですね。
しかし、docker-compose
コマンドを使って開発している場合はどうでしょうか?
Dockerfileがあるディレクトリでコマンドを実行することってほとんど無いと思います。その場合はコンテキストについてどう考えればいいのでしょうか?
サービス直下にDockerfileを置く場合
例として「Laravel, Nginx」というよくあるプロジェクトの構成で考えてみます。
ディレクトリ構成は以下の様になります。
.
├ api
│ ├ Dockerfile
│ ├ app
│ ├ bootstrap
│ └ ...その他のLaravelファイル
├ nginx
│ ├ Dockerfile
│ └ default.conf
└ docker-compose.yml
このディレクトリ構成の場合、api/Dockerfile
とnginx/Dockerfile
のコンテキストはそれぞれどこになるか分かりますか?
docker build
コマンドを実行するapi/
、nginx/
ディレクトリ?
nginx/
ディレクトリがコンテキストだとするとnginx/Dockerfile
は以下のようになります。
FROM nginx:1.18-alpine
ADD ./default.conf /etc/nginx/conf.d/default.conf
RUN mkdir -p /var/www/public
ADD ../api/public /var/www/public
以下の1文に注目してください。コンテキストが nginx/
なのに、 ../api/public
で分かる通り、コンテキストのディレクトリから外れたファイルを参照していますね。
ADD ../api/public /var/www/public
このまま実行すると
ERROR: Service 'php' failed to build: COPY failed: stat /var/lib/docker/tmp/docker-builder115741816/api: no such file or directory
のようなエラーが出ます。
Dockerはコンテキスト(カレントディレクトリ)の外のファイルにはアクセスできない仕様なのです。そこら辺に関しては以下の記事で詳しく説明されています。
ではどうやってnginx/
のコンテキストからapi/
のファイルにアクセスすればいいのでしょうか?
答えは簡単です。
コンテキストをルートディレクトリにすればいいのです
docker-compose.yml
で「コンテキスト」と「Dockerfileのある場所」を直接指定してみましょう。
version: "3"
services:
nginx:
build:
context: .
dockerfile: ./nginx/Dockerfile
ports:
- 8080:80
depends_on:
- php
php:
build:
context: .
dockerfile: ./api/Dockerfile
ports:
- 9000:9000
コンテキストはどちらのサービスも
build:
context: .
でdocker-compose.yml
があるルートディレクトリに設定。
Dockerfileの場所は
dockerfile: ./nginx/Dockerfile
dockerfile: ./api/Dockerfile
でそれぞれ指定。
dockerディレクトリにDockerfileをまとめた場合
では次にdocker
というディレクトリを作って、その中に各サービスのDockerfileをまとめた構成を考えてみます。
.
├ api
│ └ Laravelのファイル群
├ nginx
│ └ default.conf
├ docker
│ ├ php
│ │ └ Dockerfile
│ └ nginx
│ └ Dockerfile
└ docker-compose.yml
先ほどと同様にコンテキストを docker/php
やdocker/nginx
と考えた場合、どうやってもうまくいきません。
このディレクトリ構成の場合は、そもそもDockerfileからコンテキスト外のサービスのファイル群が入っている api/
とnginx/
に一切アクセスできません。
ここでも同様docker-compose.yml
でコンテキストをルートディレクトリに、Dockerfileの位置も直接指定する必要がありそうです。
version: "3"
services:
nginx:
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
ports:
- 8080:80
depends_on:
- php
php:
build:
context: .
dockerfile: ./docker/api/Dockerfile
ports:
- 9000:9000
注意点
ルートディレクトリをDockerのコンテキストにすることで、Dockerfileはどんなファイルにもアクセスできるようになりました。
一方で、build
時はその分Dockerデーモンという奴にそれだけ多くのファイルを送ることになるので遅くなることがあるようです。