DockerでNode.jsの環境を構築する

# はじめに
[Node.jsの公式ページ](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)に書いてあることとやっていることはほぼ同じです．
ご了承ください．

# 大まかな手順
1. Node.jsアプリケーションの作成
2. Dockerfileの作成
3. Dockerイメージをビルドする際に無視するファイルを指定
4. Dockerイメージの構築
5. Dockerコンテナの起動
6. アクセス確認


# Node.jsアプリケーションの作成
まずはローカル上にnode.jsアプリケーションを作成します．

### 作業ディレクトリの作成
Node.jsアプリケーション作成用に新規ディレクトリを作成し，そこに移動します．

```console
$ mkdir ディレクトリ名
$ cd ディレクトリ名
```

### package.jsonの作成
```console
$ touch package.json
```

```json:package.json
{
  "name": "docker_node_js",
  "version": "1.0.0",
  "description": "Node.js on Docker",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.16.1"
  }
}
```
- 今回はExpress.jsフレームワークを利用します．

### npmのバージョンを確認
```console
$ npm --version
```
- バージョン5以降が推奨（package-lock.jsonが作成されるため）

### npm install実行
```console
$ npm install
```
- npmのバージョンが5以降だと，この実行後に自動的にpackage-lock.jsonが作成される
- パッケージは，node_modulesフォルダ配下にインストールされている

### server.jsの作成
package.jsonが配置されているディレクトリに，server.jsを作成する

```console
$ touch server.js
```

```javascript:server.js
'use strict';

const express = require('express');

// Constants
const PORT = 8080;
const HOST = '0.0.0.0';

// App
const app = express();
app.get('/', (req, res) => {
  res.send('Hello World');
});

// 指定したポート番号で接続をlistenする
app.listen(PORT, HOST);

console.log(`Running on http://${HOST}:${PORT}`);
```
- 接続をlistenする，については，[こちら](https://qiita.com/DQNEO/items/7027fda87a1ecf44b9f2)を参照


# Dockerfileの作成
上記の手順でjsファイルを作成したディレクトリにDockerfileを作成します．

### 元となるimageの取得
```text:Dockerfile
FROM node:lts
```
- 今回は，[Docker Hub](https://hub.docker.com/_/node)にあるNode.jsのimageを元にimageを作成
- タグは，`lts` を選択（**lts : long time support**）（Laravelのltsについては[こちら](https://nodejs.org/ja/about/releases/)を参照）

### アプリケーションディレクトリの作成
```text:Dockerfile
WORKDIR /usr/src/app
```
- image内に，今回のアプリケーションのソースコードを格納するためのディレクトリを作成する
- 絶対パスを使用するのが推奨されている（[参考](https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#workdir)）


### アプリケーションに必要なライブラリをインストールする
```text:Dockerfile
COPY package*.json ./
RUN npm install
```
- 今回使用しているimageには，既にNode.jsとNPMがインストールされているので，その次の段階として必要なパッケージをインストールする
- ワイルドカード（*）を使用することで，package.jsonとpackage-lock.jsonがどちらもコピーされるようにする
- `npm install` に必要なファイルのみをコピーすることで，**package*.jsonに変更がなかった際にはキャッシュを利用することができるため，処理の負荷を削減することができる**（全てのファイルをコピーしてしまうと，package*.jsonに変更がなくても作業ディレクトリ内のいずれかのファイルので変更があっただけで `npm install` が実行されてしまい，キャッシュを有効利用することができない）

### アプリケーションのソースコードをimage内にコピーする
```text:Dockerfile
COPY . .
```
- `npm install` の処理を走らせた後に，作業ディレクトリの内容をimage内にコピー（バンドル）する

### 外部に公開するポート番号を指定する
```text:Dockerfile
EXPOSE 8080
```
- 役割としては，`docker run` コマンド実行時に `-p` オプションを指定することと同じ

### 作成したjsを実行する
```text:Dockerfile
CMD [ "node", "server.js" ]
```

### 最終的に完成したDockerfileの中身
```text:Dockerfile
FROM node:lts

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "node", "server.js" ]
```


# Dockerイメージをビルドする際に無視するファイルを指定
.dockerignoreファイル内に，ビルドの際に無視するファイルを指定しておきます．

```console
$ touch .dockerignore
```

```text:.dockerignore
node_modules
npm-debug.log
```
- この記述をすることにより，ローカルにインストールされたモジュールやデバッグログがDockerイメージ上にコピーされたり，イメージ上のモジュールが上書きされてしまったりするのを防ぐ．


# Dockerイメージの構築
作成したDockerfileを元にイメージをビルドしていきます．

```console
$ docker build -t node_tutorial:latest .
```
- `-t` : `名前:タグ` の形式で記述することで，イメージに名前とタグをつけることができる

```console:作成されたイメージの確認
$ docker images node_tutorial:latest
```


# Dockerコンテナの起動
```console
$ docker run -p 49160:8080 -d node_tutorial:latest
```
- `-p` : server.js内で指定したポートとlocalhostのポートを紐づける（localhostのポート:Dockerコンテナのポート）
- `-d` : バックグラウンドで起動

```console:実行しているDockerイメージの確認
$ docker ps 
```

# アクセス確認
```console
$ curl -i localhost:49160

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
...

Hello, World!
```
- `-i` : ResponseのHeader，Bodyを両方とも出力する



# ログの確認
```console
$ docker logs node_container
Running on http://0.0.0.0:8080
```



# 参考サイト
- [Node.js Web アプリケーションを Docker 化する](https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/)
- [Docker Buildにおけるリードタイム短縮のための3つの改善ポイント](https://tech.plaid.co.jp/improve_docker_build_efficiency/)