訳者より
この文書はDocker社の許可を得てBest practices for writing Dockerfilesを翻訳したものです.原文に基づき,ライセンスはApache License2.0とします.
稚拙な訳であるとは思いますが,Dockerfileを書く際にお役に立てればと思います.途中,意味不明な文章があり,すみません.
今はまだ稚拙な訳ですし,膨大なドキュメントの1つを翻訳したに過ぎませんが,修正を重ねて翻訳数を増やして行き,将来的にはDocker公式リポジトリに追加できればと思っています.
概要
Dockerは与えられたイメージを構築するために必要なすべてのコマンドから構成されたテキストファイルであるDockerfile
から,手順を読み込むことで自動的にイメージを構築することができます.Dockerfile
は仕様書に忠実に一通りの手順仕様として使われます.あなたはDockerfile Referenceページの基礎を学ぶことができます.もしあなたがDockerfile
を書き始める場合はそのページから始めてください.
この文書は,簡単で効果的なDockerfile
の作るためにDocker, IncとDockerコミュニティによって推奨される,最も良い手法と方法をカバーしています.私達は,以下の提言を強く推奨します(実際にあなたが公式イメージを作る際に以下の方法に忠実である必要があります).
あなたはbuildpack-depsDockerfile
の活動の中に,以下の手法と推奨事項の多くを見ることができます.
Note:Dockerfileの詳細な説明についてはDockerfile Referenceページを御覧ください.
一般的な指針と推奨事項
コンテナは短命であるべき
あなたのDockerfile
が定義するイメージから生成されるコンテナは,可能な限り短命にすべきです.「短命」であることによって,それが停止・破壊されても,絶対的に最小限のセットアップと設定によって新しいものを構築し設置することができることを表しています.
a .dockerignore fileを使いなさい
高速なアップロードとdocker build
の効率化のためには,構築手順や最終イメージからファイルやディレクトリを除外するための.dockerignore
ファイルを用意するべきです.例えばアップロード時間を悪化させる大容量を抑えるために,.git
があなたの構築手順やスクリプトに必要なければ,それを.dockerignore
に追加するべきです.
不必要なパッケージのインストールを避けなさい
複雑性,依存性,ファイルサイズ,ビルド時間を減らすために,特に理由のない「あると良いね」と思われているような余分または不必要なパッケージのインストールを避けるべきです.例えば,データベースのイメージにテキストエディタを含む必要はありません.
一つのコンテナでは一つのプロセスのみを起動する
あらゆるケースにおいて,あなたは一つのコンテナ内で一つのプロセスのみを動作させるべきです.アプリケーションを複数のコンテナに分散させることは,水平分散を容易にし,コンテナの再利用を容易にします.もし,有るサービスが別のサービスに依存しているようであればcontainer linkingを使いましょう.
レイヤー数を最小に
Dockerfile
の読みやすさ(と長期間に渡るメンテナンス)とレイヤー数の最小化のバランスを見つける必要があります.レイヤー数について戦略的かつ慎重になりましょう.
複数行のパラメータは並べ替えなさい
可能な限り,複数行に渡る英数字パラメータは並べ替えることによって変更を容易にしなさい.これによって,パッケージの重複排除と更新のためのリストの作成を容易にするでしょう.
buildpack-deps
imageの例を以下に示します.
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
可能であれば,あなたのイメージの土台として現行の公式リポジトリを使いましょう.私達は,完全なディストリビューションでありながら,とても厳密に管理され最小限を保っている(現行で100MB以下)Debina imageをお勧めします.
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
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
と共にCMD [“param”, “param”]
を使うことは避けてください.
EXPOSE
EXPOSE
命令は,コンテナが接続のために待ち受けするポートを指定します.その結果,アプリケーションはよくある伝統的なポートを使うべきです.例えばWebサーバApacheのイメージはEXPOSE 80
を用い,MongoDBのイメージはEXPOSE 27017
を使うべきです.ユーザは外部アクセスのために,指定されたポートをユーザが選択するポートに割り当てるためにdocker run
にオプションを指定して実行できます.コンテナ間接続のために,Dockerは受信するコンテナの情報を環境変数に割り当てます(例:MYSQL_PORT_3306_TCP
).
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
or 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
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参照).ENTRYPOINT
終了後,スクリプトは透過的にユーザに指定されたコマンドをユーザに明確にしながら(例えばdocker run -it mysql mysqld --some --flags
は透過的にENTRYPOINT
がinitdb
を実行した後にmysqld --some --flags
を)起動します.
例えばPostgres Official ImageのDockerfile
を見てみましょう.次のスクリプトをご覧ください.
#!/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
VOLUME
命令は,dockerコンテナで作られたデータベース格納領域,設定の保存,ファイル・フォルダを公開するために仕様すべきです.VOLUME
はイメージ内の変更可能な領域やユーザに提供する部分のために使用することを強く推奨します.
USER
サービスを特権無しで実行できる場合はUSER
を使って非rootユーザに切り替えるべきです.RUN groupadd -r postgres && useradd -r -g postgres postgres
のように,Dockerfile
内でユーザやグループを作成して起動してください.
Note: イメージ内のユーザとグループは,イメージ再構築の割り当てと関係なく得られた「次の」UID/GIDの中から非決定UID/GIDを取得します.
sudo
のインストールと使用は,TTYと信号転送の挙動を予測できず,それが解決するよりもとてもとても多くの問題を引き起こすため,回避すべきです.もしどうしてもsudo
のような機能を必要としているなら(例:rootでデーモンを初期化するが非rootで実行する場合),“gosu”を使用することも可能です.
最後に,レイヤー数と複雑さを緩和するため,USER
による切り替えを頻繁に行うことを避けてください.
WORKDIR
明瞭さと信頼性のために,WORKDIR
には常に絶対パスを指定してください.RUN cd … && do-something
のような読みづらく,トラブルシュートしづらく,メンテナンスしづらい命令を増殖させる代わりにWORKDIR
を使うべきです.
ONBUILD
ONBUILD
は,FROM
で与えられたイメージの構築にのみ有効です.例えば,Ruby’s ONBUILD
variants. に見られるような,Dockerfile
内でその言語で書かれた任意のソフトウェアを構築する言語を重ねたイメージのためにONBUILD
を使用できます.
ONBUILD
から構築されるイメージは,ruby:1.9-onbuild
やruby:2.0-onbuild
のようにタグを分けるべきです.
ONBUILD
の中にADD
やCOPY
を置く場合は注意してください.ビルド時にリソースが欠落または追加された場合は,"onbuild"イメージが壊滅的に失敗します.上記で推奨されているように,個別タグの付加によってDockerfile
製作者が選択することを軽減している.
Examples For Official Repositories
模範的なDockerfile
の公式リポジトリ: