2026年8月6日
2026年8月6日
Dockerfileの書き方ガイド|基本命令・キャッシュの効かせ方・WordPress用イメージの例
Dockerfileは「どのベースイメージに、何をコピーし、どのコマンドを実行して、最後に何を起動するか」を上から順に書いた手順書です。書き方の基本は少数の命令(FROM・WORKDIR・COPY・RUN・USER・CMD など)で足りますが、命令の順番によってビルドの速さやイメージの大きさ、安全性が大きく変わります。この記事では、最小のDockerfileから始めて、各命令の役割、キャッシュが効く並べ方、秘密情報の扱い、WordPress公式イメージを拡張する例、ビルドが失敗したときの調べ方までを説明します。
前提はDocker Engine 23.0以降で標準になっているビルダーのBuildKitです(2026年10月現在)。イメージのタグは執筆時点の例なので、使う前にDocker Hubの公式イメージのページで現行のタグを確認してください。
最小のDockerfileと動かし方
Node.jsの小さなWebアプリを例にします。プロジェクトの直下に Dockerfile という名前(拡張子なし)で置きます。
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
ビルドと起動は次の2行です。
docker build -t myapp:dev .
docker run --rm -p 3000:3000 myapp:dev
docker build の最後の . は「ビルドコンテキスト」で、COPY で参照できるのはこのディレクトリの中のファイルだけです。COPY ../config ./ のように外のファイルを指定するとエラーになるのはこのためです。
1行目の # syntax=docker/dockerfile:1 は、Dockerfileの文法を解釈するフロントエンドのバージョンを指定するコメントです。書いておくと RUN --mount などの書き方がDockerのバージョンに左右されにくくなります。
よく使う命令と役割
FROM:土台にするイメージ。node:22-slimのように、バージョンと種類(slim、alpineなど)まで指定します。latestは中身が予告なく変わるので、本番用には避けます。WORKDIR:以降の命令の作業ディレクトリ。存在しなければ作られます。RUN cd /appは次の命令に引き継がれないので、移動はWORKDIRで行います。COPY:ビルドコンテキストからイメージへファイルを入れます。--chown=ユーザー:グループで所有者も指定できます。ADD:COPYに加えて、ローカルのtarを展開したりURLから取得したりできます。意図しない展開を避けるため、普段はCOPYを使い、展開が必要なときだけADDを選びます。RUN:ビルド時にコマンドを実行し、その結果を新しいレイヤーとして保存します。ENV:環境変数。ビルド中にも、起動後のコンテナにも残ります。ARG:ビルド時だけの変数。docker build --build-argで値を渡します。FROMより前に書いたARGは、FROMの行でしか使えない点に注意します。USER:以降の命令と起動時のプロセスを実行するユーザー。公式nodeイメージにはnodeというユーザーが用意されています。EXPOSE:コンテナが待ち受けるポートの「メモ」です。これだけでは外に公開されず、docker run -pで公開します。CMDとENTRYPOINT:起動時に実行するコマンド。ENTRYPOINTを固定の実行ファイル、CMDをその既定の引数として組み合わせることもできます。HEALTHCHECK:コンテナが正常かを定期的に確かめるコマンド。チェックに使うcurlなどがイメージ内にあることが前提です。
CMDはexec形式(配列)で書く
CMD node server.js のように文字列で書くと(シェル形式)、/bin/sh -c 経由で起動され、コンテナの中でPID 1になるのはシェルです。シェルは docker stop が送るSIGTERMをアプリに渡さないことがあり、停止のたびに10秒待たされた末に強制終了される、という症状につながります。CMD ["node", "server.js"] のように配列で書くと、アプリ自身がPID 1になりシグナルを受け取れます。
キャッシュが効く順番に並べる
Dockerはビルドの各命令の結果をキャッシュし、命令とその入力が前回と同じなら再利用します。ある段階でキャッシュが外れると、それより後ろはすべて実行し直しになります。そのため「変わりにくいものを上、変わりやすいものを下」に並べるのが基本です。
最初の例で package.json と package-lock.json だけを先にコピーして npm ci しているのはこのためです。ソースを1行直しただけなら依存関係のインストールはキャッシュから再利用され、COPY . . 以降だけが実行されます。逆に COPY . . を先に書くと、どのファイルを変えても npm ci からやり直しになります。
パッケージマネージャーのダウンロードキャッシュは、BuildKitのキャッシュマウントでビルド間に持ち越せます。
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
このキャッシュはイメージのレイヤーには入らないので、イメージが大きくなりません。
aptは1つのRUNにまとめる
Debian系のイメージでパッケージを入れるときは、更新・インストール・後片付けを1つの RUN で行います。
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
&& rm -rf /var/lib/apt/lists/*
apt-get update だけを別の RUN にすると、その行がキャッシュされたまま古いパッケージ一覧でインストールしようとして失敗することがあります。また、後の RUN でファイルを消しても前のレイヤーには残るため、削除は同じ RUN の中で行います。
.dockerignoreでコンテキストを絞る
ビルドコンテキストの中身はビルダーに丸ごと送られます。node_modules や .git、ログ、.env などが入っていると、ビルドが遅くなるだけでなく、COPY . . で秘密情報がイメージに入ってしまいます。Dockerfileと同じ場所に .dockerignore を置いて除外します。
.git
node_modules
*.log
.env
.env.*
docker-compose*.yml
秘密情報をイメージに残さない
ARG や ENV でAPIトークンやパスワードを渡すと、docker history やイメージの設定情報から読み取れてしまいます。ビルド中だけ秘密情報が必要な場合(非公開のnpmパッケージを取得するなど)は、シークレットマウントを使います。
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci --omit=dev
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp:dev .
シークレットはその RUN の実行中だけファイルとして見え、レイヤーには残りません。起動後に必要なパスワードなどは、イメージに焼き込まずに実行時の環境変数やComposeのシークレット機能で渡します。
WordPress公式イメージを拡張する例
WordPressの場合は、ゼロから書くより公式の wordpress イメージを土台にして、必要な設定だけを足すのが現実的です。次はアップロード上限などのPHP設定を変える例です。
# syntax=docker/dockerfile:1
FROM wordpress:php8.3-apache
COPY php/uploads.ini /usr/local/etc/php/conf.d/uploads.ini
; php/uploads.ini
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
公式PHPイメージ系では /usr/local/etc/php/conf.d/ に置いた .ini が読み込まれます。反映されたかは、コンテナの中で php -i | grep upload_max_filesize を実行して確かめます。
テーマやプラグインをイメージに含めたい場合は注意が要ります。公式wordpressイメージは、起動時に /var/www/html にWordPress本体がなければ /usr/src/wordpress からコピーする仕組みになっています(2026年10月現在。挙動は公式イメージのドキュメントとエントリーポイントのスクリプトで確認してください)。/var/www/html を既存のボリュームにしていると、イメージ側で追加したファイルは反映されません。開発中のテーマはバインドマウントで渡し、本番用にイメージへ含めるかどうかは、更新の運用(イメージを作り直すのか、管理画面から更新するのか)を決めてから選ぶと混乱しません。作業前にはデータベースと wp-content のバックアップを取り、ステージング環境で試してください。
ビルドが失敗したときの調べ方
- 出力を省略せずに見る:
docker build --progress=plain .で各ステップの出力がそのまま表示されます。キャッシュの影響を除きたいときは--no-cacheを付けます。 - 失敗した行の直前で止める:失敗した
RUNをコメントアウトしてビルドし、docker run --rm -it myapp:dev shで中に入って同じコマンドを手で実行すると、足りないパッケージやパスの違いが分かります。 COPY failed: file not found:ビルドコンテキストの外を指定しているか、.dockerignoreで除外しているかのどちらかが多いです。exec format error:Apple Silicon(arm64)で作ったイメージをamd64のサーバーで動かしたときなどに出ます。docker build --platform linux/amd64で対象を指定します。- 書き方の点検:BuildKitには
docker build --checkでDockerfileのよくある問題を指摘する機能があります。外部のツールではhadolintも使われています。どちらも検出できるのは一部の問題だけなので、指摘がないことを品質の保証とは考えないでください。
まとめ
- Dockerfileは上から順に実行される手順書。
FROMでバージョンまで固定し、移動はWORKDIR、起動は配列形式のCMDで書きます。 - 変わりにくい依存関係のインストールを上、ソースのコピーを下に置くとキャッシュが効きます。aptの更新・導入・片付けは1つの
RUNにまとめます。 .dockerignoreで.gitや.envを除外し、ビルド時の秘密情報はRUN --mount=type=secretで渡します。- WordPressは公式イメージを土台にPHP設定などを足すのが手軽です。
/var/www/htmlのボリュームとの関係を理解してから、テーマやプラグインの入れ方を決めます。 - ビルドの失敗は
--progress=plainで出力を確認し、失敗した手前のイメージに入って手で再現すると原因に近づけます。