本稿では、Dockerfileを記述する際の「ちょっとしたテクニック」について紹介します。
※ 本稿は元々、取引先向けに記述した文章ですが、取引先から公開について快諾頂いたため、微調整の上ここに公開しています。
2016年8月頃に記述した文章であるため、古い情報を含んでいる可能性もありますのでご注意下さい。もし何かお気づきの点があれば、コメント、編集リクエストを頂ければ幸いです。
1. 本家の「ベストプラクティス」を読む
まずは、本家の公式ドキュメント『Dockerfile のベストプラクティス』(英語版: 『Best practices for writing Dockerfiles』に目を通しましょう。
2. 既存のDockerイメージを検索する
利用したいアプリケーション、ライブラリなどが有名な(利用者の多い)ものである場合、公式のDockerイメージが公開されている場合があります。Docker Hubで検索してみましょう。構築済みのアプリケーション、ライブラリを含むDockerイメージをベースとすることで、より手軽に、より素早く環境を構築できる可能性があります。
ただし、一般ユーザが公開しているDockerイメージは、公式のDockerイメージと比べてセキュリティ上のリスクが高いため、使用する場合は十分に注意しましょう。
3. RUNに与えるコマンド列の&&を行頭に記述する
RUNコマンドで複数のコマンド列を連続して実行する場合、a && b(aの終了ステータスが0ならbを実行)を
使うことが多いです。その&&を行末ではなく、行頭に書きましょう。
利点
- コマンドが連続していることが明確になる。
- (抽象的な表現ですが)「リズム感」が出る。
具体例
protobufをビルドする例:
RUN cd /root \
&& git clone --depth=1 -b release-0_12 --recursive https://github.com/grpc/grpc.git \
&& cd /root/grpc/third_party/protobuf \
&& ./autogen.sh \
&& ./configure --prefix="${INSTDIR_GRPC}" \
&& make -j \
&& make install
4. [Ubuntu/Debian] apt-get installに与えるパッケージリストをソートする
『Dockerfile のベストプラクティス』でも紹介されていますが、apt-get installに与えるパッケージのリストをソートしておくと良いです。
利点
- 誰が書いても同じ順序になる。
- パッケージ名が重複することを防ぐことができる。
欠点
- 順序による表現ができなくなる。(例えばパッケージ間の関連など)
具体例
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
autoconf \
build-essential \
curl \
git \
libtool \
pkg-config \
unzip \
zlib1g-dev \
&& rm --recursive --force /var/lib/apt/lists/*
5. [Ubuntu/Debian] apt-get installしたらapt-get cleanする
『Dockerfile のベストプラクティス』でも紹介されていますが、apt-get installを実行したらapt-get cleanも実行しましょう。
利点
- Dockerイメージサイズを小さくできる。
具体例
『4. [Ubuntu/Debian] apt-get installに与えるパッケージリストをソートする』を参照のこと。
6. git clone --depth=1を使用する
GitリポジトリのHEAD、または特定のブランチをビルドしたい場合、git cloneコマンドの--depth=1
オプションを使用することで「shallow clone」(浅いクローン)することができます。また、--single-branchオプションを併用することで、より時間を短縮できる可能性があります。
利点
-
git cloneに要する時間が短縮される。 - Gitリポジトリの取得に必要なストレージサイズが少なく済む。
欠点
- 指定できるのばブランチ名のみ。タグ名、コミットIDは指定できない。
- 指定したコミット数分のログしか確認できない。
- 「shallow clone」したリポジトリに対する操作が限定される。
具体例
『3. RUNに与えるコマンド列の&&を行頭に記述する』を参照のこと。
参考: 実験
実際にどの程度の時間差が生じるのかを実験してみました。6倍程度の時間差が生じていること、Counting objectsの数値がかなり小さくなっていることが確認できます。
| No | パターン | 実時間 |
|---|---|---|
| 1 |
--branchのみ |
0m26.482s |
| 2 |
--branch+--depth=1
|
0m4.218s |
| 3 |
--branch+--depth=1+--single-branch
|
0m4.965s |
$ time git clone --branch=release-0_12 https://github.com/grpc/grpc.git
Cloning into 'grpc'...
remote: Counting objects: 188327, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 188327 (delta 5), reused 3 (delta 3), pack-reused 188310
Receiving objects: 100% (188327/188327), 76.43 MiB | 4.67 MiB/s, done.
Resolving deltas: 100% (141584/141584), done.
Checking connectivity... done.
real 0m26.482s
user 0m13.348s
sys 0m1.696s
$ time git clone --branch=release-0_12 --depth=1 https://github.com/grpc/grpc.git
Cloning into 'grpc'...
remote: Counting objects: 4254, done.
remote: Compressing objects: 100% (2953/2953), done.
remote: Total 4254 (delta 2115), reused 2221 (delta 1216), pack-reused 0
Receiving objects: 100% (4254/4254), 3.70 MiB | 2.61 MiB/s, done.
Resolving deltas: 100% (2115/2115), done.
Checking connectivity... done.
real 0m4.218s
user 0m0.556s
sys 0m0.240s
$ time git clone --branch=release-0_12 --depth=1 --single-branch https://github.com/grpc/grpc.git
Cloning into 'grpc'...
remote: Counting objects: 4254, done.
remote: Compressing objects: 100% (2953/2953), done.
remote: Total 4254 (delta 2115), reused 2221 (delta 1216), pack-reused 0
Receiving objects: 100% (4254/4254), 3.70 MiB | 3.10 MiB/s, done.
Resolving deltas: 100% (2115/2115), done.
Checking connectivity... done.
real 0m4.965s
user 0m0.604s
sys 0m0.220s
参考情報
7. git clone --recursiveを使用する
Gitリポジトリにサブモジュールが含まれている場合、git cloneコマンドの--recursiveオプションを
使用することで、サブモジュールも一括して取得することができます。なお、--recurse-submodulesという別名のオプションも存在します。
利点
- サブモジュールを取得するコマンド(
git submodule update --initなど)を実行する必要がない。
欠点
- すべてのサブモジュールが必要ではない場合、余分なサブモジュールまで取得してしまう。
具体例
『3. RUNに与えるコマンド列の&&を行頭に記述する』を参照のこと。