【初心者向け】docker buildコマンドの使い方を徹底解説
Dockerを使い始める上で、避けて通れない重要なステップの一つが、独自のDockerイメージを作成することです。そして、そのイメージ作成の中心となるコマンドが、今回徹底的に解説するdocker buildコマンドです。
この記事は、Dockerを触り始めたばかりの初心者の方を対象としています。docker buildコマンドの基本的な使い方から、知っておくと便利なオプション、ビルドを効率化・最適化するためのテクニックまで、具体的な例を交えながら丁寧に解説していきます。
この記事を読み終える頃には、あなたは自信を持って自分のアプリケーションのDockerイメージを作成できるようになっているはずです。さあ、Dockerイメージ作成の世界へ一緒に飛び込みましょう!
1. はじめに:Dockerとは何か、そしてdocker buildの重要性
まずは、なぜDockerを使い、なぜdocker buildが重要なのかを簡単に理解しておきましょう。
1.1. Dockerとは?
Dockerは、アプリケーションとその依存関係を「コンテナ」と呼ばれる独立した環境にまとめてパッケージ化し、どの環境でも同じように実行できるようにするためのプラットフォームです。
これにより、「開発環境では動いたのに、本番環境では動かない!」といった問題を解決し、アプリケーションのデプロイ、テスト、実行を劇的に容易にします。
1.2. なぜDockerを使うのか?
- 環境の統一性: アプリケーションが動作する環境をコードとして定義できるため、開発者、テスター、運用者など、誰がどこで実行しても同じ結果が得られます。
- 依存関係の管理: アプリケーションに必要なライブラリ、フレームワーク、設定などをコンテナ内に閉じ込めることができます。
- スケーラビリティと効率性: コンテナは軽量で起動が速いため、多くのコンテナを効率的に実行でき、必要に応じて容易にスケールさせることができます。
- 可搬性: 作成したコンテナイメージを一度作成すれば、それをDockerがインストールされている任意の環境(ローカルPC、サーバー、クラウドなど)で実行できます。
1.3. Dockerイメージとコンテナ
Dockerには「Dockerイメージ」と「コンテナ」という重要な概念があります。
- Dockerイメージ (Image): アプリケーションを実行するために必要なすべてのもの(コード、ランタイム、システムツール、ライブラリ、設定ファイルなど)をパッケージ化した、読み取り専用のテンプレートです。例えるなら、「OSをインストール済みの仮想マシンのディスクイメージ」のようなものです。
- コンテナ (Container): Dockerイメージを元に作成され、実際にアプリケーションが実行される独立した環境です。イメージが設計図なら、コンテナはその設計図から作られた「実体」や「インスタンス」のようなものです。コンテナは読み書き可能なレイヤーを持ち、そこでアプリケーションが動作し、状態が変化します。
1.4. docker buildコマンドの役割
では、そのDockerイメージはどのように作成するのでしょうか?そこで登場するのがdocker buildコマンドです。
docker buildコマンドは、Dockerfileというテキストファイルに記述された命令を読み込み、その命令に従ってDockerイメージを作成します。Dockerfileは、どのようなベースイメージを使用し、どのようなファイルを追加し、どのようなコマンドを実行してイメージを構築するかを定義する「イメージ作成のためのレシピ」のようなものです。
つまり、あなたが独自のアプリケーションのDockerイメージを作成したい場合、まずそのためのDockerfileを作成し、次にそのDockerfileを使ってdocker buildコマンドを実行するという流れになります。
docker buildコマンドは、Dockerの世界における「ものづくり」の根幹を担う、非常に重要なコマンドと言えるでしょう。
2. Dockerイメージとは?もう少し詳しく
docker buildコマンドの解説に入る前に、Dockerイメージについてもう少し深く理解しておきましょう。これが理解できると、なぜDockerfileの書き方やdocker buildのオプションが重要なのかが見えてきます。
2.1. イメージは「層(Layer)」でできている
Dockerイメージの最大の特徴は、それが複数の読み取り専用の層(Layer)から構成されていることです。
Dockerfileの各命令(FROM, RUN, COPYなど)は、それぞれ新しい層を作成します。例えば、FROM ubuntuはUbuntuのベースイメージという層、RUN apt-get updateはパッケージリストを更新した状態の層、COPY . /appはアプリケーションコードを追加した状態の層、というように積み重ねられていきます。
イメージがビルドされると、これらの層が上から順に重ね合わさって、最終的な読み取り専用イメージが完成します。
2.2. レイヤーのメリット:効率性とキャッシュ
このレイヤー構造には大きなメリットがあります。
- 効率的なストレージ: 同じベースイメージを使用する複数のイメージがある場合、共通のベースイメージレイヤーを共有するため、ディスク容量を節約できます。
- 高速なビルド(キャッシュ):
docker buildコマンドを実行する際、DockerはDockerfileの各命令に対応するレイヤーがすでに存在するかどうかをチェックします。もし、同じ命令で作成されたレイヤーが以前のビルドで見つかれば、Dockerはそのレイヤーを再利用します(これを「ビルドキャッシュの利用」と呼びます)。これにより、変更がない部分のビルドをスキップでき、ビルド時間を大幅に短縮できます。
ビルドキャッシュを最大限に活用するためには、Dockerfileの書き方(特に命令の順序)が重要になります。これについては後ほど詳しく解説します。
3. Dockerfileとは?イメージ作成のレシピ
docker buildコマンドが読み込む「Dockerfile」について説明します。Dockerfileは、特定の構文に従って記述されたただのテキストファイルです。
3.1. Dockerfileの基本構造
Dockerfileは、通常、プロジェクトのルートディレクトリに配置され、ファイル名はデフォルトでDockerfileです(ただし、変更も可能です)。
基本的な構造は、命令 (Instruction) とそれに続く 引数 (Arguments) の組み合わせです。各行が一つの命令を表します。
“`Dockerfile
コメントは#で始めることができます
ベースイメージを指定します
FROM ubuntu:latest
環境変数を設定します
ENV MY_VAR=”some_value”
ディレクトリを作成し、そこに移動します
WORKDIR /app
ローカルのファイルをイメージ内にコピーします
COPY . /app
パッケージをインストールし、クリーンアップします
RUN apt-get update && \
apt-get install -y –no-install-recommends some-package && \
rm -rf /var/lib/apt/lists/*
コンテナが待ち受けるポートを指定します(ドキュメント用)
EXPOSE 80
コンテナが起動したときに実行されるコマンドを指定します
CMD [“python”, “app.py”]
“`
3.2. 主なDockerfile命令
docker buildは、Dockerfileの上から順番に命令を実行していきます。ビルドプロセスで特によく使われる、そして理解しておくべき主な命令をいくつか紹介します。
FROM <image>[:<tag>] [AS <name>]:- 新しいビルドステージを開始し、使用するベースイメージを指定します。Dockerfileは必ずこの命令から始まります(ただし、ARG命令が最初にくる場合もあります)。
- 例:
FROM ubuntu:latest(最新のUbuntuイメージを使用)
RUN <command>:- イメージの現在の層の上に新しい層を作成し、そこでコマンドを実行します。パッケージのインストール、ファイルの作成、コマンドの実行など、イメージの内容を変更するのに使われます。
- 例:
RUN apt-get update && apt-get install -y curl
COPY <src>... <dest>:- ホストマシン(
docker buildコマンドを実行しているマシン)の指定されたファイルやディレクトリ (src) を、イメージ内の指定されたパス (dest) にコピーします。srcはビルドコンテキストに対する相対パスである必要があります。 - 例:
COPY ./app /app(カレントディレクトリの./appをイメージの/appにコピー)
- ホストマシン(
ADD <src>... <dest>:COPYと似ていますが、以下の追加機能があります。srcがtarアーカイブの場合、イメージ内で自動的に展開します。srcがリモートURLの場合、ファイルをダウンロードしてイメージ内に追加します。
- 通常は
COPYを使用することが推奨されます。 - 例:
ADD https://example.com/archive.tar.gz /tmp/
WORKDIR <path>:- Dockerfile内の以降の
RUN,CMD,ENTRYPOINT,COPY,ADD命令の作業ディレクトリを設定します。フルパスまたは以前に設定されたWORKDIRに対する相対パスで指定できます。 - 例:
WORKDIR /app
- Dockerfile内の以降の
ENV <key>=<value> ...:- ビルド時に使用できる環境変数を設定します。これらの環境変数は、作成されたコンテナでも利用可能です。
- 例:
ENV NODE_VERSION=16.0.0
ARG <name>[=<default value>]:- ビルド時に
--build-argオプションを使って値を渡せる変数を定義します。RUN命令などで一時的に使用したり、ENV命令の値として設定したりできます。 - 例:
ARG VERSION=latest
- ビルド時に
EXPOSE <port> [<port>/<protocol>...]:- コンテナが実行時にリスンするネットワークポートを指定します。これはコンテナのドキュメントとして機能し、実際にポートを公開するには
docker run -pオプションが必要です。 - 例:
EXPOSE 80 443
- コンテナが実行時にリスンするネットワークポートを指定します。これはコンテナのドキュメントとして機能し、実際にポートを公開するには
CMD <command>またはCMD ["executable","param1","param2"]:- コンテナが起動したときにデフォルトで実行されるコマンドを指定します。Dockerfile内で一つだけ指定できます。
ENTRYPOINTと組み合わせて使うこともあります。 - 例:
CMD ["nginx", "-g", "daemon off;"]
- コンテナが起動したときにデフォルトで実行されるコマンドを指定します。Dockerfile内で一つだけ指定できます。
ENTRYPOINT <command>またはENTRYPOINT ["executable","param1","param2"]:- コンテナが実行可能なものとして起動されるように設定します。
CMDと組み合わせて使うことで、CMDをデフォルトの引数として扱うことができます。 - 例:
ENTRYPOINT ["nginx"]
- コンテナが実行可能なものとして起動されるように設定します。
これらの命令は、docker buildコマンドによって上から順番に解釈され、実行されていきます。各命令の実行が成功すると、新しいレイヤーがコミットされ、次の命令のベースとなります。
4. docker buildコマンドの基本
いよいよdocker buildコマンドの基本的な使い方を見ていきましょう。
4.1. 基本的な書式
docker buildコマンドの最も基本的な書式は以下の通りです。
bash
docker build [オプション] <パスまたはURL>
[オプション]: ビルドの挙動を制御するためのさまざまなオプションを指定します。これについては後ほど詳しく解説します。<パスまたはURL>: ビルドコンテキストの場所を指定します。これは通常、Dockerfileが含まれているディレクトリのパス、またはリモートのGitリポジトリのURLなどを指定します。
4.2. ビルドコンテキストとは?
<パスまたはURL>で指定する「ビルドコンテキスト」とは、docker buildコマンドを実行する際にDockerデーモンに送信される、ローカルファイルシステムのセットのことです。Dockerfileも通常はこのビルドコンテキストに含まれます。
Dockerデーモンは、このビルドコンテキスト内のファイルにしかアクセスできません。Dockerfile内でCOPYやADD命令を使ってファイルを追加する場合、これらのファイルはビルドコンテキスト内に存在している必要があります。
重要な点: ビルドコンテキストには、Dockerfileが必要とするファイルだけを含めるようにしましょう。必要ない大きなファイル(例: Gitリポジトリ全体、一時ファイル、ビルド生成物)が含まれていると、ビルドコンテキストの送信に時間がかかり、ビルドが遅くなります。
4.3. 最もシンプルな使い方: docker build .
最も一般的なdocker buildコマンドの使い方は、カレントディレクトリをビルドコンテキストとして指定し、その中に置かれたDockerfileという名前のファイルを使ってビルドする場合です。
この場合、コマンドは以下のようになります。
bash
docker build .
.(ドット)は、カレントディレクトリ(docker buildコマンドを実行している場所)をビルドコンテキストとして指定することを意味します。
実行例:
まず、以下のようなシンプルなファイル構成を用意します。
my-web-app/
├── Dockerfile
└── index.html
index.htmlの内容:
“`html
Hello, Docker!
“`
Dockerfileの内容:
“`Dockerfile
ベースイメージとして小さなNginxイメージを使用
FROM nginx:alpine
index.htmlをNginxのデフォルトのHTMLディレクトリにコピー
COPY index.html /usr/share/nginx/html/
Nginxはデフォルトで80番ポートを待ち受けます(EXPOSEは必須ではないが推奨)
EXPOSE 80
Nginxコンテナの起動コマンドはベースイメージで定義済み
CMD [“nginx”, “-g”, “daemon off;”] # これがデフォルトの起動コマンド
“`
my-web-appディレクトリに移動し、以下のコマンドを実行します。
bash
cd my-web-app
docker build .
コマンドを実行すると、Dockerデーモンにカレントディレクトリ(my-web-app)の内容がビルドコンテキストとして送信され、次にDockerfileの各命令が順番に実行されます。
[+] Building
Step 1/3 : FROM nginx:alpine
---> 8203c3a5f5b4
Step 2/3 : COPY index.html /usr/share/nginx/html/
---> Using cache
---> b4025f5820e5
Step 3/3 : EXPOSE 80
---> Using cache
---> 7d97341181b9
Successfully built 7d97341181b9
上記の出力は、各ステップ(Dockerfileの各命令)が実行され、最終的にイメージが正常にビルドされたことを示しています。最後の行の7d97341181b9は、作成されたイメージのID(長いIDの一部)です。
このコマンドだけでは、作成されたイメージに分かりやすい名前やタグが付きません。イメージIDを使って参照することはできますが、管理しづらいです。そこで、次の重要なオプションが登場します。
5. イメージに名前とタグを付ける (-t)
作成したDockerイメージに、人間が分かりやすい名前とバージョン(タグ)を付けることは非常に重要です。これを行うのが-tまたは--tagオプションです。
5.1. なぜ名前とタグが必要か
- 識別: どのイメージが何であるかを容易に区別できます。
- バージョン管理: 同じアプリケーションの異なるバージョン(例: 1.0、1.1、latest)を管理できます。
- 共有: Docker Registry(Docker Hubなど)にプッシュしたり、他の人と共有したりする際に名前とタグが必要です。
5.2. 名前とタグの形式
イメージの名前とタグは、以下の形式で指定するのが一般的です。
[リポジトリ名/][イメージ名][:<タグ>]
リポジトリ名: 通常はDocker Hubのユーザー名や組織名などです。これを指定しない場合、デフォルトでDocker Hubの公式リポジトリやローカルのリポジトリとして扱われます。イメージ名: アプリケーション名やサービス名など、イメージの内容を表す名前です。タグ: イメージのバージョン、ビルド番号、またはlatestなどの識別子です。タグを指定しない場合、デフォルトでlatestタグが付けられます。
例:
ubuntu: Docker Hub公式のUbuntuイメージ (デフォルトでlatestタグ)ubuntu:20.04: Docker Hub公式のUbuntuイメージのバージョン20.04myusername/my-app: Docker Hubのユーザーmyusernameのリポジトリにあるmy-appイメージ (デフォルトでlatestタグ)myusername/my-app:1.0: Docker Hubのユーザーmyusernameのリポジトリにあるmy-appイメージのバージョン1.0
5.3. -tオプションの使い方
-t <名前>:<タグ>の形式で指定します。
bash
docker build -t my-web-app:1.0 .
実行例:
前のセクションで使用したmy-web-appディレクトリで、以下のコマンドを実行します。
bash
cd my-web-app
docker build -t my-web-app:1.0 .
出力の最後に、イメージが正常にビルドされ、指定した名前とタグが付けられたことが表示されます。
...
Successfully built 7d97341181b9
Successfully tagged my-web-app:1.0
これで、my-web-app:1.0という名前でこのイメージを参照できるようになりました。docker imagesコマンドで確認できます。
bash
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-web-app 1.0 7d97341181b9 About a minute ago 23.4MB
nginx alpine 8203c3a5f5b4 2 weeks ago 23.4MB
5.4. 複数のタグを付ける
一つのビルドプロセスで、同じイメージに複数のタグを付けることも可能です。これは、例えば最新バージョンにlatestタグと特定のバージョン番号タグの両方を付けたい場合などに便利です。
-tオプションを複数回指定するだけです。
bash
docker build -t my-web-app:1.0 -t my-web-app:latest .
これにより、同じイメージIDに対してmy-web-app:1.0とmy-web-app:latestの両方のタグが関連付けられます。
6. Dockerfileの場所を指定する (-f)
デフォルトでは、docker buildコマンドはビルドコンテキストのルートディレクトリにあるDockerfileという名前のファイルを探します。しかし、Dockerfileを別の名前で保存したい場合や、ビルドコンテキストのサブディレクトリに配置したい場合があります。
このような場合に-fまたは--fileオプションを使用します。
6.1. -fオプションの使い方
-f <Dockerfileのパス>の形式で指定します。ここで指定するパスは、ビルドコンテキストに対する相対パス、または絶対パスのいずれかです。ただし、ビルドコンテキストの範囲外のファイルを指定しようとするとエラーになる場合があります。通常はビルドコンテキスト内での相対パスで指定します。
bash
docker build -f path/to/MyDockerfile .
実行例:
ファイル構成を以下のように変更したとします。
my-project/
├── dockerfiles/
│ └── my-web-app.Dockerfile
└── web/
└── index.html
この場合、ビルドコンテキストはmy-projectディレクトリ、Dockerfileはdockerfiles/my-web-app.Dockerfileとなります。
ビルドコンテキストはmy-projectディレクトリ全体(.で指定)とし、-fオプションでDockerfileのパスを指定します。
bash
cd my-project
docker build -t my-web-app:1.0 -f dockerfiles/my-web-app.Dockerfile .
Dockerfileの内容でCOPY命令を使う場合、そのソースパスはビルドコンテキストのルートからの相対パスになります。
例として、dockerfiles/my-web-app.Dockerfileでweb/index.htmlをコピーする場合、COPY命令は以下のようになります。
Dockerfile
FROM nginx:alpine
COPY web/index.html /usr/share/nginx/html/
EXPOSE 80
my-projectディレクトリがビルドコンテキスト(.)として指定されているため、COPY web/index.html ...はmy-project/web/index.htmlを意味します。
このように、-fオプションを使うことで、Dockerfileの配置場所やファイル名に柔軟性を持たせることができます。
7. ビルドコンテキストからファイルを除外する (.dockerignore)
前述の通り、ビルドコンテキストはdocker buildコマンド実行時にDockerデーモンに送信されます。不要なファイル(例: Gitリポジトリ、開発ツール、一時ファイル、ログファイルなど)が含まれていると、ビルドコンテキストの送信に時間がかかり、ビルドが遅くなるだけでなく、最終的なイメージサイズも大きくなる可能性があります。
これを避けるために、.dockerignoreファイルを使用します。
7.1. .dockerignoreの役割
.dockerignoreファイルは、ビルドコンテキストから除外したいファイルやディレクトリを指定するためのファイルです。Gitの.gitignoreファイルと非常によく似た書式を持ちます。
ビルドコンテキストのルートディレクトリに.dockerignoreという名前で配置します。
7.2. .dockerignoreの書き方
- 各行に無視するファイルやディレクトリのパターンを記述します。
#で始まる行はコメントとして扱われます。- スラッシュ(
/) はディレクトリの区切り文字です。 - ワイルドカード(
*)を使用してパターンを指定できます。 - 行頭の感嘆符(
!)は、無視パターンに一致するファイルやディレクトリを含める(無視しない)ための例外指定です。
.dockerignoreの例:
“`
Ignore all Git related files
.git
.gitignore
Ignore development specific files
*.swp
.vscode/
Ignore dependencies directory
node_modules
Ignore build artifacts
build/
dist/
Ignore log files
*.log
temp/
But don’t ignore the ‘required-config.json’ file inside ‘temp/’
!temp/required-config.json
“`
効果:
上記の.dockerignoreファイルがビルドコンテキストのルートに存在する場合、docker build .を実行すると、.gitディレクトリ、node_modulesディレクトリ、buildディレクトリなどがビルドコンテキストに含まれずにDockerデーモンに送信されます。これにより、ビルドコンテキストのサイズが大幅に削減され、ビルドが高速化される可能性があります。
特に大規模なプロジェクトや、多くの依存関係を持つプロジェクトでは、.dockerignoreファイルを適切に設定することが非常に重要です。
8. ビルド時のキャッシュ活用
Dockerのレイヤー構造の最大のメリットの一つは、ビルドキャッシュです。docker buildは、Dockerfileの各命令を実行する前に、その命令に対応するレイヤーが以前にビルド済みかどうかをチェックします。
8.1. キャッシュが使われる条件
Dockerは、Dockerfileの各命令を上から順に処理します。ある命令のキャッシュがヒットするための主な条件は以下の通りです。
- 命令自体が以前と同じであること:
RUN apt-get updateなどのコマンドが全く同じである必要があります。 - 関連するファイルが変更されていないこと:
COPYやADD命令の場合、コピー元(ビルドコンテキスト内)のファイルのコンテンツやメタデータ(最終更新時刻など)が前回ビルド時から変更されていない必要があります。 - 直前の命令まででキャッシュがヒットしていること: Dockerは順番にキャッシュをチェックするため、ある命令でキャッシュがミスした場合、それ以降の全ての命令はキャッシュを使用せず再実行されます。
キャッシュヒットの例:
Dockerfile
FROM ubuntu:latest # Layer A
COPY . /app # Layer B (./appの内容が変更なければキャッシュヒット)
RUN apt-get update # Layer C (コマンドが同じでLayer Bがキャッシュヒットしていればキャッシュヒット)
RUN apt-get install -y ... # Layer D (コマンドが同じでLayer Cがキャッシュヒットしていればキャッシュヒット)
このDockerfileでCOPY . /appの対象である./app内のファイルに変更がなければ、COPY命令でキャッシュがヒットします。そして、その後のRUN命令でもキャッシュがヒットする可能性が高くなります(命令自体が同じであれば)。
キャッシュミスの例:
Dockerfile
FROM ubuntu:latest
COPY . /app # ./app内のファイルが変更された -> キャッシュミス!
RUN apt-get update # Layer Bがキャッシュミスしたため、この命令は必ず再実行される
RUN apt-get install -y ... # Layer Cがキャッシュミスしたため、この命令は必ず再実行される
COPY . /appでコピーするファイルの内容が一つでも変更された場合、この命令はキャッシュミスとなり再実行されます。その結果、その後のRUN命令もキャッシュが使われず再実行されます。
8.2. キャッシュを意識したDockerfileの書き方
このキャッシュの仕組みを理解すると、Dockerfileの書き方を工夫することでビルド時間を短縮できることが分かります。
- 頻繁に変更される命令はDockerfileの下の方に書く:
- アプリケーションのコード(
COPY . /appなど)は開発中に頻繁に変更されます。 - 依存関係のインストール(
RUN apt-get install ...,RUN pip install ...など)は、依存関係リスト(例:requirements.txt,package.json)が変更されない限り、キャッシュを再利用したい部分です。 - したがって、依存関係のインストールなどのキャッシュを有効に活用したいステップは、アプリケーションコードのコピーよりも前に記述するのがベストプラクティスです。
- アプリケーションのコード(
“`Dockerfile
FROM python:3.9-slim
WORKDIR /app
依存関係リストだけを先にコピーし、依存関係をインストール
requirements.txt が変更されない限り、この層はキャッシュヒットする
COPY requirements.txt .
RUN pip install –no-cache-dir -r requirements.txt
アプリケーションコードをコピー(頻繁に変更される可能性が高い)
ここがキャッシュミスしても、上の依存関係インストールはキャッシュ利用可能
COPY . .
CMD [“python”, “app.py”]
“`
このように記述することで、アプリケーションコードだけを変更した場合は、依存関係インストールのステップはキャッシュが利用され、ビルドが高速になります。
-
RUN命令を連結する:- 複数のコマンドを一つの
RUN命令にまとめることで、作成されるレイヤーの数を減らすことができます。例えば、パッケージのアップデートとインストールは、以下のよう一つのRUN命令で実行することが推奨されます。
Dockerfile
RUN apt-get update && \
apt-get install -y --no-install-recommends some-package && \
rm -rf /var/lib/apt/lists/*
* これにより、途中の状態(例:apt-get updateだけ実行された状態)が中間レイヤーとして残ることを防ぎ、イメージサイズを小さくし、キャッシュの管理もシンプルになります。最後のrm -rf ...は、キャッシュファイルを削除してイメージサイズをさらに削減するための重要なテクニックです。 - 複数のコマンドを一つの
8.3. キャッシュ関連のオプション
--no-cache: ビルド時にキャッシュを一切使用せず、すべての命令を最初から実行します。bash
docker build --no-cache -t my-app:1.0 .- これは、キャッシュが原因で予期しない挙動が発生する場合や、確実に最新の状態でビルドしたい場合などに使用します。
--force-rm: 中間コンテナを常に削除します。デフォルトではビルド成功時にのみ中間コンテナが削除されます。デバッグ目的などで中間コンテナを残したい場合は--rm=falseを使用します。
9. ビルド引数を渡す (--build-arg)
Dockerfile内で、ビルド時に外部から値を渡したい場合があります。例えば、インストールするソフトウェアのバージョンをビルド時に指定したい、環境固有の設定値を渡したい、といったケースです。
これには、DockerfileのARG命令とdocker buildコマンドの--build-argオプションを使用します。
9.1. DockerfileでのARG命令
Dockerfile内でARG命令を使うと、ビルド時に外部から値を受け取れる変数を定義できます。
“`Dockerfile
ビルド引数を定義。デフォルト値も指定可能。
ARG APP_VERSION=latest
ARG BUILD_DATE
FROM ubuntu:latest
ARGで受け取った値をENVとしてイメージ内に設定する例
ENV APP_VERSION=${APP_VERSION}
ENV BUILD_DATE=${BUILD_DATE}
RUN命令でARGの値を使う例
RUN echo “Building version ${APP_VERSION}”
“`
ARG命令で定義された変数は、そのARG命令が記述された行から、それ以降のFROMまたはRUN命令などの引数として利用できます。ただし、ARGで定義された変数は、ビルド時のみ有効であり、作成されたイメージ内の環境変数としては引き継がれません(ENV命令で改めて設定しない限り)。
9.2. --build-argオプションの使い方
docker buildコマンド実行時に、--build-arg <変数名>=<値>の形式で値を渡します。Dockerfileで定義されていないARGに対して値を渡そうとすると警告が出ます。
bash
docker build --build-arg APP_VERSION=2.0 --build-arg BUILD_DATE="2023-10-27" -t my-app:2.0 .
実行例:
以下の内容でDockerfileを作成します。
“`Dockerfile
ARG NODE_VERSION=lts
FROM node:${NODE_VERSION}-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD [“node”, “server.js”]
“`
デフォルトではNODE_VERSION=ltsが使われますが、特定のバージョンでビルドしたい場合は--build-argを使います。
“`bash
デフォルト値 (lts) でビルド
docker build -t my-node-app:lts .
バージョン 18 でビルド
docker build –build-arg NODE_VERSION=18 -t my-node-app:18 .
バージョン 20 でビルド
docker build –build-arg NODE_VERSION=20 -t my-node-app:20 .
“`
このように--build-argを使うことで、同じDockerfileを使って、異なる設定やバージョンのイメージを柔軟にビルドできます。これは、CI/CDパイプラインなどでビルドを自動化する際に非常に便利です。
10. その他の便利なdocker buildオプション
ここまで解説したオプション以外にも、ビルドを制御するための便利なオプションがいくつかあります。
--pull: ベースイメージを常に最新の状態に更新します。bash
docker build --pull -t my-app:latest .- デフォルトでは、Dockerはローカルにキャッシュされたベースイメージを使用しようとします。
--pullを指定すると、ビルドを開始する前にベースイメージの最新版をRegistryからプルしてきます。これにより、常に最新のセキュリティパッチなどが適用されたベースイメージでビルドできますが、ビルド時間は少し長くなる可能性があります。
--quiet(-q): ビルドの進行状況や詳細な出力を抑制し、最終的なイメージIDのみを表示します。bash
docker build -q -t my-app:latest .- スクリプトなどでの自動化で、余分な出力が不要な場合に便利です。
--rm: ビルド成功時に中間コンテナを削除します。bash
docker build --rm -t my-app:latest .- これはデフォルトで有効になっています。中間コンテナを残したい場合は
--rm=falseを指定します(主にデバッグ目的)。
--network <モード>: ビルド中にRUN命令などが使用するネットワークモードを指定します。- 例:
host,none,defaultなど。通常はデフォルトで問題ありません。インターネットアクセスが必要な場合などに使用します。 bash
docker build --network host -t my-app:latest .
- 例:
--progress <タイプ>: ビルド進行状況の表示形式を指定します。auto(デフォルト),plain,ttyなどがあります。bash
docker build --progress=plain -t my-app:latest .plainは、特にCI環境などで、より単純な行単位の出力を得たい場合に便利です。
これらのオプションを適切に使うことで、ビルドプロセスをより細かく制御できます。
11. マルチステージビルド
現代のDockerイメージビルドのベストプラクティスの一つが、マルチステージビルドです。これは、一つのDockerfileの中で複数のFROM命令を使用し、それぞれのステージで異なる目的(例: ビルド、テスト、最終イメージ作成)を持つイメージを一時的に作成し、最終的なイメージには必要な成果物だけをコピーするというテクニックです。
11.1. なぜマルチステージビルドが必要か
従来のビルド方法では、アプリケーションのビルドに必要なSDKやコンパイラ、テストツール、開発用ライブラリなどもすべて最終的なイメージに含まれてしまい、イメージサイズが非常に大きくなるという問題がありました。
マルチステージビルドを使うと、以下のようなメリットが得られます。
- イメージサイズの削減: 最終的なイメージには、アプリケーションの実行に必要な最小限のファイルのみを含めることができます。ビルドツールや開発環境は中間ステージに閉じ込めます。
- ビルドプロセスの分離: ビルド環境と実行環境を明確に分離できます。
- Dockerfileの可読性向上: 各ステージが独立した役割を持つため、Dockerfileが理解しやすくなります。
11.2. Dockerfileでのマルチステージビルドの書き方
マルチステージビルドは、複数のFROM命令を使用することで実現します。それぞれのFROM命令は新しいビルドステージを開始します。
“`Dockerfile
ステージ 1: ビルダー (アプリケーションをビルドするためのステージ)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build # アプリケーションをビルドし、成果物が /app/dist に生成されると仮定
ステージ 2: 実行環境 (ビルド成果物を実行するためのステージ)
より小さなベースイメージを使用
FROM nginx:alpine
ステージ 1 (‘builder’) からビルド成果物をコピー
–from=<ステージ名> オプションを使用
COPY –from=builder /app/dist /usr/share/nginx/html
コンテナ起動時にNginxを実行
EXPOSE 80
CMD はベースイメージで定義済み
“`
この例では、最初のFROM node:18-alpine AS builderが「ビルダー」という名前のステージを作成します。このステージでNode.jsアプリケーションの依存関係をインストールし、ビルドを実行します。
2番目のFROM nginx:alpineが最終的な実行環境のステージを開始します。そして、COPY --from=builder /app/dist /usr/share/nginx/html命令を使って、最初のステージ(builderステージ)で生成されたビルド成果物 /app/dist を、現在のステージ(Nginxイメージ)の /usr/share/nginx/html にコピーしています。
この方法により、最初のステージに含まれるNode.jsの実行環境や開発ツールは最終的なNginxイメージには含まれず、必要な静的ファイルだけがコピーされます。結果として、非常に小さなイメージが作成されます。
11.3. --targetオプションで特定のステージをビルドする
マルチステージビルドでは、デフォルトではDockerfileの最後のステージのみが最終イメージとしてタグ付けされます。しかし、--targetオプションを使用することで、指定したステージまでのビルドを実行し、そのステージのイメージにタグを付けることができます。
“`bash
デフォルト (最後のステージ) をビルド
docker build -t my-web-app:latest .
中間の ‘builder’ ステージまでをビルドし、タグを付ける
ビルド成果物を確認したい場合などに便利
docker build –target builder -t my-web-app:builder .
“`
これは、デバッグ目的でビルド過程の中間状態を確認したい場合や、複数の最終イメージを一つのDockerfileから生成したい場合(例: 本番用イメージとテスト用イメージ)などに役立ちます。
12. ビルド時のトラブルシューティング
docker buildは非常に強力ですが、エラーに遭遇することもあります。ここでは、一般的なトラブルシューティングの方法を解説します。
12.1. エラーメッセージを読む
最も重要なのは、docker buildコマンドが失敗した際に表示されるエラーメッセージを注意深く読むことです。エラーメッセージには、どのDockerfile命令で問題が発生したか、そしてその原因に関するヒントが含まれていることが多いです。
Step 5/7 : RUN apt-get update && apt-get install -y some-nonexistent-package
---> Running in a9b3c1d2e4f5
Reading package lists...
...
E: Unable to locate package some-nonexistent-package
The command '/bin/sh -c apt-get update && apt-get install -y some-nonexistent-package' returned a non-zero code: 100
この例では、「Step 5/7」で問題が発生したことがわかります。実行されたコマンドはapt-get install -y some-nonexistent-packageであり、「E: Unable to locate package some-nonexistent-package」というエラーが出ていることから、存在しないパッケージをインストールしようとしたことが原因だと推測できます。
12.2. 一般的なエラーとその原因
COPYorADDfailed: stat <ファイルパス>: no such file or directory:- 原因: Dockerfileで指定したファイルまたはディレクトリが、ビルドコンテキスト内に存在しないか、指定したパスが間違っています。
.dockerignoreで除外されている可能性もあります。 - 対処法:
lsコマンドなどでビルドコンテキスト内を確認し、ファイルやディレクトリが存在すること、そしてDockerfile内のパス指定が正しいことを確認してください。
- 原因: Dockerfileで指定したファイルまたはディレクトリが、ビルドコンテキスト内に存在しないか、指定したパスが間違っています。
RUNcommand failed with non-zero exit code <コード>:- 原因:
RUN命令内で実行したコマンドがエラーで終了しました。<コード>は終了コードを示します(0以外はエラー)。 - 対処法: エラーメッセージの直前にある
RUN命令のコマンドを確認し、そのコマンドをホストマシン上で実行してみて、何が問題なのかを特定します(例: コマンドが見つからない、パッケージのインストールに失敗した、スクリプトに構文エラーがあるなど)。ネットワークアクセスが必要なコマンドの場合は、ビルド環境のネットワーク設定を確認します。
- 原因:
- Permission denied:
- 原因: Dockerデーモンがビルドコンテキスト内のファイルにアクセスする権限がないか、
RUN命令で実行したコマンドがイメージ内のファイルに書き込む権限がないなど。 - 対処法: ホストマシン上でのビルドコンテキストディレクトリの権限を確認します。イメージ内の権限問題であれば、
RUN chmod ...などをDockerfileに追加する必要があるかもしれません。
- 原因: Dockerデーモンがビルドコンテキスト内のファイルにアクセスする権限がないか、
docker buildis not recognized as an internal or external command:- 原因: Dockerが正しくインストールされていないか、環境変数PATHにDockerの実行ファイルへのパスが含まれていません。
- 対処法: Docker DesktopなどのDocker環境を再インストールするか、PATH設定を確認してください。
- Cannot connect to the Docker daemon:
- 原因: Dockerデーモンが起動していないか、実行中のユーザーにDockerデーモンへのアクセス権限がありません。
- 対処法: Dockerデーモンが実行中か確認し、必要であれば起動してください。Linux環境の場合は、ユーザーを
dockerグループに追加し、再度ログインする必要があります。
- Base image not found:
- 原因:
FROM命令で指定したベースイメージが存在しないか、Registryからプルできません。 - 対処法: イメージ名やタグのスペルミスがないか確認します。インターネット接続を確認します。プライベートRegistryを使用している場合は、ログインしているか確認します。
- 原因:
12.3. デバッグのためのヒント
--no-cacheを使う: キャッシュが原因で問題が起きている可能性がある場合、--no-cacheオプションを使って最初からビルドし直すと問題が解決したり、原因が特定しやすくなったりします。- Dockerfileの各ステップを個別にテストする: エラーが発生した
RUN命令に含まれるコマンドを、一時的にその前のステップでビルドした中間コンテナの中で手動で実行してみることで、詳細なエラーを確認できます。docker build --target <直前のステージ> -t temp-image .で中間イメージをビルドし、docker run -it temp-image /bin/bashなどでコンテナに入ってコマンドを試すことができます。 --rm=falseを使う: ビルドに失敗した場合でも中間コンテナを削除しないようにすることで、エラーが発生したステップの直前の中間コンテナを調査できます。
13. 実践的なビルドのヒント
より効率的で安全なDockerイメージをビルドするための実践的なヒントをいくつか紹介します。
- 小さなベースイメージを選択する:
alpineタグが付いたイメージ(例:ubuntu:alpine,python:3.9-alpine)は、多くの不要なツールが含まれていないため、非常にサイズが小さく、セキュリティリスクも低減されます。実行に必要な最小限のイメージを選択しましょう。 RUN命令を連結し、クリーンアップを忘れずに: 前述のように、複数のコマンドを&& \で繋げて一つのRUN命令にし、パッケージインストール後のキャッシュや不要ファイルを削除するコマンド(例:rm -rf /var/lib/apt/lists/*afterapt-get install)を追加することで、レイヤー数を減らしイメージサイズを最小限に保てます。COPYよりADDを使う場合は注意:ADDは自動展開やURLダウンロード機能がありますが、これらの挙動が意図しないセキュリティリスクやキャッシュミスを引き起こす可能性があります。特に理由がない限りはCOPYを使うのが安全です。- ユーザー権限で実行する: セキュリティの観点から、可能であればルートユーザー以外のユーザーでアプリケーションを実行するようにイメージを設定します。Dockerfileの
USER命令を使用します。Dockerfile
#... (previous steps)
RUN adduser -D appuser
USER appuser
WORKDIR /home/appuser/app
COPY --chown=appuser:appuser . .
# ... (CMD/ENTRYPOINT)
- Credentialなどの機密情報をイメージに含めない: APIキーやパスワードなどの機密情報は、ビルド時に
ENVとして設定したり、イメージ内にファイルとして含めたりしないでください。これらはビルドされたイメージのレイヤーに残ってしまう可能性があります。代わりに、コンテナ実行時に環境変数として渡すか、Docker Secretsなどのより安全な仕組みを利用します。 - マルチステージビルドを積極的に活用する: アプリケーションのビルドプロセスが複雑で、開発ツールやコンパイラが必要な場合は、必ずマルチステージビルドを使って最終イメージをクリーンに保ちましょう。
.dockerignoreファイルを必ず作成する: 不要なファイルがビルドコンテキストに含まれるのを防ぎ、ビルドを高速化し、イメージサイズを小さくするために、どんなプロジェクトでも.dockerignoreファイルを作成することを強く推奨します。
14. まとめ
この記事では、Dockerイメージを作成するための中心的コマンドであるdocker buildについて、初心者向けに徹底的に解説しました。
docker buildはDockerfileを読み込んでDockerイメージを作成するコマンドであること。- Dockerイメージは複数のレイヤーで構成され、これがキャッシュによる高速ビルドを可能にすること。
- Dockerfileはイメージ作成の手順を記述したレシピであり、主な命令(
FROM,RUN,COPY,WORKDIR,EXPOSE,CMDなど)とその役割。 docker build .という最も基本的な使い方と、ビルドコンテキストの概念。-tオプションを使ったイメージの名前とタグ付けの重要性。-fオプションを使ったDockerfileの場所指定。.dockerignoreファイルを使ったビルドコンテキストからのファイル除外による効率化。- ビルドキャッシュの仕組みと、キャッシュを意識したDockerfileの書き方。
--build-argオプションを使ったビルド引数の渡し方。- マルチステージビルドによるイメージサイズの削減とビルドプロセスの分離。
- ビルド時の一般的なエラーとそのトラブルシューティング方法。
- より良いイメージをビルドするための実践的なヒント。
docker buildコマンドとDockerfileは、Dockerにおけるアプリケーションのパッケージングの基礎となります。これらの概念をしっかり理解し、実践できるようになれば、あなたはDockerをさらに強力に活用できるようになるはずです。
次のステップ
イメージがビルドできたら、次はそのイメージを元にコンテナを起動するdocker runコマンドの使い方を学ぶと良いでしょう。また、作成したイメージをDocker Registry(Docker Hubなど)に保存・共有するためのdocker pushコマンドについても学んでみてください。
さあ、作成したイメージを使って、あなたのアプリケーションをどこでも同じように動かしてみましょう!