2部の最後は Dockerfile について学びます。
このページまでの基礎知識と Dockerfile の読み書きをきちんと理解できれば、もう頑張って学ぶフェーズは終わり、あとは特定の用途に合わせて機能を知るフェーズになります。
このページで初登場するコマンドとオプション
イメージをビルドする - image build
$ docker image build [option] <path>
$ docker build [option] <path>
オプション | 意味 | 用途 |
---|---|---|
-f, --file |
Dockerfile を指定する | 複数の Dockerfile を使い分ける |
-t, --tag |
ビルド結果にタグをつける | 人間が把握しやすいようにする |
イメージのレイヤーを確認する - image history
$ docker image history [option] <image>
$ docker history [option] <image>
Dockerfile の必要性と有用性
ここまでで次のことを理解しました。
- コンテナ内で行った操作は、コンテナ終了とともに全てなかったことになる
- イメージには
.img
のような実体はなく、レイヤーという情報の積み重なったものである
しかし Docker Hub にある公式イメージなどは軽量にするためにレイヤーが最低限しか積み上がっておらず、あまり多機能ではありません。
たとえば Ubuntu コンテナには vi
も curl
も入っていませんでした。
そこで「公式イメージでは十分なセットアップが得られない」場合に「あらかじめ必要なセットアップを済ませたイメージを自分で作成しておく」というアプローチを取ることになります。
Dockerfile は既存のイメージ ( = レイヤーたち ) に追加でレイヤーを乗せることができるので、OS 設定などの労力を払わずに簡単にイメージを作成することができます。
Dockerfile の基本の命令
Dockerfile にはいくつかの命令句がありますが、全てを一度に覚える必要はないため、代表的なものをいくつか学びます。
命令 | 効果 |
---|---|
FROM |
ベースイメージを指定する |
RUN |
任意のコマンドを実行する |
COPY |
ホストマシンのファイルをイメージに追加する |
CMD |
デフォルト命令を指定する |
Dockerfile を書きながら1つずつ説明します。
行番号が表示される設定がされた vi
の使える、時間を表示してくれるイメージを作ってみます。
Dockerfile の作成
Dockerfile は1つのイメージのために1つ用意します。
自分のホストマシンのどこでも構わないので、適当なディレクトリを用意して Dockerfile
( 拡張子はありません ) を作成してください。
$ touch Dockerfile
この Dockerfile
はこのページでのみ使います。
使うエディタはなんでも構いません。
FROM: ベースイメージを指定する
FROM
はベースになるイメージを指定する命令です。
次のように Dockerfile
に FROM
命令を追記すると「これから ubuntu:20.04
のレイヤーの上にさらにレイヤーを乗せていくぞ」という意味になります。
FROM ubuntu:20.04
Dockerfile は FROM
で始まります。
RUN: 任意のコマンドを実行する
RUN
は Linux のコマンドを実行してその結果をレイヤーとする命令です。
ubuntu:20.04
イメージに vi
をインストールするレイヤーを重ねるには、次のように Dockerfile
に RUN
命令を追記します。
FROM ubuntu:20.04
RUN apt update
RUN apt install -y vim
RUN
により「コンテナを起動するたびに vi
をインストールする」という手間を解決できます。
COPY: ホストマシンのファイルをイメージに追加する
COPY
はホストマシンのファイルをイメージに追加する命令です。
ubuntu:20.04
イメージに行番号を表示する設定を記した .vimrc
を配置するには、まずホストマシンに .vimrc
を作ります。
set number
次に、Dockerfile
に COPY
命令を追記します。
FROM ubuntu:20.04
RUN apt update
RUN apt install -y vim
COPY .vimrc /root/.vimrc
COPY
により「コンテナを起動するたびに .vimrc
を作成する」という手間を解決できます。
CMD: デフォルト命令を指定する
CMD
はイメージのデフォルト命令を設定する命令です。
bash
の起動する汎用イメージではなく、特定のフォーマットで現在時刻を表示するイメージにしたいので、次のように Dockerfile
に CMD
命令を追記します。
FROM ubuntu:20.04
RUN apt update
RUN apt install -y vim
COPY .vimrc /root/.vimrc
CMD date +"%Y/%m/%d %H:%M:%S ( UTC )"
CMD
により「container run
で date +"%Y/%m/%d %H:%M:%S ( UTC )"
という複雑な引数を毎回指定する」という手間を解決できます。
確認
Dockerfile
と .vimrc
を作成しました。
ホストマシンで確認すると次のようになっているはずです。
$ tree -a .
.
|-- .vimrc
`-- Dockerfile
FROM ubuntu:20.04
RUN apt update
RUN apt install -y vim
COPY .vimrc /root/.vimrc
CMD date +"%Y/%m/%d %H:%M:%S ( UTC )"
set number
イメージをビルドする
Dockerfile
ができたので、次はイメージのビルドを行います。
$ docker image build [option] <path>
[option]
には --tag
オプションを指定して my-ubuntu:date
というタグをつけます。
タグを指定しないでビルドを行うとランダム文字列の IMAGE ID
でしか指定できなくなってしまい不便です。
<path>
は COPY
に使うファイルがあるディレクトリ .
を指定します。
以上を踏まえ、次のコマンドでイメージをビルドします。
このコマンドは Dockerfile
のあるディレクトリで実行してください。
$ docker image build \
--tag my-ubuntu:date \
.
最後にこのような出力がされていれば成功です。
=> => writing image sha256:f099b72286fd6e3f80d099ea4301316eb6a8f0d8d3eda7cbaafc4a5b62452e0f
image ls
でイメージ一覧を確認すると、my-ubuntu:date
というイメージが作成できていることが確認できます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
my-ubuntu date f099b72286fd 51 seconds ago 160MB
nginx 1.21 2e7e2ec411a6 3 weeks ago 134MB
ubuntu 22.04 63a463683606 4 weeks ago 70.4MB
ubuntu 21.10 2a5119fc922b 4 weeks ago 69.9MB
ubuntu 20.04 9f4877540c73 4 weeks ago 65.6MB
ubuntu latest 9f4877540c73 4 weeks ago 65.6MB
意図した通りのイメージになっているか確認するため、デフォルト命令でコンテナを起動します。
$ docker container run \
--name my-ubuntu1 \
--rm \
my-ubuntu:date
2022/02/20 08:12:16 ( UTC )
CMD
によるデフォルト命令の指定が意図通りであることを確認できました。
次は RUN
と COPY
の結果を確認するために指定命令 ( vi
) でコンテナを起動します。
$ docker container run \
--name my-ubuntu2 \
--rm \
--interactive \
--tty \
my-ubuntu:date \
vi
# vi が起動する
# :q! で vi を終了する
行番号の表示される vi
が起動するはずです。
基本的な Dockerfile の作成とイメージのビルドは、このような手順になります。
Dockerfile を複数扱うには
イメージをビルドするときに Dockerfile
のパスを指定していなかったことが気になったかもしれませんが、image build
は ./Dockerfile
を使うようになっているので問題ありません。
しかし実際に Docker を使って開発をする場合は「アプリケーションのコンテナ」や「データベースのコンテナ」など複数のコンテナを活用することになります。
それに伴い Dockerfile も複数になるため、一般には Dockerfile と COPY
に使うためのファイルはイメージごとにディレクトリを分けて管理することが多いです。
このページではこれ以上 Dockerfile を作りませんが、ディレクトリを分ける方法だけ確認しておきます。
まずは次のように docker/date/
ディレクトリを作成し、このページで作成した Dockerfile
と .vimrc
を移動します。
$ tree -a .
.
`-- docker
`-- date
|-- .vimrc
`-- Dockerfile
このディレクトリ構成でイメージのビルドを行うには、image build
に --file
オプションの追加と <path>
の変更が必要です。
$ docker image build \
--tag my-ubuntu:date \
--file docker/date/Dockerfile \
docker/date
--file
オプションは ./Dockerfile
以外の Dockerfile を指定するときに必要です。
次に <path>
ですが、これは COPY
で使うファイルを指定するときの相対パスになります。
COPY .vimrc /root/.vimrc
の .vimrc
は 実行ディレクトリ/<path>/.vimrc
として解釈されます。
.vimrc
を移動したので、それにあわせて <path>
は docker/date
と指定します。
$ tree -a .
. $ docker image build [option] docker/date
`-- docker ^^^^^^^^^^^
`-- date
|-- .vimrc
`-- Dockerfile COPY (./)(docker/date/).vimrc /root/.vimrc
^^^^^^^^^^^
image build
で .
を指定したいなら、COPY
の方を調整します。
$ tree -a .
. $ docker image build [option] .
`-- docker ^
`-- date
|-- .vimrc
`-- Dockerfile COPY (./)(./)docker/date/.vimrc /root/.vimrc
^
COPY
も <path>
も .
にしたいなら実行するディレクトリを変えれば良いですが、複数のイメージをビルドするたびに cd
しなければいけないのでおすすめしません。
$ tree -a .
.
`-- docker
`-- date $ docker image build [option] .
|-- .vimrc ^
`-- Dockerfile COPY (./docker/date/)(./).vimrc /root/.vimrc
^
どの方法を用いても良いですが、僕は image build
が .
で済む COPY docker/date/.vimrc /root/.vimrc
の方法をよく使います。
ディレクトリ名 ( docker/date
) を変更すると Dockerfile が壊れますが、そうそうあることではないので許容しています。
レイヤーを確認する
ローカルに存在するイメージのレイヤー情報を image history
で確認することができます。
$ docker image history [option] <image>
ubuntu:20.04
と my-ubuntu:date
のレイヤーを比べてみます。
$ docker image history ubuntu:20.04
IMAGE CREATED CREATED BY SIZE COMMENT
9f4877540c73 3 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 days ago /bin/sh -c #(nop) ADD file:3acc741be29b0b58e… 65.6MB
$ docker image history my-ubuntu:date
IMAGE CREATED CREATED BY SIZE COMMENT
db18651e322c 33 minutes ago CMD ["/bin/sh" "-c" "date +\"%Y/%m/%d %H:%M:… 0B buildkit.dockerfile.v0
<missing> 33 minutes ago COPY .vimrc /root/.vimrc # buildkit 20B buildkit.dockerfile.v0
<missing> 49 minutes ago RUN /bin/sh -c apt install -y vim # buildkit 67.3MB buildkit.dockerfile.v0
<missing> 50 minutes ago RUN /bin/sh -c apt update # buildkit 27.6MB buildkit.dockerfile.v0
<missing> 3 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 days ago /bin/sh -c #(nop) ADD file:3acc741be29b0b58e… 65.6MB
my-ubuntu:date
は ubuntu:20.04
を FROM
でベースイメージに指定したので、my-ubuntu:date
の下 2 層は ubuntu:20.04
を同じ になっています。
その上に Dockerfile に書いた RUN
RUN
COPY
CMD
が積み重なっていることが確認できます。
一番上まで積み重ねて image build
によるビルドが完了したレイヤーに IMAGE ID
( db18651e322c
) が振られています。
( 余談 ) RUN をいくつのレイヤーにするか
若干細かい話になるので理解をこの本の読破より後に回しても大丈夫です。
普段目にする Dockerfile は RUN apt update && apt install -y vim
のように 1つの RUN
で複数の Linux コマンドを連続して実行 しているものが大半だと思います。
これは RUN
がコマンドの結果をレイヤーとして確定する という点に注目すると意図が読み取りやすいです。
たとえば次のような「.java
を持ってきてコンパイルして .jar
を手に入れたい、けど .java
自体はいらない」という仮想の Dockerfile があった場合を考えます。
RUN git clone https://github.com/suzuki-hoge/some-java-tool
RUN cd some-java-tool
RUN compile
RUN cp some-java-tool.jar some-dir
RUN cd ..
RUN rm -rf some-java-tool
RUN
は結果をレイヤーとして確定する ので、git clone
が成功した時点のレイヤーをイメージに含みます。
対して、次の Dockerfile は 6つのコマンド全てが終わってから1つのレイヤーを確定する ので、途中で存在したファイルはイメージに含まれません。
RUN git clone https://github.com/foo/some-java-tool && \
cd some-java-tool && \
compile && \
cp some-java-tool.jar some-dir && \
cd .. && \
rm -rf some-java-tool
イメージのサイズを気にする場合は、このような点に気を付けたりマルチステージビルド ( この本では解説しません ) を活用すると良いでしょう。
Linux コマンドを繋げるもう1つの理由は、レイヤーがキャッシュされる という点です。
次のような Dockerfile をビルドしたあとに、
RUN apt update
RUN apt install -y vim
次のような Dockerfile に変更してビルドをすると問題が発生するかもしれません。
RUN apt update
RUN apt install -y vim curl
変更のあったレイヤーは2つめの apt install
の方だけなので、1つめのレイヤーとして確定している apt update
は再実行されません。
次のように書いておけば 1つめのレイヤーに変更があったと判断されるため apt update
から再実行されます。
RUN apt update && apt install -y vim curl
反対に、RUN
を繋げすぎると Dockerfile の構築中などに次のようなデメリットも発生します。
- Linux コマンドに不備があり
RUN
が失敗した場合、繋げすぎたコマンドはどこで失敗したか極めてわかりづらい - 繋げすぎたコマンドの後半でエラーが出ると、キャッシュがないので初めから再実行になる
「構築時はバラして完成したら繋げる」のように状況に応じて RUN
を書けるとよいでしょう。
Docker Hub のレイヤー情報を読み解く
【 2部: イメージの基礎 】で 22 もレイヤーがある rails:5.0.1
の話をしました。
Rails の Tags ページ から
5.0.1
を選んでレイヤーを確認してみると、実に 22 ものレイヤーがあることが確認できます。
しかし 公開されている Dockerfile は 4 レイヤーしか作っていません。
FROM ruby:2.3
# see update.sh for why all "apt-get install"s have to stay as one long line
RUN apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*
# see http://guides.rubyonrails.org/command_line.html#rails-dbconsole
RUN apt-get update && apt-get install -y mysql-client postgresql-client sqlite3 --no-install-recommends && rm -rf /var/lib/apt/lists/*
ENV RAILS_VERSION 5.0.1
RUN gem install rails --version "$RAILS_VERSION"
次の2点を理解できると、なぜレイヤーの数と Dockerfile の行数が一致しないかがわかります。
- Docker Hub のレイヤーページで確認できるのはレイヤー
- Dockerfile は FROM で指定したイメージにレイヤーを重ねられる
この画面で見ている rails:5.0.1
のレイヤーは、もともと 18 のレイヤーがある ruby:2.3
を Dockerfile の FROM
で指定して、そこにさらに 4 レイヤーを重ねたものだからです。
頭の片隅にでも入れておくと、役に立つときが来るでしょう。
まとめ
簡潔にまとめます。
-
FROM
はベースイメージを指定する -
RUN
は Linux コマンドを実行してレイヤーを確定する -
COPY
はホストマシンのファイルをイメージに追加する -
CMD
はデフォルト命令を指定する - イメージをビルドするときは
<path>
とCOPY
を調整する - 次のようなことを考慮して
RUN
で確定するレイヤーの単位を決める- イメージサイズやキャッシュなどの利点
- 構築やデバッグのしづらさなどの難点
-
FROM
で指定したイメージのレイヤーに Dockerfile で指定したレイヤーが乗る
混乱してしまったときは立ち返ってみてください。
イメージについていろいろ調べる方法が気になる方は、先日公開したこの記事をご覧ください。