AngularのためのDockerfileの書き方

皆さん、Angular使ってますか？

私は仕事でも趣味でもAngularを使うAngular大好き人間ですが、共同開発なんかで動作確認してもらうとき、``node``入れて``@angular/cli``入れて``ng serve``して…となってしまい、結構環境が汚れてしまうのが難点だなーと思っていました。

あと、本番環境のとき``ng build --prod``して出来たビルドファイルをWinSCPで直上げするのも何とかしたい。いちいちコマンド叩いてWinSCP開くのめんどくせぇ…

でも``@angular/cli``の便利さは捨てがたい…特にオートリロードが…

というわけで、「AngularアプリをDockerの上で動かす」ことに挑戦してみましたので、知見を残しておきます。

# 仕様
* 本番環境としても開発環境としても使えること
* 本番環境ではnginxを用いること
* 開発環境ではオートリロードしたい
* 一つのDockerfileにまとめる

# 方法
Dockerのマルチステージビルドを使います。

```Dockerfile:Dockerfile
### ベースステージ ###
FROM node:lts-alpine as base

# @angular/cliをグローバルインストール
RUN npm install -g @angular/cli

# ワーキングディレクトリの設定
WORKDIR /some-angular-app

# package.jsonをコピー
COPY ./package*.json /some-angular-app/

# 一度node_modulesを削除してからnpm install
RUN rm -rf node_modules && npm install


### ビルドステージ ###
FROM base as build

# 全てのソースファイルをコピー
COPY ./ /some-angular-app/

# 本番用ビルド
RUN ng build --prod --output-path=./dist/build-by-docker


### プロダクションステージ ###
FROM nginx:alpine as prod

# ビルドステージで生成されたファイルをnginxの公開用ディレクトリにコピー
COPY --from=build /some-angular-app/dist/build-by-docker /usr/share/nginx/html

# nginx.confをコピー
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
```

Dockerfileではソースのコピーとビルド処理、ビルド結果のコピーのみを行い、
主たるコマンドはdocker-compose.ymlに書きます。

```yaml:docker-compose.yml
version: "3.4"

services:
    node:
        build:
            context: ./
            dockerfile: "Dockerfile"
            target: base
        ports:
            - "4200:4200"
        command: sh -c "ng serve --host 0.0.0.0 --poll=1000"
        volumes:
            - .:/some-angular-app
            - node_modules:/some-angular-app/node_modules
        tty: true
volumes:
    node_modules:
        driver: "local"

```

こちらは開発環境用のdocker-compose.ymlです。Dockerfileにおける``base``ステージを継承して動きます。

``volumes``でソースコードをマウントしているため、Dockerfile内で特にCOPYをしなくてもこれで動きます。
node_modulesをマウント対象から除外するのを忘れないようにして下さい。

commandですが、``ng serve --host 0.0.0.0 --poll=1000``がミソです。

Dockerコンテナは、ホストPCとlocalhostを共有していますが、コンテナ内で動いている``@angular/cli``にはそんなことは知ったこっちゃないため、``@angular/cli``が言うところの「localhost」は、Dockerコンテナの「localhost」だけなのです。

つまり、``--host``オプションで「同ネットワークに接続しているすべてのIPアドレス」からのアクセスを許可してあげなければなりません。

また、オートリロードを正しく作動させるために、``--poll``オプションでファイル変更状況をポーリングするように設定しています。単位はmsです。

```yaml:docker-compose.prod.yml
version: "3.4"

services:
    node:
        build:
            context: ./
            dockerfile: "Dockerfile"
            target: prod
        ports:
            - "8080:80"
        tty: true
```
こちらは本番用のdocker-compose.prod.ymlです。``prod``ステージを継承して動きますが、特筆して何かをしているわけではありません。

```.dockerignore
.dockerignore
.git
dist
*Dockerfile*
*docker-compose*
node_modules
```

COPY時にnode_modulesがコピーされないよう、.dockerignoreに書いておきます。

```nginx:default.conf
server {
    listen 80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;
    include /etc/nginx/mime.types;

    gzip on;
    gzip_min_length 1000;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
```
最後に、nginxのconfフォルダにコピーしている``default.conf``です。
Angularをnginxで動かすために必要な「すべてのURIに対するアクセスをindex.htmlに流す」処理を行っているだけです。適宜変更してください。

# 立ち上げ方
```bash
# 開発
$ docker-compose up --build

# 本番
$ docker-compose -f docker-compose.prod.yml up --build 
```

# まとめ
マルチステージビルドを使って、docker-composeコマンド一発でAngularアプリを立ち上げる環境を構築することができましたが、予想通りというかなんというか、直接ホストPCで``ng serve``したときよりはかなりビルドが重いです。

他人の環境を汚さないようにするだけでもかなりメリットはありますが、自分がゴリゴリ開発する立場だったら素直にホストPCに``@angular/cli``入れた方がいいと思いました。