Dockerfile 記述のベスト・プラクティス

Docker は Dockerfile に書かれる指示を読み込んで、自動的にイメージを構築します。 これは、あらゆる命令を含んだテキストファイルであり、順に処理することで指定されたイメージを構築するために必要となるものです。 Dockerfile は所定のフォーマットにこだわっていて、特定の指示を用いることにしています。 基本的なことは Dockerfile リファレンス で学ぶことができます。 Dockerfile を書き慣れていない方は、そのリファレンスから始めてください。

このドキュメントでは、Docker 社や Docker コミュニティが推奨するベストプラクティスおよび方法を示しています。 Dockerfile を簡単に作り出して利用できるように、効率的な Dockerfile の書き方を示すものです。 みなさんには、ここに示す推奨方法を強くお勧めします(さらに、公式イメージを生成するときには、この推奨方法に従うことが必要になってきます)。

このベストプラクティスや推奨する手法の多くは、更新中の buildpack-depsDockerfile に見ることができます。

注釈

ここで説明する Dockerfile コマンドの詳しい説明は Dockerfile リファレンス を参照してください。

一般的なガイドラインとアドバイス

コンテナは "はかない" もの

Dockerfile が定義するイメージによって生成されるコンテナは、できる限り "はかないもの"(ephemeral)と考えておくべきです。 "はかない" という語を使うのは、コンテナが停止、破棄されて、すぐに新たなものが作り出されるからです。 最小限の構成や設定があれば稼動できます。

.dockerignore ファイルの利用

Dockerfile は、たいていは空のディレクトリに配置するのが適当です。 その後にそのディレクトリへは、Dockerfile の構築に必要となるファイルのみを追加します。 ビルドの効率をよくするために、ファイルやディレクトリを除外指定する .dockerignore ファイルをそのディレクトリに置く方法もあります。 このファイルがサポートする除外パターンの指定方法は .gitignore と同様です。 このファイルの作成に関しては .dockerignore ファイル を参照してください。

不要なパッケージをインストールしない

複雑さ、依存関係、ファイルサイズ、構築時間をそれぞれ減らすためには、余分な、または必須ではない「あった方が良いだろう」程度のパッケージをインストールすべきではありません。例えば、データベース・イメージであればテキストエディタは不要でしょう。

コンテナごとに1つのプロセスだけ実行

アプリケーションを複数のコンテナに分けることにより、スケールアウトやコンテナの再利用が行いやすくなります。 たとえばウェブ・アプリケーションが3つの独立したコンテナにより成り立っているとします。 それらは個々のイメージを持つものとなり、それぞれに分かれてウェブ・アプリケーション、データベース、メモリキャッシュを管理するようになります。

「1つのコンテナには1つのプロセス」とすべき、ということを聞いたことがあるかもしれません。 この標語には見習うべきところはあるのですが、1つのコンテナに1つのオペレーティング・システムのプロセスだけを割り当てるのかというと、必ずしもそうではありません。 最近のコンテナは 初期プロセスにおいて起動 するという現実もあり、プログラムの中には都合に応じて追加のプロセスを起動するようなものもあります。 例をあげると、 Celery はワーカ・プロセスを複数起動し、 Apache はリクエストごとにプロセスを生成します。 「1つのコンテナには1つのプロセス」というのは、優れた経験則となることがありますが、決して厳密な規則というわけでもありません。 コンテナはできる限りすっきりとモジュラ化されるように、適切な判断をしてください。

コンテナが互いに依存している場合は、Docker container ネットワーク を用いることで、コンテナ間の通信を確実に行うことができます。

レイヤの数を最小に

Dockerfile は可読性とレイヤ数のバランスを考慮する必要があります。 Dockerfile を読みやすくする(つまり長期にわたって保守しやすくする)のか、利用するレイヤ数をできるだけ減らすのかということです。 使用するレイヤ数は、計画的に注意して取り決めてください。

複数行にわたる引数は並びを適切に

複数行にわたる引数は、できるなら後々の変更を容易にするために、その並びはアルファベット順にしましょう。 そうしておけば、パッケージを重複指定することはなくなり、一覧の変更も簡単になります。 プルリクエストを読んだりレビューしたりすることも、さらに楽になります。 バックスラッシュ(\) の前に空白を含めておくことも同様です。

以下は buildpack-deps イメージ の記述例です。

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

ビルドキャッシュ

イメージ構築の過程において Docker は、Dockerfile 内に示されている命令を記述順に実行していきます。 個々の命令が検査される際に Docker は、既存イメージのキャッシュが再利用できるかどうかを調べます。 そこでは新たな(同じ)イメージを作ることはしません。 キャッシュをまったく使いたくない場合は docker build コマンドに --no-cache=true オプションをつけて実行します。

一方で Docker のキャッシュを利用する場合、Docker が適切なイメージを見つけた上で、どのようなときにキャッシュを利用し、どのようなときには利用しないのかを理解しておくことが必要です。Docker が従っている規則は以下のとおりです。

  • キャッシュ内にすでに存在している親イメージから処理を始めます。 そのベースとなるイメージから派生した子イメージに対して、次の命令が合致するかどうかが比較され、子イメージのいずれかが同一の命令によって構築されているかを確認します。 そのようなものが存在しなければ、キャッシュは無効になります。
  • ほとんどの場合、 Dockerfile 内の命令と子イメージのどれかを単純に比較するだけで十分です。 しかし命令によっては、多少の検査や解釈が必要となるものもあります。
  • ADD 命令や COPY 命令では、イメージに含まれるファイルの内容が検査され、個々のファイルについてチェックサムが計算されます。 この計算において、ファイルの最終更新時刻、最終アクセス時刻は考慮されません。 キャッシュを探す際に、このチェックサムと既存イメージのチェックサムが比較されます。 ファイル内の何かが変更になったとき、たとえばファイル内容やメタデータが変わっていれば、キャッシュは無効になります。
  • ADDCOPY 以外のコマンドの場合、キャッシュのチェックは、コンテナ内のファイル内容を見ることはなく、それによってキャッシュと合致しているかどうかが決定されるわけでありません。 たとえば RUN apt-get -y update コマンドの処理が行われる際には、コンテナ内にて更新されたファイルは、キャッシュが合致するかどうかの判断のために用いられません。 この場合にはコマンド文字列そのものが、キャッシュの合致判断に用いられます。

キャッシュが無効になると、次に続く Dockerfile コマンドは新たなイメージを生成し、そのキャッシュは使われなくなります。

Dockerfile コマンド

以下は Dockerfile 記述にて推奨するベストな方法を示すものです。 Dockerfile に記述できるさまざまなコマンドの記述方法を示します。

FROM

Dockerfile リファレンスの FROM コマンド

イメージのベースは、できるだけ現時点での公式リポジトリを利用してください。 Debian イメージ がお勧めです。 このイメージはしっかりと管理されていて、充実したディストリビューションであるにもかかわらず、非常にコンパクトなものになっています(現在 150 MB 以下)。

LABEL

オブジェクト・ラベルの理解

イメージにラベルを追加するのは、プロジェクト内でのイメージ管理をしやすくしたり、ライセンス情報の記録や自動化の助けとするなど、さまざまな目的があります。 ラベルを指定するには、 LABEL で始まる行を追加して、そこにキーと値のペア(key-value pair)をいくつか設定します。 以下に示す例は、いずれも正しい構文です。 説明をコメントとしてつけています。

注釈

文字列に空白が含まれる場合は、引用符でくくるか あるいは エスケープする必要があります。 文字列内に引用符がある場合も、同様にエスケープしてください。

# 個別のラベルを設定
LABEL com.example.version="0.0.1-beta"
LABEL vendor="ACME Incorporated"
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

# 1行でラベルを設定
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

# 複数のラベルを一度に設定、ただし行継続の文字を使い、長い文字列を改行する
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

ラベルにおける利用可能なキーと値のガイドラインとしては オブジェクトラベルを理解する を参照してください。またラベルの検索に関する情報は オブジェクト上のラベルの管理 のフィルタリングに関する項目を参照してください。

RUN

Dockerfile リファレンスの RUN コマンド

いつものことながら Dockerfile は読みやすく理解しやすく、そして保守しやすくすることが必要です。 RUN コマンドが複数行にわたって長く複雑になるなら、バックスラッシュを使って行を分けてください。

apt-get

おそらく RUN において一番利用する使い方が apt-get アプリケーションの実行です。 RUN apt-get はパッケージをインストールするものであるため、注意点がいくつかあります。

RUN apt-get upgradedist-upgrade の実行は避けてください。 ベース・イメージに含まれる重要パッケージは、権限が与えられていないコンテナ内ではほとんど更新できないからです。 ベース・イメージ内のパッケージが古くなっていたら、開発者に連絡をとってください。 foo というパッケージを更新する必要があれば、 apt-get install -y foo を利用してください。 これによってパッケージは自動的に更新されます。

RUN apt-get updateapt-get install は、同一の RUN コマンド内にて同時実行するようにしてください。 たとえば以下のようにします。

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

1つの RUN コマンド内で apt-get update だけを使うとキャッシュに問題が発生し、その後の apt-get install コマンドが失敗します。 たとえば Dockerfile を以下のように記述したとします。

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl

イメージが構築されると、レイヤーがすべて Docker のキャッシュに入ります。 この次に apt-get install を編集して別のパッケージを追加したとします。

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl nginx

Docker は当初のコマンドと修正後のコマンドを見て、同一のコマンドであると判断するので、前回の処理において作られたキャッシュを再利用します。 キャッシュされたものを利用して処理が行われるわけですから、結果として apt-get update は実行 されませんapt-get update が実行されないということは、つまり curl にしても nginx にしても、古いバージョンのまま利用する可能性が出てくるということです。

RUN apt-get update && apt-get install -y というコマンドにすると、 Dockerfile が確実に最新バージョンをインストールしてくれるものとなり、さらにコードを書いたり手作業を加えたりする必要がなくなります。 これは「キャッシュ・バスティング(cache busting)」と呼ばれる技術です。 この技術は、パッケージのバージョンを指定することによっても利用することができます。 これはバージョン・ピニング(version pinning)というものです。 以下に例を示します。

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo=1.3.*

バージョン・ピニングでは、キャッシュにどのようなイメージがあろうとも、指定されたバージョンを使ってビルドが行われます。 この手法を用いれば、そのパッケージの最新版に、思いもよらない変更が加わっていたとしても、ビルド失敗を回避できることもあります。

以下の RUN コマンドはきれいに整えられていて、 apt-get の推奨する利用方法を示しています。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

s3cmd のコマンド行は、バージョン 1.1.* を指定しています。 以前に作られたイメージが古いバージョンを使っていたとしても、新たなバージョンの指定により apt-get update のキャッシュ・バスティングが働いて、確実に新バージョンがインストールされるようになります。 パッケージを各行に分けて記述しているのは、パッケージを重複して書くようなミスを防ぐためです。

apt キャッシュをクリーンアップし /var/lib/apt/lists を削除するのは、イメージサイズを小さくするためです。 そもそも apt キャッシュはレイヤー内に保存されません。 RUN コマンドを apt-get update から始めているので、 apt-get install の前に必ずパッケージのキャッシュが更新されることになります。

注釈

公式の Debian と Ubuntu のイメージは 自動的に apt-get clean を実行する ので、明示的にこのコマンドを実行する必要はありません。

パイプの利用

RUN コマンドの中には、その出力をパイプを使って他のコマンドへ受け渡すことを前提としているものがあります。 そのときにはパイプを行う文字( | )を使います。 たとえば以下のような例があります。

RUN wget -O - https://some.site | wc -l > /number

Docker はこういったコマンドを /bin/sh -c というインタープリタ実行により実現します。 正常処理されたかどうかは、パイプの最後の処理の終了コードにより評価されます。 上の例では、このビルド処理が成功して新たなイメージが生成されるかどうかは、wc -l コマンドの成功にかかっています。 つまり wget コマンドが成功するかどうかは関係がありません。

パイプ内のどの段階でも、エラーが発生したらコマンド失敗としたい場合は、頭に set -o pipefail && をつけて実行します。 こうしておくと、予期しないエラーが発生しても、それに気づかずにビルドされてしまうことはなくなります。 たとえば以下です。

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

注釈

すべてのシェルが -o pipefail オプションをサポートしているわけではありません。 その場合(例えば Debian ベースのイメージにおけるデフォルトシェル dash である場合)、RUN コマンドにおける exec 形式の利用を考えてみてください。 これは pipefail オプションをサポートしているシェルを明示的に指示するものです。 たとえば以下です。

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

CMD

Dockerfile リファレンスの CMD コマンド

CMD コマンドは、イメージ内に含まれるソフトウェアを実行するために用いるもので、引数を指定して実行します。 CMD はほぼ、CMD ["実行モジュール名", "引数1", "引数2" …] の形式をとります。 Apache や Rails のようにサービスをともなうイメージに対しては、たとえば CMD ["apache2","-DFOREGROUND"] といったコマンド実行になります。 実際にサービスベースのイメージに対しては、この実行形式が推奨されます。

上記以外では、 CMD に対して bash、python、perl などインタラクティブシェルを与えることが行われます。 たとえば CMD ["perl", "-de0"]CMD ["python"]CMD ["php", "-a"] といった具合です。 この実行形式を利用するということは、たとえば docker run -it python というコマンドを実行したときに、指定したシェルの中に入り込んで、処理を進めていくことを意味します。 CMDENTRYPOINT を組み合わせて用いる CMD ["引数", "引数"] という実行形式がありますが、これを利用するのはまれです。 開発者自身や利用者にとって ENTRYPOINT がどのように動作するのかが十分に分かっていないなら、用いないようにしましょう。

EXPOSE

Dockerfile リファレンスの EXPOSE コマンド

EXPOSE コマンドは、コンテナが接続のためにリッスンするポートを指定します。 当然のことながらアプリケーションにおいては、標準的なポートを利用します。 たとえば Apache ウェブ・サーバを含んでいるイメージに対しては EXPOSE 80 を使います。 また MongoDB を含んでいれば EXPOSE 27017 を使うことになります。

外部からアクセスできるようにするため、これを実行するユーザは docker run にフラグをつけて実行します。 そのフラグとは、指定されているポートを、自分が取り決めるどのようなポートに割り当てるかを指示するものです。 Docker のリンク機能においては環境変数が利用できます。 受け側のコンテナが提供元をたどることができるようにするものです(例: MYSQL_PORT_3306_TCP )。

ENV

Dockerfile リファレンスの ENV コマンド

新しいソフトウェアに対しては ENV を用いれば簡単にそのソフトウェアを実行できます。 コンテナがインストールするソフトウェアに必要な環境変数 PATH を、この ENV を使って更新します。 たとえば ENV PATH /usr/local/nginx/bin:$PATH を実行すれば、 CMD ["nginx"] が確実に動作するようになります。

ENV コマンドは、必要となる環境変数を設定するときにも利用します。 たとえば Postgres の 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

プログラムにおける(ハードコーディングではない)定数定義と同じことで、この方法をとっておくのが便利です。 ただ1つの ENV コマンドを変更するだけで、コンテナ内のソフトウェアバージョンは、いとも簡単に変えてしまうことができるからです。

ADD と COPY

Dockerfile リファレンスの ADD コマンド Dockerfile リファレンスの COPY コマンド

ADDCOPY の機能は似ていますが、一般的には COPY が選ばれます。 それは ADD よりも機能がはっきりしているからです。 COPY は単に、基本的なコピー機能を使ってローカルファイルをコンテナにコピーするだけです。 一方 ADD には特定の機能(ローカルでの tar 展開やリモート URL サポート)があり、これはすぐにわかるものではありません。 結局 ADD の最も適切な利用場面は、ローカルの tar ファイルを自動的に展開してイメージに書き込むときです。 たとえば ADD rootfs.tar.xz / といったコマンドになります。

Dockerfile 内の複数ステップにおいて異なるファイルをコピーするときには、一度にすべてをコピーするのではなく、 COPY を使って個別にコピーしてください。 こうしておくと、個々のステップに対するキャッシュのビルドは最低限に抑えることができます。 つまり指定されているファイルが変更になったときのみキャッシュが無効化されます(そのステップは再実行されます)。

例:

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

RUN コマンドのステップより前に COPY . /tmp/ を実行していたとしたら、それに比べて上の例はキャッシュ無効化の可能性が低くなっています。

イメージ・サイズの問題があるので、 ADD を用いてリモート URL からパッケージを取得することはやめてください。 かわりに 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.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

ADD の自動展開機能を必要としないもの(ファイルやディレクトリ)に対しては、常に COPY を使うようにしてください。

ENTRYPOINT

Dockerfile リファレンスの ENTRYPOINT コマンド

ENTRYPOINT の最適な利用方法は、イメージに対してメインのコマンドを設定することです。 これを設定すると、イメージをそのコマンドそのものであるかのようにして実行できます(その次に CMD を使ってデフォルトフラグを指定します)。

コマンドライン・ツール s3cmd のイメージ例から始めます。

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

このイメージが実行されると、コマンドのヘルプが表示されます。

$ docker run s3cmd

あるいは適正なパラメータを指定してコマンドを実行します。

$ docker run s3cmd ls s3://mybucket

このコマンドのようにして、イメージ名がバイナリへの参照としても使えるので便利です。

ENTRYPOINT コマンドはヘルパースクリプトとの組み合わせにより利用することもできます。 そのスクリプトは、上記のコマンド例と同じように機能させられます。 たとえ対象ツールの起動に複数ステップを要するような場合でも、それが可能です。

たとえば Postgres 公式イメージ は次のスクリプトを ENTRYPOINT として使っています。

#!/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 "$@"

注釈

このスクリプトは Bash コマンドの exec を用います。 このため最終的に実行されたアプリケーションが、コンテナの PID として 1 を持つことになります。 こうなるとそのアプリケーションは、コンテナに送信された Unix シグナルをすべて受信できるようになります。 詳細は ENTRYPOINT を参照してください。

ヘルパースクリプトはコンテナの中にコピーされ、コンテナ開始時に ENTRYPOINT から実行されます。

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

このスクリプトを用いると、Postgres との間で、ユーザがいろいろな方法でやり取りできるようになります。

以下は単純に Postgres を起動します。

$ docker run postgres

あるいは、PostgreSQL 実行時、サーバに対してパラメータを渡せます。

$ docker run postgres postgres --help

または Bash のような全く異なるツールを起動するために利用することもできます。

$ docker run --rm -it postgres bash

VOLUME

Dockerfile リファレンスの VOLUME コマンド

VOLUME コマンドは、データベース・ストレージ領域、設定用ストレージ、Docker コンテナによって作成されるファイルやフォルダの公開に使います。 イメージの可変的な部分、あるいはユーザが設定可能な部分については VOLUME の利用が強く推奨されます。

USER

Dockerfile リファレンスの USER コマンド

サービスが特権ユーザでなくても実行できる場合は、 USER を用いて非 root ユーザに変更します。 ユーザとグループを生成するところから始めてください。 Dockerfile 内にてたとえば RUN groupadd -r postgres && useradd -r -g postgres postgres のようなコマンドを実行します。

注釈

イメージ内のユーザとグループに割り当てられる UID、GID は確定的なものではありません。 イメージが再構築されるかどうかには関係なく、「次の」値が UID、GID に割り当てられます。 これが問題となる場合は、UID、GID を明示的に割り当ててください。

注釈

Go 言語の archive/tar パッケージが取り扱うスパースファイルにおいて 未解決のバグ があります。 これは Docker コンテナ内にて非常に大きな値の UID を使ってユーザを生成しようとするため、ディスク消費が異常に発生します。 コンテナ・レイヤ内の /var/log/faillog が NUL (\0) キャラクタにより埋められてしまいます。 useradd に対して --no-log-init フラグをつけることで、とりあえずこの問題は回避できます。 ただし Debian/Ubuntu の adduser ラッパーは --no-log-init フラグをサポートしていないため、利用することはできません。

sudo のインストールとその利用は避けてください。 TTY やシグナル送信が予期しない動作をするため、解決できることは少なく、多くの問題を引き起こすことになります。 sudo と同様の機能(たとえばデーモンの初期化を root により行い、起動は root 以外で行うなど)を実現する必要がある場合は、 gosu を使うとよいかもしれません。

レイヤ数を減らしたり複雑にならないようにするためには、 USER の設定を何度も繰り返すのは避けてください。

WORKDIR

Dockerfile リファレンスの WORKDIR コマンド

WORKDIR に設定するパスは、分かり易く確実なものとするために、絶対パス指定としてください。 また RUN cd && do-something といった長くなる一方のコマンドを書くくらいなら、 WORKDIR を利用してください。 そのような書き方は読みにくく、トラブル発生時には解決しにくく保守が困難になるためです。

ONBUILD

Dockerfile リファレンスの ONBUILD コマンド

ONBUILD コマンドは、 Dockerfile によるビルドが完了した後に実行されます。 ONBUILD は、現在のイメージから FROM によって派生した子イメージにおいて実行されます。 つまり ONBUILD とは、親の Dockerfile から子どもの Dockerfile へ与える命令であると言えます。

Docker によるビルドにおいては ONBUILD の実行が済んでから、子イメージのコマンド実行が行われます。

ONBUILD は、所定のイメージから FROM を使ってイメージをビルドしようとするときに利用できます。 たとえば特定言語のスタックイメージは ONBUILD を利用します。 Dockerfile 内にて、その言語で書かれたどのようなユーザ・ソフトウェアであってもビルドすることができます。 その例として Ruby's ONBUILD variants があります。

ONBUILD によって構築するイメージは、異なったタグを指定してください。 たとえば ruby:1.9-onbuildruby:2.0-onbuild などです。

ONBUILD において ADDCOPY を用いるときは注意してください。 "onbuild" イメージが新たにビルドされる際に、追加しようとしているリソースが見つからなかったとしたら、このイメージは復旧できない状態になります。上に示したように個別にタグをつけておけば、 Dockerfile の開発者にとっても判断ができるようになるので、不測の事態は軽減されます。

公式リポジトリの例

以下に示すのは代表的な Dockerfile の例です。