Dockerfile入門ガイド:ゼロから作る最初のDockerイメージ(実例付き)

深夜1時。私はターミナルに流れるエラーメッセージ——「COPY ../config.json: no such file or directory」——を見つめていました。今夜8回目のビルド失敗です。ローカル環境では動くのに、なぜDockerイメージにしようとするとエラーばかり出るのか?Stack Overflowをしらみつぶしにして、トップの回答通りに書き換えてみたら、今度はイメージサイズが200MBから2GBに膨れ上がりました。正直、心が折れかけました。「Dockerfileって一体なんなんだ……」
あなたも似たような経験があるかもしれません。プロジェクトにある Dockerfile というファイル。FROM、RUN、COPY、CMD……単語の意味はなんとなく分かるけれど、組み合わせると何が起きているのか分からない。ネットのチュートリアルをコピペしても動かない。一番イライラするのは、エラーメッセージが常に曖昧で、「パスが間違っているのか?」「コマンドが違うのか?」すら分からないことです。
実は、Dockerfileはそんなに難解なものではありません。私が二日間かけて各コマンドの挙動とハマりポイントを調べ上げた結果、重要なポイントはほんの数個だと分かりました。
この記事では、最も分かりやすい言葉で、ゼロから最初のDockerイメージを作る方法を解説します。机上の空論ではなく、全て実動するコード付きです。さらに、初心者が必ず踏む「3つの地雷」とその回避法も教えます。読み終わる頃には、あなたのNode.jsやPythonプロジェクトのために、自信を持ってDockerfileを書けるようになっているはずです。
Dockerfileとは何か?
簡単に言えば、**DockerfileはDockerイメージを作るための「設計図」**です。家の建築図面のようなもので、「床を貼って、壁を塗って、最後に照明をつける」といった手順が書かれています。Dockerエンジンはこの図面に従って、あなたのアプリケーションをステップバイステップで「建築」し、最終的に一つのイメージとしてパッケージングします。
このイメージは、どこでも動く「環境のスナップショット」です。例えばNode.jsアプリなら、Node 18などのランタイム、npmパッケージ、そしてあなたのソースコードが全てこの中に詰め込まれます。誰かがごのイメージを受け取って docker run すれば、環境構築の手間ゼロでアプリが動き出します。
やっていることは単純に3つだけです:
- 基礎となる環境を選ぶ(例:Node.js 18)
- コードと依存ライブラリを入れる
- 起動時に何をするか教える
書き上がったら docker build コマンド一発でイメージの完成です。簡単そうでしょう?確かに基本は簡単ですが、悪魔は細部に宿ります。ここからは、絶対に知っておくべき核心的なコマンドについて解説します。
6大核心コマンド徹底解説
Dockerイメージ構築の完全フロー
基礎イメージの選択からビルド、実行まで。手取り足取り教えるDockerfile作成手順
⏱️ Estimated time: 20 min
- 1
Step1: ステップ1:ベースイメージ選択(FROM)
FROMは必ず最初の行に書きます。適切なベースを選びましょう:
• Node.jsなら node:18-alpine(約5MB)
• Pythonなら python:3.11-slim
• Nginxなら nginx:alpine
Alpineは超軽量ですが、稀に互換性問題(musl libc)が出ることがあります。その場合はslim版を使いましょう。 - 2
Step2: ステップ2:作業ディレクトリ設定(WORKDIR)
WORKDIR /app でコンテナ内の作業場所を指定します。
• これ以降のCOPYやRUNは全てこのディレクトリ内で実行されます。
• 混乱を避けるため、必ず絶対パスを使ってください。 - 3
Step3: ステップ3:依存ファイルのコピーとインストール(COPY+RUN)
まず package*.json だけをコピーし、npm install を実行します。
それからソースコードをコピーします。
なぜ?Dockerのキャッシュを効かせるためです:
• package.jsonが変わらない限り、依存インストール層は再利用されます。
• これによりビルド時間が5分から10秒に短縮されます。
悪い例:全ファイルをコピーしてからインストール(コードを1文字変えるたびに全インストールが走ります)。 - 4
Step4: ステップ4:ソースコードのコピー(COPY)
COPY . . でソースコードをコンテナに入れます。
COPYのパスに注意:
• パスは「ビルドコンテキスト」からの相対パスです。
• 親ディレクトリ(../)や絶対パスは使えません。
• .dockerignore ファイルで node_modules や .git を除外するのを忘れずに。 - 5
Step5: ステップ5:ポートの公開(EXPOSE)
EXPOSE 3000 で使用ポートを宣言します。
• これはあくまで「ドキュメント」としての意味合いが強いです。
• 実際のポート開放は docker run -p コマンドで行います。 - 6
Step6: ステップ6:起動コマンド設定(CMD)
CMD ["npm", "start"] でコンテナ起動時のコマンドを指定します。
• CMDは docker run の引数で上書き可能です。
• 絶対に実行させたいメインコマンドがある場合は ENTRYPOINT を使います。
覚え方:ENTRYPOINTは「何をするか(実行ファイル)」、CMDは「どうするか(引数)」。 - 7
Step7: ステップ7:ビルドと実行
ビルド:
docker build -t my-app:1.0 .
実行:
docker run -p 3000:3000 my-app:1.0
検証:
ブラウザで http://localhost:3000 にアクセスして確認。
FROM - 土台を選ぶ
FROM はDockerfileの最初の命令(コメントとARGを除く)であり、イメージの「地盤」を決めます。
家を建てるのに地盤が大事なように、Dockerfileでもここが肝心です。Node.jsアプリ?なら node:18-alpine。Python? python:3.11-slim。Webサーバー? nginx:alpine。
# Node.js 18のAlpine Linux版をベースに使用
FROM node:18-alpineAlpineとSlimの違い:alpine はAlpine Linuxベースの超軽量イメージで、サイズは5MB程度。通常のNodeイメージ(約900MB)の1/180です。ただし、標準的なglibcではなくmusl libcを使っているため、一部のネイティブ拡張ライブラリが動かないことがあります。エラーが出たら node:18-slim(Debianベースの軽量版)に切り替えましょう。
ありがちなミス: 適当なタグを使ってしまい、本番環境とバージョンが合わなくなること。package.json で指定しているNodeバージョンと必ず合わせてください。
RUN - ビルド時に実行する
RUN はイメージを作っている最中に実行されるコマンドです。ライブラリのインストール、フォルダ作成、設定変更などに使います。
重要:RUNを1回書くごとに、イメージの「レイヤー(層)」が1枚増えます。
悪い例:
# ❌ ダメな書き方:3つの層ができてしまう
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get cleanこれだと、たとえ最後の行で掃除しても、前の層に残骸が残ったままイメージが膨れ上がります。
良い例(チェーン実行):
# ✅ 推奨:1つの層にまとめる
RUN apt-get update && \
apt-get install -y python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*&& で繋いで1回で実行します。特にお尻の rm は重要で、キャッシュを削除して数十MBの節約になります。
COPY vs ADD - ファイルを入れる
どちらもファイルをイメージに入れますが、公式推奨は 「可能な限りCOPYを使え」 です。
# 単一ファイル
COPY package.json /app/
# ディレクトリごと
COPY ./src /app/src
# カレントディレクトリ全部
COPY . /app最大の落とし穴:パスは「ビルドコンテキスト」基準
「ビルドコンテキスト」とは、docker build . と打った時の最後のドット(.)が指すディレクトリのことです。COPYはこのディレクトリ配下しか見えません。
# ❌ エラーになります
COPY ../config.json /app/
COPY /opt/myfile.txt /app/親ディレクトリや絶対パスは使えません。Dockerはセキュリティ上、指定されたディレクトリの外側にはアクセスできないようになっています。
ちなみに ADD は、URLからファイルをダウンロードしたり、tarファイルを自動解凍したりする機能がありますが、挙動が予期しにくい(勝手に解凍される等)ため、必要な時以外は COPY を使いましょう。
WORKDIR - 作業場所を決める
Linuxの cd コマンドと同じです。以降のコマンドを実行する場所を指定します。
WORKDIR /app
COPY . . # /app にコピーされる
RUN npm install # /app で実行される必ず絶対パスを使ってください。相対パスだと前のWORKDIRに依存してカオスになります。これで毎回 RUN cd /app && ... と書かなくて済みます。
CMD vs ENTRYPOINT - 起動コマンド
ここが一番混乱するポイントです。シンプルに覚えましょう。
CMDは「上書き可能」、ENTRYPOINTは「上書き不可(追記のみ)」。
CMD:
CMD ["node", "server.js"]これで docker run my-app すると node server.js が走ります。
しかし、docker run my-app npm test と打つと、CMDは無視され npm test が走ります。
ENTRYPOINT:
ENTRYPOINT ["node"]
CMD ["server.js"]この組み合わせだと、docker run my-app は node server.js になります。docker run my-app script.js と打つと、node script.js になります。ENTRYPOINT(node)は固定で、後ろの引数だけが変わるのです。
使い分け:
- CMDのみ:一般的なアプリ(起動コマンドを変えたい場合がある)
- ENTRYPOINT + CMD:単機能ツール(コマンドは固定で引数だけ変えたい)
私はいつも迷ったら「ENTRYPOINTは『何をするか』、CMDは『どうするか(デフォルト引数)』」と唱えています。
ENV - 環境変数
コンテナ実行時の環境変数をセットします。
ENV NODE_ENV=production
ENV PORT=3000これらはアプリコード内から(process.env.PORT などで)参照できます。
注意:ここにパスワードなどを書かないでください!イメージに残ってしまい、誰でも見れてしまいます。機密情報は実行時に -e オプションで渡すか、Docker Secretsを使いましょう。
実戦:最初のイメージを作ってみよう
理論は十分です。実際にNode.jsアプリをDocker化してみましょう。
手順1:アプリの準備
適当なフォルダを作ってファイルを用意します。
package.json
{
"name": "my-node-app",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2"
}
}server.js
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Hello from Docker!');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});手順2:Dockerfileを書く
プロジェクトルートに Dockerfile(拡張子なし)を作成します。
# 1. ベースイメージ
FROM node:18-alpine
# 2. 作業ディレクトリ
WORKDIR /app
# 3. 依存ファイルのコピー(※ここ重要!)
COPY package*.json ./
# 4. 依存インストール
RUN npm install --production
# 5. ソースコードのコピー
COPY . .
# 6. ポート宣言
EXPOSE 3000
# 7. 起動コマンド
CMD ["npm", "start"]なぜCOPYを2回に分けるのか?
これが Dockerキャッシュ のテクニックです。
Dockerは上から順にビルドし、変更がない層はキャッシュを使います。package.json はあまり頻繁には変わりませんが、ソースコードは頻繁に変わります。
先に package.json だけコピーしてインストールすれば、ソースコードを書き換えても「インストール層」まではキャッシュが効き、ビルドが一瞬で終わります。逆にすると、コードを一行変えるたびに全ライブラリの再インストールが走り、数分待たされます。
手順3:ビルドと実行
# ビルド
docker build -t my-node-app:1.0 .
# 実行
docker run -p 3000:3000 my-node-app:1.0ブラウザで http://localhost:3000 を開いて “Hello from Docker!” が出れば成功です!
初心者が踏む「3つの地雷」回避法
最後に、私が何度も爆死したポイントを共有します。
地雷1:コンテキスト外のファイル参照
現象:COPY ../config.json . が “no such file” で死ぬ。
理由:Dockerfileからは親ディレクトリは見えません。
対策:必要なファイルはプロジェクトディレクトリ内に移動させるか、ビルドコマンドのルートを調整してください。
地雷2:巨大なコンテキスト転送
現象:docker build を叩いた直後、何も始まらず数分間フリーズする。
理由:node_modules や .git などの巨大フォルダごとDockerデーモンに送信してしまっている。
対策:.dockerignore ファイル(.gitignoreと同じようなもの)を作成し、不要なファイルを除外します。
.dockerignore
node_modules
.git
.env
*.logこれだけでビルドが爆速になります。
地雷3:レイヤー肥大化
現象:たいしたコード書いてないのにイメージが数GBある。
理由:RUNコマンドをバラバラに書いて、一時ファイルを消してない。
対策:apt-get install したら同じ行で apt-get clean する。RUN は && で繋いでまとめる。
結論
Dockerfile作成の極意は、実はシンプルです。
- コマンドを理解する:FROMで土台を決め、RUNで整え、COPYで物を入れ、CMDで動かす。
- コンテキストを意識する:Dockerが見えている範囲(ディレクトリ)を把握する。
- キャッシュを味方につける:変化の少ないものを先に書く。
まずは小さなプロジェクトで試してみてください。エラーが出ても、この記事の「地雷リスト」を見返せば、きっと解決策が見つかるはずです。一度慣れてしまえば、もう環境構築で悩む日々には戻れませんよ。
次のステップ:
- Docker Compose(複数コンテナの管理)
- マルチステージビルド(イメージサイズを極限まで小さくする)
それでは、良いDockerライフを!
FAQ
Dockerfileの核心コマンドは何ですか?
1) FROM:ベースイメージの選択(必須)
2) RUN:ビルド時のコマンド実行(インストール等)
3) COPY:ファイルのコンテナへのコピー
4) WORKDIR:作業ディレクトリの指定
5) CMD:コンテナ起動時のデフォルトコマンド
6) ENV:環境変数の設定
COPYとADDの違いは何ですか?
なぜCOPYを複数回に分けるのですか?
イメージサイズを小さくするには?
2) RUNコマンドを && で連結し、レイヤー数を減らす。
3) .dockerignore を使って不要なファイルをビルドコンテキストから除外する。
4) 不要なキャッシュファイル(aptキャッシュ等)を同じRUN命令内で削除する。
CMDとENTRYPOINTはどう使い分けますか?
4 min read · 公開日: 2025年12月17日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます