目次

Dockerfile 入門:自分のアプリをイメージにパッケージ化

Dockerfile は、イメージの作り方を一行ずつ記述するプレーンテキストファイルです。Docker は記述された手順を実行してイメージを作成します。作成したイメージは、nginx と同じように docker run で起動できます。

サンプルプロジェクト

hello-node フォルダーを作成し、その中に 2 つのファイルを置きます。

package.json:

{
  "name": "hello-node",
  "version": "1.0.0",
  "scripts": { "start": "node server.js" }
}

server.js:

const http = require("http");

const server = http.createServer((req, res) => {
  res.end("Hello from Docker!\n");
});

server.listen(3000, () => console.log("Listening on port 3000"));

お使いのパソコンにNode.js をインストールする必要はありません。Node.js はイメージ内にあります。これが Docker を使う理由です。

Dockerfile を書く

同じフォルダーに Dockerfile という名前のファイル(拡張子なし)を作成します。

# 1. 公式 Node.js イメージをベースにする
FROM node:24-alpine

# 2. コンテナ内の作業ディレクトリを設定する
WORKDIR /app

# 3. 先にパッケージ一覧をコピーして依存パッケージをインストールする
COPY package*.json ./
RUN npm install --omit=dev

# 4. 残りのコードをコピーする
COPY . .

# 5. コンテナが使用するポートを指定する
EXPOSE 3000

# 6. コンテナの起動時に実行するコマンド
CMD ["npm", "start"]

これでフォルダー内に 3 つのファイルがそろいました。

$ ls
Dockerfile
package.json
server.js

イメージをビルドする:docker build

docker build -t hello-node .

-t hello-node はイメージ名です。最後の . は、現在のディレクトリを**ビルドコンテキスト(build context)**として使うことを表します。Docker はこのディレクトリのファイルをビルドエンジンに送信します。COPY 命令でコピーできるのは、ビルドコンテキスト内のファイルだけです。

ビルド中は、ターミナルに進行状況が動的に表示されます。完了後の画面は次のようになります。

$ docker build -t hello-node .

[+] Building 9.9s (10/10) FINISHED                                                   docker:default
 => [internal] load build definition from Dockerfile                                           0.0s
 => => transferring dockerfile: 399B                                                           0.0s
 => [internal] load metadata for docker.io/library/node:24-alpine                              1.9s
 => [internal] load .dockerignore                                                              0.0s
 => => transferring context: 2B                                                                0.0s
 => [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  3.6s
 => => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => => sha256:7ce7dcbafb7f750cf0c6be3c30e34372b06dd1182fa67f6ae30782c8150c5444 0B / 444B       7.9s
 => => sha256:02f40d53b06f6b91fec0cd6a6719656da99270bff8ee54ed8dcdf4c157763fc 1.26MB / 1.26MB  0.7s
 => => sha256:3c504d5a797be28f9de43147d49cd8ecb2eea46dfaf7beb8715db695d5f0a 56.52MB / 56.52MB  1.3s
 => => sha256:e2de96513ba9eb53b431787ec8a65cdde380ac4772a3e4c4b714dcfde2a102b 3.85MB / 3.85MB  0.6s
 => => extracting sha256:e2de96513ba9eb53b431787ec8a65cdde380ac4772a3e4c4b714dcfde2a102b5      0.6s
 => => extracting sha256:3c504d5a797be28f9de43147d49cd8ecb2eea46dfaf7beb8715db695d5f0a7f0      2.2s
 => => extracting sha256:02f40d53b06f6b91fec0cd6a6719656da99270bff8ee54ed8dcdf4c157763fc1      0.1s
 => => extracting sha256:7ce7dcbafb7f750cf0c6be3c30e34372b06dd1182fa67f6ae30782c8150c5444      0.0s
 => [internal] load build context                                                              0.0s
 => => transferring context: 759B                                                              0.0s
 => [2/5] WORKDIR /app                                                                         0.0s
 => [3/5] COPY package*.json ./                                                                0.0s
 => [4/5] RUN npm install --omit=dev                                                           1.4s
 => [5/5] COPY . .                                                                             0.0s
 => exporting to image                                                                         2.7s
 => => exporting layers                                                                        0.2s
 => => exporting manifest sha256:b311d231396fab904d11e3fbac8bade8302e8e7401fe14ba1e4a736f4fed  0.0s
 => => exporting config sha256:fa1ad1088d08bf3f3f035dfe9dbed3cf5a008492316926ba23fae3e58d6852  0.0s
 => => exporting attestation manifest sha256:0ea092cede0047cad91ad77043f79ef84c7cab9153913acf  0.0s
 => => exporting manifest list sha256:62664c2b6134a7c365eacb28019730c69b7d1d0fd855c559f6994c3  0.0s
 => => naming to docker.io/library/hello-node:latest                                           0.0s
 => => unpacking to docker.io/library/hello-node:latest                                        2.5s

各 => 行が1つのステップです。

ステップ対応する Dockerfile 命令説明
load build definition—Dockerfile を読み込む
load metadata for …node:24-alpineFROMベースイメージの最新バージョンを確認する
[1/5] FROM …FROM node:24-alpineベースイメージをダウンロードする(下の sha256:… は各レイヤーのダウンロード状況で、初回は時間がかかります)
load build context—ビルドコンテキストを送信する(今回は小さなファイルが3つだけなので759Bです)
[2/5] WORKDIR /appWORKDIR/app を作成して、作業ディレクトリに切り替える
[3/5] COPY package*.json ./COPYパッケージ一覧をコピーする
[4/5] RUN npm install --omit=devRUNイメージ内にパッケージをインストールする
[5/5] COPY . .COPY残りのコードをコピーする
exporting to image—結果をイメージとして保存し、hello-node:latest という名前を付ける

EXPOSE と CMD は設定にすぎず、ビルドステップは生成されません。

実行する

$ docker images hello-node
IMAGE               ID             DISK USAGE   CONTENT SIZE   EXTRA
hello-node:latest   62664c2b6134        245MB         62.2MB

$ docker run -d -p 3000:3000 --name hello hello-node
2c4745bd0187d9ba7f0e9af096519ce9a2ad4436395490f5af24cd142f3bef4c

$ curl -s http://localhost:3000
Hello from Docker!

$ docker logs hello

> hello-node@1.0.0 start
> node server.js

Listening on port 3000
ブラウザーに Hello from Docker! と表示される
ブラウザーで http://localhost:3000 を開く
  • イメージのサイズは約245 MBで、その大部分は Node.js 本体(node:24-alpine)です。
  • docker logs にある > node server.js は npm start が出力したメッセージで、その後にアプリケーション自身の Listening on port 3000 が表示されます。

コードを変更した後に再ビルドする:キャッシュ

server.js のテキストを Hello from Docker! (v2) に変更し、もう一度ビルドします。

$ docker build -t hello-node .

[+] Building 0.4s (10/10) FINISHED                                                   docker:default
 => [internal] load build definition from Dockerfile                                           0.0s
 => => transferring dockerfile: 399B                                                           0.0s
 => [internal] load metadata for docker.io/library/node:24-alpine                              0.3s
 => [internal] load .dockerignore                                                              0.0s
 => => transferring context: 2B                                                                0.0s
 => [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => [internal] load build context                                                              0.0s
 => => transferring context: 294B                                                              0.0s
 => CACHED [2/5] WORKDIR /app                                                                  0.0s
 => CACHED [3/5] COPY package*.json ./                                                         0.0s
 => CACHED [4/5] RUN npm install --omit=dev                                                    0.0s
 => [5/5] COPY . .                                                                             0.0s
 => exporting to image                                                                         0.1s
 => => exporting layers                                                                        0.0s
 => => exporting manifest sha256:667a883ab027b5db474215519a69eef0b162988f9f6384d75c7e6e819ebc  0.0s
 => => exporting config sha256:c3795d6af309587aa9f021692bf628df45d1d38a5e12d05388447644423a01  0.0s
 => => exporting attestation manifest sha256:4598cd1b9ff7979933f7bc06c48df78bb906278618ad587a  0.0s
 => => exporting manifest list sha256:fb09b954543fd2fa9a8d93763672d6a0b8d3ac26e7751e3b3f69b79  0.0s
 => => naming to docker.io/library/hello-node:latest                                           0.0s
 => => unpacking to docker.io/library/hello-node:latest                                        0.0s

今回は 0.4 秒しかかかりませんでした。

  • [2/5]、[3/5]、[4/5] の前に CACHED と表示されています。これらの手順は前回とまったく同じなので、前回の結果がそのまま使われ、npm install は再実行されません。
  • [5/5] COPY . . だけが再実行されました。server.js が変更されたためです。
$ docker run --rm -d -p 3000:3000 --name hello hello-node
9d4e4fc490b7b2e5d4c4c5ba729359c63271cbfa7934f6fff011a4fd4d6e87ca

$ curl -s http://localhost:3000
Hello from Docker! (v2)

$ docker stop hello
hello

なぜ先に package.json をコピーするのか

Dockerfile の各行はレイヤーを生成し、Docker はそれらのレイヤーをキャッシュします。ある行(およびその行でコピーするファイル)が変更されると、その行以降のすべての手順をやり直す必要があります。

最初に COPY . . を実行すると、コードを 1 行変更するたびに npm install のキャッシュが無効になり、すべてのパッケージを再インストールすることになります。先に package*.json だけをコピーしてからインストールすれば、パッケージ一覧が変更されたときだけ再インストールが必要です。プロジェクトのパッケージが多いほど、その差は大きくなります。詳しくはビルドキャッシュと最適化を参照してください。

.dockerignore:ビルドに不要なファイルを送らない

ローカルで開発していると、フォルダーには node_modules、.git、ログファイルなど、多くのファイルが含まれていることがよくあります。動作を確認するため、フォルダーに 30 MB の node_modules を置いて、もう一度ビルドします。load build context の行に注目してください。

$ docker build -t hello-node .

[+] Building 2.0s (10/10) FINISHED                                                   docker:default
 => [internal] load build definition from Dockerfile                                           0.0s
 => => transferring dockerfile: 399B                                                           0.0s
 => [internal] load metadata for docker.io/library/node:24-alpine                              0.2s
 => [internal] load .dockerignore                                                              0.0s
 => => transferring context: 2B                                                                0.0s
 => [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => [internal] load build context                                                              0.4s
 => => transferring context: 30.01MB                                                           0.3s
 => CACHED [2/5] WORKDIR /app                                                                  0.0s
 => CACHED [3/5] COPY package*.json ./                                                         0.0s
 => CACHED [4/5] RUN npm install --omit=dev                                                    0.0s
 => [5/5] COPY . .                                                                             0.1s
 => exporting to image                                                                         1.3s
 => => exporting layers                                                                        1.0s
 => => exporting manifest sha256:5c28f64104e51f64822405ae7a67a9016f62e9c5a4c8425b5b92c47791af  0.0s
 => => exporting config sha256:21b97fa4b8887f00cdf80b0dbeb6658f8dd678b8c4008f8d1dcb5c191e82ba  0.0s
 => => exporting attestation manifest sha256:bc9366f473fd5bb6fdffec42cdcd8155bd12b28cfc6ac23d  0.0s
 => => exporting manifest list sha256:f36d091892aca26420e44f681f5279c82f8881c32eb6d77978d99ee  0.0s
 => => naming to docker.io/library/hello-node:latest                                           0.0s
 => => unpacking to docker.io/library/hello-node:latest                                        0.2s

transferring context: 30.01MB:node_modules 全体がビルドエンジンに送られています。また、COPY . . によってイメージにもコピーされ、イメージ内で npm install したバージョンが上書きされます。

.dockerignore を作成し、ビルドに送らないファイルを列挙します。

node_modules
.git
.env
*.log

もう一度ビルドします。

$ docker build -t hello-node .

[+] Building 0.4s (10/10) FINISHED                                                   docker:default
 => [internal] load build definition from Dockerfile                                           0.0s
 => => transferring dockerfile: 399B                                                           0.0s
 => [internal] load metadata for docker.io/library/node:24-alpine                              0.3s
 => [internal] load .dockerignore                                                              0.0s
 => => transferring context: 69B                                                               0.0s
 => [1/5] FROM docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => => resolve docker.io/library/node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401  0.0s
 => [internal] load build context                                                              0.0s
 => => transferring context: 159B                                                              0.0s
 => CACHED [2/5] WORKDIR /app                                                                  0.0s
 => CACHED [3/5] COPY package*.json ./                                                         0.0s
 => CACHED [4/5] RUN npm install --omit=dev                                                    0.0s
 => [5/5] COPY . .                                                                             0.0s
 => exporting to image                                                                         0.1s
 => => exporting layers                                                                        0.0s
 => => exporting manifest sha256:c7dd5ef36303b2a808ee6b594946ee3c029769363a3e7eecfbc53f1c3795  0.0s
 => => exporting config sha256:b23dccadd37348cdcc5e2ca60934e15f7f7f309f477651ab90b6f3759e2e25  0.0s
 => => exporting attestation manifest sha256:217b64bdf5112df5d8d6131ebb08c0fbdf3b30db091103a9  0.0s
 => => exporting manifest list sha256:4063457524bbb10ef5c91def983dc1385e7aac61245c61fc8a4d568  0.0s
 => => naming to docker.io/library/hello-node:latest                                           0.0s
 => => unpacking to docker.io/library/hello-node:latest                                        0.0s

ビルドコンテキストは 159B になりました。.dockerignore には次の利点があります。

  1. ビルドが速くなります。特に、大きな node_modules や .git があるプロジェクトで効果的です。
  2. これらのファイルを変更しても、COPY . . のキャッシュが無効になりません。
  3. .env や秘密鍵などの機密ファイルがイメージに含まれるのを防げます。

ホスト上の node_modules には、Windows や macOS 向けにコンパイルされたネイティブモジュールが含まれていることがあります。Linux コンテナにコピーすると、エラーが発生する可能性があります。Node.js プロジェクトでは、必ず .dockerignore で node_modules を除外してください。

コンテナを停止する

$ time docker stop hello
hello
real	0m 0.69s
user	0m 0.01s
sys	0m 0.00s

0.7 秒で停止しました。コンテナのメインプロセスは npm です。docker stop が送信した SIGTERM を node に転送し、正常に終了します。node を直接メインプロセスとして実行する場合は、プログラム内で SIGTERM を処理する必要があります。そうしないと、docker stop は 10 秒間待つことになります。実測については、Dockerfile 命令一覧を参照してください。

ヒント

  • alpine または slim バージョンのベースイメージを選ぶと、イメージのサイズを大幅に小さくできます(サイズの比較はイメージを参照)。
  • node:24-alpine のように、ベースイメージのバージョンを固定してください。node だけにしないでください。
  • CMD には JSON 配列形式(["npm", "start"])を使うことをおすすめします。動作が最も明確になります。

次のセクションでは、Dockerfile の各命令について詳しく説明します。

まとめ

  • Dockerfile は FROM から始まり、各命令が 1 つのレイヤーを生成します。
  • docker build -t 名前 . でビルドします。最後の . はビルドコンテキストです。
  • パッケージ一覧を先にコピーしてからパッケージをインストールすると、コードだけを変更したときにキャッシュを利用できます。
  • .dockerignore で node_modules、.git、.env など、不要なファイルを除外します。

練習問題

練習 1:server.js の応答メッセージを変更して再ビルドすると、どのステップに CACHED と表示されますか?

解答

FROM、WORKDIR、COPY package*.json、RUN npm install はすべて CACHED です。コードだけが変更されたため、COPY . . 以降のステップだけが再実行されます。

練習 2:Dockerfile の COPY package*.json ./ と RUN npm install を COPY . . の後に移動し、server.js をもう一度変更して再ビルドすると、何が変わりますか?

解答

RUN npm install も再実行されるため、ビルドが遅くなります。COPY . . の内容が変わると、それ以降の各レイヤーを再構築する必要があるためです。詳しくはビルドキャッシュを参照してください。

よくある質問

Dockerfile の名前は必ず Dockerfile にする必要がありますか?

デフォルトではそうです。他の名前を使う場合は docker build -f ファイル名 . で指定します。オプションは [docker build](/ja/docker/docker-build/) を参照してください。

自分のパソコンに Node.js をインストールする必要がありますか?

必要ありません。Node.js はイメージ内にあり、ビルドも実行もコンテナ内で行います。

自分で Dockerfile を書きたくありません。生成するツールはありますか?

Docker Desktop には docker init が組み込まれています。いくつかの質問に答えると Dockerfile と compose.yaml を生成できます。実際の使用例は [docker init](/ja/docker/docker-init/) を参照してください。

更新日 2026年10月14日