Best practices for writing Dockerfiles 日本語訳（仮）

#### 訳者より

この文書はDocker社の許可を得て[Best practices for writing Dockerfiles](https://docs.docker.com/articles/dockerfile_best-practices/)を翻訳したものです．原文に基づき，ライセンスはApache License2.0とします．

稚拙な訳であるとは思いますが，Dockerfileを書く際にお役に立てればと思います．途中，意味不明な文章があり，すみません．

今はまだ稚拙な訳ですし，膨大なドキュメントの1つを翻訳したに過ぎませんが，修正を重ねて翻訳数を増やして行き，将来的にはDocker公式リポジトリに追加できればと思っています．

## 概要
Dockerは与えられたイメージを構築するために必要なすべてのコマンドから構成されたテキストファイルである```Dockerfile```から，手順を読み込むことで自動的にイメージを構築することができます．`Dockerfile`は仕様書に忠実に一通りの手順仕様として使われます．あなたは[Dockerfile Reference](https://docs.docker.com/reference/builder/)ページの基礎を学ぶことができます．もしあなたが`Dockerfile`を書き始める場合はそのページから始めてください．

この文書は，簡単で効果的な`Dockerfile`の作るためにDocker, IncとDockerコミュニティによって推奨される，最も良い手法と方法をカバーしています．私達は，以下の提言を強く推奨します（実際にあなたが公式イメージを作る際に以下の方法に忠実である必要があります）．

あなたは[buildpack-deps](https://github.com/docker-library/buildpack-deps/blob/master/jessie/Dockerfile)```Dockerfile```の活動の中に，以下の手法と推奨事項の多くを見ることができます．

> Note:Dockerfileの詳細な説明についてはDockerfile Referenceページを御覧ください．

## 一般的な指針と推奨事項

### コンテナは短命であるべき

あなたの`Dockerfile`が定義するイメージから生成されるコンテナは，可能な限り短命にすべきです．「短命」であることによって，それが停止・破壊されても，絶対的に最小限のセットアップと設定によって新しいものを構築し設置することができることを表しています．

### [a .dockerignore file](https://docs.docker.com/reference/builder/#the-dockerignore-file)を使いなさい

高速なアップロードと`docker build`の効率化のためには，構築手順や最終イメージからファイルやディレクトリを除外するための`.dockerignore`ファイルを用意するべきです．例えばアップロード時間を悪化させる大容量を抑えるために，`.git`があなたの構築手順やスクリプトに必要なければ，それを`.dockerignore`に追加するべきです．

### 不必要なパッケージのインストールを避けなさい

複雑性，依存性，ファイルサイズ，ビルド時間を減らすために，特に理由のない「あると良いね」と思われているような余分または不必要なパッケージのインストールを避けるべきです．例えば，データベースのイメージにテキストエディタを含む必要はありません．

### 一つのコンテナでは一つのプロセスのみを起動する

あらゆるケースにおいて，あなたは一つのコンテナ内で一つのプロセスのみを動作させるべきです．アプリケーションを複数のコンテナに分散させることは，水平分散を容易にし，コンテナの再利用を容易にします．もし，有るサービスが別のサービスに依存しているようであれば[container linking](https://docs.docker.com/userguide/dockerlinks/)を使いましょう．

### レイヤー数を最小に

` Dockerfile `の読みやすさ（と長期間に渡るメンテナンス）とレイヤー数の最小化のバランスを見つける必要があります．レイヤー数について戦略的かつ慎重になりましょう．

### 複数行のパラメータは並べ替えなさい

可能な限り，複数行に渡る英数字パラメータは並べ替えることによって変更を容易にしなさい．これによって，パッケージの重複排除と更新のためのリストの作成を容易にするでしょう．

[`buildpack-deps` image](https://github.com/docker-library/buildpack-deps)の例を以下に示します．

    RUN apt-get update && apt-get install -y \
      bzr \
      cvs \
      git \
      mercurial \
      subversion

### Buildキャッシュ

イメージ構築処理の間，Dockerは`Dockerfile`の指定順序に従って命令を順次実行していきます．それぞれの命令は，新しい（複製）イメージを作成するよりも，キャッシュの中から再利用できる既存イメージを探すためにDockerが検査します．もしあなたがキャッシュを一切利用したくない場合は，`docker build`コマンドに`--no-cache=true`オプションを付けてください．

しかし，Dockerがキャッシュを使うようにする場合，適合するイメージを見つけることや，キャッシュの適用の有無を理解することはとても重要です．Dockerが従う基本的なルールの概要は以下のとおりです．
* すでにキャッシュにあるベースイメージから開始し，次に正確に同じ手順によって構成されたイメージであれば，そのベースイメージから派生した全ての子イメージと比較されます．そうでなければキャッシュが無効化されます．
* 多くのケースでは`Dockerfile`の命令と一つの子イメージを比較すれば十分です．しかしいくつかの命令は少々の検査と説明が必要となります．
* `ADD`や`COPY`操作では，そのファイルの内容がイメージに転送され検査されます．厳密には，ファイル（達）のチェックサムが検査され，キャッシュを参照している間チェックサムが使われます．もしそのメタデータを含むファイル（達）に違いがあれば，キャッシュは無効化されます．
* 余談ですが，`ADD`や`COPY`コマンドでは，キャッシュが一致することを決定するためにコンテナ内のファイルを参照せずにチェックします．例えば，`RUN apt-get -y update`の処理によるコンテナ内のファイル更新はは，キャッシュがヒットしていることを決定する検査は行われません．この場合，コマンド文字列が一致していることを見つけるために使われます．

一度キャッシュが無効化されると，全ての`Dockerfile`コマンドによる後続手順では，キャッシュは使われずに新規イメージを生成します．

## The Dockerfile instructions

以下，あなたは`Dockerfile`の中で使用可能な様々な命令を書くための最良の方法の勧告を見ることができます．

### [`FROM`](https://docs.docker.com/reference/builder/#from)

可能であれば，あなたのイメージの土台として現行の公式リポジトリを使いましょう．私達は，完全なディストリビューションでありながら，とても厳密に管理され最小限を保っている（現行で100MB以下）[Debina image](https://registry.hub.docker.com/_/debian/)をお勧めします．

### [`RUN`](https://docs.docker.com/reference/builder/#run)

常に`Dockerfile`を読みやすく，理解しやすく，メンテナンスできるようにするために，長いまたは複雑な`RUN`文はバックスラッシュを使って複数行に分けなさい．

おそらく`RUN`の最も一般的なuse-caseは`apt-get`の適用です．`apt-get`を使う際にいくつか気をつける点があります．

* `RUN apt-get update`を単独で使ってはいけません．参照しているアーカイブが更新されていて，それに続く`apt-get install`がコメント無しに失敗する場合，キャッシュ問題を引き起こします．

* 「必須」パッケージの多くは特権のないコンテナ内部でベースイメージからのアップグレードに失敗してしまうため`RUN apt-get upgrade`や`dist-upgrade`は避けましょう．もし更新の必要な固有パッケージ`foo`が分かっているのであれば，`apt-get install -y foo`を使えば自動的に更新されます．

* このように命令を書きましょう

    RUN apt-get update && apt-get install -y package-bar package-foo package-baz

このように命令を書くことにより，簡単かつメンテナンスしやすいだけでなく，`apt-get update`を含むことにより，さらなるコーディングや手作業を必要とせずにキャッシュが無効化され，最新バージョンがインストールされることを保証する．

* さらにバージョン指定パッケージ（例：package-foo=1.3.*）による自然なキャッシュ無効化を実現します．これは，キャッシュの内容に関わらず，指定したバージョンの強制的に検索します．このように`apt-get`を記述することは，メンテナンスをとても簡単にし，要求しているパッケージの予期せぬ変更による障害を削減します．

#### Example

以下は，上記推奨事項を示す，的確な`RUN`命令です．最下行の`s3cdm`パッケージでバージョン`1.1.0*`を指定していることに注目して下さい．もしこのイメージが旧いバージョンを使っていたとしたら，新バージョンの指定が`apt-get update`によるキャッシュ無効化を引き起こし，（新しい要求された機能を持つケースでは）新バージョンのインストールを保証します．

    RUN apt-get update && apt-get install -y \
        aufs-tools \
        automake \
        btrfs-tools \
        build-essential \
        curl \
        dpkg-sig \
        git \
        iptables \
        libapparmor-dev \
        libcap-dev \
        libsqlite3-dev \
        lxc=1.0* \
        mercurial \
        parallel \
        reprepro \
        ruby1.9.1 \
        ruby1.9.1-dev \
        s3cmd=1.1.0*

このような書式で命令を書くことは，以下の様な命令よりも読みやすいので，与えられたパッケージの潜在的な重複を避けることができます．

    RUN apt-get install -y package-foo && apt-get install -y package-bar

### [`CMD`](https://docs.docker.com/reference/builder/#cmd)

`CMD`命令は，イメージに含まれるソフトウェアを，任意のパラメータ付きで実行するために使用すべきです．`CMD`は通常`CMD [“executable”, “param1”, “param2”…]`の書式で使うべきです．しかし，(ApacheやRailsなどのような）サービスのためのイメージであれば，`CMD ["apache2","-DFOREGROUND"]`のように実行します．実際には，このような書き方はサービスを提供するベースイメージを使用することが推奨されます．

その他の多くのケースでは，`CMD`は`CMD ["perl", "-de0"]`， `CMD ["python"]`，`CMD [“php”, “-a”]`のような双方向シェル（bash, python, perl, 他）に与えられるべきです．`docker run -it python`のように何かを実行する際にこのような書式を使うことで，自然に利用可能なシェルを手に入れ，すぐに使うことができます．`CMD`は，あなたとあなたが期待するユーザーが`ENTRYPOINT`の使い方に精通していなければ，[`ENTRYPOINT`](https://docs.docker.com/reference/builder/#entrypoint)と共に`CMD [“param”, “param”]`を使うことは避けてください．

### [`EXPOSE`](https://docs.docker.com/reference/builder/#expose)

`EXPOSE`命令は，コンテナが接続のために待ち受けするポートを指定します．その結果，アプリケーションはよくある伝統的なポートを使うべきです．例えばWebサーバApacheのイメージは`EXPOSE 80`を用い，MongoDBのイメージは`EXPOSE 27017`を使うべきです．ユーザは外部アクセスのために，指定されたポートをユーザが選択するポートに割り当てるために`docker run`にオプションを指定して実行できます．コンテナ間接続のために，Dockerは受信するコンテナの情報を環境変数に割り当てます（例：`MYSQL_PORT_3306_TCP`）．

### [`ENV`](https://docs.docker.com/reference/builder/#env)

新しいソフトウェアを簡単に実行させる目的で，コンテナにインストールされたソフトウェアに適した`PATH`環境変数を更新するために`ENV`を使用できます．例えば，`ENV PATH /usr/local/nginx/bin:$PATH`は`CMD [“nginx”]`の実行を確実なものにします．

`ENV`命令はPostgreSQLの`PGDATA`のようなコンテナ化したいサービスに要求される環境変数を提供するためにも有用です．

最終的に，`ENV`は以下の例に見られるように，バージョンの揺らぎに依らず使用するバージョン番号を指定するためにも使われ，メンテナンスを容易にします．

    ENV PG_MAJOR 9.3
    ENV PG_VERSION 9.3.4
    RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
    ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

プログラム中で（ハードコーディングで数値を与えるような）定数値を用いるよりも，この方法ならばあなたのコンテナのソフトウェアのバージョンを指定するために，単一の`ENV`命令を変更するだけでです．

### [`ADD`](https://docs.docker.com/reference/builder/#add) or [`COPY`](https://docs.docker.com/reference/builder/#copy)

'ADD'と'COPY'は似たような機能だが，一般的に`COPY`の方が望ましいです．`ADD`よりも透明性が高いからです．`ADD`は即効性が明らかではないいくつかの機能（ローカルのみのtar展開やリモートURLのサポートなど）を持っていますが，`COPY`は基本的にローカルに有るファイルをコンテナ内にコピーすることのみサポートします．

このため，`ADD`の最適な使い道は，`ADD rootfs.tar.xz /`のように，ローカルに有るtarファイルをイメージ内に展開することです．

もし前後関係から複数のファイルが用いられるような`Dockerfile`の場合は，一度にすべてのファイルをコピーするのではなく，個別にコピーしてください．もし特定のファイルに変更があった場合，（再実行する段階を強制し）各構築段階のキャッシュのみが無効化されることを保証します．

例：

    COPY requirements.txt /tmp/
    RUN pip install /tmp/requirements.txt
    COPY . /tmp/

`RUN`の前に`COPY . /tmp/`を置く場合と比べて，`RUN`のキャッシュ無効化を抑制する結果になります．

イメージサイズの問題のため，リモートURLからパッケージを取得するために`ADD`を使うことは全く推奨されません．その代わりに`curl`や`wget`を使ってください．この方法では，展開後には必要とされないファイルを消すことができ，イメージに余分なレイヤーを追加せずにすみませ．例えば以下のように回避すべきです．

    ADD http://example.com/big.tar.xz /usr/src/things/
    RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
    RUN make -C /usr/src/things all

上記の代わりに以下のようにしましょう．

    RUN mkdir -p /usr/src/things \
        && curl -SL http://example.com/big.tar.gz \
        | tar -xJC /usr/src/things \
        && make -C /usr/src/things all

その他，`ADD`によるtarの自動展開を必要としない場合（ファイル，ディレクトリ）には，常に`COPY`を仕様すべきです．

### [`ENTRYPOINT`](https://docs.docker.com/reference/builder/#entrypoint)

`ENTRYPOINT`の最適な使い道は，ヘルパースクリプトです．その他の用途に`ENTRYPOINT`を使用することはあなたのコードを理解しづらくします．例：

....docker run -it official-image bash

は，以下の例よりも理解しやすいです．

....docker run -it --entrypoint bash official-image -i

これは，ごく自然に上記のコマンドが正常に動作すると思い込んでいる新たなDockerユーザに特に当てはまります．ラッパースクリプト以外の用途に`ENTRYPOINT`が使われているイメージでは，コマンドが失敗し，`ENTRYPOINT`や`--entrypoint`についての学習を余儀なくされるでしょう．

コマンドがユーザに対して視認できない状態で実行される場面を避けるために，`exec "$@"`のようなもので終了するスクリプトにしましょう([the exec builtin command](http://wiki.bash-hackers.org/commands/builtin/exec)参照)．`ENTRYPOINT`終了後，スクリプトは透過的にユーザに指定されたコマンドをユーザに明確にしながら（例えば`docker run -it mysql mysqld --some --flags`は透過的に`ENTRYPOINT`が`initdb`を実行した後に`mysqld --some --flags`を）起動します．

例えば[Postgres Official Image](https://github.com/docker-library/postgres)の`Dockerfile`を見てみましょう．次のスクリプトをご覧ください．

```bash
#!/bin/bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"
```

このスクリプトは，コンテナ内にコピーされ，コンテナ起動時に`ENTRYPOINT`から実行されます．

    COPY ./docker-entrypoint.sh /
    ENTRYPOINT ["/docker-entrypoint.sh"]

### [`VOLUME`](https://docs.docker.com/reference/builder/#volume)

`VOLUME`命令は，dockerコンテナで作られたデータベース格納領域，設定の保存，ファイル・フォルダを公開するために仕様すべきです．`VOLUME`はイメージ内の変更可能な領域やユーザに提供する部分のために使用することを強く推奨します．

### [`USER`](https://docs.docker.com/reference/builder/#user)

サービスを特権無しで実行できる場合は`USER`を使って非rootユーザに切り替えるべきです．`RUN groupadd -r postgres && useradd -r -g postgres postgres`のように，`Dockerfile`内でユーザやグループを作成して起動してください．

> **Note:** イメージ内のユーザとグループは，イメージ再構築の割り当てと関係なく得られた「次の」UID/GIDの中から非決定UID/GIDを取得します．

`sudo`のインストールと使用は，TTYと信号転送の挙動を予測できず，それが解決するよりもとてもとても多くの問題を引き起こすため，回避すべきです．もしどうしても`sudo`のような機能を必要としているなら（例：rootでデーモンを初期化するが非rootで実行する場合），[“gosu”](https://github.com/tianon/gosu)を使用することも可能です．

最後に，レイヤー数と複雑さを緩和するため，`USER`による切り替えを頻繁に行うことを避けてください．

### [`WORKDIR`](https://docs.docker.com/reference/builder/#workdir)

明瞭さと信頼性のために，`WORKDIR`には常に絶対パスを指定してください．`RUN cd … && do-something`のような読みづらく，トラブルシュートしづらく，メンテナンスしづらい命令を増殖させる代わりに`WORKDIR`を使うべきです．

### [`ONBUILD`](https://docs.docker.com/reference/builder/#onbuild)

`ONBUILD`は，`FROM`で与えられたイメージの構築にのみ有効です．例えば，[Ruby’s `ONBUILD` variants](https://github.com/docker-library/ruby/blob/master/2.1/onbuild/Dockerfile). に見られるような，`Dockerfile`内でその言語で書かれた任意のソフトウェアを構築する言語を重ねたイメージのために`ONBUILD`を使用できます．

`ONBUILD`から構築されるイメージは，`ruby:1.9-onbuild`や`ruby:2.0-onbuild`のようにタグを分けるべきです．

`ONBUILD`の中に`ADD`や`COPY`を置く場合は注意してください．ビルド時にリソースが欠落または追加された場合は，"onbuild"イメージが壊滅的に失敗します．上記で推奨されているように，個別タグの付加によって`Dockerfile`製作者が選択することを軽減している．

## Examples For Official Repositories

模範的な`Dockerfile`の公式リポジトリ：

* [Go](https://registry.hub.docker.com/_/golang/)
* [Perl](https://registry.hub.docker.com/_/perl/)
* [Hy](https://registry.hub.docker.com/_/hylang/)
* [Rails](https://registry.hub.docker.com/_/rails)

## Additional Resources:

* [Dockerfile Reference](https://docs.docker.com/reference/builder/#onbuild)
* [More about Base Images](https://docs.docker.com/articles/baseimages/)
* [More about Automated Builds](https://docs.docker.com/docker-hub/builds/)
* [Guidelines for Creating Official 
Repositories](https://docs.docker.com/docker-hub/official_repos/)