初心者向けDockerfileの書き方入門|主要命令と実務の基本構文

Dockerを使ってWebアプリケーションやデータベースの開発環境を整える際、中心となるのがDockerfileの作成です。

Dockerfileを作成しておけば、OSやミドルウェアのセットアップ手順をコードとして管理でき、誰のPCでも全く同じコンテナ環境をコマンド1つで再現できるようになります。チーム開発における「自分のPCでは動くけれど、他の人のPCでは動かない」といった環境差異のトラブルも防止できます。

しかし、初めてDockerfileを書こうとすると「FROMやRUN、CMDなど命令がたくさんあって何から書けばいいかわからない」「RUNとCMDはどう違うのか」「イメージサイズが大きくなってしまう」と疑問に感じる方も多いのではないでしょうか。

この記事では、Docker初心者の方に向けて、Dockerfileの役割と基本構文、実務で頻出する主要命令の意味と使い分け、最小構成でのビルド・実行手順、そしてイメージを軽量化する実践テクニックまでわかりやすく解説します。

この記事で行うこと

  • Dockerfileの役割と基本構文の理解
  • 最小構成のDockerfile作成からビルド・起動までの実践
  • 実務で必ず使う主要命令(FROM、WORKDIR、COPY、RUN、CMD、EXPOSE、ENV)の把握
  • 混同しやすいRUN、CMD、ENTRYPOINTの違いと使い分けの理解
  • イメージサイズを抑えてビルドを高速化する3大テクニックの習得

前提条件・対象読者

  • 対象読者: Dockerの基本用語(イメージやコンテナ)を聞いたことがあり、自分でDockerfileを書いて開発環境を作りたい初学者の方
  • 前提環境: Docker Desktop または WSL2(Ubuntu等)上のDocker Engineがインストールされている環境
  • 検証環境: Docker Engine v26以降 / Docker Compose v2(Windows 11 / WSL2環境)

【結論】最小構成のDockerfileとビルド・実行手順

Dockerfileとは、Dockerコンテナの元となる「Dockerイメージ」を自動生成するためのテキスト形式の手順書(設計図)です。

まずは全体像をつかむために、静的Webページを配信するシンプルなNginxサーバーを構築する最小構成のDockerfileを見てみましょう。

1. 最小構成のDockerfileの例

任意の空フォルダを作成し、その中に以下の2つのファイルを用意します。

まず、配信するHTMLファイルを作成します。

ファイル名: index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Hello Docker</title>
</head>
<body>
    <h1>Dockerfileから作成したWebサーバーです</h1>
</body>
</html>

次に、同じディレクトリに「Dockerfile」(拡張子なし、頭文字は大文字)を作成します。

ファイル名: Dockerfile

# ベースイメージに軽量なNginx(Alpine Linux版)を指定
FROM nginx:alpine

# ローカルのHTMLファイルをコンテナ内の公開ディレクトリへコピー
COPY index.html /usr/share/nginx/html/index.html

# コンテナが80番ポートで待ち受けることを明示
EXPOSE 80

# コンテナ起動時にNginxをフォアグラウンドで実行
CMD ["nginx", "-g", "daemon off;"]

2. イメージのビルドとコンテナの起動手順

ファイルを作成したディレクトリでターミナル(PowerShellやWSL bashなど)を開き、以下のコマンドを順番に実行します。

手順1:Dockerイメージをビルドする

docker build -t my-web-app .

末尾の「.(ドット)」は現在のディレクトリを指します。このコマンドにより、Dockerfileに書かれた手順に沿って「my-web-app」という名前のDockerイメージが作成されます。

手順2:ビルドしたイメージからコンテナを起動する

docker run -d -p 8080:80 --name my-web-container my-web-app

オプションの意味は以下のとおりです。

  • -d: バックグラウンド(デタッチモード)で起動する
  • -p 8080:80: ホストPCのポート8080を、コンテナのポート80に転送する
  • –name my-web-container: コンテナに識別しやすい名前を付ける

手順3:ブラウザで動作確認する ブラウザを起動し、以下のアドレスにアクセスします。

http://localhost:8080

画面に「Dockerfileから作成したWebサーバーです」と表示されれば、最小構成でのイメージ構築と起動は成功です。

確認が終わったら、以下のコマンドでコンテナを停止・削除できます。

docker stop my-web-container
docker rm my-web-container

必ず覚えるべきDockerfileの主要命令一覧

Dockerfileには多くの命令が用意されていますが、実務で日常的に使用する基本命令は以下の7つです。

命令役割記述例
FROMベースとなる既存イメージを指定する(必須)FROM node:20-alpine
WORKDIRコンテナ内の作業ディレクトリを設定するWORKDIR /app
COPYホストPCのファイルやフォルダをコンテナ内に配置するCOPY . /app
RUNイメージビルド時にコマンドを実行し、環境をセットアップするRUN npm install
CMDコンテナ起動時にデフォルトで実行するコマンドを指定するCMD [“node”, “index.js”]
EXPOSEコンテナが外部に公開するポート番号を明示するEXPOSE 3000
ENVコンテナ内の環境変数を設定するENV NODE_ENV=production

それぞれの命令について、役割と実務での記述ポイントを詳しく解説します。

FROM:ベースイメージの指定

すべてのDockerfileは、原則としてFROM命令から始まります(ARG命令を除きます)。ゼロからOSを作るのではなく、公式が配布しているLinuxディストリビューション(UbuntuやAlpineなど)や、言語ランタイム(Python、Node.js、Goなど)が導入済みのイメージを土台として選択します。

# Python 3.12の軽量版(slim)をベースにする例
FROM python:3.12-slim

ポイント: イメージ名の後ろにはコロン(:)に続けてタグ(バージョン)を明記します。「python:latest」のようにlatestを指定すると、将来的にベースイメージが自動更新された際に予期せぬ不具合が発生する原因となるため、実務では「python:3.12-slim」のようにバージョンを明示するのが原則です。

WORKDIR:作業ディレクトリの指定

コンテナ内での基準となるカレントディレクトリを指定します。以降のCOPY、RUN、CMDなどの命令は、すべてこのWORKDIRで指定したディレクトリを基準に実行されます。

WORKDIR /app

ポイント: 指定したディレクトリが存在しない場合は、自動的に作成されます。Linuxの「cd /app」をRUNで実行する代わりにWORKDIRを使用します。RUN cdは後続の行に引き継がれないため、作業ディレクトリの移動には必ずWORKDIRを使用してください。また、パスは相対パスではなく「/app」のように絶対パスで指定することが推奨されます。

COPY:ファイルの配置

ホストPC上にあるファイルやディレクトリを、コンテナイメージ内の指定パスへコピーします。

# ホストのカレントディレクトリの全ファイルを、コンテナの作業ディレクトリにコピー
COPY . .

# 特定のファイルだけをコピー
COPY package.json package-lock.json ./

ポイント: 似た命令に「ADD」があります。ADDはURLからのダウンロード機能やtarアーカイブの自動解凍機能を備えていますが、意図しない挙動を防ぐため、単純なローカルファイルの配置には「COPY」を使用することがDocker公式のベストプラクティスとされています。

RUN:パッケージやライブラリのインストール

イメージのビルド中にコンテナ内でコマンドを実行し、パッケージのインストールやファイルの生成などを行う命令です。RUNで実行した結果は新しいイメージレイヤーとしてイメージ内に永続化されます。

# Ubuntu系でパッケージを更新・インストールする例
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

ポイント: RUN命令はイメージ作成時に一度だけ実行され、完成したイメージの中に結果が固定されます。コンテナを起動するたびに実行されるわけではありません。

CMD:コンテナ起動時のデフォルトコマンド

ビルドされたイメージを元に「docker run」でコンテナが立ち上がった瞬間に、デフォルトで実行されるプロセスを指定します。

# Node.jsアプリケーションを起動する例
CMD ["node", "src/server.js"]

ポイント: Dockerfile内に記述できるCMD命令は原則として最後の1つだけです。複数記述した場合は、最後のCMDのみが有効になります。また、CMDで指定したコマンドは、コンテナ起動時(docker run)に引数を渡すことで簡単に上書きできます。

EXPOSE:公開ポートの宣言

コンテナが通信を待ち受けるネットワークポートを明示します。

EXPOSE 8080

ポイント: EXPOSE命令は、あくまで「このコンテナはこのポートで通信を受け付ける想定である」というメタデータ(ドキュメント)としての役割を持ちます。実際にホストPCからコンテナにアクセスするためには、コンテナ起動時に「-p 8080:8080」のようにポートフォワーディングを指定する必要があります。

ENV:環境変数の定義

コンテナ内で利用可能な環境変数をキーと値のペアで設定します。ビルド時だけでなく、コンテナが起動して動作している最中にも環境変数として参照できます。

ENV PORT=8080
ENV APP_ENV=production

ポイント: アプリケーションの実行モードやパスの設定、言語設定(LANG=ja_JP.UTF-8)などに利用します。ただし、APIキーやデータベースのパスワードなどの機密情報は、イメージ内に焼き込まれて漏洩するリスクがあるため、ENV命令には直接書かず、コンテナ起動時の「-e」オプションやDocker Composeの環境変数ファイル(.env)から注入するのが鉄則です。

RUN と CMD と ENTRYPOINT の違いと使い分け

Dockerfileを書き始めた初学者が最もつまずきやすいのが、「RUN」「CMD」「ENTRYPOINT」の3つの命令の違いです。すべてコマンドを実行する命令に見えますが、実行されるタイミングと目的が全く異なります。

3つの命令の比較表

命令実行タイミング結果の保存先起動時の上書き主な用途
RUNイメージビルド時新しいレイヤーに保存不可パッケージインストール、環境構築
CMDコンテナ起動時保存されない簡単に上書き可能アプリの起動、デフォルト引数の設定
ENTRYPOINTコンテナ起動時保存されない上書きには専用オプションが必要コンテナの主プロセスの固定実行

RUNとCMDの違い

  • RUN: イメージを作成する段階で実行されます。ライブラリの追加や環境構築など、ビルド成果物をイメージ内に固定化するために使います。
  • CMD: コンテナが起動した瞬間に実行されます。Webサーバーの起動やアプリケーションの開始など、コンテナが生きて動作し続けるためのメインプロセスを動かすために使います。

CMDとENTRYPOINTの違いと使い分け

CMDとENTRYPOINTはどちらもコンテナ起動時に動きますが、「docker run時の引数で上書きできるかどうか」が決定的に異なります。

CMDの場合:

FROM alpine
CMD ["echo", "Hello World"]

起動コマンド:

docker run my-image
# 出力: Hello World

docker run my-image echo Good Morning
# 出力: Good Morning (CMDが完全に上書きされる)

ENTRYPOINTの場合:

FROM alpine
ENTRYPOINT ["echo"]
CMD ["World"]

起動コマンド:

docker run my-image
# 出力: World (ENTRYPOINTのechoに、CMDのWorldが引数として渡される)

docker run my-image Tokyo
# 出力: Tokyo (CMD部分だけが上書きされ、echo Tokyoが実行される)

実務での使い分け指針:

  • Webサーバーやバッチ処理など、一般的なアプリケーションコンテナを作る場合は「CMD」を使用するのが標準的で扱いやすい設計です。
  • コンテナ自体を1つのCLIツールやコマンド(gitやcurlなど)のように振る舞わせたい場合は「ENTRYPOINT」を使用し、デフォルトの引数を「CMD」に定義します。

Exec形式とShell形式の推奨記法

RUN、CMD、ENTRYPOINTには、2通りの記述形式(Exec形式とShell形式)があります。

Exec形式(推奨):

CMD ["node", "app.js"]

JSON配列構文で記述する形式です。シェル(/bin/sh)を介さず、指定した実行可能ファイルを直接プロセスID(PID)1として起動します。これにより、OSの停止シグナル(SIGTERM)がアプリケーションに直接届き、コンテナが安全に終了(グレースフルシャットダウン)できます。実務では基本的にExec形式を使用します。

Shell形式:

CMD node app.js

文字列として直接記述する形式です。コンテナ内部で「/bin/sh -c node app.js」のようにシェルを介して実行されます。環境変数の展開が自動で行われる利点がありますが、シェルがPID 1となり、アプリケーションプロセスへの終了シグナルが遮断される場合があるため注意が必要です。

Dockerfileからイメージをビルドするコマンド(docker build -t)

作成したDockerfileからイメージを生成するには、「docker build」コマンドを使用します。

基本構文

docker build -t [イメージ名]:[タグ] [ビルドコンテキスト]

実例:

docker build -t myapp:1.0 .

末尾の「.(ドット)」が持つ重要な意味

コマンド末尾の「.」は、ビルド対象のディレクトリ(ビルドコンテキスト)を指定しています。 ビルドを実行すると、Dockerクライアントはこのディレクトリ配下にあるファイル群をDockerデーモンへ転送します。 Dockerfile自体の場所を指定しているだけでなく、「COPY命令などで参照できるファイルのルート位置」を決める役割を持っている点に注意してください。

よく使うビルドオプション

  • -t(–tag): イメージ名とタグを設定します。「アプリ名:バージョン」の形式で指定し、タグを省略した場合は自動的に「:latest」が付与されます。
  • -f(–file): Dockerfileのファイル名がデフォルトの「Dockerfile」以外である場合や、別フォルダにある場合に指定します。 例: 開発用と本番用で分ける場合docker build -f Dockerfile.dev -t myapp:dev .
  • –no-cache: キャッシュを一切使わずに、すべてのステップをゼロから再実行してビルドします。パッケージの最新バージョンを取り直したい場合や、不具合の切り分けに便利です。docker build --no-cache -t myapp:latest .

イメージサイズを軽量化する基本テクニック

Dockerfileを何も工夫せずに書くと、イメージサイズが数百MBから数GBに肥大化し、ビルドやデプロイ(docker push/pull)に時間がかかる原因になります。 実務で必ず実践すべき3つの軽量化テクニックを紹介します。

テクニック1:RUN命令を&&で結合しキャッシュを削除する

Dockerイメージは、命令ごとに「レイヤー」と呼ばれる差分データが積み重なって作成されます。 それぞれのレイヤーは一度作成されると、後続のレイヤーでファイルを削除してもイメージ全体のサイズからは減りません。

非推奨な書き方(サイズが大きくなる):

RUN apt-get update
RUN apt-get install -y curl git
RUN rm -rf /var/lib/apt/lists/*

上記の書き方では、apt-get updateでダウンロードされた一時キャッシュが1つ目のレイヤーに保存されてしまい、3行目で削除してもイメージサイズは小さくなりません。

推奨される書き方(最小限のレイヤーにまとめる):

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

コマンドを「&&」でつなげて1つのRUN命令で実行すれば、キャッシュのダウンロードから削除までが同一レイヤー内で完結するため、不要な中間ファイルがイメージに残りません。また、「–no-install-recommends」を付けることで、推奨パッケージの自動インストールを防ぎ、さらにサイズを削減できます。

テクニック2:ビルドキャッシュを意識した記述順序にする

Dockerはビルドを高速化するために、変更がないレイヤーを再利用するキャッシュ機構を持っています。 前のステップでファイルや記述に変更があると、それ以降のすべてのステップでキャッシュが無効化され、再実行されます。

非推奨な順序(ソース修正のたびに全インストールが再実行される):

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "index.js"]

この順序では、ソースコード(1文字の変更でも)を修正するたびに「COPY . .」のキャッシュが無効化され、続く「RUN npm install」が毎回ゼロから走り、ビルドに長い時間がかかってしまいます。

推奨される順序(依存定義ファイルだけを先にコピーする):

FROM node:20-alpine
WORKDIR /app

# 依存関係定義ファイルだけを先にコピー
COPY package.json package-lock.json ./

# ライブラリインストール(package.jsonに変更がない限りキャッシュされる)
RUN npm install

# アプリケーションコードをコピー
COPY . .

CMD ["node", "index.js"]

「変更頻度の低い命令(パッケージ定義やライブラリのインストール)」を上部に配置し、「変更頻度の高い命令(ソースコードのコピー)」を下部に配置することで、日常的なコード修正時のビルドが数秒で完了するようになります。

テクニック3:.dockerignoreを活用して不要ファイルを除外する

Gitの「.gitignore」と同様に、Dockerにも「.dockerignore」という仕組みがあります。 Dockerfileと同じディレクトリに「.dockerignore」を配置することで、ビルドコンテキストに不要なファイルを転送対象から除外できます。

設定例(.dockerignore):

.git
.gitignore
node_modules
npm-debug.log
.env
.DS_Store
dist
README.md

メリット:

  • ビルドコンテキストの転送サイズが小さくなり、ビルド開始が高速化する
  • ローカルの「node_modules」や仮想環境フォルダがコンテナ内に紛れ込んでOS不一致のエラーを起こすのを防ぐ
  • 機密情報を含む「.env」ファイルが誤ってコンテナイメージに含まれる事故を防止する

補足:軽量なベースイメージの選定

ベースイメージを選ぶ際は、フルセットのOSイメージ(例: ubuntuやdebian)ではなく、スリム化された派生イメージを選ぶのが効果的です。

  • alpine: 超軽量なLinuxディストリビューション(約5MB)。サイズ削減に最も有効ですが、C言語系ライブラリ(musl libc)の違いによる互換性に注意が必要です。
  • slim: Debianなどの標準ディストリビューションから最小限のパッケージのみを残した軽量版(例: python:3.12-slim、node:20-slim)。互換性と軽量さのバランスに優れています。

次に読むおすすめ記事

Dockerfile単体でのビルドやコンテナ起動に慣れたら、複数のコンテナ(Webアプリ、DB、キャッシュなど)を連携して一括管理できる「Docker Compose」の活用がおすすめです。よく使うコマンド一覧やv2対応の構文は以下の記事で詳しく解説しています。

docker/docker compose よく使うコマンド一覧|v2(スペース区切り)対応
DockerやDocker Composeをしばらく使っていないと、基本的なコマンドを忘れてしまい困ることがあります。特に、2023年7月にDocker Compose v1(ハイフン区切りの docker-compose)がサポートを終了…

参考情報