ExpressアプリをDockerで開発するための手順

## 成果物

githubに作業したものをおいておきます。
https://github.com/togana/sample-express

## ゴール

- Dockerfileを使い、親イメージからExpressアプリイメージを構築できる
- docker-composeを用いて複数コンテナの管理ができる
- Expressアプリが開発できる

## ツールのバージョン

- docker-engine 1.11.2
- docker-machine 0.7.0
- docker-compose 1.7.1
- Virtualbox 5.1.2

## Dockerホストの準備

実行環境を用意して環境変数までセットします。

```
$ docker-machine create -d virtualbox sample-express
$ docker-machine env sample-express
$ eval $(docker-machine env sample-express)
```

## Expressアプリの作成

プロジェクトディレクトリを用意します。すでにあるアプリに適応する場合は[Dockerfileでwebサーバーコンテナを作成する](## Dockerfileでwebサーバーコンテナを作成する)からどうぞ。

```
$ mkdir sample-express && cd sample-express
```

Expressアプリを作成するには`express-generate`を用いて雛形を作成しなくてはなりません(ジェネレーター使うよね？使ってください)。
しかしローカルの環境にNode.jsを導入したくないのです(nodenvとかから開放されたい)。なので `express .`すらDockerを利用します。dockerhubからNode.jsの公式イメージを取得して実行します。今回は6.3.1を使用しますが、他のバージョンが使用したい場合はTagsを参照してください。
イメージの選択には[Dockerでnode.jsアプリを作るときにイメージ選択で悩んだ話](http://qiita.com/togana/items/77a5e5aaf7d6ded9ebb2)でまとめましたので気になる方は参照ください。

```
$ docker pull node:6.3.1-slim
```

そして`package.json` を生成するために下記のコマンドを実行します。

```
$ docker run --rm -v "$PWD":/usr/src/sample-express -w /usr/src/sample-express node:6.3.1-slim npm init -y
```

```
オプションに関する補足
docker run オプション image名:タグ名 実行コマンド
--rm: 実行後のコンテナを削除します。指定しない場合はゴミが残り続けます。
-v: ホストのディレクトリをコンテナ内のディレクトリにマウントします。"$PWD"はカレントディレクトリを意味します。
-w: ワーキングディレクトリを指定します。
```

下記のように `package.json` を編集します。

```
$ docker run --rm -v "$PWD":/usr/src/sample-express -w /usr/src/sample-express node:6.3.1-slim npm install -D express-generator
```

`package.json`のscriptにexpressコマンドを使うために追加します。

```diff:package.json
"scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "express": "express"
},
```

そして、雛形を生成します。

```
$ docker run --rm -v "$PWD":/usr/src/sample-express -w /usr/src/sample-express node:6.3.1-slim yes | npm run express . -f
```

開発環境と本番環境のモジュールのバージョンを揃えるために`npm-shrinkwrap.json` を作成します

先にこの先使わない`express-generator`を削除します。

```
$ docker run --rm -v "$PWD":/usr/src/sample-express -w /usr/src/sample-express node:6.3.1-slim npm rm express-generator
```

`--dev`をつけることでdevDependenciesのバージョンも固定できます。

```
$ docker run --rm -v "$PWD":/usr/src/sample-express -w /usr/src/sample-express node:6.3.1-slim npm shrinkwrap --dev
```

雛形ができバージョン固定もできたので`.gitignore` を生成して置くと良いです。

下記のサイトでNodeを選択して生成したものを使用しています。(テンプレートを生成できるので迷ったら参考にするといいです)

https://www.gitignore.io/

[生成される.gitignoreファイル](https://www.gitignore.io/api/node)

## Dockerfileでwebサーバーコンテナを作成する

とりあえず`Dockerfile` を作成します。

```rb:Dockerfile
FROM node:6.3.1-slim
ENV APP_ROOT /usr/src/sample-express

WORKDIR $APP_ROOT
COPY package.json $APP_ROOT
COPY npm-shrinkwrap.json $APP_ROOT
RUN npm install && npm cache clean

COPY . $APP_ROOT
EXPOSE 3000
CMD ["npm", "start"]
```

ここで紹介できるテクニックはAPP_ROOTをコピーするより前に`package.json`及び`npm-shrinkwrap.json`をコピーすることでこのファイルに変更がない限り`npm install`をキャッシュを使ってビルドされるところです。

ちなみに僕はここでビルドしてイメージを再作成しても良いんですが-vオプションつけたりだとか-pオプションつけたりだとかがどうせ発生するのでdocker-composeでビルドすることにしてます。

## docker-compose.ymlによる管理

さくっと`docker-compose.yml`を作成します。

```yml:docker-compose.yml
version: '2'
services:
  app:
    build: .
    environment:
      NODE_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/sample-express
      - /usr/src/sample-express/node_modules
```

NODE_ENVをdevelopmentにしているので環境ごとに変更してください。

開発時のファイル変更をすぐ反映できるようにvolumesに`.:/usr/src/sample-express`を追加しています。
Node.jsのDockerでの開発での罠の対策としてvolumesに`/usr/src/sample-express/node_modules`を追加しています。

罠の内容はコンテナの`/usr/src/sample-express` をホストのアプリケーションディレクトリにマウント(.:/usr/src/sample-express)しているため、開発中にはまります(しかし無いとコードを変更するたびにビルドし直す必要があります)。node_modulesフォルダがホスト上に存在しないためコンテナにマウントされたタイミングでnode_modulesが消失します。ここまでの手順を上から順番に行って来た場合は問題ないのですがgit pullして開発を始める場合はアプリケーションディレクトリにnode_modulesが存在しないディレクトリをマウントしてしまうためイメージに焼き付けたnode_modulesが消失してしまうのです。そこでイメージのnode_modulesをアプリケーションディレクトリをマウントした後に更にマウントする形をとっています。

下記のコマンドでビルドして実行してみる。

```
$ docker-compose build
$ docker-compose up -d
```

ブラウザからアクセスできるか確認するためにipを確認する(雑なやり方)

```
$ docker-machine ls
NAME             ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
sample-express   *        virtualbox   Running   tcp://192.168.99.100:2376           v1.11.2
```

ブラウザからアクセスしてみる。

http://192.168.99.100:3000

下記の画面が出れば問題ないです！後はコード書いていきましょ!

<img width="333" alt="express-index.png" src="https://qiita-image-store.s3.amazonaws.com/0/82745/64b8fbd8-da8b-2fc3-d564-940f8a314693.png">