Dockerfile Tips

本稿では、`Dockerfile`を記述する際の「ちょっとしたテクニック」について紹介します。

*※ 本稿は元々、取引先向けに記述した文章ですが、取引先から公開について快諾頂いたため、微調整の上ここに公開しています。
2016年8月頃に記述した文章であるため、古い情報を含んでいる可能性もありますのでご注意下さい。もし何かお気づきの点があれば、コメント、編集リクエストを頂ければ幸いです。*

## 1. 本家の「ベストプラクティス」を読む

まずは、本家の公式ドキュメント『[Dockerfile のベストプラクティス](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)』（英語版: 『[Best practices for writing Dockerfiles](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/)』に目を通しましょう。


## 2. 既存のDockerイメージを検索する

利用したいアプリケーション、ライブラリなどが有名な（利用者の多い）ものである場合、公式のDockerイメージが公開されている場合があります。[Docker Hub](https://hub.docker.com/)で検索してみましょう。構築済みのアプリケーション、ライブラリを含む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 のベストプラクティス](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)』でも紹介されていますが、`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 のベストプラクティス](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)』でも紹介されていますが、`apt-get install`を実行したら`apt-get clean`も実行しましょう。

### 利点

* Dockerイメージサイズを小さくできる。

### 具体例

『[4. [Ubuntu/Debian] apt-get installに与えるパッケージリストをソートする](#4-ubuntudebian-apt-get-install%E3%81%AB%E4%B8%8E%E3%81%88%E3%82%8B%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B)』を参照のこと。

## 6. `git clone --depth=1`を使用する

Gitリポジトリの`HEAD`、または特定のブランチをビルドしたい場合、`git clone`コマンドの`--depth=1`
オプションを使用することで「shallow clone」（浅いクローン）することができます。また、`--single-branch`オプションを併用することで、より時間を短縮できる可能性があります。

### 利点

* `git clone`に要する時間が短縮される。
* Gitリポジトリの取得に必要なストレージサイズが少なく済む。

### 欠点

* 指定できるのばブランチ名のみ。タグ名、コミットIDは指定できない。
* 指定したコミット数分のログしか確認できない。
* 「shallow clone」したリポジトリに対する操作が限定される。

### 具体例

『[3. RUNに与えるコマンド列の&&を行頭に記述する](#3-run%E3%81%AB%E4%B8%8E%E3%81%88%E3%82%8B%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E5%88%97%E3%81%AE%E3%82%92%E8%A1%8C%E9%A0%AD%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8B)』を参照のこと。

### 参考: 実験

実際にどの程度の時間差が生じるのかを実験してみました。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
```

### 参考情報

* [Git - git-clone Documentation](https://git-scm.com/docs/git-clone)
* [巨大なリポジトリ を Git で上手く扱う方法 - Atlassian Japan](http://japan.blogs.atlassian.com/2014/05/handle-big-repositories-git/)
* [git で shallow clone - Qiita](http://qiita.com/sonots/items/ce08c30d161ea0b4d5fd)

## 7. `git clone --recursive`を使用する

Gitリポジトリにサブモジュールが含まれている場合、`git clone`コマンドの`--recursive`オプションを
使用することで、サブモジュールも一括して取得することができます。なお、`--recurse-submodules`という別名のオプションも存在します。

### 利点

* サブモジュールを取得するコマンド（`git submodule update --init`など）を実行する必要がない。

### 欠点

* すべてのサブモジュールが必要ではない場合、余分なサブモジュールまで取得してしまう。

### 具体例

『[3. RUNに与えるコマンド列の&&を行頭に記述する](#3-run%E3%81%AB%E4%B8%8E%E3%81%88%E3%82%8B%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E5%88%97%E3%81%AE%E3%82%92%E8%A1%8C%E9%A0%AD%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%99%E3%82%8B)』を参照のこと。

### 参考情報

* [Git - git-clone Documentation](https://git-scm.com/docs/git-clone)