0からはじめるDocker+Node.jsの開発環境構築

※追記 (2018/04/15)

Next.jsなどDocker側でサーバーを立ててホストのブラウザでつなぐ場合は docker-compose.yml のapp以下に

```yml
ports:
  - "3000:3000"
```

と下記記事のように起動オプションをつけないといけないようです。
https://qiita.com/sekitaka_1214/items/3b5cfdd15fafb74789fa

※追記 (2018/11/11)

Dockerについて知識不足なところがありましたので全体的に見直しました、またNode.jsのLTSのバージョンが10系に変わりましたのでそちらを使うようにしました。

## この記事の対象者

* Node.jsを使ってフロントエンド開発を経験したことがある
* Dockerを使ったことがないけど興味があり、すぐに実践してみたい

## 前提条件

* Dockerがインストール済みの環境
    * Windows 10の場合はHyper-Vがオンの環境
* プレーンテキストが編集できるエディタ
* シェル(ターミナル)が使えること

## プロジェクトの作成

ディレクトリを作ってcdしてください。今回はとりあえず `node-playground` と名前をつけておきます。VSCodeを使う場合は

1. ファイルメニュー > 開く… を選びます
2. 適当なディレクトリに node-playground フォルダを作成し開きます
3. Dockerfileという名前でテキストファイルを作成します

## Dockerfileを書く

```Dockerfile
# hub.docker.comから公式で用意されたイメージをベースとする
FROM node:10.13-alpine
# カレントディレクトリを app に
WORKDIR /app
# デフォルトで node が起動するので sh を代わりに起動
CMD ["sh"]
```

Dockerfileの記法については [公式ページ](https://docs.docker.com/engine/reference/builder/) を参照してください。

### ビルド

Dockerfileから **イメージ** を作成するためにビルドを行います。node-playgroundフォルダにcdしておいてください。

```shell
$ docker build -t <ユーザー名>/node-playground .
```

<ユーザー名>のところは自由につけてください。上記を実行するとローカル環境にDockerfileで記述したイメージが出来上がります。

```shell
$ docker images
```

このコマンドを打つと現在のローカルに存在するイメージの一覧が表示され、先ほど作った名前のイメージがREPOSITORY列に表示されているかと思います。

### 実行

```
$ docker run --rm -it <ユーザ名>/node-playground
```

これを実行するとイメージから一時的なコンテナを作成しshが立ち上がります。

(`--rm` を使っているのはNode.jsを使用しての開発ではイメージに対しての差分が発生しないためコンテナを残しておく必要がないのと、実行する度にコンテナが作成されいつの間にか `docker ps -a` の結果が凄まじいことになってしまうのを避けるためです)

### Alpine Linuxの世界へ

```shell
/app # uname -a
Linux 9f4f6075edf7 4.9.93-linuxkit-aufs #1 SMP Wed Jun 6 16:55:56 UTC 2018 x86_64 Linux
```

Linuxで動いています (macOS/Windowsでこの表示を見た時少し感動しました)

```shell
/app # whoami
root
```

ネ申

```shell
/app # node --version
v10.13.0
```

## Hello World

package.json を作ってみますが、いまのままだとコンテナ内だけに作られてしまいコンテナの外側（MacやWindowsなど）で開いたり編集することができません。そこでDockerfileと同じ階層に app というディレクトリを作りそのフォルダをコンテナ内とシェアするよう実行時のコマンドを変更します。
ちなみにアプローチとしてはローカルのファイルをコンテナ内にコピーする方法もありますが、実行時にコピーされるだけなのでwebpackなどでwatchできず修正後都度runするのは面倒なため今回は使用しません。

一旦コンテナのシェルをexitと入力して終了し、下記で再実行します:

```shell
$ docker run -v <プロジェクトの絶対パス>:/app --rm -it ユーザー名/node-playground
```

初回実行時セキュリティ的に問題ないか確認するダイアログが表示されることがあります。ソースコードをコンテナと共有するだけなので許可してください（ログインパスワードの入力が求められます）。

![share_drive.png](https://qiita-image-store.s3.amazonaws.com/0/282588/93083f53-57fd-758b-854a-eced468f0de1.png)

ただでさえ面倒なコマンドが絶望的な状態になりました、この後改善するので今だけ辛抱してください。
再びシェルに入ったらpackage.jsonを作ります:

```shell
/app # npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (app) node-playground
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /app/package.json:

{
  "name": "node-playground",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) yes
```

ローカルに package.json が現れましたか？ `npm install` すれば package-lock.json もできあがるはずです。
続いてappフォルダ内にindex.jsを作ります:

```javascript
console.log('Hello, World!');
```

動かしてみます:

```shell
/app # node .
Hello, World!
```

:tada: 

![helloworld.png](https://qiita-image-store.s3.amazonaws.com/0/282588/5be202f9-9f66-9198-f29d-228b2fac6d0c.png)

## Docker Compose を使おう

毎回コンテナ名を指定したり、フルパスでappフォルダを指定するのは全くもって実用的ではありませんね。そこでDocker Composeを使って今までオプションで渡していた情報をファイルに記述し呼び出しやすくしてみます。
Dockerfileと同じディレクトリに docker-compose.yml というファイルを作成し次のように入力します: 

```yml
version: '3'
services:
  app:
    build: .
    volumes: 
      - ./app:/app
```

できたらコンテナ外から docker-compose 経由でシェルに入ってみます。

```shell
$ docker-compose build
```

```shell
$ docker-compose run --rm app
/app # node .
Hello, World!
```

上記であれば下記に短縮可能です

```shell
$ docker-compose run --rm app node .
Hello, World!
```

とても使いやすくなりましたね！

## まとめ

docker-composeだけを使うシンプルなスタイルでまとめました

1. プロジェクトのディレクトリを作る
1. Dockerfileを作り、 `FROM` と `WORKDIR` を指定する
1. docker-compose.yml を作る
1. `docker-compose build` でイメージ作成
1. `docker-compose run --rm app` で一時的なコンテナを作ってシェルに入る
1. 以降は通常通り `npm init` や `npm install` などをコンテナ内で実施

以上となります。お疲れさまでした。