Dockerfileは、FROMでベースイメージを指定し、RUNやCOPYなどの命令を上から順に並べるだけで書けます。
効率よく書くコツは大きく2つです。
ビルド時間を縮めるには変更頻度の低い命令を上に書いてレイヤーキャッシュを効かせること、イメージサイズを縮めるにはマルチステージビルドと軽量なベースイメージを組み合わせることです。
「Dockerfileを使い始めたけど、書き方がいまいち分からない」「とりあえず動くものは作れたけど、もっと効率的な書き方はないの?」
これは、私が開発現場でDockerを使い始めたころに抱えていた悩みです。
当時は手探りで情報を集め、ビルドに時間がかかったり、イメージサイズが大きくなりすぎたりして、何度も書き直した経験があります。
しかし、いくつかの重要なポイントを押さえることで、ビルド時間を最大で30%削減し、イメージサイズを半分以下にできたのです。
この記事では、Dockerfileの基本構文からNode.js・Python・Javaの実装例までを解説します。
あわせて、レイヤーキャッシュやマルチステージビルドによる最適化、セキュリティ対策まで、現場で使える書き方を体系的にまとめました。
あなたのアプリケーションを効率よく、そして安全にコンテナ化するための一歩を、この記事とともに踏み出しましょう。
Dockerfileとは?基本を理解しよう

Dockerfileとは、Dockerイメージをビルドするための手順を記述したテキストファイルです。
書き方を学ぶ前に、まずDockerfileが何であるかを正確に理解する必要があります。
ここでは、Dockerfileの基本的な役割や、関連するDockerの仕組みについて分かりやすく解説します。
Dockerfileの役割とメリット
Dockerfileは、Dockerイメージを自動で作成するための設計図のようなものです。
テキストファイルに、どのようなOSを使い、どんなソフトウェアをインストールし、どうやってアプリケーションを動かすか、といった一連の指示を記述します。
この設計図を使う大きなメリットは、環境構築をコードで管理できる点にあります。誰が実行しても全く同じ環境を何度でも再現できます。
これにより、「私のパソコンでは動いたのに、他の人の環境では動かない」といった問題をなくせます。
また、Gitなどでバージョン管理できるため、変更履歴を追うのも簡単になります。
イメージとコンテナの関係
Dockerを理解する上で、「イメージ」と「コンテナ」の関係を知ることは非常に重要です。この2つはよく混同されがちですが、役割が全くちがいます。
Dockerfileはこの「イメージ」を作るための設計図であり、docker buildというコマンドでDockerfileからイメージが作られ、docker runというコマンドでイメージからコンテナが起動されます。
Dockerの基本的な仕組み
Dockerは、コンテナという独立した環境でアプリケーションを動かすためのプラットフォームです。
その心臓部であるDocker Engineが、Dockerfileの指示を読み取ってイメージを構築します。
Dockerfileに書かれた命令は、上から順番に実行されます。
そして、各命令が実行されるたびに「レイヤー」と呼ばれる層が作られ、積み重なっていくことで1つのイメージが完成します。
このレイヤー構造が、後で説明するビルドの高速化において非常に重要な役割を果たします。
Dockerfileの書き方:基本構文と命令

ここからは、具体的なDockerfileの書き方について解説します。
基本構造と、よく使われる命令を覚えれば、簡単なアプリケーションならすぐにコンテナ化できます。
Dockerfileの基本構造
Dockerfileの書き方はとてもシンプルです。基本的には、以下のような形式で1行ずつ指示を記述します。
# コメント
命令 引数#で始まる行はコメントとして扱われ、実行時には無視されます。
命令はFROMやRUNなど、Dockerにあらかじめ用意されているものを大文字で書くのが一般的です。
その後に、命令に対する具体的な指示を引数として記述します。
よく使う命令:FROM, RUN, COPY, ADD, WORKDIR, CMD, ENTRYPOINT, EXPOSE
Dockerfileの書き方で中心となるのが、これらの命令です。ここでは、特に使用頻度の高い8つの命令の役割と使い方を説明します。
- FROMベースとなるイメージを指定します。すべてのDockerfileは、このFROM命令から始まります。例えば、UbuntuやNode.js、Pythonなど、Docker Hubで公開されている公式イメージを土台にできます。
FROM ubuntu:24.04 - RUNシェルコマンドを実行します。主に、ライブラリのインストールや必要なディレクトリの作成などに使われます。
RUN apt-get update && apt-get install -y nginx - COPYホストマシン(自分のPC)にあるファイルやディレクトリを、イメージの中にコピーします。アプリケーションのソースコードをイメージに含める際によく使われます。
COPY ./app /usr/src/app - ADDCOPYと似ていますが、URLを指定してファイルをダウンロードしたり、圧縮ファイル(tar.gzなど)を自動で展開したりする機能も持っています。特別な理由がない限りは、よりシンプルなCOPYを使うのが推奨されます。
- WORKDIR以降のRUNやCMDなどの命令を実行する作業ディレクトリを指定します。cdコマンドのように、これ以降の命令はこのディレクトリを基準に実行されます。
WORKDIR /usr/src/app - CMDコンテナを起動したときに、デフォルトで実行されるコマンドを指定します。1つのDockerfileに複数書いた場合は、最後のものだけが有効になります。
CMD ["node", "server.js"] - ENTRYPOINTCMDと同様に、コンテナ起動時に実行されるコマンドを指定します。CMDとのちがいは、ENTRYPOINTで指定したコマンドは上書きされにくく、CMDは引数として渡される点です。
- EXPOSEコンテナが外部に公開するポート番号を指定します。これはドキュメントとしての役割であり、実際にポートを公開するにはdocker runコマンドで-pオプションが必要です。
EXPOSE 8080
CMDの書き方:shell形式とexec形式の違い
CMD命令にはCMD ["node", "server.js"]のように配列で書くexec形式と、CMD node server.jsのように文字列で書くshell形式の2種類があります。
この違いは見た目以上に重要で、コンテナの終了処理に直結します。
exec形式で起動したプロセスは、コンテナ内でPID 1として直接実行されます。
そのためdocker stop実行時に送られるSIGTERMシグナルを、アプリケーションが直接受け取ってから正常終了できます。
一方、shell形式で書くと実際には/bin/sh -c "node server.js"のようにシェル経由で起動するため、シグナルがアプリケーション本体まで届かず、タイムアウト後に強制終了(SIGKILL)されます。
特別な理由がない限り、CMDやENTRYPOINTはexec形式で書くのが安全です。
命令の実行順序とレイヤー構造
前述したように、Dockerfileは上から下に書かれた命令を順に実行し、そのたびにレイヤーが作成されます。
例えば、以下のようなDockerfileがあったとします。
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y curl
COPY ./app /appこの場合、
ubuntu:24.04のイメージを土台にする(レイヤー1)apt-get updateを実行する(レイヤー2)curlをインストールする(レイヤー3)appディレクトリをコピーする(レイヤー4)
というように、4つの層が積み重なったイメージが作られます。
この仕組みを理解することが、Dockerfileの書き方をマスターし、最適化を行うための鍵となります。
Dockerfile実践:アプリケーションの構築

基本的な命令を学んだところで、次は実際のアプリケーションをコンテナ化するDockerfileの書き方を見ていきましょう。
ここでは、Node.js、Python、Javaの3つの例を紹介します。
Node.jsアプリケーションのDockerfile
Node.jsアプリケーションでは、package.jsonを使って依存パッケージを管理するのが一般的です。
効率的なDockerfileの書き方のポイントは、ソースコードのコピーより先にパッケージのインストールを行うことです。
# ベースイメージとして公式のNode.jsイメージを選択
FROM node:24
# アプリケーション用のディレクトリを作成
WORKDIR /usr/src/app
# 依存関係の定義ファイルを先にコピー
COPY package*.json ./
# 依存パッケージをインストール
# ソースコードが変更されても、package.jsonに変更がなければキャッシュが使われる
RUN npm install
# アプリケーションのソースコードをコピー
COPY . .
# アプリケーションが使用するポートを公開
EXPOSE 3000
# アプリケーションを起動
CMD [ "node", "server.js" ]PythonアプリケーションのDockerfile
Pythonの例もNode.jsと似ています。
依存関係を定義したrequirements.txtを先にコピーして、pip installを実行するのが定石です。
# ベースイメージとして公式のPythonイメージを選択
FROM python:3.13
# 作業ディレクトリを設定
WORKDIR /usr/src/app
# 依存関係の定義ファイルを先にコピー
COPY requirements.txt ./
# 依存パッケージをインストール
RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションのソースコードをコピー
COPY . .
# アプリケーションが使用するポートを公開
EXPOSE 5000
# アプリケーションを起動
CMD [ "python", "./main.py" ]JavaアプリケーションのDockerfile
Javaアプリケーション(ここではMavenを使う例)では、ビルド(コンパイル)と実行で必要な環境が異なります。
ビルドにはJDKが必要ですが、実行時にはより軽量なJREだけで十分です。
このような場合に、後述するマルチステージビルドというテクニックが非常に有効です。
# --- ビルドステージ ---
# ビルド用のベースイメージとしてMavenが含まれるものを選択
FROM maven:3.9-eclipse-temurin-21 AS builder
# 作業ディレクトリを設定
WORKDIR /app
# Mavenのプロジェクト定義ファイルをコピー
COPY pom.xml .
# 依存関係をダウンロード(ソースコードコピーより先に行う)
RUN mvn dependency:go-offline
# ソースコードをコピー
COPY src ./src
# アプリケーションをビルド
RUN mvn package
# --- 実行ステージ ---
# 実行用の軽量なベースイメージを選択
FROM eclipse-temurin:21-jre-alpine
# 作業ディレクトリを設定
WORKDIR /app
# ビルドステージからビルドされたjarファイルのみをコピー
COPY --from=builder /app/target/*.jar app.jar
# アプリケーションが使用するポートを公開
EXPOSE 8080
# アプリケーションを起動
ENTRYPOINT ["java", "-jar", "app.jar"]複数コンテナ構成ならdocker-compose.ymlと組み合わせる
ここまでのDockerfileは、いずれも1つのアプリケーションを1つのイメージにする例でした。
しかし実際の開発では、アプリケーション本体に加えてデータベースやキャッシュサーバーなど、複数のコンテナを連携させたいケースがよくあります。
Dockerfileが「1つのイメージの設計図」であるのに対し、docker-compose.ymlは「複数のコンテナをまとめて起動・連携させるための設定ファイル」です。
役割が異なるため、両方を組み合わせて使うのが一般的です。
docker-compose.ymlのbuildキーにDockerfileの場所を指定すると、そのDockerfileからイメージをビルドしつつ、他のコンテナと一緒に起動できます。
例えば、Webアプリとデータベースを連携させる最小構成は以下のようになります。
services:
web:
build: .
ports:
- "3000:3000"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example1つのコンテナだけで完結するアプリケーションであればDockerfile単体で十分ですが、データベースなど連携先が増えるほどdocker-compose.ymlが必要になります。
目安として、docker runのオプションが増えて管理しづらくなってきたら、docker-compose.ymlへの移行を検討するとよいでしょう。
Dockerfileの最適化:効率的なイメージ作成

Dockerfileの書き方の基本をマスターしたら、次はより効率的なイメージを作成するための最適化手法を学びましょう。
ビルド時間の短縮やイメージサイズの削減は、開発効率を大きく向上させます。
レイヤーキャッシュの活用
Dockerは、一度実行した命令とその結果をレイヤーとしてキャッシュします。
Dockerfileを再度ビルドする際、変更がない行まではキャッシュされたレイヤーを再利用するため、ビルドが高速になります。
この仕組みを最大限に活用するための重要なテクニックは、変更頻度の低い命令を上に、変更頻度の高い命令を下に書くことです。
実際に、アプリケーションのソースコードは頻繁に変更されますが、npm installやpip installでインストールするライブラリはそれほど頻繁には変わりません。
そのため、先にpackage.jsonやrequirements.txtだけをコピーしてライブラリをインストールし、その後にソースコード全体をコピーすることで、ソースコードの小さな変更で毎回ライブラリをインストールし直す必要がなくなります。
RUN命令をまとめてレイヤー数を減らす
複数のRUN命令を別々に書くと、そのたびにレイヤーが作られてイメージサイズが膨らみます。
具体的には以下のように書くと、パッケージ管理のキャッシュを最後に削除しても、その前のレイヤーに残ったファイルは消えません。
# Bad: RUNを分けるとキャッシュ削除が効かない
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*これを&&でつないで1つのRUNにまとめると、1つのレイヤーの中でインストールとキャッシュ削除が完結するため、不要なファイルがイメージに残りません。
# Good: 1つのRUNにまとめてキャッシュ削除まで完結させる
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*ただし、まとめすぎると可読性やデバッグ性が落ちるため、意味のあるまとまり単位で分けるのが実務的なバランスです。
BuildKitが使える環境では、RUN --mount=type=cacheでキャッシュ専用のマウントを使う方法もあり、こちらもイメージにキャッシュを残さずに済む選択肢です。
マルチステージビルドの活用
先ほどのJavaの例でも使ったマルチステージビルドは、イメージサイズを劇的に削減できる強力なテクニックです。
1つのDockerfileの中に複数のFROM命令を記述することで、ビルド工程を複数のステージに分割します。
例えば、最初のステージでソースコードのコンパイルや依存関係のインストールを行います。
次のステージでは、前のステージで生成された実行可能なファイル(jarファイルやバイナリなど)だけを、より軽量な実行用のベースイメージにコピーします。
これにより、最終的なイメージにはビルドツール(JDK、Maven、コンパイラなど)や中間ファイルが含まれなくなり、必要最小限のファイルだけで構成された非常に小さなイメージを作成できます。
ただし、マルチステージビルドは万能ではありません。
依存パッケージがほとんど無い軽量なスクリプトや、開発時にしか使わない使い捨てのイメージにまで機械的に適用すると、Dockerfileの見通しが悪くなるわりにメリットが薄くなります。
ビルド成果物とランタイムの差が小さいアプリケーションでは、マルチステージ化の手間に見合いません。この点を判断基準にしてください。
イメージサイズの削減
マルチステージビルド以外にも、イメージサイズを削減するためのDockerfileの書き方がいくつかあります。
- .dockerignoreファイルの使用:
COPYやADD命令でイメージに含めたくないファイル(.gitディレクトリ、ログファイル、ローカルの設定ファイルなど)を.dockerignoreファイルに記述します。これにより、不要なファイルがイメージに含まれるのを防ぎ、イメージサイズを小さく保てます。設定漏れに気づくコツとしては、docker build実行直後に表示されるSending build context to Docker daemon XXXMBというログ行を見ることです。この数値が数百MB〜GB単位まで大きい場合は、.dockerignoreの設定漏れを疑うとよいでしょう。 - パッケージマネージャのキャッシュ削除:
RUN命令でapt-getやyumなどを使う際は、インストール後にキャッシュを削除することで数十〜数百MBのサイズを削減できます。RUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* - 適切なベースイメージの選択:
ubuntuのようなフル機能のOSイメージではなく、alpineやslimといった、より軽量なイメージをベースにすることで、イメージの基本サイズを大幅に小さくできます。
Dockerfileベストプラクティスと応用

最後に、セキュリティや保守性を高めるための、より進んだDockerfileの書き方について紹介します。
これらのプラクティスを実践することで、本番環境でも安心して使える、より堅牢なイメージを作成できます。
セキュリティ対策:ユーザー設定と権限
Dockerコンテナは、デフォルトではrootユーザーでプロセスを実行します。
これはセキュリティ上のリスクになるため、一般ユーザーを作成してアプリケーションを実行してください。
FROM node:24-alpine
WORKDIR /app
# 一般ユーザー'node'がすでに存在するので、所有者を変更
# ユーザーが存在しない場合は'RUN addgroup'と'RUN adduser'で作成
COPY --chown=node:node . .
# ユーザーを切り替え
USER node
CMD [ "node", "server.js" ]このようにUSER命令を使うことで、コンテナ内のプロセスをroot以外の権限で実行し、万が一アプリケーションに脆弱性があった場合のリスクを低減できます。
環境変数の設定と利用
アプリケーションの設定値(データベースの接続情報、APIキーなど)をDockerfileに直接書き込むのは避けるべきです。
代わりにENV命令を使って環境変数を設定します。
# デフォルトの環境変数を設定
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE ${PORT}
CMD [ "node", "server.js" ]ENVで設定した値は、コンテナ起動時にdocker runコマンドの-eオプションで上書きできます。
これにより、イメージを再ビルドすることなく、環境ごとに設定を柔軟に変更できます。
Dockerfileのテストとデバッグ
Dockerfileのビルドや実行でエラーが出た場合、まずdocker buildのログを確認します。
デバッグの基本は、エラーメッセージをよく読み、どの命令で失敗したかを特定することです。
また、うまく動かないコンテナの原因を調査したい場合は、docker execコマンドが役立ちます。
docker exec -it <コンテナID> /bin/shこのコマンドを使うと、起動中のコンテナの中に入って、ファイルの状態を確認したり、コマンドを実行したりして、問題の原因を探れます。
よくある質問
Q1. Dockerfileとは何ですか?
A1. Dockerfileとは、Dockerイメージをビルドするための手順を記述したテキストファイルです。FROMやRUN、COPYなどの命令を上から順に記述することで、誰の環境でも同じイメージを再現できます。
Q2. DockerfileのCOPYとADDの違いは何ですか?
A2. COPYはファイルをそのままイメージ内にコピーするだけの命令です。ADDはそれに加えてURLからのダウンロードやtar.gzファイルの自動展開もできますが、挙動が複雑なため、特別な理由がなければCOPYを使うのが推奨されます。
Q3. CMDとENTRYPOINTの違いは何ですか?
A3. どちらもコンテナ起動時に実行するコマンドを指定する命令です。CMDはdocker run時の引数で上書きされやすいのに対し、ENTRYPOINTは上書きされにくく、CMDをその引数として渡す使い方が一般的です。
Q4. マルチステージビルドとは何ですか?
A4. 1つのDockerfile内に複数のFROM命令を書き、ビルド専用のステージと実行専用のステージを分ける手法です。ビルドツールや中間ファイルを最終イメージから除外できるため、イメージサイズを大幅に削減できます。
Q5. .dockerignoreは何のために使いますか?
A5. COPYやADD命令でイメージに含めたくないファイル(.gitディレクトリやログファイルなど)を除外するための設定ファイルです。ビルドコンテキストとイメージサイズの両方を小さく保てます。
Q6. Dockerfileとdocker-compose.ymlはどう使い分ければよいですか?
A6. Dockerfileは1つのイメージの設計図、docker-compose.ymlは複数コンテナをまとめて起動・連携させるための設定ファイルです。単一コンテナで完結するならDockerfileのみで十分ですが、データベースなど連携先が増える場合はdocker-compose.ymlを併用します。
Dockerfileの書き方を学ぶことは、単にアプリケーションをコンテナで動かすための手順を覚えるだけではありません。
それは、アプリケーションの実行環境そのものをコードとして管理し、再現性、移植性、そして保守性を高めるための重要なスキルです。
今回紹介した基本的な命令から最適化のテクニック、そしてベストプラクティスまでを理解し実践することで、あなたの開発プロセスは大きく変わります。
Dockerfileは、一度書いて終わりではなく、アプリケーションの成長と共に改善していくものです。
- 再現性の確保: 誰がどこで実行しても同じ環境が手に入ります。
- 効率的な開発: キャッシュの活用により、日々のビルド時間を短縮できます。
- 軽量なデプロイ: マルチステージビルドでイメージサイズを削減し、デプロイや起動を高速化できます。
- セキュリティの向上: 適切なユーザー設定で、より安全なアプリケーション実行環境を構築できます。
ぜひ、この記事を参考に、あなたのプロジェクトに最適なDockerfileの書き方を見つけてください。




