はじめに
Dockerを根本的・体系的に理解するために、このシリーズを書き始めました。
Docker理解への道① ~Makefile / docker compose編~
Docker理解への道② 〜docker-compose.ymlの徹底的な理解〜
いきなりDockerそのものを理解するのではなく、理解しやすいものから学習していくつもりです。
前回docker-compose.ymlのサンプルを書いてみて、各サービス(コンテナ)に必要な設定を学びました。今回はその中で出てきた、「image」と「build」の2つについて深掘りしていこうと思います。
まずは意味をおさらい。
nginx: # 任意のコンテナ名
image: nginx:alpine # 実際に使う既製品
sample-api: # 任意のコンテナ名
build: # 自分でDockerfile用意してコンテナを作る
context: ./sample-api # Dockerfileがあるフォルダ
dockerfile: Dockerfile # ファイル名
args: # ビルド時に渡す変数
NODE_VERSION: 18
既存のイメージを使いたい場合は、「image: イメージ名」で指定できます。ここの例で使っているnginx:alpineは、公開リポジトリとして提供されています。既製品なので、ymlファイル上で直接手を加えることはできません。
反対にbuildは、用意したDockerfileを元に自分でコンテナを作ることができます。contextでそのDockerfileのパス、dockerfileでそのファイル名、そしてargsで渡したい変数を定義できます。
ビルドといっても、一から作ることは稀で、大体ベースとなる既存イメージをfrom命令で指定して、必要な変更を加えるのが一般的です。
ymlファイルではイメージの外部設定(ネットワーク設定、他イメージとの依存など)しか定義しないのが一般的である一方で、Dockerfileではイメージ内で使用するコマンドなど、イメージの内部設定も定義します。とはいえ、argsを通せば、ymlファイルでもイメージ内部に干渉できます。
まとめているなかで、一つ自分の中で疑問が浮かびました。
「え、両ファイルともイメージ内外の定義ができるなら、使い分ける意味あるの?全部ymlにまとめて書いた方が楽じゃない?」
自分の中で考えた結果、両ファイルの違いとして、ymlはプロジェクト全体の設計図、Dockerfileは各イメージの設計図なので、ymlファイルに全部定義することも可能ではあるが、分けることでプロジェクトごとにymlファイルやDockerfileを使いまわせます利点があるという結論に至りました。もちろん役割が分かれているので視認性も上がります。
またもう一つ疑問が浮かびました。
「あれイメージの設計図って何?イメージも設計図では?」
ここの理解かなり苦しみました。厳密にはDockerfileは人間が読み書きできるファイルで、イメージを構成する手順が書かれています。一方の、イメージはソースコードとしては存在せず、Dockerfileに従ってビルドされた不変のパッケージとして存在しています。docker compose upの際にコンテナはymlファイルの内容とこのパッケージをもとに起動します。
つまりDockerfileからイメージと、イメージからコンテナで、2段階の構築が行われているのです。