Dockerビルド失敗の解決:包括的なトラブルシューティングガイド

不正なパス、不足パッケージ、キャッシュの驚き、ネットワーク問題、権限、ディスク容量によるDockerビルド失敗をデバッグします。

Dockerビルド失敗の解決:包括的なトラブルシューティングガイド

Dockerビルドの失敗は、ビルド出力をトランスクリプトとして扱うと修正が容易になります。Dockerはどのステップが失敗したか、どのコマンドが実行されたか、そのコマンドが何を出力したかを教えてくれます。重要なのは、最初の実際のエラーを見つけることであり、ビルドが失敗したことを示す最後の行ではありません。

デフォルトの出力が情報を隠しすぎる場合は、プレーンな進捗表示でビルドを実行します:

docker build --progress=plain -t my-app:debug .

失敗したステップ番号(例:#8)とその横の命令を探します。失敗した命令がCOPYの場合、おそらくビルドコンテキストまたはパスの問題です。RUN apt-get installの場合は、パッケージ、ネットワーク、リポジトリ、またはアーキテクチャの問題です。RUN npm cipip installの場合は、Docker設定を変更する前にパッケージマネージャのエラーを読みましょう。

COPY failed:ファイルがビルドコンテキストにない

最も一般的なビルドエラーの一つは、最も単純なものの一つでもあります:

COPY failed: file not found in build context or excluded by .dockerignore

Dockerはビルドコンテキスト内のファイルのみをコピーできます。ビルドコンテキストは通常、docker buildの最後の引数です:

docker build -t my-app .

ここで.がコンテキストです。サブディレクトリにあるDockerfileは、そのコンテキスト外の../secret.txtをコピーできません。Dockerはこれを意図的にブロックします。なぜなら、ビルドはそのコンテキストから再現可能であるべきだからです。

次の3つを確認します:

pwd
ls -la
docker build --progress=plain -f path/to/Dockerfile .

Dockerfileがdocker/Dockerfileにあり、アプリがリポジトリルートにある場合、ルートからビルドし、-fでDockerfileを指定します:

docker build -f docker/Dockerfile -t my-app .

また、.dockerignoreを確認します。コピーしようとしているファイルを除外している可能性があります。これはdisttarget.env、または生成ファイルでよく発生します。Dockerfileがファイルを期待している場合、そのファイルがビルド内で作成されない限り、無視しないでください。

パッケージインストールの失敗

パッケージマネージャの失敗は通常、いくつかのカテゴリに分類されます:古いパッケージインデックス、間違ったパッケージ名、不足しているリポジトリ、ネットワーク問題、または間違ったLinuxディストリビューションのコマンド使用。

これはAlpineでは失敗します。Alpineはapt-getを使用しないからです:

FROM alpine:3.20
RUN apt-get update && apt-get install -y curl

ベースイメージに適したパッケージマネージャを使用します:

FROM alpine:3.20
RUN apk add --no-cache curl

DebianまたはUbuntuイメージの場合、更新とインストールを同じレイヤーに保ちます:

RUN apt-get update &&     apt-get install -y --no-install-recommends curl ca-certificates &&     rm -rf /var/lib/apt/lists/*

apt-get installがパッケージを見つけられないと言う場合、そのディストリビューションのバージョンに適したパッケージ名を確認します。パッケージ名はDebian、Ubuntu、Alpine、Fedora、および言語固有のイメージ間で異なります。最小限のイメージには、bashcurlgittarca-certificatesなど、存在すると仮定しているツールが欠けている場合もあります。

HTTPSダウンロードが証明書エラーで失敗する場合、curlwget、HTTPS経由のgit、npm、pip、または言語パッケージマネージャを使用する前にCA証明書をインストールします:

RUN apt-get update &&     apt-get install -y --no-install-recommends ca-certificates &&     rm -rf /var/lib/apt/lists/*

キャッシュの驚き

Dockerのキャッシュは通常役立ちますが、壊れたビルドを一貫性のないものに見せることがあります。古いキャッシュを疑う場合、次を実行します:

docker build --no-cache --progress=plain -t my-app:debug .

キャッシュなしでのみビルドが失敗する場合、古いレイヤーに依存していた可能性があります。キャッシュありでのみ失敗する場合、生成ファイルや依存関係ロックファイルが、Dockerが期待する方法で変更されたかどうかを確認します。

依存関係のインストールでは、完全なソースツリーの前にロックファイルをコピーします:

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

Pythonの場合:

WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

このパターンは高速なだけでなく、依存関係のインストールが依存関係ファイルに依存し、すべてのソース変更に依存しないため、ビルド失敗を理解しやすくします。

ネットワークとレジストリの失敗

Dockerがベースイメージをプルできない場合、Dockerfileが実行される前にビルドが失敗することがあります:

docker pull python:3.12-slim

それが失敗する場合、まずレジストリアクセスを修正します。プライベートレジストリの認証、企業プロキシ設定、DNS、ファイアウォールルールを確認します。プロキシの背後では、Dockerデーモンにプロキシ設定が必要です。インタラクティブシェルでのみHTTP_PROXYを設定しても不十分な場合があります。

RUNステップ内のダウンロードについては、ホストからURLをテストし、次に同じネットワークパス上の一時コンテナからテストします:

curl -I https://example.com/file.tar.gz
docker run --rm curlimages/curl -I https://example.com/file.tar.gz

可能であれば、固定されていないリモートスクリプトに依存しないでください。移動するブランチからinstall.shをcurlするDockerfileは、リモートスクリプトが変更されたために壊れる可能性があります。バイナリにはバージョン管理されたダウンロードとチェックサム検証を優先します:

RUN curl -fsSLo tool.tar.gz https://example.com/tool-1.2.3-linux-amd64.tar.gz &&     echo '<sha256>  tool.tar.gz' | sha256sum -c - &&     tar -xzf tool.tar.gz -C /usr/local/bin &&     rm tool.tar.gz

<sha256>をプロジェクトリリースページの実際のチェックサムに置き換えます。

アーキテクチャの不一致

Apple Siliconや混在CI環境では、アーキテクチャが原因でビルド失敗が発生することがあります。イメージやダウンロードしたバイナリがamd64であり、ビルダーがarm64である場合、またはその逆の場合があります。症状には、exec format error、アーキテクチャ向けのパッケージ不足、ビルド中に失敗するバイナリなどがあります。

ホストとターゲットを確認します:

docker version
docker buildx ls

必要に応じて特定のプラットフォーム向けにビルドします:

docker buildx build --platform linux/amd64 -t my-app:amd64 .

注意:クロスプラットフォームビルドはエミュレーションが伴う場合、遅くなることがあります。CIでは、各プラットフォーム向けのネイティブビルダーの方が高速で、驚きが少ないことがよくあります。

ビルド中の権限エラー

権限エラーは、ファイルが予期しない所有権でコピーされたり、スクリプトが実行可能でなかったり、Dockerfileがセットアップ完了前に非rootユーザーに切り替わったりするときに発生します。

スクリプトがpermission deniedで失敗する場合、Dockerfileに仮定をコピーする前に確認します:

ls -l scripts/start.sh

次に、git内またはイメージ内で修正します:

COPY scripts/start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh

非rootランタイムユーザーを使用する場合、ユーザーを切り替える前にディレクトリを作成し、所有権を設定します:

RUN useradd -r -u 10001 appuser && mkdir -p /app/data && chown -R appuser:appuser /app
USER appuser

COPY --chown=appuser:appuser . .は、rootとしてコピーして後で広範囲の再帰的chownを実行するよりも、多くの場合クリーンです。

ディスク容量とビルドキャッシュのクリーンアップ

大規模なビルドは、Dockerホストのディスク容量不足で失敗することがあります。Dockerの使用状況を確認します:

docker system df

適切な場合、未使用のビルドキャッシュを削除します:

docker builder prune

広範なクリーンアップコマンドには注意が必要です。docker system prune -aは未使用のイメージを削除し、大規模な再プルを強制したり、ローカルイメージに依存するワークフローを壊したりする可能性があります。影響を理解した上で使用します。

ビルドが頻繁にディスクを満たす場合、より良い修正は通常、より小さなビルドコンテキスト、マルチステージビルド、レイヤー内の巨大な一時ファイルの回避です。一時的なアーティファクトは、それを作成する同じRUN命令内でクリーンアップします。

失敗するRUNステップをインタラクティブにデバッグする

長いRUN行が失敗する場合、一時的に分割します:

RUN apt-get update
RUN apt-get install -y --no-install-recommends packageA packageB
RUN some-command-that-fails

失敗するコマンドを見つけたら、クリーンなイメージのために関連コマンドを再度結合できます。

もう一つの便利なトリックは、既知の良好なステージで停止することです。Dockerfileにステージがある場合、1つのターゲットをビルドします:

docker build --target builder -t my-app-builder .
docker run --rm -it my-app-builder sh

そこからファイルを検査し、失敗するコマンドを手動で実行し、環境変数を確認し、ファイルシステムに実際に何が含まれているかを確認できます。

シェルがないイメージの場合、プロダクションイメージを汚染するのではなく、一時的なデバッグステージを追加します:

FROM builder AS debug
RUN apt-get update && apt-get install -y --no-install-recommends bash curl

--target debugをビルドし、調査し、完了したらデバッグターゲットを削除または無視します。

ビルドを予測可能に保つ

信頼性の高いDockerビルドは、最も良い意味で退屈です。バージョン管理されたベースイメージを使用します。依存関係ロックファイルをソース管理に保持します。プロダクションDockerfileでは、プロセスが意図的に移動するタグに対して再ビルドとテストを行わない限り、latestを避けます。.dockerignoreを厳密に保ちます。ネットワークダウンロードをバージョン管理し、検証します。頻繁に変更されるソースコードは、依存関係インストールの後に配置します。

ビルドが失敗した場合、一度にDockerfile全体を書き換えないでください。失敗した命令を特定し、プレーンなログで再現し、コマンドを分離し、最小の実際の原因を修正します。そのアプローチはより速く、次の人が理解できるDockerfileを残します。