Dockerfileの書き方入門|自分のアプリをイメージ化する基礎と注意点

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > Dockerfileの書き方入門|自分のアプリをイメージ化する基礎と注意点
「Dockerfileってどう書けばいいのか、どこから手を付ければいいかわからない」
そんな悩みを持つ方は多いと思います。Docker自体は使えるようになったけれど、自分のアプリを独自イメージとしてパッケージするとなると、急に手が止まってしまう。

この記事では、Dockerfileの基本構文から実践的な書き方まで、Ubuntu 24.04 LTS / Rocky Linux 9で動作確認した手順を交えながら解説します。
よくあるエラーの対処法やベストプラクティスも網羅していますので、初めてDockerfileを書く方から「なんとなく書いてきたけど最適化したい」という方まで、そのまま実務に使えます。

この記事のポイント

・FROM・RUN・COPY・CMD の4命令を押さえれば最初のDockerfileは書ける
・RUN命令は&&でつなぎ1レイヤーにまとめるとイメージが小さくなる
・.dockerignore でビルドコンテキストを絞るのが速度改善の第一歩
・エラーの9割は「ファイルパスの指定ミス」か「キャッシュの古さ」が原因


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

Dockerfileとは何か

Dockerfileとは、Dockerイメージを自動的に作成するための「設計書」です。

通常、Dockerコンテナを起動するには既存のイメージ(ubuntu、nginx、pythonなど)を使います。ただし、自分のアプリケーションを動かすには「必要なパッケージをインストールし、アプリのコードをコピーし、起動コマンドを設定した」状態のイメージが必要です。

Dockerfileにはその手順を1行ずつ命令として記述します。docker build コマンドを実行すると、Dockerはこのファイルを上から順番に読み込んでイメージを作成します。

Dockerfileを使うメリットは次の3点です。

再現性:誰がどの環境で docker build しても同じイメージができる
自動化:CI/CDパイプラインに組み込めば、コード変更のたびに自動でイメージを更新できる
可搬性:Dockerfileと .dockerignore さえあれば、アプリ環境をどこでも再現できる

Dockerfileはプロジェクトのルートディレクトリに Dockerfile(拡張子なし)という名前で保存するのが慣例です。

Dockerfileの基本構文と主要命令

Dockerfileの各行は「命令(INSTRUCTION)」と「引数(arguments)」の形式で書きます。

# 命令 引数 INSTRUCTION arguments

命令は大文字が慣例です。コメントは # から始めます。以下に主要命令を解説します。

1. FROM — ベースイメージの指定

すべてのDockerfileは FROM 命令から始まります。どの既存イメージを土台にするかを指定します。

FROM ubuntu:24.04 # または FROM python:3.12-slim # または FROM node:20-alpine

公式イメージは Docker Hub で探せます。:タグ でバージョンを固定するのが鉄則です。:latest は更新のたびに内容が変わるため、本番環境では使いません。

2. RUN — コマンドの実行

イメージビルド時にシェルコマンドを実行します。パッケージのインストール、設定ファイルの配置などに使います。

# NG:RUNを3行に分けると3つのレイヤーができる RUN apt-get update RUN apt-get install -y python3 RUN apt-get clean # OK:&&でつなぎ1つのRUNにまとめる(レイヤーを削減) RUN apt-get update \ && apt-get install -y python3 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*

RUN 命令1行ごとにレイヤー(層)が作られます。レイヤーが多いほどイメージサイズは大きくなります。後述のレイヤー最適化で詳しく説明します。

3. COPY — ファイルのコピー

PCのファイルをイメージ内にコピーします。アプリのソースコードや設定ファイルをイメージに組み込む際に使います。

# ローカルの app.py をイメージ内の /app/app.py にコピー COPY app.py /app/app.py # カレントディレクトリ全体を /app にコピー COPY . /app/

ADD という命令も似た用途ですが、URLからのダウンロードやtar展開など余計な挙動があります。ファイルコピー専用には COPY を使うのがベストプラクティスです。

4. WORKDIR — 作業ディレクトリの設定

以降の命令(RUN・COPY・CMD等)の作業ディレクトリを指定します。ディレクトリが存在しない場合は自動作成されます。

WORKDIR /app # 以降の COPY や RUN は /app を起点に実行される COPY . . RUN pip install -r requirements.txt

5. ENV — 環境変数の設定

コンテナ内で使う環境変数を設定します。ビルド時・実行時の両方で参照できます。

ENV APP_ENV=production ENV PORT=8080

6. EXPOSE — ポートの宣言

コンテナが使用するポートを宣言します。実際のポート公開は docker run -p で行いますが、EXPOSE は「このコンテナはこのポートを使う」という意図をドキュメントとして残す役割があります。

EXPOSE 8080

7. CMD / ENTRYPOINT — コンテナ起動時のコマンド

コンテナが起動したときに実行するデフォルトコマンドを指定します。

# exec 形式(推奨) CMD ["python3", "app.py"] # shell 形式(シェル展開が必要な場合のみ) CMD python3 app.py

exec形式(配列形式)は、PID 1 として指定したプロセスが直接起動します。シグナル処理が確実に届くため、本番では exec 形式を使います。

ENTRYPOINT は常に実行される固定コマンド、CMD はそのデフォルト引数、という使い分けが一般的です。

実践:シンプルなWebアプリのDockerfileを書く

実際に Python + Flask で作ったシンプルなWebアプリをDocker化する例で手順を確認します。

1. アプリの構成

myapp/ ├── Dockerfile ├── .dockerignore ├── requirements.txt └── app.py

2. app.py(サンプルアプリ)

from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello from Docker!' if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)

3. requirements.txt

flask==3.0.3

4. Dockerfile

# ベースイメージ:Python 3.12 スリム版(Debian Bookworm ベース) FROM python:3.12-slim # 作業ディレクトリを /app に設定 WORKDIR /app # 依存ライブラリをインストール(キャッシュ活用のため先にコピー) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # アプリのソースコードをコピー COPY app.py . # コンテナが使用するポートを宣言 EXPOSE 8080 # コンテナ起動時のコマンド CMD ["python3", "app.py"]

5. .dockerignore の作成

ビルドコンテキスト(docker build が読み込むファイル群)から不要ファイルを除外します。

__pycache__ *.pyc *.pyo .git .env *.log

6. ビルドと実行

# イメージをビルド(-t でタグ名を付ける、最後の . がビルドコンテキスト) docker build -t myapp:1.0 . # 実行確認(-p でホストのポートとコンテナのポートをマッピング) docker run --rm -p 8080:8080 myapp:1.0

実際のビルド出力(Rocky Linux 9 上での動作確認):

[+] Building 12.3s (9/9) FINISHED => [internal] load build definition from Dockerfile => [internal] load .dockerignore => [internal] load metadata for docker.io/library/python:3.12-slim => [1/4] FROM docker.io/library/python:3.12-slim => [2/4] WORKDIR /app => [3/4] COPY requirements.txt . => [4/4] RUN pip install --no-cache-dir -r requirements.txt => exporting to image => => writing image sha256:a3f7... => => naming to docker.io/library/myapp:1.0

PCのブラウザで http://localhost:8080/ を開くと「Hello from Docker!」と表示されれば成功です。

Dockerfileのベストプラクティス(レイヤー最適化・キャッシュ活用)

1. レイヤーを最小化する

Dockerイメージはレイヤーの積み重ねで構成されます。RUN 命令1行ごとに1レイヤーが作成されます。中間レイヤーに削除したファイルが残るため、インストールとクリーンアップは同一 RUN 命令内で行うのが鉄則です。

# NG:クリーンアップしても前のレイヤーにキャッシュが残る RUN apt-get update && apt-get install -y curl RUN apt-get clean && rm -rf /var/lib/apt/lists/* # OK:同一 RUN 命令内でインストール&クリーンアップ RUN apt-get update \ && apt-get install -y curl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*

2. キャッシュを有効活用するための順序

Docker はビルドの各ステップをキャッシュします。変更があったステップ以降はキャッシュが無効になります。「変更頻度が低いもの(依存ライブラリ)を先に、変更頻度が高いもの(ソースコード)を後に」という順序が基本です。

# 推奨順序:依存ファイルを先にコピーして pip install COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 依存が変わらない限り上記はキャッシュが使われる # ソースコードは最後にコピー COPY app.py .

requirements.txt が変わらなければ pip install はキャッシュが効くため、ビルドが大幅に速くなります。

3. 軽量ベースイメージを選ぶ


タグ例 特徴 推奨用途
python:3.12 フルサイズ(約1GB) 開発・デバッグ
python:3.12-slim Debianベース軽量版(約130MB) 本番アプリ
python:3.12-alpine Alpine Linuxベース最小版(約50MB) サイズ優先・musl libc対応アプリ
Alpineは最小サイズですが、musl libc(glibc非互換)を使うため一部のCライブラリが動かない場合があります。まずは slim からはじめるのが無難です。

4. 非rootユーザーで実行する

コンテナをroot権限で実行すると、脆弱性を突かれたときのリスクが高まります。

# 専用ユーザーを作成してアプリを実行 RUN useradd -m appuser USER appuser CMD ["python3", "app.py"]

本番環境では必ず非rootユーザーで実行するよう習慣づけてください。

5. マルチステージビルドで最終イメージを軽量化する

ビルド時に必要なツール(コンパイラ、テストツール等)を最終イメージから除外するパターンです。Go言語のアプリを例に示します。

# ステージ1:ビルド FROM golang:1.22 AS builder WORKDIR /src COPY . . RUN go build -o app . # ステージ2:実行(最小イメージ) FROM gcr.io/distroless/base-debian12 COPY --from=builder /src/app /app CMD ["/app"]

ビルドツール一式を含む golang イメージ(約800MB)を使いつつ、最終イメージには実行バイナリしか入らないため、配布イメージを大幅に削減できます。

また、Linuxサーバーのポート確認コマンド(ss/lsof)EXPOSE したポートが実際にリッスンしているか確認するのも、動作確認時の基本操作です。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Dockerfileの書き方からコンテナ運用の実務スキルを短期間で習得できる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

よくあるエラーと対処法

エラー1:「COPY failed: file not found」

# エラーメッセージ例 COPY failed: file not found in build context or excluded by .dockerignore: stat requirements.txt

原因:Dockerfileのある場所から見て、requirements.txt のパスが間違っているか、.dockerignore で除外されています。
対処:docker build を実行するディレクトリ(ビルドコンテキスト)と、COPY に指定したパスの起点が一致しているか確認してください。

# Dockerfile があるディレクトリで実行する(最後の . = ビルドコンテキスト) cd myapp/ docker build -t myapp:1.0 .

エラー2:「RUN: exit code 100」(apt-get が失敗する)

# エラーメッセージ例 #6 4.321 E: Unable to locate package xxx

原因:キャッシュが古く、パッケージリストが最新でない場合や、パッケージ名が間違っています。
対処:apt-get updateapt-get install を必ず同一 RUN 内に書くこと。また --no-cache オプション付きでリビルドしてください。

# キャッシュを無効化してビルド docker build --no-cache -t myapp:1.0 .

エラー3:コンテナは起動するがアプリに接続できない

原因1:-p オプションでポートマッピングをしていない。
原因2:アプリが 127.0.0.1 でリッスンしており、コンテナ外からアクセスできない。
対処:

# -p ホストポート:コンテナポート を正しく指定する docker run --rm -p 8080:8080 myapp:1.0 # アプリ側も 0.0.0.0 でリッスンさせる(Flask の例) app.run(host='0.0.0.0', port=8080)

コンテナ内で動作しているプロセスとポートの状態を確認したい場合は、tarコマンドでのファイルアーカイブと同じように、まずは docker exec でコンテナに入って確認するのが近道です。

# 起動中のコンテナへ入る docker exec -it <コンテナID> /bin/bash # コンテナ内でポートリッスン状態を確認 ss -tlnp

エラー4:「permission denied」でアプリが起動しない

原因:USER 命令で切り替えた非rootユーザーに、ファイルの読み取り権限がない。
対処:COPY 後に権限を設定するか、所有者を変更してください。

# COPY と同時に所有者を指定する(Docker 17.09+) COPY --chown=appuser:appuser . /app/

本記事のまとめ

Dockerfileの主要命令と実践的な書き方を解説しました。

やりたいこと 命令・コマンド
ベースイメージを指定する FROM python:3.12-slim
パッケージをインストールする RUN apt-get update && apt-get install -y xxx && apt-get clean && rm -rf /var/lib/apt/lists/*
ファイルをイメージに追加する COPY ローカルパス コンテナパス
作業ディレクトリを設定する WORKDIR /app
ポートを宣言する EXPOSE 8080
起動コマンドを設定する CMD ["python3", "app.py"]
イメージをビルドする docker build -t myapp:1.0 .
キャッシュなしでビルドする docker build --no-cache -t myapp:1.0 .
コンテナを起動してポートを開ける docker run --rm -p 8080:8080 myapp:1.0
Dockerfileを書けるようになると、自分のアプリをどの環境でも同じ状態で動かせるようになります。まずはこの記事のFlaskサンプルを手元で動かしてみて、次はDockerボリュームによるデータ永続化や、Docker Composeによる複数コンテナ構成にステップアップしていきましょう。

Linuxサーバーの基礎からDockerの実務活用まで、体系的に学びたい方は以下のリンクもあわせてご参照ください。

Linux ポート確認の全コマンド
mount コマンドの使い方

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、DockerfileからDockerの実践活用まで体系的に学べる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。