docker-compose.ymlのbuild設定はとりあえずcontextもdockerfileも埋めとけって話

docker-composeを使った開発では以下の2つのディレクトリ構成になっていることが多いです。

```text:サービスディレクトリ直下にDockerfile
.
├ app
│ ├ Dockerfile
│ └ ...その他ファイル群
├ api
│ ├ Dockerfile
│ └ ...その他ファイル群
├ nginx
│ ├ Dockerfile
│ └ ...その他ファイル群
└ docker-compose.yml
```

```text:dockerディレクトリ下にサービス毎にDockerfile
.
├ app/
├ api/
├ nginx/
├ docker
│ ├ app
│ │ └ Dockerfile
│ ├ api
│ │ └ Dockerfile
│ └ nginx
│ 　 └ Dockerfile
└ docker-compose.yml	
```

Dockerの __コンテキスト__ という概念を知っていないと、ディレクトリ構成が違うだけで何度も__コンテキスト__周りのエラーで悩まされることがあります(1敗)。
なので自分的結論を出してみました。

# TL;DR
docker-compose を使った開発では

```yml:docker-compose.yml
version: "3"
services:
  nginx:
    build: ./docker/nginx/
```
のようにDockerfileがあるディレクトリを指定するだけでなく

```yml:docker-compose.yml
services:
  nginx:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
```

のようにコンテキストをルートディレクトリに指定して、Dockerfileの場所も直接指定しておけばおk


# Dockerの「コンテキスト」とは？
> docker build コマンドを実行したときの、カレントなワーキングディレクトリのことを ビルドコンテキスト（build context）と呼びます。 デフォルトで Dockerfile は、カレントなワーキングディレクトリにあるものとみなされます。 ただしファイルフラグ（-f）を使って別のディレクトリとすることもできます。 Dockerfile が実際にどこにあったとしても、カレントディレクトリ配下にあるファイルやディレクトリの内容がすべて、ビルドコンテキストとして Docker デーモンに送られることになります。

[Dockerfile 記述のベストプラクティス](https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/dockerfile_best-practices/#%E3%83%93%E3%83%AB%E3%83%89%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E7%90%86%E8%A7%A3)

これをさらに要約すれば 「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`は以下のようになります。

```dockerfile: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` で分かる通り、コンテキストのディレクトリから外れたファイルを参照していますね。

```dockerfile:nginx/Dockerfile
ADD ../api/public /var/www/public
```

このまま実行すると

```shell
ERROR: Service 'php' failed to build: COPY failed: stat /var/lib/docker/tmp/docker-builder115741816/api: no such file or directory
```

のようなエラーが出ます。

Dockerはコンテキスト(カレントディレクトリ)の外のファイルにはアクセスできない仕様なのです。そこら辺に関しては以下の記事で詳しく説明されています。

https://qiita.com/toshihirock/items/c85f3eb5f4752b15ca3d

ではどうやって`nginx/`のコンテキストから`api/`のファイルにアクセスすればいいのでしょうか？
答えは簡単です。

__コンテキストをルートディレクトリにすればいいのです__

`docker-compose.yml`で「コンテキスト」と「Dockerfileのある場所」を直接指定してみましょう。

```yml:docker-compose.ym;
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の位置も直接指定する必要がありそうです。

```yml:docker-commpose.yml
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デーモンという奴にそれだけ多くのファイルを送ることになるので遅くなることがあるようです。