LoginSignup
95
84

More than 5 years have passed since last update.

Best practices for writing Dockerfiles 日本語訳(仮)

Last updated at Posted at 2014-11-26

訳者より

この文書は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の命令と一つの子イメージを比較すれば十分です.しかしいくつかの命令は少々の検査と説明が必要となります.
* ADDCOPY操作では,そのファイルの内容がイメージに転送され検査されます.厳密には,ファイル(達)のチェックサムが検査され,キャッシュを参照している間チェックサムが使われます.もしそのメタデータを含むファイル(達)に違いがあれば,キャッシュは無効化されます.
* 余談ですが,ADDCOPYコマンドでは,キャッシュが一致することを決定するためにコンテナ内のファイルを参照せずにチェックします.例えば,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 upgradedist-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"]のように実行します.実際には,このような書き方はサービスを提供するベースイメージを使用することが推奨されます.

その他の多くのケースでは,CMDCMD ["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:$PATHCMD [“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を使うことは全く推奨されません.その代わりにcurlwgetを使ってください.この方法では,展開後には必要とされないファイルを消すことができ,イメージに余分なレイヤーを追加せずにすみませ.例えば以下のように回避すべきです.

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は透過的にENTRYPOINTinitdbを実行した後にmysqld --some --flagsを)起動します.

例えばPostgres Official ImageDockerfileを見てみましょう.次のスクリプトをご覧ください.

#!/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-onbuildruby:2.0-onbuildのようにタグを分けるべきです.

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

Examples For Official Repositories

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

Additional Resources:

95
84
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
95
84