Dockerfileとは|書き方・主要命令・ベストプラクティスを解説【2026年版】
Dockerfileは、Dockerイメージのビルド手順を1つのテキストに書いた「設計図」です。これを用意すれば、同じ環境を誰のマシンでも何度でも再現でき、開発から本番までの環境差をなくせます。この記事では、Dockerfileの定義から、ファイル名や置き場所といった最初のつまずき、FROM・RUN・CMDなどの主要命令、RUN・CMD・ENTRYPOINTの違いやCOPYとADDの違い、そして多くの入門記事が触れないBuildKit前提の書き方(syntaxディレクティブ・キャッシュマウント・シークレット)まで、動く最小例とともに解説します。
まとめ(先に要点)
結論を先に示します。
- Dockerfileとは、Dockerイメージを作る命令を上から順に書いたテキストファイルです。コンテナそのものではなく「イメージの設計図」にあたります。
- ファイル名は拡張子なしの
Dockerfile。置き場所はビルドコンテキスト(docker buildの末尾に渡すパス)の中であればよく、別名・別階層に置く場合は-fで指定します。 - 基本の流れは「書く →
docker buildでイメージ化 →docker runで起動」。最小構成はFROM→WORKDIR→COPY→RUN→CMDの数行です。 - 混同しやすい2組:RUNはビルド時に実行されレイヤを作り、CMD・ENTRYPOINTは起動時に効きます。COPYは単純コピー、ADDはURL取得やtar展開もできますが公式は原則COPYを推奨しています。
- 2026年に書くなら、1行目に
# syntax=docker/dockerfile:1を置いてBuildKitの最新構文を使うのが実質の標準です。キャッシュマウントで依存の再取得を避け、シークレットマウントで認証情報をイメージに焼き付けずに済みます。
Dockerfileとは|イメージ・コンテナとの関係
Dockerfileとは、Dockerイメージを作成する一連の命令を記述したテキストファイルです。ベースとなるイメージを指定し、その上にソフトウェアのインストールやファイルの配置、起動コマンドを積み重ねる形で書きます。命令は上から順に実行され、その結果がイメージとして保存されます。
押さえるべきは3者の関係です。Dockerfileは設計図、イメージはその設計図から作られた不変のテンプレート、コンテナはイメージから起動した実行中の実体です。設計図をコードとしてGitで管理できるため、「自分の環境では動く」問題が構造的に消えます。Docker自体の位置づけを整理したい場合はDockerとは|仮想マシンとの違い・必要スペック・インストール手順を解説【2026年版】、名前空間やcgroupsによる隔離の原理まで踏み込むならDockerの仕組みを原理から理解する|コンテナ隔離・アーキテクチャ・VMとの違いをあわせて読んでください。
Dockerfileの書き方:ファイル名・置き場所から最小構成まで
書き方でつまずく箇所は、命令そのものより「ファイルをどう置くか」に集中しています。順に片付けます。
ファイル名・置き場所とビルドコンテキストの範囲
ファイル名は拡張子を付けず、そのまま Dockerfile とします。Dockerfile.txt のように拡張子を付けると既定では認識されません。Windowsのメモ帳は保存時に .txt を自動付与するため、ここで詰まる例が多く見られます。
置き場所は、慣例的にプロジェクトのルートです。docker build の末尾に渡す . は「ビルドコンテキスト」=ビルド時にDockerへ送るファイル群の起点で、COPY が参照できるのはこの範囲だけです。コンテキストの外にあるファイルは COPY ../foo のようには取り込めません。用途別に複数のDockerfileを持つ場合は、Dockerfile.prod のように接尾辞を付けて -f で明示指定します(この場合はファイル名が自由なので拡張子の制約は外れます)。
# カレントをコンテキストにして、別名のDockerfileを指定してビルド
docker build -f docker/Dockerfile.prod -t myapp:prod .
最小構成のDockerfileとビルド・起動
Pythonアプリを動かす最小構成です。1行目のsyntaxディレクティブについては後述します。
# syntax=docker/dockerfile:1
# ベースイメージ(軽量なslim版・バージョンは明示する)
FROM python:3.14-slim
# 以降の命令の作業ディレクトリ
WORKDIR /app
# 依存定義を先にコピー(キャッシュを効かせるため本体より前に置く)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリ本体をコピー
COPY . .
# 起動時に実行する既定コマンド
CMD ["python", "app.py"]
Pythonは3.14が最新の安定版(2025年10月7日リリース)で、公式イメージの python:3.14-slim が使えます。requirements.txt だけを先にコピーしているのは、アプリのソースを変更しただけで依存インストールが再実行されるのを防ぐためです。公式のベストプラクティスも、変更頻度の低い命令を先に置いてキャッシュ再利用を最大化するよう明記しています。
docker buildとdocker runで押さえるオプション
ビルドと起動はDockerfileと対で覚えます。よく使うのは次の範囲です。
# イメージをビルド(-t で 名前:タグ を付与)
docker build -t myapp:1.0 .
# ビルド時変数を渡す(Dockerfile側の ARG を埋める)
docker build --build-arg APP_VERSION=1.0 -t myapp:1.0 .
# キャッシュを使わず作り直す
docker build --no-cache -t myapp:1.0 .
# コンテナを起動(終了時に破棄・ポート8000を公開)
docker run --rm -p 8000:8000 myapp:1.0
# 既定のCMDを上書きしてシェルに入る
docker run --rm -it myapp:1.0 bash
docker build の直後に必ずタグを付けておくと、後から docker images で追えます。タグを省くと <none> の宙ぶらりんなイメージが溜まります。起動後の run・start・exec の使い分けや「コンテナが起動しない」ときの切り分けは、Dockerコンテナ起動コマンドの使い分け|run・start・execと起動しない時の対処で扱っています。
Dockerfileの主要命令リファレンス
まずは FROM / RUN / COPY / CMD / WORKDIR の5つで、大半のケースは書けます。
| 命令 | 役割 | 記述例 |
|---|---|---|
| FROM | ベースイメージ指定(先頭) | FROM python:3.14-slim |
| RUN | ビルド時にコマンド実行 | RUN pip install -r requirements.txt |
| COPY | ファイルをイメージへコピー | COPY . /app |
| ADD | コピー+URL取得/tar展開 | ADD app.tar.gz /app |
| CMD | 起動時の既定コマンド | CMD ["python","app.py"] |
| ENTRYPOINT | 起動時の固定実行コマンド | ENTRYPOINT ["nginx"] |
| WORKDIR | 作業ディレクトリ指定 | WORKDIR /app |
| ENV | 環境変数の設定(実行時も残る) | ENV APP_ENV=production |
| ARG | ビルド時のみ有効な変数 | ARG APP_VERSION=1.0 |
| EXPOSE | 使用ポートの明示(公開はしない) | EXPOSE 8000 |
| USER | 実行ユーザー指定 | USER appuser |
| VOLUME | 永続化ディレクトリ指定 | VOLUME /data |
| HEALTHCHECK | 正常性チェック定義 | HEALTHCHECK CMD curl -f ... |
| LABEL | メタデータ付与 | LABEL version="1.0" |
FROMのタグ固定とslim・alpineの選択基準
FROM はDockerfileの先頭に書きます(syntaxディレクティブとコメントを除く)。唯一の例外が ARG で、これだけは FROM より前に置けます。ベースイメージのバージョンを変数化したいときに使う書き方です。
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.14
FROM python:${PYTHON_VERSION}-slim
ここで決めた土台がイメージサイズと脆弱性の総量をほぼ決めるため、Dockerfileで最も影響の大きい1行です。
タグは latest のままにせず、python:3.14-slim のように必ず固定します。latest は中身が黙って入れ替わり、昨日通ったビルドが今日壊れます。さらに厳密にやるなら、公式ドキュメントが推奨するとおりダイジェスト(python:3.14-slim@sha256:...)まで固定すると、タグが同じでも中身がすり替わらないことを保証できます。ただしダイジェスト固定は自動更新が止まるので、Renovate等の更新ボットとセットで運用するのが現実的です。
軽量化のために alpine 系を選ぶ判断は、無条件には勧めません。musl libcを使うため、PythonのC拡張やglibc前提のバイナリで実行時エラーやビルド失敗に遭遇します。数十MBの削減と引き換えに数時間を溶かすくらいなら、slim 系を選ぶほうが総コストは低くなります。
ARG・ENV・EXPOSEの誤解しやすい点
この3つは挙動を取り違えたまま使われがちです。ARG はビルド時にだけ存在する変数で、できあがったコンテナの中には残りません。ENV はイメージに焼き込まれ、実行中のコンテナからも参照できます。つまり秘密情報を ARG で渡しても安全ではない点に注意が必要です。値はビルド履歴(docker history)から読み取れてしまいます。
EXPOSE も誤解の多い命令です。これはポートを公開する命令ではなく、「このイメージはこのポートを使う」という宣言(ドキュメント)にすぎません。実際に外へ通すのは docker run -p 8000:8000 の側です。EXPOSE を書き忘れても -p があれば通信できますし、逆に EXPOSE だけ書いても外部からは届きません。
混同しやすい命令の違い
検索でも繰り返し調べられている2組を、比較で整理します。
RUN・CMD・ENTRYPOINTの違い
3つとも「コマンドを書く」点は同じですが、実行されるタイミングが決定的に違います。RUNはビルド時、CMDとENTRYPOINTは起動時です。
| 命令 | 実行タイミング | レイヤ | 上書き | 主な用途 |
|---|---|---|---|---|
| RUN | ビルド時 | 作る | — | インストール・構築 |
| CMD | 起動時 | 作らない | run の引数で可 | 既定コマンド・既定引数 |
| ENTRYPOINT | 起動時 | 作らない | –entrypoint が必要 | 固定の実行ファイル |
RUN はイメージを作る作業(パッケージのインストールなど)に使い、実行ごとに新しいレイヤを作ります。CMD は起動時の既定コマンドで、docker run イメージ名 別コマンド と書けば簡単に上書きされます(複数書いても有効なのは最後の1つだけ)。ENTRYPOINT はそのコンテナの主たる実行ファイルを固定する命令で、docker run に渡した引数はENTRYPOINTの後ろに追加されます。
実務での定番は両者の組み合わせです。ENTRYPOINTで実行ファイルを固定し、CMDでその既定引数を与えます。どちらも配列で書くexec形式(CMD ["python","app.py"])にしてください。シェル形式で書くとプロセスが /bin/sh -c の子として起動し、PID 1がシェルになるためSIGTERMがアプリに届かず、docker stop が10秒待って強制終了する挙動になります。公式ドキュメントも、CMDでENTRYPOINTの既定引数を与える場合は両方をexec形式にするよう明記しています。
COPYとADDの違い
ファイルを取り込む命令は2つありますが、原則COPYを使うのが公式の推奨です。
| 観点 | COPY | ADD |
|---|---|---|
| 基本動作 | 単純コピー | コピー+暗黙の展開/取得 |
| リモートURL | 不可 | 可(–checksum で検証可) |
| tar自動展開 | しない | する |
| Git リポジトリ | 不可 | 可 |
| 推奨 | 通常はこちら | 展開・取得が要るときだけ |
ADDの「暗黙の挙動」が事故のもとです。tarファイルを置きたいだけなのに勝手に展開される、といった予期しない結果を生みます。公式のベストプラクティスも、ADDはチェックサム検証付きのリモート取得やtar自動展開が必要なときに限り、それ以外はCOPYを使うよう案内しています。マルチステージビルドでステージ間のファイルを移すときも、COPY(COPY --from=)が正解です。
syntaxディレクティブとBuildKit(Dockerfileの”バージョン”)
「Dockerfileにバージョンはあるのか」という疑問への答えがここです。ファイルの1行目に置くパーサーディレクティブで、使用するDockerfile構文の実装(フロントエンド)を指定できます。
# syntax=docker/dockerfile:1
FROM python:3.14-slim
:1 と書いておくと、BuildKitがビルドのたびにその時点の安定版フロントエンドを取得して使います(2026年7月時点で :1 が指すのは1.24系。最新リリースは2026年6月公開の1.25.0)。この1行を書かない場合、使えるDockerfile構文はDocker Engineに同梱されたフロントエンドのバージョンに固定されます。Engineを更新せずに新しい構文(v1.10.0で入ったシークレットの環境変数マウントなど)を使えるようにする、というのがこのディレクティブの本質です。古いEngineではヒアドキュメントが構文エラーになるため、その回避策としても効きます。
BuildKit自体はDocker Engine 23.0以降で既定のビルダーなので、有効化の設定は不要です。以下の機能は、そのまま使えます。
BuildKit前提で書くDockerfile(キャッシュ・シークレット・ヒアドキュメント)
入門記事の多くは、BuildKitが既定になる前の書き方のまま止まっています。ここが差の出る領域です。実務で効く順に挙げます。
キャッシュマウントは、パッケージマネージャのキャッシュディレクトリをビルド間で永続化します。命令のキャッシュが無効化されても、ダウンロード済みのパッケージは残るため、依存を1つ足しただけで全件を再取得する事態を避けられます。
# syntax=docker/dockerfile:1
FROM python:3.14-slim
WORKDIR /app
COPY requirements.txt .
# pipのキャッシュをビルド間で使い回す
# sharing=locked で並行ビルド時のキャッシュ競合を防ぐ
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
pip install -r requirements.txt
キャッシュマウントを知ると「--mount=type=bind でソースを直接読めばCOPYは要らない」という主張も目に入りますが、これは用途が違います。bindマウントはビルド中だけ見える一時的なマウントで、最終イメージにファイルは残りません。テストの実行やビルド時だけ必要な設定ファイルの参照には向きますが、実行時に必要なアプリ本体は素直にCOPYしてください。
シークレットマウントは、プライベートリポジトリのトークンなどをイメージにも中間レイヤにも残さずビルド時だけ渡す仕組みです。前述のとおり ARG や ENV で秘密情報を渡すとイメージ内に残ってしまうため、この用途では必ずシークレットを使ってください。Dockerfile v1.10.0以降は環境変数としてのマウントにも対応しています。
# Dockerfile 側
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# ビルド側(ホストのファイルを渡す。イメージには残らない)
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .
ヒアドキュメントは、バックスラッシュでの行継続地獄から解放してくれます。複数行のシェルスクリプトをそのままRUNに書けるため、apt-getの定型(updateとinstallを同一RUNにまとめ、最後にaptのリストを削除する)が素直に表現できます。
# syntax=docker/dockerfile:1
FROM debian:12-slim
RUN <<EOF
set -eux
apt-get update
apt-get install -y --no-install-recommends curl ca-certificates
rm -rf /var/lib/apt/lists/*
EOF
先頭の set -eux は省略できません。ヒアドキュメントの中身は1つのシェルスクリプトとして実行され、終了コードは最後のコマンドのものだけが採用されます。set -e がないと、途中の apt-get install が失敗しても最後の rm が成功した時点でビルドが通り、壊れたイメージができあがります。
なお apt-get update と apt-get install を別々のRUNに分けるのも典型的な失敗です。updateの結果がキャッシュされたまま、installだけが古いパッケージ一覧を見にいく状態(キャッシュバスティング問題)が起きます。必ず1つのRUNにまとめてください。
マルチステージビルドでイメージを絞る
マルチステージビルドは、複数の FROM でステージを分け、最終ステージに必要なものだけを持ち込む手法です。FROM ... AS 名前 でステージに名前を付け、COPY --from=名前 で成果物だけを引き継ぎます。ビルドツールやソースコードが最終イメージに残らないため、サイズと攻撃面が同時に減ります。
# syntax=docker/dockerfile:1
# ① ビルドステージ
FROM node:24-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=locked npm ci
COPY . .
RUN npm run build
# ② 実行ステージ(成果物だけを軽量イメージへ)
FROM nginx:alpine
COPY --from=builder --link /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
ベースにNode.js 24を使っているのは、2026年7月時点のActive LTSだからです(Node.js 26はCurrent段階で、LTS入りは2026年10月の予定)。COPY --link はBuildKitの機能で、コピーで作られたレイヤを独立したものとして扱います。コピー先のベース(前段の命令やベースイメージ)が変わっても、このレイヤは再計算されずに再利用されます。ただしコピー元、つまり builder の出力そのものが変われば当然作り直されます。最終イメージにはNode.jsのランタイムも開発依存もソースも含まれず、Nginxと配信ファイルだけが残るため、レジストリへの転送とデプロイ時間が短縮されます。VS Codeの開発環境そのものをコンテナ化する構成はDev Container(devcontainer)のメリットとは|Dockerで開発環境を統一する仕組みと設定【2026年版】で扱っています。
ベストプラクティスとやりがちな失敗
動くDockerfileが書けたら、次は「速く・軽く・安全に」です。効果の大きい順に挙げます。
- キャッシュが効く順序で書く:変更頻度の低い命令(依存インストール)を上に、変わりやすいアプリ本体のコピーを下に置きます。最小例で
requirements.txtを先にコピーしているのはこのためです。 - .dockerignoreを必ず置く:Dockerfileと同じ階層に置き、
.git・node_modules・ローカルの.envを除外します。書き忘れるとCOPY . .が認証情報ごとイメージに取り込みます。ビルドコンテキストの転送も遅くなります。 - 非rootユーザーで動かす:既定はrootです。UIDを明示してユーザーを作り、
USERで切り替えます。 - タグを固定する:
latestは使わない。再現性の要です。 - 機密情報を書かない:ENVやARGへの直書きは
docker historyで露出します。シークレットマウントか外部のシークレット管理を使います。 - hadolintで静的検査する:ベストプラクティス違反をCIで機械的に弾けます。人のレビューに頼ると必ず抜けます。
# .dockerignore の最小例
.git
node_modules
.env
**/__pycache__
# 非rootユーザーを作って切り替える(Debian系のベースイメージの場合)
RUN useradd --create-home --uid 10001 appuser
USER appuser
ユーザー作成のコマンドはベースイメージ依存です。useradd はDebian系(python:3.14-slim など)のもので、Alpine系には存在しません。alpine 上では adduser -D -u 10001 appuser を使います。
逆に、レイヤ削減を目的にRUNを何でも1行に詰め込むのは行き過ぎです。BuildKitのキャッシュマウントがある今、レイヤ数そのものを削る旨みは以前ほど大きくありません。読みやすさとキャッシュの効きを優先し、まとめるべきなのは「同一RUNでないと壊れるもの(apt-getのupdateとinstall、一時ファイルの削除)」だけと考えてください。JavaのアプリをDockerfileでコンテナ化する具体例はDockerfileの作成とSpring Bootアプリケーションのコンテナ化が参考になります。
よくある質問(FAQ)
Dockerfileとは何ですか?
Dockerイメージを作るための命令を上から順に書いたテキストファイルです。コンテナそのものではなく、イメージを生成するための設計図にあたります。
Dockerfileの拡張子は何ですか?
拡張子はありません。ファイル名は Dockerfile のみです。Dockerfile.txt のように拡張子が付くと既定では認識されないため、Windowsのメモ帳で保存する際は注意してください。用途別に分ける場合は Dockerfile.prod のような接尾辞を付け、docker build -f で明示指定します。
Dockerfileはどこに置けばいいですか?
プロジェクトのルートが慣例です。重要なのはビルドコンテキスト(docker build の末尾に渡すパス)の中に、COPY したいファイルがすべて含まれていることです。コンテキストの外にあるファイルは取り込めません。
DockerfileとDocker Composeの違いは何ですか?
役割が違います。Dockerfileは1つのイメージのビルド手順を定義し、Docker Composeは複数コンテナの構成と起動を compose.yaml でまとめて管理します。実務では「DockerfileでイメージをビルドしつつComposeで複数サービスを連携起動する」形で併用します。
RUNとCMDの違いは何ですか?
実行タイミングです。RUN はビルド時に実行されてレイヤを作り(インストールなどに使う)、CMD はコンテナ起動時の既定コマンドで、docker run の引数で上書きできます。
Dockerfileが実行されるタイミングはいつですか?
docker build の実行時に、各命令が上から順に処理されてイメージが作られます。ただし CMD と ENTRYPOINT はビルド時には実行されず、docker run でコンテナを起動したときに初めて動作します。ARG はビルド時にだけ評価されて消え、ENV はビルド時に評価された値がイメージに焼き込まれて実行時も残ります。あわせて、Chocolateyについても解説しています。