Dockerfile 完全入門|FROM・RUN・COPY・CMD・ENTRYPOINT など全命令を実例で解説
Dockerfile は、Dockerイメージを自動で再現可能に作るための設計図(レシピ)です。「どのベースOSから始め、何をインストールし、どのファイルを置き、起動時に何を実行するか」を1つのテキストファイルに記述します。
第2章でイメージを docker pull して使う方法を学びました。この章では自分だけのイメージを作る技術、Dockerfile の書き方をゼロから習得します。
目次
- なぜ Dockerfile が必要か
- Dockerfile の基本構造
- FROM — ベースイメージを指定する
- RUN — ビルド時にコマンドを実行する
- COPY / ADD — ファイルをイメージに取り込む
- CMD / ENTRYPOINT — 起動コマンドを定義する
- ENV / WORKDIR / EXPOSE — 環境設定系命令
- ARG / LABEL / USER — ビルド変数・メタデータ・ユーザー
- 実践:シンプルな Web アプリの Dockerfile
- シミュレータで確認する
- まとめ
1. なぜ Dockerfile が必要か
Dockerfile がない場合、イメージを作るには次のような手作業が必要です。
# ① ubuntu コンテナに入って手動でセットアップ
docker run -it ubuntu bash
apt-get update && apt-get install -y python3
# ...アプリファイルを置いて...
# ② 現在の状態をイメージ化(スナップショット)
docker commit <container_id> my-app:v1
これには致命的な問題があります。
| 手作業の問題 | Dockerfile で解決 |
|---|---|
| 手順が属人化、再現不可能 | テキストで手順が残る → 誰でも同じイメージを再現できる |
| 変更履歴が追えない | Git で差分管理できる |
| CI/CD に組み込めない | docker build 1コマンドで自動化できる |
| 中間状態が不明 | 各命令がレイヤーとして記録される |
docker commit は緊急時のデバッグには使えますが、本番イメージの作成には使いません。常に Dockerfile からビルドするのがベストプラクティスです。
2. Dockerfile の基本構造
Dockerfile は拡張子なしの Dockerfile というファイル名が慣例です(別名も使えますが追加オプションが必要)。
# コメントはシャープで書く
# 命令は大文字(慣例)、引数は小文字でも可
FROM ubuntu:22.04 # ← ベースイメージ(必須・最初に書く)
RUN apt-get update \ # ← ビルド時のコマンド実行
&& apt-get install -y python3
WORKDIR /app # ← 作業ディレクトリ
COPY . . # ← ホストからファイルをコピー
CMD ["python3", "app.py"] # ← コンテナ起動時のコマンド
各行は 命令(instruction) と引数で構成されます。命令は大文字が慣例ですが小文字でも動きます。命令の順番はビルドキャッシュに直結するため、後から学ぶレイヤーキャッシュの観点から重要です(3-2で詳しく解説)。
レイヤー 5(最上層)
レイヤー 4
レイヤー 3
レイヤー 2
レイヤー 1(ベース)
3. FROM — ベースイメージを指定する
すべての Dockerfile は FROM で始まります。どのイメージを土台にするかを指定します。
# 基本形
FROM ubuntu:22.04
# タグを省略すると :latest が使われる(非推奨:再現性が下がる)
FROM ubuntu
# ダイジェスト指定(完全に固定したい場合)
FROM ubuntu@sha256:a6d2b38300ce0316684...
# 何もない状態から作る(超軽量バイナリ向け)
FROM scratch
ベースイメージの選び方
| ベースイメージ | サイズ目安 | 特徴 | 向いているもの |
|---|---|---|---|
ubuntu:22.04 |
~78MB | apt が使える・情報が豊富 | 開発環境・汎用 |
debian:bookworm-slim |
~74MB | Ubuntu より若干小さい | 汎用・本番 |
alpine:3.19 |
~7MB | 超軽量・musl libc | 軽量化優先(互換性注意) |
python:3.12-slim |
~130MB | Python 込み・Debian ベース | Python アプリ |
node:20-alpine |
~55MB | Node.js + Alpine | Node.js アプリ(軽量) |
scratch |
0B | 完全に空 | Go/Rust などの静的バイナリ |
FROM python:3.12 ではなく FROM python:3.12-slim のようにバリアント付きのタグを使いましょう。:latest は時間が経つと内容が変わり、ビルドの再現性が失われます。
4. RUN — ビルド時にコマンドを実行する
RUN はイメージをビルドする際に実行されるコマンドです。パッケージのインストール・設定ファイルの生成・ファイルのダウンロードなど、環境構築の手順をそのまま書きます。
2つの書き方(Shell 形式 と Exec 形式)
# Shell 形式 — /bin/sh -c で実行される(&&、パイプが使える)
RUN apt-get update && apt-get install -y curl
# Exec 形式 — JSON 配列で直接 exec される(シェルを介さない)
RUN ["apt-get", "install", "-y", "curl"]
RUN は 1命令ごとに新しいレイヤーを作ります。このため、関連するコマンドは && で1行にまとめるのが一般的なベストプラクティスです。
apt-get の正しいパターン
# NG:別々の RUN に分けるとキャッシュが古くなる(apt-get upgrade が効かない等)
RUN apt-get update
RUN apt-get install -y curl git
# OK:1つの RUN にまとめ、最後にキャッシュを削除してイメージを軽量化
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
apt-get は依存パッケージに加えて「推奨パッケージ」も自動インストールします。
--no-install-recommends をつけると推奨パッケージをスキップし、イメージサイズを削減できます。
レイヤーキャッシュの仕組みと最適化については3-2で詳しく解説します。
5. COPY / ADD — ファイルをイメージに取り込む
COPY — シンプルなファイルコピー(推奨)
ホスト側のファイルやディレクトリをイメージの中にコピーします。
# 基本形
COPY <ホスト側パス> <コンテナ内パス>
# 例:ホストのカレントディレクトリを /app にコピー
COPY . /app
# 例:特定のファイルだけコピー
COPY requirements.txt /app/requirements.txt
# 例:ファイルを別名でコピー
COPY config/production.env /app/.env
# --chown でコピー先の所有者を指定(root 以外で動かすときに重要)
COPY --chown=node:node . /app
ADD — 追加機能を持つコピー(用途限定)
ADD は COPY の機能に加えて2つの特殊な動作をします。
# ① tar アーカイブを自動展開(.tar, .tar.gz, .tar.bz2 など)
ADD app.tar.gz /app/
# → /app/ に展開される
# ② URL からダウンロード(非推奨:キャッシュが効かない)
ADD https://example.com/file.txt /tmp/file.txt
| COPY | ADD | |
|---|---|---|
| ローカルファイルのコピー | ✅ | ✅ |
| tar の自動展開 | ❌ | ✅ |
| URL からのダウンロード | ❌ | ✅(非推奨) |
| 動作が予測しやすい | ✅ | ⚠️ 意図しない展開に注意 |
| 推奨度 | ⭐⭐⭐ 基本はこれ | ⭐ tar 展開時のみ使う |
ADD https://... はビルドキャッシュが正しく機能しません。URL からのダウンロードが必要なら RUN curl -fsSL https://... -o /tmp/file を使いましょう。
.dockerignore を使うと COPY . . の対象から不要なファイルを除外できます。詳しくは3-3で解説します。
6. CMD / ENTRYPOINT — 起動コマンドを定義する
これが Dockerfile の中でもっとも混乱しやすい部分です。2つの命令はどちらも「コンテナが起動するときに何を実行するか」を定義しますが、役割が異なります。
CMD — デフォルトコマンド(上書き可能)
# Exec 形式(推奨):JSON 配列で書く
CMD ["python3", "app.py"]
# Shell 形式:/bin/sh -c で実行される
CMD python3 app.py
# docker run の引数で上書きできる
docker run my-image python3 other_script.py
# ↑ CMD を上書き
ENTRYPOINT — 固定コマンド(上書き困難)
# Exec 形式(推奨)
ENTRYPOINT ["python3"]
# docker run の引数は ENTRYPOINT に追記される
docker run my-image app.py
# 実行されるのは: python3 app.py
# 上書きするには --entrypoint が必要
docker run --entrypoint bash my-image
CMD と ENTRYPOINT の組み合わせ
もっとも柔軟なパターンは2つを組み合わせることです。
ENTRYPOINT ["python3"] # 固定:必ず python3 を使う
CMD ["app.py"] # デフォルト引数:変更可能
# ① デフォルト動作
docker run my-image
# → python3 app.py
# ② CMD だけ上書き
docker run my-image other.py
# → python3 other.py
| 組み合わせ | ENTRYPOINT なし | ENTRYPOINT あり |
|---|---|---|
| CMD なし | エラー(何も実行されない) | ENTRYPOINT が単独で実行 |
| CMD あり | CMD がコマンドとして実行 | ENTRYPOINT + CMD が引数として実行 |
Shell 形式(
CMD python3 app.py)では /bin/sh -c がPID 1 になり、docker stop が送る SIGTERM がアプリに届きません。Exec 形式(CMD ["python3", "app.py"])ではアプリが直接 PID 1 になり、正しくシグナルを受け取ってグレースフルシャットダウンできます。
7. ENV / WORKDIR / EXPOSE — 環境設定系命令
ENV — 環境変数を設定する
# 基本形(推奨:複数まとめて書ける)
ENV APP_ENV=production \
PORT=8080 \
LOG_LEVEL=info
# 古い書き方(1行に1つ)
ENV APP_ENV production
# 後の命令で参照できる
WORKDIR /app
ENV APP_HOME=/app
COPY . $APP_HOME
ENV で設定した値はコンテナ起動後も有効です(最終イメージに残ります)。docker run -e PORT=3000 で起動時に上書きすることもできます。
パスワードや API キーを
ENV SECRET_KEY=xxx と書くとイメージのレイヤーに永続的に残り、docker history で見えてしまいます。シークレットは Docker Secrets や実行時の -e フラグ、または環境変数ファイルで渡しましょう(8-7で詳しく解説)。
WORKDIR — 作業ディレクトリを設定する
WORKDIR /app
# ・ディレクトリが存在しなければ自動作成
# ・以降の RUN / COPY / ADD / CMD / ENTRYPOINT のカレントディレクトリになる
# ・複数回書けば変更できる
WORKDIR /app
WORKDIR src # → /app/src になる(相対パスが使える)
RUN mkdir -p /app && cd /app はアンチパターンです。cd の効果は次の RUN には引き継がれません。必ず WORKDIR を使いましょう。
EXPOSE — ポートのドキュメント
# アプリが使うポートを宣言(ドキュメント目的)
EXPOSE 8080
EXPOSE 443/tcp
EXPOSE 5353/udp
# ← これだけではポートは実際には公開されない
# 実際に公開するには docker run -p 8080:8080 が必要
EXPOSE は実際にポートを開放する命令ではありません。「このイメージは 8080 番を使う」というドキュメントです。docker run -P(大文字のP)でランダムポートに全 EXPOSE ポートをマッピングするときに参照されます。
8. ARG / LABEL / USER — ビルド変数・メタデータ・ユーザー
ARG — ビルド時だけ使える変数
# デフォルト値あり
ARG NODE_VERSION=20
# FROM でも使える(FROM の前に書く必要あり)
ARG BASE_TAG=22.04
FROM ubuntu:${BASE_TAG}
# ビルド時に --build-arg で上書きできる
docker build --build-arg NODE_VERSION=18 .
# ARG の値はビルド後のコンテナには残らない(ENV と違う)
ARG BUILD_DATE
RUN echo "Built at: $BUILD_DATE" # ビルド時のみ使える
| ARG | ENV | |
|---|---|---|
| 有効な範囲 | ビルド時のみ | ビルド時+コンテナ起動後 |
| 最終イメージに残る | ❌ | ✅ |
| docker run -e で上書き | ❌ | ✅ |
| docker build –build-arg で変更 | ✅ | ❌ |
| 使い所 | Nodeバージョン・ビルド日時など | アプリ設定・PORT など |
LABEL — イメージにメタデータを付ける
LABEL maintainer="you@example.com" \
version="1.0.0" \
description="My Web Application" \
org.opencontainers.image.source="https://github.com/you/repo"
# 確認方法
docker inspect --format='{{json .Config.Labels}}' my-image
ラベルは docker inspect や docker images --filter label=key=value での検索に使えます。CI/CD でビルド日時や Git commit SHA を埋め込むのが一般的なパターンです。
USER — 実行ユーザーを切り替える
デフォルトでは root で実行されます。本番環境では root での実行はセキュリティリスクです。
# ユーザーとグループをまず作成
RUN groupadd --gid 1001 appgroup \
&& useradd --uid 1001 --gid appgroup --shell /bin/sh --create-home appuser
# アプリファイルの所有者を変更
COPY --chown=appuser:appgroup . /app
# USER 以降の RUN / CMD / ENTRYPOINT はこのユーザーで実行される
USER appuser
CMD ["python3", "app.py"]
USER で非 root ユーザーを設定することは、コンテナセキュリティのベストプラクティスです。root で動かすと、コンテナエスケープが発生した際にホストへの影響が大きくなります。root が必要な作業(apt-get 等)は USER root で一時的に戻し、終わったら非 root ユーザーに戻す構成が理想的です。詳しくは第8章で解説します。
9. 実践:シンプルな Web アプリの Dockerfile
ここまでの命令を組み合わせた実践的な Dockerfile を2パターン見てみましょう。
パターン A:Python(Flask)アプリ
# ── Python Flask アプリの Dockerfile ──
FROM python:3.12-slim
# メタデータ
LABEL maintainer="you@example.com" \
version="1.0.0"
# 作業ディレクトリ
WORKDIR /app
# 依存ファイルを先にコピー(キャッシュ効率化のため)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリ本体をコピー
COPY . .
# 環境変数
ENV FLASK_ENV=production \
PORT=8080
# ポートを宣言
EXPOSE 8080
# 非 root ユーザーで実行
RUN useradd --no-create-home --shell /bin/false appuser
USER appuser
# 起動コマンド(Exec 形式)
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=8080"]
パターン B:Node.js アプリ
# ── Node.js アプリの Dockerfile ──
FROM node:20-alpine
WORKDIR /app
# package.json のみ先にコピー(npm install のキャッシュを活かす)
COPY package*.json ./
RUN npm ci --only=production
# アプリ本体
COPY . .
ENV NODE_ENV=production \
PORT=3000
EXPOSE 3000
# Alpine には node ユーザーが最初から存在する
USER node
CMD ["node", "server.js"]
COPY . . の前に COPY requirements.txt . → RUN pip install を書くと、アプリのソースコードが変わっても依存ライブラリが変わらない限りインストールのレイヤーキャッシュが再利用されます。ビルドが大幅に速くなります。これがレイヤーキャッシュを活かす典型的なパターンです(3-2で詳しく解説)。
ビルドと実行
# Dockerfile があるディレクトリで実行
docker build -t my-flask-app:1.0.0 .
# ↑ タグ名:バージョン ↑ ビルドコンテキスト(カレントディレクトリ)
# ビルドしたイメージでコンテナを起動
docker run -d -p 8080:8080 --name flask my-flask-app:1.0.0
# イメージのレイヤー一覧を確認
docker history my-flask-app:1.0.0
10. シミュレータで確認する
下のシミュレータで、Python Flask アプリの Dockerfile が どの順番でビルドされ、レイヤーが積み上がるかを1命令ずつ体験できます。「▶ 次へ」でステップ実行、「⏩ 全実行」で一気に再生できます。
ツールバーの「2回目のビルド(キャッシュあり)」にチェックを入れてリセットし、もう一度全実行してみましょう。
COPY requirements.txt → RUN pip install が CACHED(スキップ)になり、ソースコードを変えるだけで重いインストール処理を省略できる理由がわかります。
11. まとめ
| 命令 | タイミング | 主な用途 | ポイント |
|---|---|---|---|
| FROM | ビルド開始 | ベースイメージ指定 | タグを必ず固定する |
| RUN | ビルド時 | パッケージインストール等 | && でまとめてレイヤーを減らす |
| COPY | ビルド時 | ファイルのコピー | 基本はこれを使う |
| ADD | ビルド時 | tar 展開が必要な場合 | それ以外は COPY を使う |
| CMD | コンテナ起動時 | デフォルトコマンド | docker run 引数で上書き可 |
| ENTRYPOINT | コンテナ起動時 | 固定コマンド | CMD と組み合わせて使う |
| ENV | ビルド時+起動後 | 環境変数 | シークレットは書かない |
| WORKDIR | ビルド時 | 作業ディレクトリ | mkdir の代わりに使う |
| EXPOSE | — | ポートのドキュメント | 実際には開放されない |
| ARG | ビルド時のみ | ビルド変数 | 最終イメージに残らない |
| LABEL | ビルド時 | メタデータ | バージョン・管理者情報など |
| USER | ビルド時+起動後 | 実行ユーザー | 非 root での実行が推奨 |
3-2「レイヤーキャッシュの仕組みとビルド最適化」では、
RUN の順番がビルド速度に与える影響を詳しく学びます。Dockerfile の書き方が変わると docker build の速さが劇的に変わります。
参考リンク
- Dockerfile リファレンス(Docker 公式) — 全命令の正式な仕様と詳細オプション
- Dockerfile ベストプラクティス(Docker 公式) — セキュリティ・軽量化・キャッシュ最適化のガイド
- Docker Hub — 公式ベースイメージの一覧(python・node・ubuntu 等)



コメント