当前位置 : 首页 > 购买指南

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` の内容は同じです。

栏目列表