開発環境のセットアップは面倒で時間がかかるもの。しかし、Dockerを使えばその悩みは一気に解消できます!
本記事では、Node.jsアプリをDockerコンテナ上で動かし、環境依存のトラブルを回避する方法を解説します。
「Dockerfileって何?」というレベルから大丈夫。この記事を読めば、あなたのWeb開発が劇的にスムーズになり、わずか数ステップでWebサイトを公開できるようになります。
必要なファイルの準備
Dockerの中にNode.jsの環境を構築するためには以下の3つのファイルを用意する必要があります。
- Dockerfile:Dockerイメージを作成するための設計図となるファイル。
- package.json:Node.jsプロジェクトの設定ファイルで、使用するパッケージを記載。
- index.js (またはアプリケーションファイル): 実際に実行するNode.jsのコード。
この中で最もコアになるのはDocerkfileです。
作成の順番としては、package.jsonとindex.jsを作成しておき、それをDockerfileの中で指定する流れになります。
①package.jsonの作成
まずはプロジェクトの依存関係を定義するファイルを作成します。
{
"name": "docker-node-app",
"version": "1.0.0",
"description": "Node.js sample app",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.19.2"
}
}nameとversionは必須項目です。
main
mainは、Node.jsプロジェクトのエントリーポイント(開始ファイル)を指定するものです。他のプロジェクトからライブラリとして利用される場合に指示する役割を果たします。
DockerではCMDで実行ファイルを指定するので必須ではありませんが、慣習的に記述することがあります。
ここではファイル名に「index.js」を指定していますが、ファイル名は他のものでも問題ありません。起動時の処理を書いたファイルを正しく指定してください。
script
scriptsはDocerfileのCMDで「npm プロパティ名」を指定した場合に、npmが実行するコマンドを指定します。
dependecies
dependeciesは依存関係のあるパッケージの指定です。ここではWEBサイト開発の基盤として「express」というパッケージをインストール指示を記述しています。
なお、^4.19.2の頭の「^(キャレット)」は、マイナーの更新があった場合に最新のものをインストールするという指示です。(4系のexpressで最新バージョンを取得します)
package.jsonとは何か?についての詳しい解説は下記をご参考ください。
②index.jsの作成
次に実際に実行するNode.jsのコードを作成します。
package.jsonに記述したExpressにより簡単にWEBサイトを公開できるようにしているため、Expressの書き方で記述します。
// index.js (Webページの内容を変更する場合)
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
// HTMLコンテンツを直接送信
res.send('<h1>こんにちは!</h1><p>Docker上のNode.js環境で公開しています。</p>');
});
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});require
requireはNode.jsの機能で、require(‘モジュール名’)とすることで外部のモジュール(ライブラリ)を読み込む命令です。
ここでは、Webアプリケーション開発を簡単にするためのフレームワークである Express をプログラムに取り込んでいます。
const app = express();
読み込んだexpressを実行し、定数appに代入します。
ルーティングの定義(リクエストの処理)
最上位のディレクトリ「/」にアクセスしたら実行する処理を記述します。ここではh1タグとpタグを直接返します。
app.get('/', (req, res) => {
// HTMLコンテンツを直接送信
res.send('<h1>こんにちは!</h1><p>Docker上のNode.js環境で公開しています。</p>');
});サーバーの起動
app.listen(port, ...)で、expressのオブジェクト(Webサーバー)を起動し、定義したport(3000番)でリクエストを待ち受けるようにします。
この行が実行されることで、Webサーバーが動作開始します。
サーバーが起動したら処理を実行します。ここではコンソールにURLを出力します。
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});③Dockerfileの作成
Dockerイメージを作成するための最も重要なDockerfileを作成します。
# 1. ベースイメージの指定
FROM node:20-alpine
# 2. 作業ディレクトリの設定
WORKDIR /usr/src/app
# 3. 依存関係ファイルのコピー
# 効率的なキャッシュのために分けてコピー
COPY package*.json ./
# 4. 依存関係のインストール
RUN npm install
# 5. アプリケーションコードのコピー
# Dockerfileと同じディレクトリにある全てのファイルを、WORKDIRにコピー
COPY . .
# 6. アプリケーションが使用するポートの公開
EXPOSE 3000
# 7. コンテナ起動時に実行するコマンド
CMD [ "npm", "start" ]Dockerfile内の、FROM, WORKDIR, RUN, EXPORSE, CMDなどはコマンドではなく「命令」です。Dockerfileを元にイメージを作成(ビルド)するときの指示になります。
FROM|ベースイメージの指定
FROMは、作成するイメージの土台となる基本イメージを指定します。
Node.jsの環境を作成するので、ベースイメージとしてNode.jsの20-alpineを指定します。
ゼロからOSを構築するのではなく、Node.js 20の実行環境と軽量なLinux OSであるAlpineが既にセットアップされた状態からスタートすることで、手間を省き、イメージサイズを小さく保ちます。
なお、FROMは以下のような基本構造になっています。
FROM リポジトリ名:メジャーバージョン タグつまりここでは、Node.jsのバージョン20系がインストールされたalpine=軽量版をベースイメージとしてとってくるという指示です。
イメージの特徴を表す「タグ(Tag)」でalpineを指定しています。
これは「Alpine Linux」のことで、非常に軽量でセキュリティを考慮したLinuxディストリビューションです。
通常のLinuxディストリビューション(例: DebianやUbuntu)に基づくイメージと比べて、サイズが格段に小さくなります。
イメージサイズが小さくなることで、ダウンロードやビルドにかかる時間が短縮され、デプロイメントが高速化します。
(ご参考)
LinuxディストリビューションやUbuntuとは何かについては下記をご参考ください。
> 【初心者向け】WSL2とは何か?インストール方法を実例でわかりやすく簡単解説(LinuxとLinuxディストリビューションの違い, Ubuntuとは何か・使い方
WORKDIR|作業ディレクトリの設定
WORKDIRは、この記述以降のコマンドをコンテナ内のどこで実行するかを指定しています。
イメージビルド時やコンテナ実行時に、ファイル操作やコマンド実行の起点を統一し、パス指定をシンプルにするためのものです。
ここでは以下のように記述しているため、以降のコマンドがコンテナ内の/usr/src/appディレクトリで実行されます。
WORKDIR /usr/src/app結論から言うとWORKDIRは必須ではありません。
WORKDIRがない場合、コマンドはルートディレクトリ「/」をデフォルトとして、その後のコマンドが実行されたディレクトリから実行されることになります。このため、都度フルパスを記述、あるいはディレクトリ移動のコマンドを記述しなければいけないということが発生します。
WORKDIRを記述しておけばディレクトリの起点が固定されるので、そこからのパスのみを書けばOKになります。
COPY|package.jsonのコピー
上記で作成したpackage.jsonとpackage-lock.jsonをイメージ内にコピーします。
コピー先は「./」です。つまり、上記のWORKDIRで指定した「user/src/app」の中になります。
COPY package*.json ./イメージ内にコピーするファイルを指定するときに「*(アスタリスク)」を使っています。これは、「package~.json」に一致するファイルを全てコピーするという意味です。
このため「package.json」だけでなく「package-lock.json」もコピーします。package-lock.jsonもコピーすることはDockerビルド時の速度と信頼性を高めるためにほぼ必須です。
RUN|パッケージ(=ライブラリ)のインストール
RUNは、イメージのビルド中に、指定したコマンドを実行します。
ここでは、npm installを実行することで、package.jsonに基づいて必要な外部ライブラリ(Expressなど)をインターネットからダウンロードします。
ダウンロードしたライブラリは、/usr/src/app/node_modulesフォルダに格納されます。
RUN npm installCOPY . |同じディレクトリの全てのファイルをコピー
再度、COPYを命令しています。ここでは「.」なので、現在のディレクトリにあるすべてのファイルを指定しています。
コピー先は「.」なので、WORKDIRで指定した、/usr/src/appにコピーします。
COPY . .このコマンドを実行すると既にコピーしてあるpackage.jsonとpackage-lock.jsonも再度コピーします。なぜこのようなことをしているのかについては下記をご参考ください。
> 【Docker】DockerfileでCOPYを複数回に分けて記述する理由|なぜCOPY . .だけではダメなのか?(レイヤーキャッシュとは何か)
EXPOSE|コンテナのポート番号を指定
EXPOSEは、コンテナが指定したポートでサービスを提供することの宣言です。
以下の場合、3000番ポートで提供することを宣言しています。(※宣言なので必須ではありません)
EXPOSE 3000この命令自体は、ホストOSからコンテナにアクセスできるようにするわけではありません。これは単なる宣言であり、実際にポートを外部に公開し接続可能にするのは、docker run -pコマンドの役割です。
もし、自分のPC(localhost)とこのコンテナをつなぎたい場合は以下のコンテナ起動時に以下のコマンドを実行する必要があります。
docker run -p 8080:3000 [イメージ名]これは、ホストOSのポート(8080)へのアクセスを、コンテナ内部のポート(3000)に転送(マッピング)し、外部に公開するという意味です。
これにより、http://localhost:8080 でアクセスできるようになり、そのリクエストがコンテナ内のNode.jsアプリ(3000番ポート)に届きます。
※index.jsでポート番号を3000に指定しているため。
CMD|コンテナ起動時のコマンド
CMDは、コンテナが起動した際に最初に実行するコマンドの指定です。
以下のようにすることで、 npm start を実行します。
CMD [ "npm", "start" ]
これにより、npmがコンテナ内のpackage.jsonを探し、scriptプロパティの中の、startスクリプトを実行します。
つまり以下の、node index.jsを実行し、Node.jsのWebサーバーを起動させます。
{
...
"scripts": {
"start": "node index.js"
},
...
}イメージを作成する
作成方法
3つの重要なファイルを作成したので、Dockerfileを元にイメージを作成(ビルド)します。
まずは、3つのファイルが同じディレクトリにあることを確認します。

DockerDesktopを起動してから、PowerShell(もしくはコマンドプロンプト)でDockerfileがあるディレクトリに移動して以下のコマンドを実行します。
docker build -t test-docker-node-app .以下のようにイメージのビルドが始まります。

ビルドは数秒で完了します。
DockerDesktopで確認すると、作成したイメージを確認できます。

コマンドの解説(docker build)
Dockerイメージ作成のために実行したのは以下のコードです。
docker build -t test-docker-node-app .Dockerイメージをビルドする際の基本コマンドは以下のようになっています。
docker build <ファイルパス>ファイルパスはDockerfileの場所です。
より実用的には -tオプションでタグ付けを行い、イメージを識別しやすいようにします。
docker build -t <イメージ名>:<タグ> <ファイルパス>例えば、「my-app」という名前でバージョンを「v1.0」、現在のディレクトリにDockerfileがある場合は以下のようにします。
docker build -t my-app:v1.0 .-tオプションでバージョンを省略した場合は、自動的に「latest」がつきます。
なお、今回使用したコマンドを分解すると以下のようになります。
| 要素 | 説明 |
docker build | DockerイメージをビルドするDockerの基本コマンドです。 |
-t | タグ (tag) を指定するためのフラグです。ビルドするイメージに名前とオプションでバージョン(タグ)を付けます。 |
test-docker-node-app | ビルドするDockerイメージの名前です。これにより、後でこの名前を使ってイメージを参照したり、コンテナを起動したりできます。バージョン(タグ)を指定しない場合、自動的に最新版を示す:latestが付きます(例: test-docker-node-app:latest)。 |
. | ビルドコンテキストの場所を示します。この場合、カレントディレクトリ (.) を指しています。このディレクトリ内のファイルを使ってイメージをビルドします。 |
コンテナの作成と起動
コンテナの作成と起動手順
イメージが作成できたら、イメージを元にしてコンテナを作成します。
docker run -it -p 8080:3000 test-docker-node-appコマンドを実行するとすぐにコンテナが作成され起動します。
その際、index.jsに記載したconsole.logの内容が出力されます。

localhost:8080にアクセスすると、index.jsで指定したHTMLがブラウザに表示されます。

以上で、DockerでNode.jsの環境を作成し、超簡単なWEBサイトを公開する作業は完了です。
イメージ作成後に初めて「docer run」コマンドを実行する場合は「コンテナの作成と起動」を合わせて行います。
一度コンテナを作成した後は「コンテナの起動」のみを行います。
コマンド解説(docker run)
Dockerのコンテナを作成&起動するために実行したのは以下のコードです。
docker run -it -p 8080:3000 test-docker-node-app
これは以下の基本構文が元になっています。
docker run -it -p <ホストポート>:<コンテナポート> <イメージ名>docker run
docker runは、指定したイメージを元に新しいコンテナを作成し、そのコンテナ内で定義されているメインコマンドを実行して起動する、という一連の処理をまとめて行います。
-it
Dockerコンテナを対話的(インタラクティブ)に操作するために、使われる2つのオプションを組み合わせたものです。
このオプションをつけておくことでDockerを起動した後も、コマンドラインから操作をすることができます。
-itオプションをつけないでコンテナを起動すると、コマンドラインからDockerを操作することができなくなります。
その場合、「ctrl + c」でコンテナを終了するしか選択肢がなくなってしまいます。
-p
-pオプションは、ポートマッピング(Port Mapping)を行うためのオプションです。これで、外部からのアクセス経路を設定します。
-p 8080:3000とすることで、外部の8080ポートを、コンテナ内の3000ポートにつなぐという指示になります。
ホスト側のポート番号(上記の場合8080)は自由に変更することができます。
指定した値で「localhost:ホストのポート番号」で指定したコンテナ内のポートにアクセスすることができます。
※コンテナ内のポート3000はinde.jsで指定しているものです。
test-docker-node-app
docker runで元になるイメージを指定しています。先ほどのdocker buildで指定したイメージ名を指定しています。
コンテナの停止
起動中のコンテナを停止する方法はいくつかあります。ここでは主な方法を紹介します。
方法1:ctrl + c
Dockerを停止するには「ctrl + c」を実行します。

この状態から以下のようになり、コンテナが停止します。

方法2:「ctrl + q」と「docker stop」
「ctrl +q」でコマンドラインで入力できるようにした後に、以下コマンドを実行することでもコンテナを停止することができます。
docker stop <コンテナID>なお、コンテナIDは、docker psで実行中のコンテナ情報を確認できます。

停止した後に、再度docker ps を実行すると先ほどのコンテナが表示されなくなり、停止したことがわかります。



