Docker + Node.js + Alpine 开发环境搭建指南
<|begin▁of▁sentence|>---
title: "Docker で Node.js の開発環境を作る (node:alpine イメージ使用)"
url: "/p/8j5m5y5"
date: "2020-09-10"
tags: ["Docker", "Node.js"]
weight: 301
---
{{% private %}}
- [Docker で Node.js アプリのイメージを作成する](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)
{{% /private %}}
何をするか?
----
ここでは、Docker コンテナ上で Node.js アプリを実行するための開発環境を作成します。
Node.js の実行環境として、Docker Hub で公開されている [node イメージ](https://hub.docker.com/_/node) を使用します。
`node` イメージにはいくつかのバリエーションがありますが、ここではサイズの小さい Alpine Linux ベースの `node:alpine` イメージを使用します。
- 参考: [Node.js の Docker イメージの種類](https://maku77.github.io/p/9ips5er)
最終的には、ホストマシン上のソースコードを編集すると、コンテナ上の Node.js アプリが自動リロードされるようにします。
プロジェクトの作成
----
まず、Docker コンテナで実行するための Node.js プロジェクトを作成します。
ここでは、プロジェクト用のディレクトリとして `hello-docker` という名前を使うことにします。
{{< code >}}
$ mkdir hello-docker
$ cd hello-docker
{{< /code >}}
### package.json の作成
`npm init` コマンドを実行して `package.json` ファイルを作成します。
すべてデフォルト値でよいので、`-y` オプションを付けて実行します。
{{< code >}}
$ npm init -y
{{< /code >}}
### ソースコードファイルの作成
簡単な Web サーバーを起動するためのコードを `index.js` として作成します。
{{< code lang="js" title="index.js" >}}
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
{{< /code >}}
### パッケージのインストール
`express` パッケージを使用するので、`npm install` でインストールしておきます。
{{< code >}}
$ npm install express
{{< /code >}}
これで、`node index.js` で Web サーバーを起動できるようになります。
現時点ではホストマシン上で Node.js がインストールされている必要がありますが、最終的には Docker コンテナ上で実行するので、ホストマシンに Node.js がインストールされていなくてもかまいません。
Docker イメージのビルド
----
Docker コンテナ上で Node.js アプリを実行するための `Dockerfile` を作成します。
{{< code lang="docker" title="Dockerfile" >}}
# ベースイメージとして node:alpine を使用
FROM node:alpine
# アプリケーションディレクトリを作成
WORKDIR /usr/src/app
# パッケージ定義ファイルをコンテナにコピー
COPY package*.json ./
# パッケージをインストール
RUN npm install
# アプリのソースコードをコンテナにコピー
COPY . .
# コンテナのポート 3000 を公開
EXPOSE 3000
# アプリを起動
CMD [ "node", "index.js" ]
{{< /code >}}
`Dockerfile` を作成したら、次のように `docker build` コマンドを実行して Docker イメージをビルドします。
ここでは、イメージのタグを `hello-docker:1.0` としています。
{{< code >}}
$ docker build -t hello-docker:1.0 .
{{< /code >}}
イメージがビルドされたら、`docker run` コマンドでコンテナを起動します。
ホストマシンのポート 3000 を、コンテナのポート 3000 に繋ぐようにします。
{{< code >}}
$ docker run -p 3000:3000 hello-docker:1.0
{{< /code >}}
Web ブラウザで `http://localhost:3000` を開いて、`Hello World!` と表示されれば成功です。
{{< code title="実行例" >}}
$ docker run -p 3000:3000 hello-docker:1.0
Example app listening at http://localhost:3000
{{< /code >}}
ここまでで、Docker コンテナ上で Node.js アプリを実行できることを確認できました。
Ctrl-C でコンテナを停止してください。
ホストのソースコードをコンテナにマウントする
----
現状の `Dockerfile` では、ソースコード (`index.js`) をイメージビルド時にコンテナ内にコピーしているため、ホストマシン上の `index.js` を変更しても、コンテナ上で実行されている Node.js アプリには反映されません。
変更を反映するには、イメージの再ビルドと、コンテナの再起動が必要です。
開発中は、ホストマシン上のソースコードの変更を、コンテナ内に即反映できると便利です。
そこで、`docker run` するときに __`-v`__ オプションを使って、ホストマシンのカレントディレクトリをコンテナ内の `/usr/src/app` ディレクトリにマウントしてしまいます。
こうすれば、イメージビルド時に `COPY` したソースコードは、マウントしたホストのファイルで上書きされるので、ホスト側のファイルを変更するとコンテナ側にも反映されます。
ただし、`package.json` が変更されたときは `npm install` を実行する必要があるので、完全にホストのファイルで上書きしてしまうと不都合が発生します。
そこで、`Dockerfile` の中では `package.json` と `package-lock.json` だけを先にコンテナにコピーして `npm install` を実行するようにしています。
こうすることで、イメージのビルド時点での `package.json` の内容に基づいて `node_modules` 以下が構築されます。
その後、`docker run` 時の `-v` オプションでホストのファイルをマウントしますが、`node_modules` ディレクトリはホスト側には存在しないので、コンテナ内にはイメージビルド時に構築された `node_modules` がそのまま残ります。
つまり、次のような流れになります。
1. イメージビルド時に `package.json` をコンテナにコピーして `npm install`
2. コンテナ起動時にホストのファイルをコンテナにマウント(`node_modules` は上書きされない)
`docker run` 時に `-v` オプションを指定して、ホストのカレントディレクトリをコンテナの `/usr/src/app` にマウントして起動します。
{{< code >}}
$ docker run -p 3000:3000 -v $(pwd):/usr/src/app hello-docker:1.0
{{< /code >}}
これで、ホストの `index.js` を変更すると、コンテナ上のファイルも変更されます。
ただし、Node.js アプリを起動しているプロセスはファイルの変更を検知して自動リロードしてはくれないので、手動でコンテナを再起動する必要があります(後述の nodemon を使うと解決します)。
node_modules をホストとコンテナで共有する場合
----
`docker run` 時の `-v` オプションで、ホストのファイルをコンテナ内にマウントする場合は、`node_modules` ディレクトリに関しては注意が必要です。
例えば、ホスト側に `node_modules` ディレクトリが存在する状態で、`-v` オプションを使ってホストのファイルをコンテナ内にマウントすると、コンテナ内の `node_modules` はホスト側の `node_modules` で上書きされてしまいます。
コンテナ内の `node_modules` は Alpine Linux 用にビルドされたネイティブコードを含んでいる可能性があるので、これをホスト(例えば macOS)の `node_modules` で上書きしてしまうと、正常に動作しなくなる可能性があります。
この問題を避けるには、次のいずれかの方法を取ります。
1. ホスト側に `node_modules` を作成しない(`.gitignore` で ignore する)
2. `docker run` の `-v` オプションで `node_modules` をマウント対象から外す
1 番目の方法は、ホスト側で `npm install` を実行しなければよいだけなので簡単です。
プロジェクトの `.gitignore` ファイルで `node_modules` を ignore する設定はしておきましょう。
2 番目の方法は、`docker run` の `-v` オプションで、ホストの `node_modules` をマウントしないように設定する方法です。
例えば、Linux では次のように指定します。
{{< code >}}
$ docker run -p 3000:3000 -v $(pwd):/usr/src/app -v /usr/src/app/node_modules hello-docker:1.0
{{< /code >}}
このように、コンテナ側の `/usr/src/app/node_modules` を、ホスト側のどのディレクトリにもマッピングしないように指定することで、コンテナ内の `node_modules` はイメージビルド時に構築されたものを使用するようになります。
ただ、この方法は Docker の仕組みをよく理解していないと難しいので、ここでは 1 番目の方法(ホスト側に `node_modules` を作らない)ことを前提に話を進めます。
つまり、開発時はホスト側で `npm install` を実行するのではなく、すべてコンテナ内で `npm install` を実行するようにします。
コンテナ内で `npm install` を実行するには、次のように `docker run` コマンドを実行します。
{{< code >}}
$ docker run -it -v $(pwd):/usr/src/app hello-docker:1.0 npm install express
{{< /code >}}
`package.json` の変更を反映するためにイメージを再ビルドする必要はなく、`docker run` 時に `npm install` を実行して `node_modules` を更新すれば、次回のコンテナ起動時からその変更が反映されます。
イメージの再ビルドは、`Dockerfile` の内容を変更したときだけ実行すればよいことになります。
nodemon でホストの変更を検知して自動リロード
----
`docker run` で `-v` オプションを指定すると、ホストのファイルを変更したときに、コンテナ内のファイルも同時に変更されるようになります。
しかし、Node.js はデフォルトではファイルの変更を検知して自動リロードはしてくれないので、変更を反映するにはコンテナを再起動する必要があります。
開発中は、[nodemon](https://nodemon.io/) のようなツールを使用すると、ファイルの変更を検知して自動的に Node.js アプリを再起動してくれます。
`nodemon` は開発時のみ必要なので、`package.json` の `devDependencies` として登録します。
{{< code >}}
$ npm install --save-dev nodemon
{{< /code >}}
`package.json` の `scripts` セクションに、`nodemon` を使った起動スクリプトを追加します。
{{< code lang="js" title="package.json" >}}
{
"name": "hello-docker",
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
{{< /code >}}
`Dockerfile` では、`CMD` で `npm run dev` を実行するように変更します。
{{< code lang="docker" title="Dockerfile" >}}
FROM node:alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "npm", "run", "dev" ]
{{< /code >}}
`Dockerfile` を変更したので、イメージを再ビルドします。
{{< code >}}
$ docker build -t hello-docker:1.0 .
{{< /code >}}
そして、コンテナを起動します。
{{< code >}}
$ docker run -p 3000:3000 -v $(pwd):/usr/src/app hello-docker:1.0
{{< /code >}}
これで、ホストの `index.js` を変更すると、コンテナ上の nodemon が変更を検知して Node.js アプリを自動リロードしてくれるようになります。
Web ブラウザで `http://localhost:3000` を開いたまま、ホストの `index.js` の `Hello World!` の部分を `Hello Docker!` などに変更して保存すると、Web ブラウザの表示も自動的に変化するはずです。
{{< code title="実行例" >}}
$ docker run -p 3000:3000 -v $(pwd):/usr/src/app hello-docker:1.0
> hello-docker@1.0.0 dev /usr/src/app
> nodemon index.js
[nodemon] 2.0.4
[nodemon] to restart at any time, enter 'rs'
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
Example app listening at http://localhost:3000
{{< /code >}}
これで、Docker コンテナ上で Node.js アプリを実行するための開発環境が整いました。
(おまけ)docker-compose を使う
----
`docker run` コマンドのオプションが長ったらしくなってきたら、`docker-compose` を使うと楽になります。
`docker-compose.yml` ファイルを作成して、コンテナの起動オプションを記述します。
{{< code lang="yaml" title="docker-compose.yml" >}}
version: '3'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/usr/src/app
{{< /code >}}
`docker-compose.yml` ファイルがあるディレクトリで、次のようにコンテナを起動します。
{{< code >}}
$ docker-compose up
{{< /code >}}
`docker-compose` コマンドを使用する場合も、`Dockerfile` の内容は同じです。
最新文章
- 激光雷达与毫米波雷达驱动下的V2X车联网自动驾驶革命
- 汽车防腐蚀涂层技术应用研究
- 三角警示牌与车身框架:现代汽车安全设计全解析
- 汽车与未来出行新趋势
- 陆风汽车性能解析 陆风越野新体验 陆风SUV驾驶感受 陆风车型推荐指南 陆风汽车科技亮点 陆风动力系统介绍 陆风四驱技术详解 陆风安全配置解析 陆风内饰设计赏析 陆风外观造型点评
- 智能座舱与电动化、自动驾驶技术重塑未来出行
- 智能驾驶革命:5G时代下的自动驾驶与车联网新生态
- 汽车四轮定位全解析:悬架系统调整与轮胎保养指南
- 发动机过热冷却系统故障需检查冷却液和风扇
- 自动驾驶辅助系统:技术成熟度与安全隐患深度解析
- 2023年自动驾驶与固态电池技术革命性突破
- 交通事故与交通规则解析
- 电动汽车三大核心技术:电池、电机、电控发展趋势解析
- 电动化浪潮下:电池技术突破与自动驾驶未来展望
- 自动驾驶与新能源革命:重塑未来出行新生态
- 电动车与燃油车保险差异:电池组、电机及电控系统保障解析
- 广汽传祺引领智能出行新时代
- 汽车翼子板:设计要点、修复技巧与日常维护指南
- 自动驾驶技术突破:激光雷达与V2X车联网引领未来交通
- 涡轮增压车漆氧化防护指南:5大保养技巧让爱车焕新如初
