Docker build 入門:使い方と基本を徹底解説
はじめに
現代のソフトウェア開発において、アプリケーションのビルド、配布、実行を標準化するためのコンテナ技術は不可欠な存在となりました。その中でも、Dockerはコンテナ技術のデファクトスタンダードとして広く利用されています。Dockerを利用する上で最も基本的な、そして強力な機能の一つが「イメージビルド」です。
Dockerイメージは、アプリケーションとその実行に必要な全ての要素(コード、ランタイム、ライブラリ、環境変数、設定ファイルなど)を一つにまとめた、軽量で独立した実行可能なパッケージです。このイメージを基に「コンテナ」が生成され、実行されます。
そして、そのDockerイメージを作成するためのコマンドが docker build
です。docker build
は、Dockerfileというテキストファイルに記述された手順に従って、イメージを自動的に構築します。Dockerfileを一度作成すれば、誰でも、どこでも、同じ手順で全く同じイメージを再現できるため、開発、テスト、本番環境を通じて一貫性を保つことができます。
この記事では、Dockerを使い始めたばかりの方や、docker build
コマンドや Dockerfile の使い方について基本からしっかり学びたい方を対象に、その使い方、Dockerfile の主要な命令、そしてより効率的・効果的なイメージビルドのためのベストプラクティスや高度な概念までを、詳細かつ分かりやすく解説します。約5000語のボリュームで、この一つの記事で Docker build の基本をマスターできることを目指します。
1. Docker と コンテナ の基本的な理解(Docker build の前提として)
docker build
を理解するためには、まず Docker における「イメージ」と「コンテナ」の関係性を明確にしておく必要があります。
-
Docker イメージ (Image): アプリケーションを実行するために必要な全ての要素(コード、ランタイム、システムツール、ライブラリ、依存関係など)をパッケージ化した、読み取り専用のテンプレートです。例えるなら、OSのインストールイメージや、仮想マシンのテンプレートのようなものです。イメージ自体は実行できませんが、これを基にコンテナを生成します。イメージは複数の「レイヤー」で構成されており、それぞれのレイヤーは Dockerfile の一つの命令に対応することが多いです。このレイヤー構造が、イメージの効率的な共有やビルドキャッシュの活用を可能にします。
-
Docker コンテナ (Container): Docker イメージから生成される、実行可能なインスタンスです。イメージは読み取り専用ですが、コンテナは書き込み可能なレイヤーを持ち、状態を持つことができます。コンテナはホストOSから分離されており、独自のファイルシステム、ネットワークインターフェース、プロセス空間を持ちます。イメージは設計図、コンテナは設計図から作られた実際の建物、と考えると分かりやすいでしょう。
docker build
は、まさにこの「Docker イメージ」を作成するためのプロセスです。Dockerfile に書かれた手順に従って、様々なファイルをコピーしたり、コマンドを実行したりして、複数のレイヤーを積み重ねて最終的なイメージを構築します。
2. Docker build の基本コマンド
docker build
コマンドの最も基本的な使い方を見ていきましょう。
コマンドの基本的な構文は以下の通りです。
bash
docker build [OPTIONS] PATH | URL | -
PATH
: Dockerfile が存在するディレクトリへのパスを指定します。最も一般的な使い方です。URL
: GitリポジトリのURLなどを指定し、そこに含まれるDockerfileを使ってビルドします。-
: 標準入力から Dockerfile を受け取ってビルドします。
最もよく使われるのは PATH
を指定する方法です。通常、プロジェクトのルートディレクトリなどに Dockerfile
という名前のファイルを作成し、そのディレクトリで docker build .
のように実行します。
ここで重要なのが「コンテキスト (Context)」という概念です。docker build
コマンドの最後の引数(PATH
や .
など)は、ビルドプロセスが参照できるファイルのセット(ビルドコンテキスト)を指定します。Dockerfile 内の COPY
や ADD
命令は、このビルドコンテキスト内のファイルやディレクトリしか参照できません。したがって、Dockerfile と、ビルドに必要な全てのファイル(アプリケーションコード、設定ファイルなど)は、ビルドコンテキストとして指定するディレクトリ配下に配置する必要があります。間違えてルートディレクトリなどを指定すると、不要なファイルまでコンテキストに含まれ、ビルドが遅くなったり、キャッシュが効きにくくなったりする可能性があります。
例:簡単なウェブサーバーイメージのビルド
まずは、簡単な静的なウェブサイトを表示するNginxコンテナのイメージをビルドしてみましょう。
以下のファイル構造を考えます。
.
├── Dockerfile
└── html
└── index.html
html/index.html
の内容:
“`html
Hello from Docker!
“`
Dockerfile
の内容:
“`Dockerfile
ベースイメージとして公式のnginxイメージを使用
FROM nginx:latest
ビルドコンテキストにあるhtmlディレクトリの内容を、
Nginxが静的ファイルを読み込むディレクトリにコピーする
COPY html /usr/share/nginx/html
デフォルトのNginxポート80を公開する
EXPOSE 80
“`
このディレクトリで以下のコマンドを実行します。
bash
docker build -t my-nginx-app .
-t my-nginx-app
: ビルドしたイメージにmy-nginx-app
という名前(タグ)を付けます。タグはname:tag
の形式で指定でき、タグを省略するとデフォルトで:latest
が付きます。名前を付けることで、後でそのイメージを参照しやすくなります。.
: カレントディレクトリをビルドコンテキストとして指定します。このカレントディレクトリに Dockerfile が存在すると仮定します。
コマンドを実行すると、Docker は指定されたディレクトリ(.
)をビルドコンテキストとしてDockerデーモンに送信し、Dockerfile を読み込んで手順に従ってイメージをビルドします。
ビルドが成功すると、以下のようなメッセージが表示されます。
[+] Building
... (ビルドの進行状況が表示されます) ...
Successfully built XXXXXXXXXXXX
Successfully tagged my-nginx-app:latest
これで、my-nginx-app:latest
という名前のDockerイメージがローカルに作成されました。以下のコマンドで確認できます。
bash
docker images
作成したイメージを実行してみましょう。
bash
docker run -d -p 8080:80 my-nginx-app
-d
: バックグラウンドでコンテナを実行します。-p 8080:80
: ホストマシン(自分のPC)のポート8080を、コンテナのポート80にマッピングします。my-nginx-app
: 実行するイメージの名前です。
ブラウザで http://localhost:8080/
にアクセスすると、「Hello from Docker!」と表示されるはずです。これで、Dockerfile を使ってカスタムイメージをビルドし、実行する一連の流れが完了しました。
3. Dockerfile の詳細な解説
docker build
は Dockerfile の内容に従って処理を進めます。Dockerfile は一連の命令(Instruction)で構成されており、各命令はイメージの新しいレイヤーを作成します。ここでは、主要な Dockerfile の命令について、その役割と使い方を詳しく見ていきます。
3.1. FROM
Dockerfile
FROM <image> [AS <name>]
全ての Dockerfile は FROM
命令から始まります(ARG 命令のみ FROM より前に置くことができますが、通常は FROM が最初です)。これは、新しくビルドするイメージの「ベースイメージ」を指定するものです。Docker Hub などのレジストリから既存のイメージを取得し、その上に独自の変更を加えていきます。
<image>
: ベースとして使用するイメージの名前とタグを指定します。例:ubuntu:latest
,python:3.9-slim
,node:lts-alpine
など。タグを省略するとデフォルトで:latest
が使用されますが、特定のバージョンを指定する方が再現性の観点から推奨されます。AS <name>
: 後述するマルチステージビルドにおいて、このステージに名前を付けます。
ベースイメージの選択は重要です。公式サイトから提供されている公式イメージ(ubuntu
, python
, node
, nginx
など)は信頼性が高く、よくメンテナンスされています。また、よりサイズの小さいディストリビューション(alpine
など)をベースにすることで、最終的なイメージサイズを削減できます。
例:
Dockerfile
FROM ubuntu:20.04 # Ubuntu 20.04 をベースにする
3.2. RUN
Dockerfile
RUN <command>
RUN
命令は、イメージをビルドしている途中のコンテナ内でコマンドを実行します。これは、パッケージのインストール、ディレクトリの作成、ファイルのダウンロードなど、イメージに含める必要のある操作を行うために使用されます。
RUN
命令には主に2つの形式があります。
-
シェル形式 (Shell form):
Dockerfile
RUN apt-get update && apt-get install -y some-package
コマンドは/bin/sh -c <command>
のようにシェルによって実行されます。シェルの機能(パイプ|
やリダイレクト>
など)が使えます。 -
実行可能形式 (Exec form):
Dockerfile
RUN ["apt-get", "update", "&&", "apt-get", "install", "-y", "some-package"]
コマンドは/bin/sh -c
を介さずに直接実行されます。変数を展開する際はENV
で設定した環境変数を指定する必要があります(例:RUN ["echo", "$MY_VAR"]
は変数展開されないが、RUN ["sh", "-c", "echo $MY_VAR"]
は展開される)。シェルを使わないため、シグナル処理がより適切に行われますが、一般的なパッケージ管理コマンドなどはシェル形式で書くことが多いです。
注意点:
- レイヤーの増加: 各
RUN
命令は新しいレイヤーを作成します。多くのRUN
命令があると、イメージのレイヤーが増え、管理が煩雑になったり、ビルドキャッシュが効きにくくなったりすることがあります。 - コマンドの連結: 複数の関連するコマンド(特に
apt-get update
とapt-get install
のようにセットで実行されるもの)は、&&
で連結して一つのRUN
命令にまとめることが推奨されます。これにより、レイヤー数を減らし、キャッシュの利用効率を高めることができます。また、apt-get update
とapt-get install
を同じRUN
命令で行うことで、apt-get update
のキャッシュが古くなる問題を回避できます。 - クリーンアップ:
RUN
命令でインストールしたパッケージのキャッシュなど、ビルドプロセスでのみ必要で最終的なイメージには不要なファイルは、同じRUN
命令内で削除することが重要です。これにより、イメージサイズを削減できます。
例:
Dockerfile
RUN apt-get update && \
apt-get install -y --no-install-recommends some-package another-package && \
rm -rf /var/lib/apt/lists/* # キャッシュを削除してイメージサイズを削減
行末の \
は、Dockerfile の記述を複数行に分割するために使用します。
3.3. COPY
Dockerfile
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 実行可能形式
COPY
命令は、ビルドコンテキスト(docker build
コマンドの引数で指定したディレクトリ)から、ビルド中のイメージ内の指定されたパスにファイルやディレクトリをコピーします。
<src>
: コピー元のファイルまたはディレクトリのパス。ビルドコンテキストからの相対パスで指定します。複数のパスを指定することも可能です。ワイルドカード (*
) も使用できます。<dest>
: コピー先のイメージ内のパス。絶対パスで指定するか、WORKDIR
で指定したパスからの相対パスで指定します。ディレクトリの場合は末尾を/
で終わらせるのが一般的です。--chown=<user>:<group>
: コピーしたファイルの所有者とグループを指定します(BuildKit 利用時のみ)。
注意点:
COPY
はビルドコンテキスト内のファイルしかコピーできません。ビルドコンテキスト外のファイルが必要な場合は、事前にビルドコンテキスト内に移動させる必要があります。<src>
に指定したファイルやディレクトリの内容が変更されると、そのCOPY
命令以降のレイヤーのキャッシュが無効になります。アプリケーションコードなど、頻繁に変更されるファイルをコピーするCOPY
命令は、Dockerfile のなるべく後ろの方に記述することで、その手前の、変更頻度の低い命令(ベースイメージの指定、パッケージインストールなど)のキャッシュを活用しやすくできます。
例:
Dockerfile
COPY . /app # ビルドコンテキストの全てをイメージの/appディレクトリにコピー
COPY config.json /etc/myapp/config.json # config.json を特定のパスにコピー
COPY src/*.py /app/ # srcディレクトリ以下の全ての.pyファイルを/appにコピー
3.4. ADD
Dockerfile
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] # 実行可能形式
ADD
命令は COPY
と似ていますが、以下の追加機能があります。
<src>
がURLの場合、そのURLからファイルをダウンロードして<dest>
に配置します。<src>
がtarアーカイブ(gzip, bzip2, xz)の場合、<dest>
に自動的に展開します。
これらの追加機能は便利な場合もありますが、意図しない挙動やセキュリティ上のリスク(特に外部URLからのダウンロード)につながる可能性もあるため、特別な理由がない限り、単にローカルファイルをコピーする場合は COPY
を使用することが推奨されます。
例:
Dockerfile
ADD http://example.com/somefile.tar.gz /app/ # URLからダウンロードして展開
ADD myarchive.tar.gz /tmp/ # ローカルのアーカイブを展開
注意点:
- URLからのダウンロードは、そのURLが変更された場合にキャッシュが無効になる可能性があります。
- tarアーカイブの自動展開は便利ですが、予期せぬファイルが含まれているリスクも考慮が必要です。
3.5. WORKDIR
Dockerfile
WORKDIR /path/to/workdir
WORKDIR
命令は、その後の RUN
, CMD
, ENTRYPOINT
, COPY
, ADD
命令が実行される際の現在の作業ディレクトリを設定します。複数の WORKDIR
命令を使うことができ、相対パスを指定した場合は直前の WORKDIR
で設定されたパスからの相対パスとなります。
これにより、Dockerfile 内で複雑なパスを指定する必要がなくなり、可読性が向上します。また、コンテナが起動した際にどのディレクトリでコマンドが実行されるかを指定できます。
例:
Dockerfile
WORKDIR /app # 以降の作業ディレクトリを /app に設定
COPY . . # /app ディレクトリにビルドコンテキストの内容をコピー
RUN build-script.sh # /app ディレクトリで build-script.sh を実行
3.6. CMD
Dockerfile
CMD ["executable","param1","param2"] # 実行可能形式 (推奨)
CMD ["param1","param2"] # ENTRYPOINT のデフォルトパラメータとして使用
CMD command param1 param2 # シェル形式
CMD
命令は、コンテナが起動したときにデフォルトで実行されるコマンドを指定します。Dockerfile には一つだけ CMD
命令を記述できます。複数ある場合は最後のものが有効になります。
- 実行可能形式:
CMD ["nginx", "-g", "daemon off;"]
のように、実行するコマンドとその引数を配列で指定します。これは、ENTRYPOINT
命令と組み合わせて使用する際に、ENTRYPOINT
へのデフォルト引数としても機能します。推奨される形式です。 - シェル形式:
CMD nginx -g "daemon off;"
のように、シェルコマンドとして記述します。これはCMD ["/bin/sh", "-c", "nginx -g \"daemon off;\""]
と解釈され、シェルを介してコマンドが実行されます。シェル機能(変数展開など)が利用できますが、コンテナのメインプロセスがシェルになるため、シグナル処理などに注意が必要です。
注意点:
CMD
は、コンテナ起動時にコマンドが指定されなかった場合のみ実行されます。docker run <image> <command>
のように<command>
を指定すると、Dockerfile のCMD
は無視され、指定されたコマンドが実行されます。ENTRYPOINT
とCMD
を組み合わせることで、コンテナの「役割」を明確にしつつ、起動時の引数をカスタマイズできるようにすることができます。
例:
“`Dockerfile
シェル形式の例
CMD [“python”, “app.py”] # Pythonスクリプトをデフォルトで実行
ENTRYPOINT と組み合わせる例 (後述)
ENTRYPOINT [“/app/entrypoint.sh”]
CMD [“–foreground”]
“`
3.7. ENTRYPOINT
Dockerfile
ENTRYPOINT ["executable", "param1", "param2"] # 実行可能形式 (推奨)
ENTRYPOINT command param1 param2 # シェル形式
ENTRYPOINT
命令は、コンテナが実行される際に常に実行されるコマンドを指定します。Dockerfile には一つだけ ENTRYPOINT
命令を記述できます。
CMD
と ENTRYPOINT
の主な違いは、docker run
コマンドの引数の扱いです。
CMD
:docker run <image> <command>
で指定されたコマンドによって上書きされます。ENTRYPOINT
:docker run <image> <arg1> <arg2> ...
で指定された引数は、ENTRYPOINT
で指定されたコマンドへの引数として追加されます(ENTRYPOINT
が実行可能形式の場合)。
ENTRYPOINT
を実行可能形式で使用し、CMD
をデフォルト引数として組み合わせるパターンが一般的です。これにより、コンテナを特定の実行ファイルとして振る舞わせつつ、オプション引数で挙動をカスタマイズできるようになります。
例:
“`Dockerfile
/app/entrypoint.sh はコンテナ起動時に常に実行されるスクリプトと仮定
COPY entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh
ENTRYPOINT [“/app/entrypoint.sh”] # 常にこのスクリプトを実行
CMD [“–help”] # entrypoint.sh に引数を指定しなかった場合のデフォルト引数
“`
この Dockerfile でビルドしたイメージを以下のように実行すると、
docker run <image>
->/app/entrypoint.sh --help
が実行されるdocker run <image> --config /path/to/config
->/app/entrypoint.sh --config /path/to/config
が実行される
3.8. ENV
Dockerfile
ENV <key>=<value> ...
ENV <key> <value>
ENV
命令は、ビルド中のイメージ、およびそのイメージから作成されるコンテナ内で使用される環境変数を設定します。
<key>=<value>
: 複数の環境変数を一度に設定できます(推奨)。<key> <value>
: 一つの環境変数を設定します。
設定された環境変数は、その後の Dockerfile 命令(RUN
, WORKDIR
など)や、コンテナ内で実行されるアプリケーションから参照可能になります。
例:
Dockerfile
ENV APP_VERSION=1.0.0 \
DATABASE_URL=postgresql://user:password@host:port/dbname
ENV PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app/bin" # PATHに追加
3.9. ARG
Dockerfile
ARG <name>[=<default value>]
ARG
命令は、イメージをビルドする際に docker build
コマンドの --build-arg <name>=<value>
オプションで値を渡すことができる変数を定義します。ARG
変数はビルド時のみ有効であり、最終的なコンテナ内では利用できません(ただし、ENV
で ARG
の値を参照して設定すれば、コンテナ内でも利用可能になります)。
ARG
は FROM
命令よりも前に定義することも可能です。これは、使用するベースイメージのバージョンなどをビルド時に指定したい場合に便利です。
例:
“`Dockerfile
ARG UBUNTU_VERSION=20.04 # デフォルト値20.04を設定
FROM ubuntu:${UBUNTU_VERSION}
ARG APP_USER=appuser # ビルド時のユーザー名を指定可能に
RUN useradd -m ${APP_USER}
この Dockerfile をビルドする際に、特定の Ubuntu バージョンを指定できます。
bash
docker build –build-arg UBUNTU_VERSION=22.04 -t my-ubuntu-app .
``
–build-argオプションを指定しない場合は、デフォルト値の
20.04` が使用されます。
ARG
と ENV
の違い:
* ARG
: ビルド時のみ有効な変数。--build-arg
で値を渡す。Dockerfile の FROM
より前にも置ける。
* ENV
: ビルド時とコンテナ実行時両方で有効な環境変数。Dockerfile 内で値を設定。docker run -e
で上書き可能。
3.10. VOLUME
Dockerfile
VOLUME ["/data"]
VOLUME /data
VOLUME
命令は、コンテナ内の指定されたパスが、ホストマシン上のボリュームまたは別のコンテナ上のボリュームにマウントされることを宣言します。これは、データの永続化や、コンテナ間でデータを共有するために使用されます。
ただし、VOLUME
命令はあくまで「このディレクトリはボリュームとして使うべき場所である」という 意図 を示すものであり、ビルド時にホスト側のディレクトリを実際にマウントするわけではありません。実際のボリュームのマウントは、docker run -v
コマンドで行います。Dockerfile の VOLUME
は、そのイメージからコンテナを起動する際に、指定されたパスに匿名ボリュームを自動的に作成・マウントする挙動を促します。
例:
Dockerfile
VOLUME /app/data # /app/data をボリュームとして使うことを宣言
3.11. EXPOSE
Dockerfile
EXPOSE <port> [<port>/<protocol>...]
EXPOSE
命令は、コンテナがリッスンするポートを指定します。これはドキュメントとしての意味合いが強く、実際にポートを公開するには docker run -p
オプションを使用する必要があります。EXPOSE
で指定されたポートは、docker run -P
(大文字P) オプションを使った際に自動的にホスト側のランダムなポートにマップされます。
プロトコルは TCP または UDP を指定できます。省略した場合は TCP とみなされます。
例:
Dockerfile
EXPOSE 80 # TCP 80番ポートを公開
EXPOSE 80/tcp 443/tcp # TCP 80番と443番を公開
EXPOSE 53/udp # UDP 53番を公開
3.12. USER
Dockerfile
USER <user>[:<group>]
USER
命令は、その後の RUN
, CMD
, ENTRYPOINT
命令を実行するユーザーおよびグループを指定します。デフォルトでは root ユーザーで実行されます。セキュリティを高めるために、可能な限り root 以外のユーザーでアプリケーションを実行することが推奨されます。
例:
Dockerfile
RUN useradd --no-create-home appuser # アプリケーション実行用のユーザーを作成
USER appuser # 以降のコマンドを appuser で実行
CMD ["./myapp"] # appuser でアプリを起動
3.13. LABEL
Dockerfile
LABEL <key>=<value> [<key>=<value> ...]
LABEL
命令は、イメージにメタデータを追加します。バージョン情報、メンテナー、ライセンス情報など、イメージに関する情報を構造化して格納するために使用できます。docker inspect <image>
コマンドでラベルを確認できます。
例:
Dockerfile
LABEL maintainer="Your Name <[email protected]>" \
version="1.0" \
description="My awesome web application" \
org.label-schema.schema-version="1.0" # 一般的なスキーマに従うことも可能
3.14. HEALTHCHECK
Dockerfile
HEALTHCHECK [OPTIONS] CMD command # コンテナが正常かチェックするコマンドを実行
HEALTHCHECK NONE # ヘルスチェックを無効にする
HEALTHCHECK
命令は、コンテナが「正常に動作しているか」を定期的にチェックする方法を指定します。指定されたコマンドがゼロ終了コードで終了すれば正常、非ゼロ終了コードであれば異常と判断されます。
OPTIONS
: チェックの間隔、タイムアウト、リトライ回数などを指定します。--interval=DURATION
: チェックの間隔 (デフォルト: 30s)--timeout=DURATION
: コマンドのタイムアウト (デフォルト: 30s)--start-period=DURATION
: コンテナ起動後の猶予期間。この期間中の失敗は無視される (デフォルト: 0s)--retries=N
: 正常と判断されるまでの連続成功回数 (デフォルト: 3)
CMD command
: 実行するチェックコマンドを指定します(RUN
やCMD
/ENTRYPOINT
と同じ形式)。
例:
Dockerfile
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1 # 5分ごとにlocalhostにアクセス、3秒でタイムアウト
この例では、curl
コマンドを使ってウェブサーバーが応答するかどうかをチェックしています。
3.15. SHELL
Dockerfile
SHELL ["executable", "parameters"]
SHELL
命令は、シェル形式の RUN
, CMD
, ENTRYPOINT
命令を実行する際のデフォルトシェルを指定します。Linux のデフォルトは ["/bin/sh", "-c"]
、Windows のデフォルトは ["cmd", "/S", "/C"]
です。
例:
Dockerfile
SHELL ["/bin/bash", "-c"] # デフォルトシェルをbashに変更
RUN echo "Using bash"
3.16. STOPSIGNAL
Dockerfile
STOPSIGNAL signal
STOPSIGNAL
命令は、コンテナを停止する際にカーネルに送信されるシステムコールシグナルを指定します。デフォルトは SIGTERM
です。アプリケーションによっては、SIGKILL
以外の特定のシグナルで gracefully shut down(優雅な終了)するようになっている場合があります。
例:
Dockerfile
STOPSIGNAL SIGINT
3.17. ONBUILD
Dockerfile
ONBUILD <instruction>
ONBUILD
命令は、このイメージをベースイメージとして使用して別のイメージをビルドする際に実行されるトリガー命令を登録します。この命令自体は、この Dockerfile のビルド時には何も実行しません。
これは、特定のフレームワークやライブラリのベースイメージを作成し、そのベースイメージを使う全てのアプリケーションイメージで共通のセットアップ(依存関係のインストール、ビルド成果物のコピーなど)を行いたい場合に便利です。
例:
“`Dockerfile
my-app-base イメージ
FROM ubuntu:20.04
WORKDIR /app
ONBUILD COPY . /app # このイメージをFROMとするDockerfileが実行された際に、COPY . /app が実行される
ONBUILD RUN make install # その後、RUN make install が実行される
``
FROM my-app-base
別の Dockerfile でと指定してビルドすると、そのビルド中に
ONBUILDで定義された
COPY . /appと
RUN make install` が自動的に実行されます。
3.18. ARG と ENV の組み合わせ
ビルド時に受け取った ARG
の値を、コンテナ実行時に環境変数として利用したい場合があります。この場合、ENV
命令の中で ARG
変数を参照します。
Dockerfile
ARG APP_VERSION=unknown
ENV APP_VERSION=${APP_VERSION} # ビルド時のARG APP_VERSION の値をENV APP_VERSION に設定
この記述により、docker build --build-arg APP_VERSION=1.2.3 .
とビルドすれば、コンテナ内で $APP_VERSION
が 1.2.3
となります。--build-arg
を指定しない場合は unknown
になります。
4. Dockerfile を記述する上でのベストプラクティス
効率的で、保守しやすく、サイズの小さいセキュアなイメージを作成するために、いくつかのベストプラクティスがあります。
4.1. キャッシュの活用(レイヤーの理解)
Dockerイメージはレイヤー構造になっています。Dockerfile の各命令(一部を除く)は新しい読み取り専用のレイヤーを作成します。docker build
は、各命令を実行する前に、同じ命令が記述された過去のビルド済みレイヤーがキャッシュとして存在するかをチェックします。キャッシュが見つかれば、そのレイヤーの構築をスキップし、キャッシュされたレイヤーを再利用します。
- キャッシュの仕組み: キャッシュのチェックは Dockerfile の命令の記述順に行われます。ある命令でキャッシュがヒットしなかった場合、それ以降の全ての命令はキャッシュを使わず新規に実行されます。
- 利用効率の向上: 変更頻度の低い命令(ベースイメージの指定、システムパッケージのインストールなど)を Dockerfile の上の方に、変更頻度の高い命令(アプリケーションコードのコピーなど)を下の方に記述することで、コードを変更してもキャッシュを活用しやすくなります。
- RUN 命令の連結: 複数の
RUN
命令を&&
で連結して一つにまとめることで、中間レイヤーの数を減らし、キャッシュの管理をシンプルにできます。また、前述のようにapt-get update
とapt-get install
を同じRUN
にまとめることは必須級のテクニックです。
例(キャッシュ効率を考慮):
“`Dockerfile
FROM python:3.9-slim # 変更頻度低い
依存パッケージのインストール(変更頻度低い)
RUN apt-get update && \
apt-get install -y –no-install-recommends some-deps && \
rm -rf /var/lib/apt/lists/*
Python依存ライブラリのインストール(requirements.txt 変更時のみキャッシュ無効)
COPY requirements.txt /app/
WORKDIR /app
RUN pip install –no-cache-dir -r requirements.txt # requirements.txt 変更時のみ再実行
アプリケーションコードのコピー(変更頻度高い)
COPY . /app/
CMD [“python”, “app.py”]
``
requirements.txt
この例では、を先にコピーして
pip installを実行しています。こうすることで、アプリケーションコード (
.全体) を変更しても
requirements.txtが変わっていなければ、
pip install` までのレイヤーはキャッシュが使われます。
4.2. イメージサイズの削減
コンテナイメージのサイズは、デプロイ時間やストレージコストに影響します。可能な限りイメージサイズを小さくすることが推奨されます。
- 軽量なベースイメージの選択:
alpine
ディストリビューションベースのイメージは非常に小さいです (alpine
,python:3.9-alpine
など)。必要なツールが含まれているか確認し、可能であれば利用しましょう。 - 不要なファイルの削除:
RUN
命令でパッケージをインストールした後、キャッシュファイル(apt のキャッシュ/var/lib/apt/lists/*
など)や一時ファイルは同じRUN
命令内で削除します。 - 一つの RUN 命令で複数の操作: 複数の操作を一つの
RUN
命令にまとめることで、各操作の中間ファイルが最終的なレイヤーに残るのを防ぎ、イメージサイズを小さくできます。 - .dockerignore ファイルの活用: ビルドコンテキストから除外するファイルやディレクトリを
.dockerignore
ファイルに記述します。Gitリポジトリの.gitignore
と似ており、ログファイル、一時ファイル、バージョン管理システム関連ファイル、Node.js のnode_modules
ディレクトリなど、ビルドに不要なものを除外することで、ビルドコンテキストの転送時間を短縮し、意図しないファイルをイメージに含めるのを防ぎます。
例: .dockerignore
gitignore
.git
.gitignore
Dockerfile
node_modules
npm-debug.log
dist/ # ビルド成果物は最終的にCOPYするが、ビルド過程の中間ファイルは不要
temp/
- マルチステージビルド: 最も効果的なイメージサイズ削減手法の一つです。次項で詳しく説明します。
4.3. セキュリティ
- root 以外のユーザーで実行: アプリケーションを root ユーザーで実行する必要がない場合は、専用のユーザーを作成し、そのユーザーで実行するようにします(
USER
命令)。これにより、コンテナが侵害された場合のリスクを軽減できます。 - 不要なパッケージを含めない: 必要なパッケージのみをインストールします。攻撃対象領域を減らすことにつながります。
- 機密情報の取り扱い: APIキーやパスワードなどの機密情報を Dockerfile 内にハードコードしたり、
COPY
でイメージに含めたりしてはいけません。これらはビルドキャッシュに残り、イメージをインスペクトすることで漏洩する可能性があります。代わりに、ビルド時の秘密情報(BuildKit の--secret
オプション)や、実行時のシークレット管理機能(Docker Secrets, Kubernetes Secrets, HashiCorp Vault など)を利用します。 - 公式イメージの利用: Docker Hub の公式イメージは、通常、セキュリティパッチが適用されており、信頼性が高いです。ただし、ベースイメージの脆弱性には注意が必要です。
4.4. 可読性と保守性
- コメントの活用: Dockerfile は
#
から始まる行をコメントとして扱います。各命令の目的や理由をコメントで補足することで、他の開発者が Dockerfile を理解しやすくなります。 - 命令の整理: 関連する命令はまとめて記述するなど、論理的に整理します。
- 変数 (ARG/ENV) の利用: ハードコードせず、変更の可能性がある値は変数として定義することで、保守性が向上します。
- シェルの選択:
SHELL
命令でデフォルトシェルを指定することで、RUN
命令などの記述方法を一貫させられます。
5. 高度な Docker build の概念
基本的な docker build
と Dockerfile の命令を理解した上で、より高度なビルドテクニックを学びましょう。
5.1. マルチステージビルド (Multi-stage builds)
マルチステージビルドは、イメージサイズを大幅に削減するための強力な機能です。一つの Dockerfile の中に複数の FROM
命令を記述し、それぞれの FROM
を異なるビルドステージとします。最終的なイメージは最後のステージの内容のみを含みます。
これにより、ビルドに必要なツール(コンパイラ、ビルドツール、テストツールなど)を含む大きめのイメージを最初のステージで使用し、そのステージで生成された成果物(コンパイル済みバイナリ、ライブラリなど)だけを、必要最小限のランタイム環境を含む小さめのイメージ(例えば alpine
や distroless
)にコピーして最終イメージとすることができます。ビルドツールやソースコードなど、実行時には不要なものが最終イメージに含まれなくなるため、サイズが劇的に小さくなります。
例:Go言語アプリケーションのマルチステージビルド
“`Dockerfile
Stage 1: ビルドステージ
FROM golang:1.20 AS builder # golang:1.20 イメージを ‘builder’ という名前で使用
WORKDIR /app
COPY go.mod go.sum ./ # 依存関係ファイルを先にコピー
RUN go mod download # 依存関係をダウンロード(変更がなければキャッシュされる)
COPY . . # ソースコードをコピー
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp . # アプリケーションをビルドして myapp バイナリを作成
Stage 2: 実行ステージ
FROM alpine:latest # より軽量なalpineイメージをベースにする
WORKDIR /root/
Stage 1 でビルドしたバイナリを Stage 2 にコピー
COPY –from=builder /app/myapp .
CMD [“./myapp”] # 最終イメージで実行するコマンド
``
FROM golang:1.20
この Dockerfile をビルドすると、最初ので始まるステージで Go アプリケーションがビルドされ、
/app/myappに実行可能バイナリが生成されます。次に、2番目の
FROM alpine:latestで始まるステージが開始され、前のステージ (
builder) で作成された
/app/myappバイナリだけが
COPY –from=builder命令によって新しいイメージにコピーされます。最終的なイメージには、Go のコンパイラやソースコードなどは含まれず、alpine Linux とビルド済みの
myapp` バイナリだけが含まれるため、非常に小さくなります。
マルチステージビルドは、コンパイル型言語(Go, Rust, Java, C++ など)のアプリケーションや、フロントエンド資産(Webpack などでバンドル)のビルドに非常に有効です。
5.2. ビルドキャッシュの管理と活用
キャッシュはビルド時間を短縮するために重要ですが、意図的にキャッシュを使いたくない場合や、外部のキャッシュを利用したい場合もあります。
--no-cache
:docker build --no-cache .
のように指定すると、全てのビルドキャッシュを無視して最初からビルドを実行します。デバッグ時や、キャッシュが原因で問題が発生している場合に利用します。--pull
:docker build --pull .
のように指定すると、ビルド開始時にベースイメージを強制的にプルします。ローカルに古いベースイメージがキャッシュされている場合に、常に最新版を使いたい場合に便利です。--cache-from
: 外部のイメージリポジトリやローカルの別のイメージからキャッシュをインポートして使用します。分散ビルド環境やCI/CDパイプラインで、過去のビルド結果をキャッシュとして利用する際に有効です。
5.3. BuildKit
BuildKit は、Docker Engine に統合された、より高性能で安全な新しいイメージビルドエンジンです。従来のビルダー(”classic” builder)と比較して、以下のような利点があります。
- 高速化: 並列ビルド、不要な中間レイヤーのスキップなどにより高速なビルドを実現します。
- 機能拡張:
- マルチステージビルドの改善
- Dockerfile の新しい命令や構文のサポート (例:
--mount
オプション) - ビルド時の秘密情報(シークレット)の安全な利用 (
--secret
) - SSH エージェントフォワーディング (
--ssh
) - キャッシュのより柔軟な管理
- マルチプラットフォームイメージのビルド (
docker buildx
)
- セキュリティ: Rootless build (root 権限なしでのビルド) をサポートします。
多くの新しい Docker バージョンではデフォルトで BuildKit が有効になっています。有効になっているかは docker build
実行時に [+] Building 1.2.3 ...
のような表示が出るかで確認できます。手動で有効にするには、環境変数 DOCKER_BUILDKIT=1
を設定するか、Docker Engine の設定ファイルで有効にします。
例:BuildKit の --mount
オプション
BuildKit では、RUN
命令に --mount
オプションを付けて、特定の目的でファイルシステムを一時的にマウントできます。例えば、キャッシュディレクトリをマウントしてビルドツール間の依存関係を高速化したり、シークレットをマウントして安全に利用したりできます。
“`Dockerfile
BuildKit が有効な場合
FROM ubuntu:20.04
RUN –mount=type=cache,target=/root/.cache apt-get update && apt-get install -y some-package
``
/root/.cache
この例では、をキャッシュタイプとしてマウントしています。これにより、
apt-get` のキャッシュがホスト側に永続化され、同じイメージを再度ビルドする際にキャッシュが利用され、ビルド時間が短縮されます。
5.4. docker buildx
docker buildx
は、BuildKit を基盤とした Docker CLI プラグインで、特に以下の機能を提供します。
- マルチプラットフォームビルド: 異なるアーキテクチャ(例: amd64, arm64)や OS 用のイメージをまとめてビルドできます。これにより、単一のコマンドで様々な環境で動作するイメージを作成し、一つのマニフェストリストとしてプッシュできます。
- より高度なキャッシュ管理: リモートレジストリへのキャッシュエクスポート/インポートなど。
docker buildx
を使うには、通常別途インストールが必要ですが、最近の Docker Desktop にはバンドルされています。使い方の詳細は公式ドキュメントを参照する必要がありますが、マルチプラットフォーム対応が求められる現代のコンテナイメージビルドにおいて非常に重要なツールです。
6. ビルドしたイメージの活用
docker build
でイメージを作成したら、それをどのように活用するかを見ていきましょう。
- イメージの確認:
docker images
コマンドでローカルにあるイメージ一覧を確認できます。
bash
docker images - イメージの実行:
docker run <image_name>[:<tag>] [command]
コマンドで、ビルドしたイメージからコンテナを作成・実行します。
bash
docker run my-nginx-app:latest
docker run -d -p 8080:80 my-nginx-app # バックグラウンド実行、ポートマッピング - イメージのタグ付け:
docker build -t
オプションでビルド時にタグを付けられますが、ビルド後にもdocker tag
コマンドでタグを追加・変更できます。通常、リポジトリ名/イメージ名:タグ の形式で指定します。
bash
docker tag my-nginx-app:latest myregistry/my-nginx-app:v1.0 -
イメージのリポジトリへのプッシュ: 作成したイメージを Docker Hub や Amazon ECR などのコンテナレジストリにプッシュすることで、他の人や環境と共有できます。プッシュするには、まずレジストリ名を含むタグを付ける必要があります。
“`bash
# Docker Hub にプッシュする場合 (my-dockerhub-id は自分のID)
docker tag my-nginx-app:latest my-dockerhub-id/my-nginx-app:latest
docker push my-dockerhub-id/my-nginx-app:latestAmazon ECR にプッシュする場合 (レジストリ名はAWSコンソールなどで確認)
例: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag my-nginx-app:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx-app:latest
docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-nginx-app:latest
* **イメージの削除**: 不要になったイメージは `docker rmi <image_id_or_name>` コマンドで削除できます。実行中のコンテナが使用しているイメージは削除できません。
bash
docker rmi my-nginx-app:latest
docker rmi XXXXXXXXXXXX # イメージIDで指定
“`
7. トラブルシューティング
Docker build 時に遭遇しやすい問題とその対処法を見ていきましょう。
- コンテキストの問題:
COPY
やADD
で指定したファイルが見つからない。- 原因:
docker build
コマンドの最後の引数(ビルドコンテキストパス)が、Dockerfile や必要なファイルを含むディレクトリになっていない。または、.dockerignore
で意図せずファイルが除外されている。 - 対処:
docker build .
のように、Dockerfile が含まれるディレクトリでコマンドを実行し、ビルドに必要なファイルが全てそのディレクトリ以下にあるか確認します。.dockerignore の内容も確認します。
- 原因:
- Dockerfile のシンタックスエラー: Dockerfile の命令や構文が間違っている。
- 原因: タイプミス、不正な引数、命令の順序間違い(例:
FROM
より前にRUN
など)。 - 対処: エラーメッセージをよく読み、エラーが発生した行の Dockerfile を確認します。Docker 公式ドキュメントの Dockerfile リファレンスを参照して、正しい構文を確認します。
- 原因: タイプミス、不正な引数、命令の順序間違い(例:
- RUN 命令の失敗:
RUN
命令で実行したコマンドがエラーで終了する。- 原因: パッケージが見つからない、コマンドの実行権限がない、依存関係の問題、ネットワーク接続の問題など。
- 対処: エラーメッセージを確認し、どのコマンドが失敗したか特定します。失敗したコマンドをローカル環境で単独で実行してみて原因を調査します。
RUN
命令の実行ユーザー(USER
命令)や、環境変数(ENV
命令)が適切か確認します。一時的にインタラクティブモードでコンテナを起動し、手動でコマンドを実行してデバッグすることも有効です(ただしビルドプロセス中ではない)。失敗したRUN
命令の前のレイヤーを基にコンテナを起動し (docker run -it <image_id_of_previous_layer> /bin/bash
)、デバッグを行います。
- キャッシュが効かない: 以前と同じ Dockerfile でビルドしているはずなのに、キャッシュが利用されずビルドに時間がかかる。
- 原因: Dockerfile のある命令や、その命令が参照するファイル(
COPY
やADD
のソースファイル)が変更された。RUN
命令で生成されるファイルやディレクトリの内容が変わった。 - 対処: Dockerfile の変更履歴を確認します。ビルドコンテキスト内のファイルに意図しない変更がないか確認します。
RUN
命令で生成されるファイルがビルド間で一貫しているか確認します。どうしてもキャッシュを使いたい場合は、--cache-from
オプションの利用を検討します。
- 原因: Dockerfile のある命令や、その命令が参照するファイル(
- ビルドプロセスが固まる/タイムアウトする: ビルド中に特定のステップで長時間停止する。
- 原因: ネットワーク接続の問題(パッケージダウンロードや外部リソースへのアクセス失敗)、ビルドステップが無限ループに陥っている、リソース不足(CPU, メモリ)。
- 対処: どのステップで停止しているか確認します。ログにエラーが出ていないか確認します。外部リソースへのアクセスが必要な場合はネットワーク設定を確認します。大量のリソースを使用するビルドの場合は、Docker デーモンに割り当てられたリソース(メモリなど)を増やせるか確認します。
8. まとめ
この記事では、「Docker build 入門」として、その基本的な使い方から、Dockerfile の主要な命令の詳細、効率的なビルドのためのベストプラクティス、そしてマルチステージビルドや BuildKit といった高度な概念までを網羅的に解説しました。
docker build
は、アプリケーションのコンテナ化において最も基礎となるステップです。Dockerfile を理解し、効果的に記述できるようになることは、再現可能で移植性の高いコンテナイメージを作成するための鍵となります。
ここで学んだ知識を活かして、ぜひご自身のアプリケーションの Dockerfile を作成・改善してみてください。最初はシンプルなものから始め、徐々にベストプラクティスや高度な機能を導入していくのが良いでしょう。
Docker の世界は docker build
だけに留まりません。ビルドしたイメージを複数のコンテナとして管理・連携させる Docker Compose、そして大規模なコンテナ化アプリケーションを管理・運用するための Kubernetes といったツールへとつながっていきます。しかし、これらの応用を学ぶ上でも、Docker イメージのビルドに関する理解は非常に重要です。
この記事が、あなたの Docker 活用の最初の一歩、あるいはさらなるステップアップの一助となれば幸いです。実践を通じて、さらに理解を深めていってください。
Happy Containerizing!