こんにちは、かつコーチです。
前回の記事でDockerfileの全体像を見ましたが、実際に手を動かすとなると「RUN と CMD は何が違うの?」「COPY と ADD はどちらを使えばいいの?」という疑問が必ず出てきます。
この記事では、Dockerfileの中でも特によく使う4つの命令、FROM・COPY・RUN・CMD に絞って、それぞれの役割と使い方を深掘りします。
Dockerfileをレシピに例えるなら、この4命令は「材料を選ぶ」「材料を運ぶ」「調理する」「提供する」という料理の基本工程に対応していると考えると整理しやすいです。
Dockerfileの主要命令とは?
FROM・COPY・RUN・CMDの役割
4つの命令をレシピの工程に当てはめると、次のように対応します。
| 命令 | 料理での例え | Dockerfileでの役割 |
|---|---|---|
| FROM | どの厨房を使うか決める | ベースイメージを指定する |
| COPY | 材料を厨房に運び込む | ホストのファイルをイメージにコピーする |
| RUN | 材料を調理する | ビルド時にコマンドを実行する |
| CMD | 完成した料理を出す | コンテナ起動時の既定コマンドを指定する |
FROM と COPY・RUN は「イメージを作るとき」に実行され、CMD は「コンテナを起動するとき」に実行される、というタイミングの違いを意識すると理解が進みます。
なぜこの4つを最初に覚えるべきか
Dockerfileの命令は他にも ENV・EXPOSE・ARG などたくさんありますが、FROM・COPY・RUN・CMD の4つがあれば、たいていのシンプルなアプリケーションはコンテナ化できます。
まずはこの4つを使いこなし、必要になったタイミングで他の命令を覚えていく方が、挫折せずに学習を進められます。
各命令の使い方
FROM:ベースイメージを選ぶ
FROM は、Dockerfileの1行目に書く命令で、どのイメージを土台にするかを指定します。
FROM python:3.12-slim
タグを省略すると latest(最新版)が使われますが、バージョンが意図せず変わってしまうリスクがあるため、3.12-slim のように具体的なバージョンを指定するのが基本です。
-slim や -alpine が付いたイメージは、不要なパッケージを削ったサイズの小さいベースイメージです。
COPY:ファイルをコンテナに配置する
COPY は、ホスト側のファイルをイメージ内にコピーする命令です。
COPY requirements.txt /app/requirements.txt
COPY ./src /app/src
第1引数がホスト側のコピー元、第2引数がイメージ内のコピー先です。
似た命令に ADD がありますが、ADD はURLの取得やアーカイブの自動展開といった特殊な機能を持ちます。
単純にファイルをコピーするだけなら、動作がわかりやすい COPY を使うのが基本方針です。
RUN:ビルド時にコマンドを実行する
RUN は、イメージをビルドする過程でコマンドを実行する命令です。
RUN pip install --no-cache-dir -r requirements.txt
パッケージのインストールやディレクトリの作成など、「イメージの中身を作り込む」処理はすべて RUN の役目です。
RUN を実行するたびに新しいレイヤーが作られるので、関連するコマンドは && でつなげてレイヤー数を抑える書き方もよく使われます。
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
CMD:コンテナ起動時の既定コマンドを指定する
CMD は、RUN とは違い、コンテナが起動したときに実行されるコマンドを指定します。
CMD ["python", "app.py"]
配列形式(exec形式と呼びます)で書くのが基本で、シェルを経由せずに直接プロセスとして実行されるため、シグナルの伝達などの挙動が安定します。
Dockerfile内に CMD は1つしか有効になりません。
複数書いた場合は、最後の1つだけが有効になる点に注意してください。
よくあるつまずきポイント・エラー対処
CMDで指定したコマンドが実行時に上書きされて混乱する
実際に私が経験したつまずきが、CMD の役割の勘違いです。
❌ Before:CMDを「絶対に実行されるコマンド」だと思い込む
FROM node:20-slim
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "index.js"]
このイメージに対して、デバッグ目的で以下のように実行したところ、index.js が動かずシェルが起動して「CMDが無視された、壊れているのでは」と焦りました。
docker run -it my-app:1.0 /bin/bash
✅ After:CMDは「引数を渡さなかったときの既定値」だと理解する
docker run の末尾にコマンドを指定すると、Dockerfileの CMD はその時だけ上書きされる、という仕様を知って解決しました。
# CMDどおりに起動する(引数なし)
docker run my-app:1.0
# CMDを一時的に上書きしてシェルに入る
docker run -it my-app:1.0 /bin/bash
デバッグ時にコンテナ内を確認したい場合は、意図的に CMD を上書きしてシェルを起動する、という使い方だと理解しておくと迷わなくなります。
応用・一歩先の使い方
CMDとENTRYPOINTの違い
CMD と似た命令に ENTRYPOINT があります。
ENTRYPOINT は「必ず実行される固定のコマンド」、CMD は「ENTRYPOINTに渡すデフォルトの引数」という役割分担で組み合わせることもできます。
ENTRYPOINT ["python", "app.py"]
CMD ["--mode=production"]
こうしておくと、docker run my-app で python app.py --mode=production が実行され、docker run my-app --mode=debug とすれば引数部分だけを差し替えられます。
CLIツールのようにコンテナを「コマンドのように」使いたい場合に有効な組み合わせです。
まとめ
この記事のポイント
- FROMはベースイメージ、COPYはファイル配置、RUNはビルド時実行、CMDは起動時の既定コマンド
- FROMのタグは
latestを避け、具体的なバージョンを指定する - CMDは
docker runの末尾で指定したコマンドに上書きされる - ENTRYPOINTと組み合わせると、固定コマンド+可変引数という設計ができる
次に読むべき記事
- Dockerfileの書き方基本
- マルチステージビルドでイメージを軽量化する
タグ: Docker, 中級者向け, Dockerfile