【入門】docker-compose buildの基本と使い方を徹底解説!
はじめに:なぜDocker Composeを使うのか、buildコマンドの重要性
コンテナ技術のデファクトスタンダードとなったDockerは、アプリケーションとその実行環境をパッケージ化し、どこでも同じように実行できる移植性の高い単位を提供します。単一のコンテナで完結するアプリケーションも多いですが、実際のプロダクション環境や開発環境では、複数のサービスが連携して動作することがほとんどです。例えば、Webサーバー、アプリケーションサーバー、データベース、キャッシュサーバーなどがそれぞれ異なるコンテナとして稼働し、互いに通信し合う構成です。
このような複数のコンテナで構成されるアプリケーションを管理する際に、各コンテナを個別に docker run コマンドで起動したり、ネットワークを設定したりするのは非常に手間がかかります。また、それぞれのコンテナの依存関係や起動順序などを手動で管理するのは複雑でエラーが発生しやすくなります。
そこで登場するのが Docker Compose です。Docker Composeは、複数のDockerコンテナで構成されるアプリケーションを定義・管理するためのツールです。YAML形式の設定ファイル (docker-compose.yml) を一つ記述するだけで、アプリケーションを構成する全てのサービス(コンテナ)を一括で起動、停止、再起動、削除することができます。これにより、開発環境の構築やアプリケーションのデプロイが劇的にシンプルかつ再現可能になります。
Docker Composeファイルの各サービス定義では、そのコンテナを起動するために使用するDockerイメージを指定します。イメージの指定方法は主に以下の2種類があります。
- 既存のイメージを使用する: Docker Hubやプライベートレジストリに公開されている既存のイメージ(例:
nginx:latest,mysql:5.7)を指定します。この場合、Composeは指定されたイメージをレジストリからプルして使用します。
yaml
services:
web:
image: nginx:latest - Dockerfileからカスタムイメージをビルドする: アプリケーション独自のコードを含めたり、特定のライブラリをインストールしたりするために、Dockerfileを記述して独自のイメージを作成する必要がある場合があります。この場合、ComposeファイルでDockerfileの場所を指定し、Composeにイメージのビルドを指示します。
yaml
services:
app:
build: ./app # ./app ディレクトリにあるDockerfileを使用
ここで重要な役割を果たすのが、この記事の主題である docker-compose build コマンドです。このコマンドは、docker-compose.ymlファイルの中で build: ディレクティブが指定されているサービスに対して、対応するDockerfileからDockerイメージをビルドするために使用されます。
docker-compose up コマンドも、サービス定義に build: がある場合は自動的にビルドを実行しますが、docker-compose build コマンドを使うことで、コンテナを起動せずにイメージのビルドだけを独立して実行できます。これは以下のような場合に特に役立ちます。
- イメージビルドが成功するか事前に確認したい場合。
- ビルドしたイメージをレジストリにプッシュする前に確認したい場合。
- CI/CDパイプラインで、コンテナ起動とは別にビルドステップを設けたい場合。
- ビルドオプション(キャッシュ無効化、ビルド引数など)を細かく制御したい場合。
この記事では、この docker-compose build コマンドに焦点を当て、その基本的な使い方から、docker-compose.ymlファイルにおける build ディレクティブの詳細、ビルドキャッシュの活用、ビルド引数の渡し方、マルチステージビルドとの連携、さらには様々なビルドオプション、ビルドプロセスの内部、よくある問題と解決策、そして実践的なワークフローまで、初心者の方でも理解できるように詳細かつ網羅的に解説します。
この記事を読むことで、あなたは Docker Compose を使ったアプリケーション開発において、独自のサービスイメージを効率的かつ柔軟にビルドできるようになるでしょう。
DockerとDocker Composeの基本をおさらい
docker-compose build を深く理解するために、まずはDockerとDocker Composeの基本的な概念を簡単におさらいしましょう。
Docker イメージとコンテナ、Dockerfile
- Docker イメージ: アプリケーションとその実行に必要な全てのものがパッケージ化された読み取り専用のテンプレートです。コード、ランタイム、システムツール、システムライブラリ、設定などが含まれます。イメージは階層構造(レイヤー)になっており、変更があったレイヤーだけが追加・更新されるため、効率的にイメージを管理できます。
- Docker コンテナ: Docker イメージを実行したものです。イメージは設計図、コンテナは設計図から作られた実際の「箱」のようなものです。コンテナは独立した実行環境を提供し、他のコンテナやホストシステムから隔離されています。
- Dockerfile: Dockerイメージを自動的にビルドするための一連の手順を記述したテキストファイルです。
FROM(ベースイメージの指定)、RUN(コマンド実行)、COPY(ファイルをコピー)、EXPOSE(ポート公開)、CMD/ENTRYPOINT(コンテナ起動時のコマンド)などの命令を記述します。docker buildコマンドは、この Dockerfile を読み込んでイメージをビルドします。
Docker Composeファイル (docker-compose.yml) の役割と基本構造
docker-compose.yml ファイルは、Docker Compose が複数のコンテナアプリケーションを定義・管理するために使用する設定ファイルです。通常、YAML形式で記述されます。
基本的な構造は以下のようになります。
“`yaml
version: ‘3.8’ # Docker Composeファイルのバージョン指定
services: # アプリケーションを構成する各サービスを定義
service1_name: # サービス名 (任意)
# このサービスのコンテナ設定を記述
# 例: image:, build:, ports:, volumes:, environment:, depends_on: など
service2_name:
# 別のサービスのコンテナ設定を記述
# 例: image:, build:, ports:, volumes:, environment:, depends_on: など
networks: # オプション: カスタムネットワークを定義
volumes: # オプション: 永続化ボリュームを定義
“`
version: Composeファイルフォーマットのバージョンを指定します。新しいバージョンほど多くの機能が利用できます。現在の一般的なバージョンは ‘3.x’ や ‘2.x’ です。services: ここにアプリケーションを構成する個々のサービス(コンテナ)を定義します。キーがサービス名となります。- 各サービス定義内では、
imageまたはbuildディレクティブを使って、そのサービスを実行するためのDockerイメージを指定します。
docker-compose build コマンドは、この services セクション内の各サービス定義の中から、build: ディレクティブを持つものを探し出し、指定された設定に基づいてイメージビルドを実行します。
docker-compose build コマンドとは
docker-compose build コマンドは、docker-compose.yml ファイルで定義されたサービスのうち、build: ディレクティブを持つサービスに対応するDockerイメージを、指定されたDockerfileからビルドするためのコマンドです。
簡単に言えば、Docker Composeのコンテキストで docker build コマンドを実行する役割を果たします。ただし、直接 docker build コマンドを使うのではなく、docker-compose.yml ファイルの設定(特に build: ディレクティブの内容)に基づいてビルドが行われます。
コマンドの構文
基本的な構文は以下の通りです。
bash
docker-compose build [OPTIONS] [SERVICE...]
[SERVICE...]: オプションの引数です。ビルドしたい特定のサービス名を一つ以上指定できます。サービス名を指定しない場合、docker-compose.ymlファイルに定義されているbuild:ディレクティブを持つ全てのサービスイメージがビルドされます。[OPTIONS]: ビルドの挙動を制御するための様々なオプションを指定できます(後述)。
なぜ docker-compose build を独立して使うのか?
前述の通り、docker-compose up コマンドも build: がある場合は自動でビルドを実行します。しかし、docker-compose build コマンドを単独で使うことにはいくつかの利点があります。
- ビルドのみの実行: コンテナを実際に起動せずに、イメージのビルドだけを行いたい場合に便利です。例えば、Dockerfileの変更が正しくイメージに反映されるか確認したり、ビルドエラーがないかチェックしたりできます。
- 高速なイテレーション: 開発中にコードやDockerfileを変更した場合、
docker-compose buildで素早く新しいイメージをビルドし、その後docker-compose up(あるいはdocker-compose up --no-build) で新しいイメージを使ってコンテナを起動するというワークフローが可能です。コード変更が多い開発初期段階では、ビルドと起動を分離することで、意図しないコンテナの再作成などを避けることもできます。 - ビルドオプションの制御:
docker-compose upからビルドされる場合、ビルドオプションはComposeファイルの設定に依存します。一方、docker-compose buildコマンドを直接実行する場合、コマンドラインオプションを使ってビルドの挙動を細かく制御できます。例えば、キャッシュを使わずに完全にクリーンなビルドを行いたい場合 (--no-cache) や、ビルド時に特定の引数を渡したい場合 (--build-arg) などです。 - CI/CDパイプライン: CI/CDシステムでは、通常「イメージのビルド」と「イメージを使ったテスト/デプロイ」は独立したステップとして実行されます。
docker-compose buildは、CI環境でアプリケーションイメージをビルドし、そのイメージをコンテナレジストリにプッシュするステップでよく利用されます。
実行例
docker-compose.yml が以下のようになっているとします。
“`yaml
docker-compose.yml
version: ‘3.8’
services:
webapp:
build: ./app # ./app ディレクトリにあるDockerfileを使用
ports:
– “80:80”
db:
image: postgres:13
ports:
– “5432:5432”
“`
全てのサービスをビルド:
webapp サービスは build: を持っていますが、db サービスは image: を持っています。docker-compose build は build: を持つサービスのみを対象とします。
bash
docker-compose build
このコマンドは ./app/Dockerfile を使用して webapp サービスのイメージをビルドします。db サービスは image: を指定しているため、ビルドの対象にはなりません。
特定のサービスをビルド:
もし webapp サービスだけをビルドしたい場合(この例では全てのビルド対象が webapp だけですが、複数のサービスが build: を持つ場合に有効)、サービス名を指定します。
bash
docker-compose build webapp
これは上記と同じ結果になります。もし別のサービス(例えば backend という名前で build: を持つサービスがあった場合)もビルドしたい場合は、サービス名を複数指定します。
bash
docker-compose build webapp backend
このように、docker-compose build コマンドは、Composeプロジェクト内のカスタムイメージビルドを柔軟に実行・管理するための基本となります。
docker-compose.yml ファイルにおける build ディレクティブの詳細
docker-compose build コマンドが使用するビルド設定は、docker-compose.yml ファイルの各サービス定義内にある build: ディレクティブで指定されます。この build: ディレクティブは、単一の文字列としてDockerfileのあるディレクトリパスを指定することもできますが、より詳細な設定を行うためにはオブジェクト形式で記述します。
build: ディレクティブで指定できる主なサブディレクティブは以下の通りです。
context: ビルドコンテキストのパスを指定します。必須項目です。dockerfile: 使用するDockerfileの名前を指定します(デフォルトはDockerfile)。args: ビルド時にDockerfileに渡すビルド引数(ARG命令で受け取る)を指定します。cache_from: ビルドキャッシュとして利用する既存のイメージを指定します。labels: ビルドされるイメージに追加するラベルを指定します。shm_size: ビルド中の中間コンテナの/dev/shmのサイズを設定します。target: マルチステージビルドにおいて、ビルドする特定のステージを指定します。network: ビルド中の中間コンテナのネットワークモードを指定します。platform: ビルドターゲットプラットフォームを指定します。
それぞれのサブディレクティブについて詳しく見ていきましょう。
context
- 役割: Dockerイメージをビルドする際に使用されるビルドコンテキストのパスを指定します。ビルドコンテキストとは、Dockerfileの実行中に
COPYやADD命令でコンテナ内にコピーできるファイルやディレクトリの集合です。通常、Dockerfileが置かれているディレクトリ、またはその親ディレクトリを指定します。 - 形式:
- パス文字列: 例:
./app,../,/path/to/context - オブジェクト形式の一部として: 例:
build: { context: ./app }
- パス文字列: 例:
- 重要性:
COPY . /appのように、Dockerfile内でカレントディレクトリ(ビルドコンテキストのルート)からの相対パスでファイルを指定する場合、このcontextで指定したディレクトリがカレントディレクトリとして扱われます。ビルドコンテキストに含める必要のないファイル(例: ソースコード管理の一時ファイル、ログファイル、大きなデータファイルなど)は、.dockerignoreファイルを使って除外することが推奨されます。 これにより、ビルド時間の短縮、イメージサイズの削減、機密情報の漏洩防止に繋がります。 - 例:
yaml
services:
webapp:
build: ./app # ここではパス文字列が context として扱われる
yaml
services:
webapp:
build:
context: ./app # オブジェクト形式で明示的に context を指定
dockerfile
- 役割: ビルドコンテキスト内で使用するDockerfileの名前を指定します。
- 形式: ファイル名文字列。例:
Dockerfile.dev,Dockerfile.prod - デフォルト: このサブディレクティブを省略した場合、Composeは
contextで指定されたディレクトリのルートにあるDockerfileという名前のファイルをデフォルトで使用します。 -
使用例: プロジェクト内に複数のDockerfileがあり、用途によって使い分けたい場合に便利です。例えば、開発用のDockerfile (
Dockerfile.dev) と本番用のDockerfile (Dockerfile.prod) を用意し、それぞれのComposeファイルやサービス定義で指定する、といった使い方ができます。
“`yaml
services:
dev_app:
build:
context: ./app
dockerfile: Dockerfile.dev # 開発用Dockerfileを指定prod_app:
build:
context: ./app
dockerfile: Dockerfile.prod # 本番用Dockerfileを指定
“`
args
- 役割: ビルド時にDockerfileの
ARG命令に値を渡すために使用します。ARG命令はDockerfile内でビルド時の変数として機能し、例えば、インストールするパッケージのバージョンや、ビルド環境に依存する設定値などを外部から渡すのに使われます。 - 形式: キーと値のマップ、またはリスト形式。環境変数のように
$VARIABLE形式でホストOSの環境変数や.envファイルの値を使うことも可能です。 -
例:
yaml
services:
webapp:
build:
context: ./app
args: # ビルド引数を指定
APP_VERSION: 1.2.3
BUILD_DATE: 2023-10-27
# ホストOSの環境変数から値を渡す場合
# GITHUB_TOKEN: ${GH_TOKEN}
Dockerfile (./app/Dockerfile) 側では以下のようにARGで引数を受け取ります。
“`dockerfile
FROM ubuntu:latestARG APP_VERSION
ARG BUILD_DATELABEL version=$APP_VERSION
LABEL build_date=$BUILD_DATEARGで受け取った値はビルド中のみ有効で、RUNコマンドなどで利用できます
RUN echo “Building version ${APP_VERSION}”
… その他のビルド命令 …
``ARG
* **注意**:はビルド時のみ有効であり、実行中のコンテナ内の環境変数とは異なります。コンテナ実行時の環境変数を設定するには、サービス定義のenvironment` ディレクティブを使用します。また、機密情報(パスワード、APIキーなど)をビルド引数として渡すことは避けるべきです。ビルド引数の値はビルド履歴に残る可能性があるためです。機密性の高い情報が必要な場合は、ビルド後に実行時環境変数として渡すか、Dockerのsecrets機能を利用することを検討してください。
cache_from
- 役割: ビルドキャッシュとして利用する既存のイメージの名前を指定します。これにより、ローカルだけでなく、リモートのDockerレジストリにあるイメージをキャッシュソースとして利用できます。CI/CD環境などで、以前のビルドイメージをキャッシュとして活用したい場合に非常に有効です。
- 形式: イメージ名のリスト。
- 例:
yaml
services:
webapp:
build:
context: ./app
cache_from:
- myregistry/myapp:latest # リモートのイメージをキャッシュとして利用
- myapp_base:latest # ローカルのイメージをキャッシュとして利用 - 注意:
cache_fromを利用するには、Docker BuildKit (Docker Engine 18.09 以降で利用可能、Compose V2 はデフォルトで有効) が必要です。また、リモートレジストリからのキャッシュ利用は、そのイメージが存在し、かつアクセス可能である必要があります。
labels
- 役割: ビルドされるDockerイメージにカスタムのメタデータとしてラベルを追加します。ラベルはイメージの管理や検索に役立ちます。
- 形式: キーと値のマップ。
- 例:
yaml
services:
webapp:
build:
context: ./app
labels:
com.example.description: "My Web Application Image"
com.example.version: "1.0"
maintainer: "Your Name <[email protected]>"
ビルドされたイメージは、docker image inspect <image_name>コマンドでラベルを確認できます。
shm_size
- 役割: ビルド中の中間コンテナの
/dev/shm(共有メモリ) のサイズを設定します。一部のビルド処理(例: WebKitレンダリングを伴うフロントエンドビルド)で、デフォルトの共有メモリサイズでは不足する場合に必要になります。 - 形式: バイト単位の整数またはサイズ指定文字列(例:
1g,512m)。 - 例:
yaml
services:
frontend:
build:
context: ./frontend
shm_size: '2g' # 共有メモリを2GBに設定
target
- 役割: マルチステージビルドのDockerfileにおいて、ビルドの最終成果物とする特定のステージ(
FROM ... AS stage_nameで名前を付けたステージ)を指定します。 - 形式: ステージ名の文字列。
- 重要性: マルチステージビルドは、ビルドに必要なツールや中間ファイルを多く含む「ビルドステージ」と、最終的な実行に必要なものだけを含む「実行ステージ」を一つのDockerfile内で定義し、実行ステージだけを最終イメージとして出力することで、イメージサイズを大幅に削減できる手法です。
build.targetを使うことで、同じDockerfileから開発用の重いイメージ(デバッグツールを含む)と、本番用の軽いイメージを作成し分けることが可能です。 -
例:
Dockerfile (./app/Dockerfile) が以下のようになっているとします。
“`dockerfile
# ビルドステージ
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build # アプリケーションをビルド実行ステージ (本番用)
FROM nginx:alpine AS production
COPY –from=builder /app/dist /usr/share/nginx/html
EXPOSE 80開発ステージ (開発用 – ビルドツールやソースコードを含む)
FROM node:18 AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . . # 開発中はソースコードごとコピー
EXPOSE 3000
CMD [“npm”, “start”]
`docker-compose.yml` で開発用と本番用を定義する場合:yaml
services:
dev_app:
build:
context: ./app
target: development # 開発ステージをビルドターゲットにする
ports:
– “3000:3000”prod_app:
build:
context: ./app
target: production # 実行ステージをビルドターゲットにする
ports:
– “80:80”
``target` を指定することで、Dockerfile全体を実行するのではなく、指定したステージまでの命令のみが実行され、そのステージのファイルシステムが最終的なイメージとして利用されます。
このように
network
- 役割: ビルド中の中間コンテナが接続するネットワークを指定します。デフォルトでは
defaultネットワークが使用されますが、ビルド中に特定のネットワーク上のリソース(例: プライベートパッケージリポジトリ)にアクセスする必要がある場合に、そのネットワークを指定できます。 - 形式: ネットワーク名の文字列。
-
例:
“`yaml
services:
builder:
build:
context: .
network: my_build_network # my_build_network に接続してビルド
# …networks:
my_build_network:
external: true # またはここでネットワークを定義
“`
platform
- 役割: ビルドするイメージのターゲットプラットフォームを指定します。Docker Engineがマルチプラットフォームビルドをサポートしている場合(Buildxなど)、このオプションを使って異なるアーキテクチャ(例:
linux/amd64,linux/arm64)用のイメージをビルドできます。 - 形式: プラットフォーム指定文字列。例:
linux/amd64,linux/arm64 - 例:
yaml
services:
multiarch_app:
build:
context: .
platform: linux/arm64 # ARM64アーキテクチャ用のイメージをビルド
# ...
複数のプラットフォームを指定して同時にビルドすることも可能です(Buildxの設定によります)。
これらのサブディレクティブを組み合わせることで、Composeを使ったイメージビルドを非常に柔軟かつ強力に制御できます。
ビルドキャッシュの活用
Dockerのビルドプロセスにおける最も重要な最適化の一つが、ビルドキャッシュです。docker build (そして docker-compose build) は、Dockerfileの各命令を順番に実行しますが、各ステップの実行結果(中間イメージ)をキャッシュとして保存します。次回同じDockerfileをビルドする際に、命令が変更されていない場合、Dockerはキャッシュされた中間イメージを再利用し、そのステップ以降の処理から再開します。これにより、ビルド時間を大幅に短縮できます。
Dockerのビルドキャッシュの仕組み
Dockerは、Dockerfileの各命令と、その命令によってビルドコンテキストに加えられた変更のスナップショットを組み合わせて、キャッシュキーを生成します。ビルドの際に、Dockerは上から順番にDockerfileの命令を処理し、各ステップで生成されたキャッシュキーに対応するキャッシュが存在するかどうかを確認します。
- キャッシュヒット: 現在の命令とそのビルドコンテキストの変更に対応するキャッシュが存在する場合、Dockerはそのキャッシュされた中間イメージを再利用し、次の命令の処理に進みます。
- キャッシュミス: キャッシュが見つからない場合、Dockerはその命令を実行し、新しい中間イメージを作成します。一度キャッシュミスが発生すると、それ以降の全ての命令はキャッシュを利用せず実行されます。
キャッシュの有効性を判断する際のルールは命令によって異なりますが、一般的なルールは以下の通りです。
FROM: ベースイメージのダイジェストが同じであればキャッシュヒット。RUN: コマンド文字列が完全に一致すればキャッシュヒット。COPY/ADD: コピー元/コピー先のパスに加え、コピーされるファイルのコンテンツ(チェックサム)、タイムスタンプ、パーミッションなどがキャッシュキーに含まれます。ファイルの変更があればキャッシュミスになります。- その他の命令 (
ENV,WORKDIR,VOLUME,EXPOSE,USER,ARG,ENTRYPOINT,CMD): 命令の文字列が完全に一致すればキャッシュヒット。
特に COPY/ADD はファイルコンテンツの変更を検出するため、ソースコードの変更頻度が高い開発中には、この命令以降のキャッシュが頻繁に無効化されやすくなります。
docker-compose build がキャッシュをどのように利用するか
docker-compose build コマンドを実行すると、Composeは内部的に docker build コマンドを呼び出します。したがって、標準のDockerビルドキャッシュ機構がそのまま利用されます。Composeは、以前にビルドした同じサービス名のイメージをキャッシュとして自動的に参照します。
キャッシュを無効にする (--no-cache)
意図的にキャッシュを使用せずに、全てのステップを最初から実行したい場合があります。例えば、キャッシュが原因で予期しないビルド結果になる場合や、クリーンな環境でビルドしたい場合などです。
docker-compose build コマンドに --no-cache オプションを付けることで、キャッシュを完全に無効にできます。
bash
docker-compose build --no-cache webapp
このコマンドを実行すると、webapp サービスのイメージはキャッシュを使わずに最初からビルドされます。
キャッシュ効率を高めるためのDockerfileの書き方
キャッシュを効果的に利用するためには、Dockerfileの命令の順番を工夫することが重要です。変更頻度の高い命令を、変更頻度の低い命令よりも後に書くことで、キャッシュミスが発生するレイヤーを後回しにし、前半のレイヤーのキャッシュヒット率を高めることができます。
典型的な例として、依存関係のインストールとアプリケーションコードのコピーの順序があります。多くのプロジェクトでは、アプリケーションコードそのものよりも、依存関係ファイル(例: package.json, Gemfile, requirements.txt)の変更頻度の方が低いです。
“`dockerfile
悪い例:コードの変更で npm install のキャッシュが無効になる
FROM node:18
WORKDIR /app
COPY . . # ① コードをコピー
RUN npm install # ② 依存関係をインストール (①の変更でキャッシュ無効になりやすい)
…
“`
“`dockerfile
良い例:依存関係ファイルだけを先にコピーしてインストール
FROM node:18
WORKDIR /app
COPY package*.json ./ # ① 依存関係ファイルだけを先にコピー
RUN npm install # ② 依存関係をインストール (①が変更されない限りキャッシュヒット)
COPY . . # ③ 残りのコードをコピー (変更頻度高いが、もう依存関係インストールは済んでいる)
…
“`
この「良い例」のパターンでは、アプリケーションコード(③)だけが変更された場合、①と②のステップはキャッシュがヒットするため、依存関係の再インストールという時間のかかる処理をスキップできます。これにより、ビルド時間を大幅に短縮できます。
cache_from を使った高度なキャッシュ戦略
前述の build.cache_from ディレクティブを使うと、ローカルキャッシュだけでなく、指定した既存のイメージ(ローカルまたはリモートレジストリ)をキャッシュソースとして利用できます。
これは、CI/CD環境で特に強力です。CIパイプラインでイメージをビルドし、成功したらそのイメージをレジストリにプッシュしておきます。次のビルド時には、そのレジストリにあるイメージを cache_from で指定することで、以前のビルド結果をキャッシュとして再利用できます。
yaml
services:
webapp:
build:
context: .
dockerfile: Dockerfile
cache_from:
- ${CI_REGISTRY}/mycompany/webapp:latest # CIレジストリの最新イメージをキャッシュに
CI環境でビルドを実行する際に、このComposeファイルを使用し、${CI_REGISTRY} 環境変数にレジストリURLを設定しておけば、リモートキャッシュが有効になります。
ビルドキャッシュを理解し、適切に活用することは、Dockerイメージのビルド時間を短縮し、開発やデプロイの効率を高める上で非常に重要です。Dockerfileの記述順序や .dockerignore ファイルの活用と合わせて、積極的に最適化に取り組みましょう。
ビルド引数 (args) の詳細
build.args ディレクティブは、Dockerfileの ARG 命令と連携してビルド時に動的な情報を提供するための仕組みです。ここではその詳細と応用について解説します。
Dockerfileの ARG 命令と docker-compose.yml の build.args の連携
Dockerfileの ARG 命令は、ビルド時に外部から渡される変数を定義します。
“`dockerfile
ARG VERSION=latest # デフォルト値を指定することも可能
ARG UBUNTU_RELEASE=latest
FROM ubuntu:${UBUNTU_RELEASE}
…
ARG AUTHOR=”Unknown”
LABEL maintainer=${AUTHOR}
…
“`
docker-compose.yml の build.args ディレクティブで、これらの ARG に具体的な値を渡します。キーが ARG の変数名、値が渡したい値になります。
yaml
services:
webapp:
build:
context: ./app
args:
VERSION: 1.5.0
UBUNTU_RELEASE: 20.04
AUTHOR: "Your Name"
docker-compose build webapp を実行すると、Composeは指定された args を docker build コマンドに --build-arg オプションとして渡します。
環境変数からの引数指定
build.args の値には、ホストOSの環境変数やプロジェクトルートにある .env ファイルで定義された環境変数を使用することができます。これは、環境に依存する値をビルド引数として渡したい場合に非常に便利です。
yaml
services:
webapp:
build:
context: ./app
args:
APP_ENV: ${NODE_ENV} # ホストOSまたは .env の NODE_ENV 環境変数の値を使う
BUILD_TIMESTAMP: ${CURRENT_TIMESTAMP:-$(date +%s)} # 環境変数が未設定ならシェルコマンドの結果を使う
.env ファイル例:
“`dotenv
.env
NODE_ENV=development
“`
この設定の場合、もしホストOSや .env ファイルに NODE_ENV 環境変数が定義されていれば、その値が APP_ENV ビルド引数として渡されます。${VAR:-default} のようにデフォルト値を指定することも可能です。$() 構文を使ってシェルコマンドの実行結果を埋め込むこともできますが、これはComposeの機能ではなく、Composeファイルを解釈するシェル(または環境変数展開機能を持つCompose実装)に依存します。より確実に静的な値を渡すか、Dockerfile側でビルド引数のデフォルト値を指定する方が安全です。
.env ファイルとの連携
プロジェクトルートに .env という名前のファイルがあると、Docker Compose は自動的にそのファイルを読み込み、そこに定義された KEY=VALUE 形式の変数を環境変数としてロードします。これらの変数は、docker-compose.yml ファイル内で ${KEY} 構文を使って参照できます。これは build.args ディレクティブでも有効です。
“`dotenv
.env
API_ENDPOINT=https://api.example.com/v1
“`
yaml
services:
backend:
build:
context: ./backend
args:
API_URL: ${API_ENDPOINT} # .env ファイルで定義された API_ENDPOINT を参照
この連携により、環境ごとに異なる設定値(開発、ステージング、本番など)を .env ファイルで管理し、それらをビルド引数としてイメージに埋め込むことが容易になります。
コマンドラインからのビルド引数指定 (--build-arg)
docker-compose build コマンドを実行する際に、コマンドラインから --build-arg オプションを使ってビルド引数を渡すこともできます。
bash
docker-compose build --build-arg API_KEY=xyz123 webapp
このコマンドラインオプションで渡されたビルド引数は、docker-compose.yml ファイルの build.args ディレクティブで指定された値を上書きします。これは、一時的に特定の引数を変更してビルドしたい場合などに便利です。
yaml
services:
webapp:
build:
context: ./app
args:
DEFAULT_KEY: "default" # Composeファイルでデフォルト値を指定
上記Composeファイルがある状態で以下のコマンドを実行すると、DEFAULT_KEY には “overridden” という値が渡されます。
bash
docker-compose build --build-arg DEFAULT_KEY=overridden webapp
ただし、前述の通り、機密情報をビルド引数として渡すことは避けてください。Dockerのビルドレイヤーには、ビルド引数の値がメタデータとして残る可能性があります。代替手段としては、実行時環境変数、Docker Secrets、またはビルド後に設定ファイルをコンテナにマウントするなどの方法を検討すべきです。
ビルド引数は、同じDockerfileから異なる設定を持つイメージを生成したり、環境に依存するビルドプロセスを柔軟に制御したりするための強力な機能です。適切に活用することで、イメージの汎用性を高めることができます。
マルチステージビルド (target) との連携
マルチステージビルドは、単一のDockerfile内で複数の FROM 命令を使用して、複数のビルドステージを定義する手法です。これにより、ビルドに必要な中間ファイルやツールを含む重い「ビルドステージ」と、最終的な実行に必要なファイルだけを含む軽い「実行ステージ」を分離し、最終的なイメージサイズを劇的に削減できます。
docker-compose.yml の build.target ディレクティブは、このマルチステージビルドと密接に連携します。
マルチステージビルドの目的とメリット
- イメージサイズの削減: ビルドに必要なSDK、コンパイラ、テストツールなどは最終的なアプリケーションの実行には不要です。マルチステージビルドでは、これらのツールを含むステージでビルドを行い、生成された実行可能ファイルや成果物だけを、最小限の実行環境を含む別のステージにコピーします。これにより、最終イメージから不要なものが排除され、サイズが小さくなります。
- セキュリティの向上: 不要なツールやライブラリが含まれないため、攻撃対象となる可能性のあるソフトウェアを減らせます。
- ビルドプロセスの明確化: ビルド手順と実行環境の構築手順を同じDockerfile内で管理できます。
- キャッシュの活用: 各ステージは独立してビルドされるため、特定のステージだけを変更した場合、他のステージのキャッシュはそのまま利用できます。
Dockerfileでのマルチステージビルドの記述
マルチステージビルドでは、複数の FROM 命令を使用し、必要に応じて AS stage_name で各ステージに名前を付けます。後続のステージでは、COPY --from=stage_name /path/in/stage /path/in/current のように stage_name を指定して、前のステージからファイルをコピーできます。
“`dockerfile
Stage 1: builder (アプリケーションのビルドを実行)
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN go build -o /app/my-app
Stage 2: production (本番用 – 実行可能ファイルのみを含む最小イメージ)
FROM alpine:latest AS production
WORKDIR /root/
COPY –from=builder /app/my-app . # builderステージから実行可能ファイルをコピー
CMD [“./my-app”]
Stage 3: development (開発用 – ソースコードやデバッグツールを含む)
FROM golang:1.20 AS development
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
EXPOSE 8080
CMD [“go”, “run”, “main.go”] # 開発中はホットリロードなどを想定
“`
docker-compose.yml での target 指定
上記のDockerfileがある場合、docker-compose.yml で build.target を使用して、どのステージを最終イメージとするかを指定できます。
“`yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: production # 本番用の production ステージをターゲットに
ports:
– “8080:8080” # productionステージのCMDは8080でlistenしているとする
dev_app:
build:
context: .
dockerfile: Dockerfile
target: development # 開発用の development ステージをターゲットに
ports:
– “8080:8080” # developmentステージのCMDも8080でlistenしているとする
volumes:
– .:/app # 開発中はソースコードをマウントしてホットリロードなどを有効に
“`
この例では、app サービスは production ステージを、dev_app サービスは development ステージをターゲットとしてビルドします。docker-compose build app を実行すると、builder ステージと production ステージが実行され、production ステージが最終イメージとなります。docker-compose build dev_app を実行すると、builder ステージと development ステージが実行され、development ステージが最終イメージとなります。
build.target を活用することで、単一のDockerfileを使い回しながら、開発、テスト、本番など、様々な用途や環境に最適化されたイメージを効率的に作成できます。これは Docker Compose を使用する上で非常に強力なパターンの一つです。
ビルドオプションの詳細
docker-compose build コマンドには、ビルドプロセスをさらに細かく制御するための様々なコマンドラインオプションが用意されています。主要なオプションとその使い方を説明します。
これらのオプションは、docker-compose build コマンドの直後に指定します。
bash
docker-compose build [OPTIONS] [SERVICE...]
--no-cache
- 役割: ビルドキャッシュを完全に無効にし、Dockerfileの全ての命令を最初から実行します。
- 使用例: キャッシュによる予期しない問題をデバッグしたい場合や、完全にクリーンな状態でビルドを行いたい場合に使用します。
- 注意: ビルド時間が長くなる可能性があります。
- 例:
bash
docker-compose build --no-cache webapp
--pull
- 役割: Dockerfileの
FROM命令で指定されたベースイメージを、ローカルキャッシュの有無に関わらず、必ずリモートレジストリからプルします。これにより、常に最新のベースイメージを使用できます。 - 使用例: ベースイメージのセキュリティアップデートなどを確実に反映させたい場合に使用します。
- 例:
bash
docker-compose build --pull webapp
--compress
- 役割: ビルドコンテキストをDockerデーモンに送信する際に、gzip形式で圧縮します。ビルドコンテキストが大きい場合に、送信にかかる時間を短縮できる可能性があります。
- 使用例: ビルドコンテキストに大量のファイルが含まれており、ビルド開始までの時間が長い場合に試すと効果があるかもしれません。
- 例:
bash
docker-compose build --compress webapp
--force-rm
- 役割: ビルド中に作成される全ての中間コンテナを、成功・失敗に関わらず強制的に削除します。通常、ビルドが成功した中間コンテナはキャッシュとして保持されますが、このオプションを付けると削除されます。
- 使用例: ビルド後も中間コンテナがディスク容量を圧迫するのを避けたい場合に有効です。
- 注意: キャッシュとして利用できる中間コンテナが削除されるため、次回ビルド時のキャッシュヒット率に影響する可能性があります。
- 例:
bash
docker-compose build --force-rm webapp
--memory, --memory-swap, --cpu-shares, --cpu-period, --cpu-quota, --cpuset-cpus
- 役割: ビルド中の中間コンテナに対して、一時的なリソース制限を設定します。ビルドプロセスがホストシステムのリソースを過剰に消費するのを抑制したい場合に利用できます。
- 使用例: ビルドがメモリやCPUを大量に消費し、他のプロセスに影響を与える場合。
- 形式:
--memory: メモリ制限(例:500m,2g)--memory-swap: スワップを含むメモリ制限(例:1g)--cpu-shares: CPU使用率の相対的な重み(デフォルト1024)--cpu-period,--cpu-quota: CPU使用率の絶対的な制限(例:--cpu-period=100000 --cpu-quota=50000でCPU時間の50%に制限)--cpuset-cpus: 使用するCPUコアの指定(例:0,1または0-3)
- 例:
bash
docker-compose build --memory 2g --cpu-shares 512 webapp
--progress
- 役割: ビルドの進捗表示形式を指定します。BuildKitを使用している場合に有効です。
- 形式:
auto,plain,ttyauto(デフォルト): 端末がTTYであればtty、そうでなければplainを使用。plain: 詳細なログを一行ずつ表示。CI環境などでログを解析しやすい。tty: プログレスバーや色付けされた出力など、インタラクティブな表示。
- 使用例: CIログを分かりやすくしたい場合は
plain、ローカルで進捗を視覚的に確認したい場合はtty(またはデフォルトのauto) を使用します。 - 例:
bash
docker-compose build --progress=plain webapp
--parallel
- 役割: 複数のサービスを同時にビルドします。
- 注意: Compose V2以降ではこのオプションはデフォルトで有効であり、指定する必要はありません。Compose V1 では
--parallelオプションを使用することで並列ビルドが可能でした。 - 例 (Compose V1 の場合):
bash
docker-compose build --parallel webapp backend
--build-arg
- 役割: コマンドラインからビルド引数を指定します。
docker-compose.ymlのbuild.argsを上書きまたは追加します。 - 形式:
KEY=VALUE - 例:
bash
docker-compose build --build-arg DEBUG=true webapp
複数の引数を指定する場合は、オプションを繰り返します。
bash
docker-compose build --build-arg DEBUG=true --build-arg VERSION=1.0 webapp - 注意: 前述の通り、機密情報の受け渡しには不向きです。
--target
- 役割: コマンドラインからマルチステージビルドのターゲットステージを指定します。
docker-compose.ymlのbuild.targetを上書きします。 - 形式: ステージ名文字列。
- 使用例: Composeファイルではデフォルトのターゲットを指定しておきつつ、特定のビルドだけ別のターゲットを使用したい場合に便利です(例: ローカル開発ではデバッグターゲット、CIでは本番ターゲットを指定)。
- 例:
bash
docker-compose build --target development webapp
これらのオプションを適切に組み合わせることで、様々な要件に応じたビルドプロセスを実行できます。
ビルドプロセスの理解
docker-compose build が実行されたときに、内部でどのような処理が行われるのかを理解することは、トラブルシューティングやビルドの最適化に役立ちます。
docker-compose build コマンドは、基本的には以下の手順を実行します。
docker-compose.ymlの解析: 指定されたComposeファイルを読み込み、ビルド対象となるサービス(build:ディレクティブを持つサービス)とそのビルド設定(context,dockerfile,argsなど)を特定します。- ビルドコンテキストの準備:
build.contextで指定されたディレクトリの内容を収集します。この際、.dockerignoreファイルが存在すれば、そこに記述されたパターンに一致するファイルやディレクトリはビルドコンテキストから除外されます。 - Dockerデーモンへのビルド要求: 準備したビルドコンテキストと、
build.dockerfileで指定されたDockerfile(またはデフォルトのDockerfile)、そしてbuild.argsやコマンドラインオプション (--no-cache,--pull,--build-arg,--targetなど) を含めたビルド要求を、ホストシステム上で実行されているDockerデーモンに送信します。 - Dockerfileの実行 (Dockerデーモン側): Dockerデーモンは受け取ったビルドコンテキストとDockerfileを使って、ビルドを実行します。
- Dockerfileの各命令(
FROM,RUN,COPY,ADDなど)を順番に処理します。 - 各命令の実行ごとに中間コンテナが作成され、その変更内容が新しいイメージレイヤーとしてコミットされます。
- ビルドキャッシュが有効な場合、既存のキャッシュレイヤーが利用されます。
COPYやADD命令は、ビルドコンテキスト内のファイルを指定されたパスにコピーします。RUN命令は、中間コンテナ内でコマンドを実行します。
- Dockerfileの各命令(
- 最終イメージのタグ付け: 全ての命令の実行が完了したら、最終的なイメージが作成され、Composeプロジェクト名とサービス名に基づいたデフォルトのタグ(例:
myproject_webapp)が付けられます。 - 中間コンテナのクリーンアップ: ビルド中に作成された中間コンテナは、特に
--force-rmオプションが指定されていない限り、キャッシュとして利用できるよう保持されるか、不要になったものは削除されます。
ビルドコンテキストの重要性
ビルドコンテキストは、Dockerデーモンがイメージをビルドする際にアクセスできる唯一のファイルシステム領域です。COPY や ADD 命令で指定するパスは、このビルドコンテキストのルートからの相対パスである必要があります。
build.context: ./app と指定した場合、./app ディレクトリの内容全体がビルドコンテキストとしてDockerデーモンに送信されます。Dockerfile内で COPY . /app と記述すると、./app ディレクトリ内の全てのファイルがコンテナ内の /app にコピーされます。
もし context: ./ (プロジェクトルート) と指定した場合、プロジェクトルート以下の全てがビルドコンテキストに含まれます。この状態で ./app/Dockerfile を使用してビルドしたい場合は、dockerfile: ./app/Dockerfile と明示的に指定する必要があります。そして、COPY ./app /app のように、コピー元パスもコンテキストルートからの相対パスで記述する必要があります。
.dockerignore ファイル:
ビルドコンテキストに含める必要のないファイルやディレクトリを除外するために、ビルドコンテキストのルートディレクトリに .dockerignore という名前のファイルを置くことが強く推奨されます。記述方法は .gitignore と似ており、除外したいファイルやディレクトリのパスパターンを一行ずつ記述します。
“`gitignore
.dockerignore 例
.git
.vscode/
node_modules/ # Node.js プロジェクトの場合、依存関係はビルド中にインストールするのが一般的
tmp/
logs/
dist/ # ビルド成果物はコピーしない
.log
.swp
“`
.dockerignore ファイルは、不要なファイルをビルドコンテキストから除外することで、以下のようなメリットがあります。
- ビルド速度の向上: 送信するデータ量が減るため、ビルドコンテキストの送信時間が短縮されます。
- キャッシュ効率の向上: 不要なファイルがビルドコンテキストに含まれないため、
COPY命令によるキャッシュミスが発生しにくくなります。 - イメージサイズの削減: 誤って不要な大きなファイルがイメージに含まれるのを防ぎます。
- セキュリティ: 機密情報などが含まれる可能性のあるファイルをビルドコンテキストに含めないようにできます。
各Dockerfile命令の実行ステップ
Dockerfileの各命令は、原則として独立したステップとして実行され、それぞれが中間イメージ(レイヤー)を作成します。
dockerfile
FROM ubuntu:22.04 # レイヤー1: ベースイメージ
RUN apt-get update && apt-get install -y nginx # レイヤー2: apt-get の実行
COPY . /app # レイヤー3: ファイルのコピー
EXPOSE 80 # レイヤー4: ポート設定
CMD ["nginx", "-g", "daemon off;"] # レイヤー5: コマンド設定
docker build (または docker-compose build) の出力を見ると、各ステップの実行状況がレイヤーごとに表示されるのが確認できます。このレイヤー構造がDockerイメージの重要な特徴であり、ビルドキャッシュの仕組みの基盤となっています。
ビルドプロセスとその内部動作を理解することで、ビルドが遅い原因を特定したり、エラー発生時にどのステップで問題が起きているのかを把握したりするのに役立ちます。
よくある問題とトラブルシューティング
docker-compose build の使用中によく遭遇する問題とその解決策を紹介します。
1. build.context または build.dockerfile のパス指定ミス
- 問題:
docker-compose.ymlで指定したcontextのパスが間違っている、またはdockerfileの名前が間違っているために、Dockerfileが見つからない、あるいは期待したDockerfileが使われない。 - エラー例:
ERROR: build path /path/to/incorrect/context either does not exist, is not accessible, or is not a valid URL.
ERROR: Cannot locate Dockerfile: Dockerfile.typo - 解決策:
docker-compose.ymlファイルを開き、build.contextおよびbuild.dockerfileのパスが、docker-compose.ymlファイル自身が置かれているディレクトリからの相対パスとして正しいことを確認してください。- 指定したパスに実際にディレクトリやファイルが存在するか確認してください。
- パスにシンボリックリンクが含まれる場合、Dockerはデフォルトではシンボリックリンクを辿らない場合があります。絶対パスを使用するか、Dockerのビルドオプションを確認してください。
2. Dockerfile内のパス指定ミス (COPY/ADD)
- 問題: Dockerfile内の
COPYやADD命令で指定したコピー元のパスが、build.contextのルートからの相対パスとして正しくないため、ファイルが見つからない。 - エラー例:
COPY failed: stat /var/lib/docker/tmp/docker-builder<random_chars>/path/to/nonexistent/file: no such file or directory - 解決策:
- Dockerfileを開き、
COPYやADD命令のコピー元パスが、docker-compose.ymlのbuild.contextで指定したディレクトリのルートからの相対パスとして正しいか確認してください。 - 例えば
context: ./appと指定している場合、DockerfileのCOPY ./src /appは./app/srcディレクトリの内容を指します。 - ビルドコンテキストに含め忘れているファイルやディレクトリがないか確認してください(
.dockerignoreで意図せず除外されていないかも確認)。
- Dockerfileを開き、
3. ビルド引数 (args) が正しく渡せていない
- 問題:
docker-compose.ymlのbuild.argsで指定した引数が、DockerfileのARG命令で受け取れていない、または期待した値になっていない。 - 現象: Dockerfile内の
ARGを使用した部分でエラーが発生する、またはデフォルト値が使われてしまう。 - 解決策:
docker-compose.ymlのbuild.argsで、引数名(キー)がDockerfileのARG命令で定義された変数名と完全に一致しているか確認してください。大文字・小文字も区別されます。build.argsの値で環境変数 (${VAR}) を使っている場合、その環境変数がComposeコマンド実行時にセットされているか、または.envファイルで正しく定義されているか確認してください。- コマンドラインオプションの
--build-argを使っている場合、オプション名と値の形式が正しいか、そしてそれがComposeファイルの設定を上書きしている意図通りの挙動か確認してください。 - Dockerfile内で
ARG命令が、それを使用する命令(RUN,COPY,ENVなど)よりも前に定義されているか確認してください。
4. キャッシュの問題
- 問題:
- 意図しないキャッシュヒット: Dockerfileを変更したのに、キャッシュが使われて変更が反映されないように見える。
- 意図しないキャッシュミス: Dockerfileやビルドコンテキストに変更がないはずなのに、キャッシュが使われずビルドに時間がかかる。
- 解決策:
- 意図しないキャッシュヒット: 本当に変更が反映されていないか確認し、必要であれば
docker-compose build --no-cacheオプションを使ってキャッシュを無効にしてビルドし直してください。Dockerfileの特定の命令より後のキャッシュを無効にしたい場合は、その命令にコメントを追加するなどの一時的な変更を加えることで、その命令以降のキャッシュを無効化できます。 - 意図しないキャッシュミス: Dockerfile内の
COPY/ADD命令が、ビルドコンテキスト内の変更頻度の高いファイルを参照していないか確認してください。.dockerignoreファイルを使って、ビルドコンテキストから不要なファイル(特にタイムスタンプが頻繁に変わる一時ファイルなど)を除外することで、キャッシュミスを減らせます。Dockerfileの命令順序を見直し、変更頻度の低い命令を先に配置してください。
- 意図しないキャッシュヒット: 本当に変更が反映されていないか確認し、必要であれば
5. 権限エラー
- 問題: ビルドプロセス中にファイルへのアクセス権限がないためにエラーが発生する。
- エラー例:
COPY failed: stat /path/to/file: permission denied
RUN permission denied: unknown user <username> - 解決策:
COPY/ADDエラー: ビルドコンテキスト内のファイルに対して、Dockerデーモンを実行しているユーザー(Linuxの場合は通常rootまたはdockerグループに所属するユーザー)が読み取り権限を持っているか確認してください。ホスト側のファイル権限を調整する必要があるかもしれません。RUNエラー: Dockerfile内でRUN命令を実行するユーザー(USER命令で指定)が、コマンド実行に必要なファイルやディレクトリへの権限を持っているか確認してください。必要であれば、Dockerfile内で権限を設定する命令 (RUN chmod ...,RUN chown ...) を追加してください。
6. ネットワークの問題(ビルド中に外部リソースにアクセスできない)
- 問題: Dockerfileの
RUN命令で外部ネットワークへのアクセスが必要なコマンド(例:apt-get update,npm install,go mod download)が、ネットワークエラーで失敗する。 - 原因: ホストのファイアウォール設定、プロキシ設定、DNS解決の問題、Dockerデーモンのネットワーク設定など。
- 解決策:
- ホストマシンから同じリソースにアクセスできるか確認してください。
- Dockerデーモンのネットワーク設定(特にプロキシ設定)を確認してください。環境変数
HTTP_PROXY,HTTPS_PROXY,NO_PROXYなどを設定する必要があるかもしれません。これらはDockerデーモンの設定だけでなく、DockerfileのARGやENVで中間コンテナ内に設定する必要がある場合もあります。 - DNS解決に問題がないか確認してください。
- Composeファイルの
build.networkオプションを使って、ビルド時に使用するネットワークを指定することも検討してください。
7. デバッグ方法
ビルドエラーの原因が特定できない場合は、以下の方法で詳細な情報を得ることができます。
- 詳細ログ:
docker-compose buildコマンド自体には-v(verbose) オプションはありませんが、Dockerデーモンのログレベルを上げたり、BuildKitのデバッグ機能を有効にしたりすることで詳細なビルドログを確認できる場合があります。 - インタラクティブビルド: エラーが発生したDockerfileの特定のステップで処理を一時停止させ、中間コンテナ内でシェルを起動して内部を確認したい場合があります。これは標準の
docker buildコマンドでdocker run ...を組み合わせるなどの高度なテクニックが必要ですが、エラーメッセージからどのステップで失敗しているかを確認するだけでもデバッグの大きな手がかりになります。 - ステップごとの確認: エラーが発生する直前のDockerfile命令までをコメントアウトし、一つずつコメントアウトを解除しながらビルドを実行することで、正確なエラー発生箇所を特定できます。
--no-cacheの活用: キャッシュが悪さをしている可能性がある場合は、--no-cacheでクリーンなビルドを試してください。--progress=plainの活用: 特にCI環境などで、tty形式のプログレス表示よりも、各ステップの詳細なログを一行ずつ出力するplain形式の方がエラーメッセージを見つけやすい場合があります。
これらのトラブルシューティングのヒントは、多くのビルドに関する問題を解決するのに役立つはずです。エラーメッセージをよく読み、ビルドプロセスの各ステップで何が起きているのかを冷静に分析することが重要です。
docker-compose build と docker-compose up の違いと連携
docker-compose build と docker-compose up は密接に関連していますが、目的と挙動が異なります。
docker-compose build: サービスのイメージをビルドすることだけを目的とします。docker-compose.ymlのbuild:ディレクティブで定義されたサービスに対して、Dockerfileからのイメージビルドを実行します。コンテナの起動は行いません。docker-compose up:docker-compose.ymlファイル全体を読み込み、定義されたサービスを起動することを目的とします。サービス定義にimage:が指定されていれば、そのイメージをプルしてコンテナを起動します。サービス定義にbuild:が指定されていれば、コンテナを起動する前にそのサービスに対応するイメージをビルドします。その後、ビルドまたはプルしたイメージを使ってコンテナを起動します。
docker-compose up がビルドをトリガーする場合
docker-compose up コマンドを実行した際に、サービス定義に build: ディレクティブが含まれている場合、Composeはまずそのイメージが最新であるかを確認します。
- もし対応するイメージが存在しない場合、またはDockerfileやビルドコンテキストに変更がある場合、Composeは自動的にビルドを実行します。
- デフォルトでは、ComposeはDockerfileやビルドコンテキストの変更を検出し、必要に応じてビルドを行います。ただし、キャッシュがヒットする場合はビルドは高速に完了します。
この自動ビルド機能は開発中に便利ですが、以下のような場合には意図しない挙動につながる可能性もあります。
- 意図しないリビルド: ファイルのタイムスタンプの変更など、わずかな変更でもビルドコンテキストの変更と見なされ、予想外にリビルドが走ることがあります。
- ビルドオプションの制御が難しい:
docker-compose upには--no-cacheなどのビルドオプションを直接渡すことができません。(Compose V2では--buildオプションがありますが、これはComposeファイルで定義されたビルドオプションではなく、ビルドを強制的に実行させるためのものです。)
docker-compose build を先に実行するワークフロー
これらの問題を避けるため、特に開発中やCI/CD環境では、docker-compose build でイメージを先にビルドしておき、その後に docker-compose up --no-build でコンテナを起動するというワークフローが推奨される場合があります。
- ビルド: コードやDockerfileを変更したら、まず
docker-compose build [service_name]を実行して、新しいイメージをビルドします。必要に応じて--no-cacheや--build-argなどのオプションを使用します。 - 起動: ビルドが成功したら、
docker-compose up --no-build [service_name]を実行します。--no-buildオプションは、Composeがコンテナ起動時に自動ビルドを実行しないように指示します。Composeは直前にビルドされた最新のイメージを使用してコンテナを起動します。
このワークフローの利点:
- ビルドと起動の分離: それぞれのステップを明確に分けられます。
- ビルドオプションの完全な制御:
docker-compose buildコマンドを直接使うため、全てのビルドオプションを利用できます。 - 予期しないリビルドの防止:
docker-compose up --no-buildはビルドコンテキストの変更などをチェックしないため、意図しないビルドが発生しません。 - 高速な起動: イメージが既にビルドされているため、
docker-compose upはビルドステップをスキップしてすぐにコンテナ起動に進みます。
特に開発サイクルの早い段階で、Dockerfileやビルド関連の設定を頻繁に変更する場合、この分離されたワークフローは効率的です。コードだけを変更し、Dockerfileに変更がない場合は、ビルドが必要ないこともあります(依存関係のインストールなどがコードに含まれていない場合)。その場合は docker-compose up だけを実行すれば、Composeはキャッシュされたイメージを利用して高速に起動します。
ワークフローは開発チームやプロジェクトの性質によって異なりますが、docker-compose build を単独で実行する能力を理解しておくことは、より柔軟で効率的なComposeの使い方につながります。
実践的なワークフロー
docker-compose build コマンドは、様々な開発、テスト、デプロイのシナリオで活用できます。
開発環境でのビルドと実行
ローカルでのアプリケーション開発において、Composeは非常に強力なツールです。一般的な開発ワークフローは以下のようになります。
- プロジェクトのセットアップ:
docker-compose.ymlファイルと各サービスのDockerfileを作成します。 - 初期ビルド: プロジェクトを開始する際に、全てのカスタムイメージをビルドします。
bash
docker-compose build
あるいは、特定のサービスのみビルド。
bash
docker-compose build webapp database - コンテナ起動: ビルドしたイメージを使ってサービスを起動します。初回はイメージプルなども行われます。
bash
docker-compose up -d # バックグラウンドで起動 - コード修正: アプリケーションコードを修正します。
- リビルド(必要な場合): Dockerfileに変更があった場合、あるいはコード変更をイメージに反映する必要がある場合(例: フロントエンドのビルド成果物をイメージにコピーする場合)、該当するサービスをリビルドします。
bash
docker-compose build webapp
コードの変更が多い場合は、docker-compose build webapp && docker-compose up --no-build -d webappのようにビルドと再起動を組み合わせるのが効率的です。 - コンテナ再起動/再作成: 修正したコードを反映した新しいイメージでコンテナを再起動または再作成します。
bash
docker-compose restart webapp # コンテナ設定が同じなら高速
# または
docker-compose up --no-build -d webapp # イメージが変更された場合、コンテナは再作成される - 開発の継続: ステップ4〜6を繰り返しながら開発を進めます。
CI/CDパイプラインでのビルド
CI/CDパイプラインにおいて、docker-compose build はイメージビルドの標準的なステップとして組み込まれます。
- コード変更のプッシュ: 開発者がコードをバージョン管理システム(Gitなど)にプッシュします。
- CIトリガー: プッシュを検知してCIシステムが起動します。
- コードのチェックアウト: CIエージェントが最新のコードをチェックアウトします。
- 依存関係のインストール: ホスト側でComposeやDockerをインストールします。
- イメージビルド:
docker-compose buildコマンドを実行して、アプリケーションのDockerイメージをビルドします。--no-cacheオプションを使うかどうかはチームの方針によります。クリーンなビルドを保証したい場合は使用します。cache_fromを使って、以前のビルド結果をキャッシュとして活用することも一般的です。--progress=plainオプションでCIログを見やすくすることも有効です。
bash
docker-compose build --no-cache --pull --progress=plain webapp backend
- イメージのタグ付け: ビルドされたイメージに、コミットハッシュやタグ名、ビルド番号などのユニークなタグを付けます。これは
docker tagコマンドやCIツールの機能で行います。 - イメージのプッシュ: タグ付けされたイメージをコンテナレジストリ(Docker Hub, Quay.io, ECR, GCRなど)にプッシュします。
bash
docker push myregistry/myproject_webapp:latest
docker push myregistry/myproject_webapp:abcdef123
# あるいは
docker-compose push webapp backend # Composeコマンドでプッシュする場合 - テストの実行: プッシュされたイメージ、またはビルドされたローカルイメージを使って、テスト用のコンテナを起動し、単体テスト、統合テスト、E2Eテストなどを実行します。
bash
docker-compose up --no-build -d --force-recreate test_runner
docker-compose logs test_runner
docker-compose down - デプロイ: テストが成功した場合、ビルドされレジストリにプッシュされたイメージを本番環境にデプロイします。デプロイメントは、Composeファイルを使う場合もあれば、Kubernetesなどの別のツールを使う場合もあります。
CI/CDにおける docker-compose build は、再現性のあるビルドを自動化し、デプロイ可能な成果物(Dockerイメージ)を作成する上で不可欠なステップです。
本番環境へのデプロイ
本番環境へのデプロイ戦略は様々ですが、Composeを使うシンプルな構成や、Kubernetesなどのオーケストレーションツールを使う構成などがあります。
- Composeを使ったデプロイ: 小規模なアプリケーションや単一サーバーへのデプロイであれば、本番サーバーにComposeをインストールし、本番用の
docker-compose.ymlファイル(image:でレジストリのイメージを指定)を配置し、docker-compose pullでイメージを取得、docker-compose up -dでサービスを起動するという方法が考えられます。この場合、本番環境でdocker-compose buildを実行することは稀です。ビルドはCI環境など別の場所で行い、ビルド済みのイメージをレジストリ経由で本番環境にプルするのが一般的かつ推奨されるプラクティスです。本番環境でビルドを行うと、ビルドに必要なSDKなどが本番サーバーに必要になったり、ビルド中のリソース消費がサービス稼働に影響を与えたりする可能性があるためです。 - オーケストレーションツールを使ったデプロイ: KubernetesやDocker Swarmなどのオーケストレーションツールを使用する場合、Composeは開発・テスト環境の構築ツールとして使われることが多いです。本番環境では、CIでビルド・プッシュされたイメージをこれらのオーケストレーションツールがプルしてデプロイします。
したがって、本番環境での docker-compose build の使用は限定的であり、通常はビルド済みイメージのプル (docker-compose pull) や、レジストリからの直接のイメージ指定 (image: myregistry/myapp:latest) が主な戦略となります。
このように、docker-compose build コマンドは、開発の初期段階からCI/CDパイプライン、そしてデプロイ戦略を検討する上で、Dockerイメージの作成という重要なステップを担っています。
まとめ
この記事では、Docker Compose を使用した複数コンテナアプリケーション開発におけるイメージビルドの要となる docker-compose build コマンドについて、その基本から応用まで詳細に解説しました。
主要なポイントを振り返りましょう。
- Docker Compose は、複数のDockerコンテナを連携させてアプリケーションを定義・管理するツールです。
docker-compose.ymlファイルのサービス定義では、image:で既存イメージを指定するか、build:でDockerfileからカスタムイメージをビルドするかを選択できます。docker-compose buildコマンドは、build:ディレクティブを持つサービスに対応するイメージを、コンテナを起動せずにビルドするために使用します。特定のサービスを指定してビルドしたり、コマンドラインオプションでビルドの挙動を細かく制御したりできます。docker-compose.ymlのbuild:ディレクティブは、context,dockerfile,args,cache_from,labels,shm_size,target,network,platformといった様々なサブディレクティブを持ち、ビルドプロセスを柔軟に設定できます。- ビルドキャッシュ は、Dockerのビルド効率を大幅に向上させる重要な機能です。Dockerfileの命令順序や
.dockerignoreファイルを工夫することで、キャッシュヒット率を高め、ビルド時間を短縮できます。--no-cacheオプションでキャッシュを無効にすることも可能です。 - ビルド引数 (
args) は、docker-compose.ymlから Dockerfile のARG命令に値を渡す仕組みです。環境変数や.envファイルと連携させて動的な値を渡せますが、機密情報の受け渡しには注意が必要です。--build-argオプションでコマンドラインから引数を上書きすることもできます。 - マルチステージビルド (
target) は、単一のDockerfileから異なる構成(例: 開発用、本番用)の軽量イメージを作成する強力な手法です。build.targetディレクティブでビルドする最終ステージを指定できます。 docker-compose buildには、--no-cache,--pull,--progress,--build-arg,--targetなど、ビルドを制御するための様々なオプションがあります。- ビルドプロセスでは、
contextディレクトリの内容がDockerデーモンに送信され、.dockerignoreファイルによって不要なファイルが除外されます。Dockerfileの各命令は順番に実行され、新しいイメージレイヤーを作成します。 docker-compose buildとdocker-compose upは連携して使われますが、upが自動的にビルドを実行する場合がある一方で、buildを先に独立して実行し、その後up --no-buildで起動するというワークフローは、ビルドの制御性や効率を高める上で有効です。docker-compose buildは、ローカル開発、CI/CDパイプライン、そして場合によってはデプロイメントにおいて、Dockerイメージを作成するための重要なステップとして位置づけられます。
Docker Composeは、複数コンテナアプリケーションの開発・管理を効率化するための強力なツールであり、その中でも docker-compose build コマンドは、アプリケーションの心臓部とも言えるカスタムイメージを作成する上で不可欠な要素です。この記事を通して、docker-compose build の使い方や、関連するComposeファイルの build ディレクティブ、ビルドキャッシュ、ビルド引数、マルチステージビルド、様々なオプションについての理解が深まったことと思います。
これらの知識を活用して、より効率的で堅牢なDockerイメージビルド、そしてコンテナ化されたアプリケーション開発を進めていただければ幸いです。