Docker環境内でcreate-react-app

業務において、開発環境をDockerで提供することになり、
ビルドのために利用しているNode.jsやnpmコマンドもDockerで提供したところ、そのあたりのバージョン管理が楽ちんになったので、
自分で作っているアプリケーションも積極的にDocker化しようかと思っている今日この頃です。

自分はネットワーク周り苦手なのですが、勉強として「Dockerを使ってcreate-react-appを使ったアプリケーションを構築」をしたいと思います。
図や単語の使い方が間違っているかもしれませんが、その場合は指摘して頂けますと幸いです。

イメージは以下のような感じです。

![Untitled (2).png](https://qiita-image-store.s3.amazonaws.com/0/20685/4419226f-bff2-d4dc-cfbd-c58da30468a9.png)

# 必要なもの
- Docker for Mac
- エディタ

# 手順
## 1. docker-compose.ymlと、Dockerfileの作成
今回はこんな感じのファイル構成にします。

```
/path/to/sample-app
├── docker
│   └── node
│       └── Dockerfile
└── docker-compose.yml
```
### Dockerfile
```
FROM "node:8.15.3-alpine"
# マウント先のディレクトリに移動しておく
WORKDIR /usr/src/app
```
軽くしたいのでalpineがベースになっている[Node.js公式イメージ](https://hub.docker.com/_/node/)を使います

### docker-compose.yml
```
version: "2"
services:
  node:
    build: ./docker/node
    environment:
      - NODE_ENV=production
    volumes:
      - ./:/usr/src/app
    ports:
      - "3000:3000"
```
#### volumesについて
- プロジェクトディレクトリ直下にnode_modulesと、package.jsonを置きたいので、Mac側のプロジェクトディクトリをDockerコンテナの適当なディレクトリ（今回は/usr/src/app）にマウントします。

#### portsについて
- create-react-appで使われているwebpack-dev-serverの使用ポートがデフォルトで3000番なので、
Dockerコンテナの3000番ポートとMac側の3000番ポートを繋げます。（ポートフォワーディング）

## 2. Dockerコンテナのビルド
Mac側のプロジェクトのディレクトリに移動して、

```
$ cd /path/to/sample-app
$ docker-compose build
```

## 3. create-react-appのインストールと、アプリの作成
create-react-appはAngularCLIと違って、初期構築時にしか使わなさそうだったので、こんな感じで入れて使いました。
永続的に利用したい場合は、先程作ったDockerfileに`RUN npm i -g create-react-app`を追加するとよいかと思います。

```
$ docker-compose run --rm node sh -c "npm i -g create-react-app && create-react-app react-sample"
```
これが完了すると、プロジェクトディレクトリに、`react-sample`というディレクトリが作成されているかと思います。
私は、プロジェクトディレクトリ直下にreact-sampleの中身を置きたいので、中身を移動させてこんな感じの構成にしました。

```
.
├── README.md
├── docker
│   └── node
│       └── Dockerfile
├── docker-compose.yml
├── node_modules
├── package.json
├── public
└── src
```

#### `docker-compose run`について
https://docs.docker.com/compose/reference/run/
- `docker-compose run SERVICE`で、docker-compose.ymlに定義されたサービスに対して一回きりコマンドを実行します。
- 実行する度に新しくコンテナができていきます。`--rm`オプションを付与することで、runし終わった後にDockerのimageを自動的に消してくれます。
（なので、次runする場合はcreate-react-appがインストールされていないコンテナが使われます）


## 4. `npm install`
```
$ docker-compose run --rm node npm install
```

## 5. 開発サーバー立ち上げ
```
$ docker-compose run --rm --service-ports node npm start
```
※--service-portsが付与されていることに注意

こちらを実行後、 成功すれば下図が表示されます。
<img width="364" alt="スクリーンショット 2017-09-23 16.58.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/20685/41f6a1ac-4374-f87b-cc17-e185aaf53c2d.png">


http://localhost:3000 を開くとDcokerコンテナ内で立ち上がっているReactの開発サーバが見れるはずです。
![スクリーンショット 2017-09-23 16.55.05.png](https://qiita-image-store.s3.amazonaws.com/0/20685/47a086c7-31aa-7429-b92b-a277197c43e0.png)

# ハマったこと
## 1. http://127.0.0.1:3000 で見れない（http://0.0.0.0:3000 では見れる）  
   あれこれしているうちに、自分のMacでその現象が解決してしまったので原因が分かりませんでした…  
   一応Daemonの設定に `"ip": "127.0.0.1"` を追加するか、docker-compose.ymlの`"3000:3000"`を`"127.0.0.1:3000:3000"` とすれば表示できるようになります。
   ![image.png](https://qiita-image-store.s3.amazonaws.com/0/20685/46abfbb1-e3c3-00b5-a0d6-7af2f47aa5ec.png)
## 2. `docker-compose run` するときは、 `-p [port:]port`オプションや`--service-ports`を付与しないと、ポートフォワーディングされない  
  すでに開いているポートとの衝突を防ぐためにそのような仕様になっているとのことでした。

> The second difference is that the docker-compose run command does not create any of the ports specified in the service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports to be created and mapped to the host, specify the --service-ports flag
https://docs.docker.com/compose/reference/run/