【Docker+Nginx+Angular】Dockerfileでイメージ作ってMacでアプリ立ち上げてみた

#はじめに

あるプロジェクトで、フロントでは[Angular](https://angular.jp/)を使用していたけど、
ローカルに直にWebサーバーを立ち上げる感じだったので、
Dockerコンテナ上にAngularを入れて、Webサーバー起動できないかなと思い、
挑戦してみました！

#TL;DR
- 構成の説明
- Angularのローカル上のセットアップ
- Dockerfile解説
- docker-compose.yml解説
- nginx.conf紹介
- 実践とキャプチャ


#構成
とりあえず自分は以下のようなディレクトリ構成で進めました。

```
docker-local
├── .env 
├── docker-compose.yml
├── docker
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
└── web
    ├── app ── (Angular一式)
    └── env
        └── nginx ── site.conf
```

#Angularのローカル上のセットアップ
Angularに関連するファイル等をDockerコンテナ側にマウントするために、
一旦ローカルで、Angularのセットアップをします。

###1.`Node.js`のインストール
[nodejs.org](https://nodejs.org/ja/)に行って、
2021/03時点で、LTS(長期サポート)の推奨版`14.16.0 LTS`をダウンロード。

###2.npmの関連ディレクトリのオーナーを自分のアカウントに権限変更
```shell
$ npm config get prefix
/usr/local
$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
```
自分の環境では、アクセス権限エラーが出たので、
`/usr/local/bin, /usr/local/share, /usr/local/lib/node_modules`
に対してオーナー権限を自分に与えた。

###3.`angular/cli`のインストール
```bash
npm install -g @angular/cli
```
- `-g, --global`：グローバルインストール
    - npmのインストール場所にパッケージインストール
    - つけない場合は、カレントディレクトリのnode_modules内にインストール

###4.Angularアプリの作成
```bash
$ cd ./web
$ ng new app
? Do you want to enforce stricter type checking and stricter bundle budgets in t
he workspace?
  This setting helps improve maintainability and catch bugs ahead of time.
  For more information, see https://angular.io/strict Yes
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? Less   [ http://lesscss.org   
```
`web`のディレクトリに移動してから、`angular`アプリを`app`と言う名前で作成。
3つほど、質問されるので、適宜選択。

- `strict mode`でインストールするか
- `routing`を追加するか
- `stylesheet`のフォーマットはどうするか

###5.一応、立ち上げ確認
`localhost:4200`上に立ち上がりました！ 
これで、マウント用のフォルダはOK。
![スクリーンショット 2021-03-29 10.55.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/9cca5317-7d81-1ead-e305-26eb4f6204bb.png)

#Dockerfile
```dockerfile:Dockerfile
FROM node:14.16.0-alpine3.12 as build-stage
WORKDIR /app
COPY ./web/app/package*.json /app/
RUN npm install
COPY ./web/app/ /app/
ARG configuration=production
RUN npm run build -- --output-path=./dist/out --configuration $configuration

FROM nginx:1.17.3-alpine
COPY --from=build-stage /app/dist/out/ /var/www/html
COPY ./docker/nginx/nginx.conf /etc/nginx/
```
###解説
ローカル上でやった手順を`Dockerfile`上に書いてあげるイメージです。

`linux`は`docker`コンテナに向いている超軽量の`alpine`を使用。

今回は、[マルチステージビルド](https://docs.docker.jp/develop/develop-images/multistage-build.html)を利用してます。Dockerfileに複数の`FROM`命令を記述し、異なるベースイメージ使って、それぞれで新しいビルドステージを開始する感じです。

- `Angualr`のビルド環境として`node:14.16.0-alpine3.12`をインストール。(`build-stage`と名付け)
- Angularのビルド生成物を、Nginxのステージでコピーして使ってます。
- docker側のワーキングディレクトリを`/app`に指定
- ローカルの`package.json`を`/app/`配下にコピー
- その`package.json`を使って、`npm install`
- ローカルのAngularのソースをコピー
- ビルドを走らせて、生成物は`./dist/out`に出力
    - `configuration `はオプションだと思う。
- `nginx`のイメージをインストール
- 先述の`Angular`のビルド生成物を`Docker`上の`Nginx`のドキュメントとして扱うため、`nginx`のルートディレクトリ`/var/www/html`にコピー
- Nginx の設定ファイル`nginx.conf`もコピー

###※ビルドのポイント
`docker-compose up`をする前に、`docker-compose.yml`がある場所を**カレントディレクトリ**として、事前にdockerイメージのビルドをしておきます。
理由として、
`./web/app/`のファイル等をコピーしたいが、
`Dockerfile`は**親のディレクトリを参照することができない**ので、
今回のフォルダ構成的に、コピーすることができず、こういったやり方にしてます。(他にももっと良いやり方あるかも。)

```shell
# buildコマンド実行場所を docker build のカレントディレクトリにできる
$ docker build -f ./docker/nginx/Dockerfile -t image-name:nginx-1.17.3-alpine .
```
**※最後のドット . が大事**
※buildに`cache`使いたくない場合は、`--no-cache`つける。
※`docker-compose up --build`とかでも良いかもしれないが、自分はまだ試していない、

#docker-compose
```docker:docker-compose.yml
version: "3.8"

services:
  nginx:
    image: image-name:nginx-1.17.3-alpine
    build: 
      context: ./docker/nginx
      dockerfile: Dockerfile
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./web/env/nginx:/etc/nginx/conf.d
    logging:
      driver: "none"
    restart: always
```
※`site.conf`は、docker-compose側でマウントしちゃう感じ。（実際Dockerfile内でもかけるかも）
**※`image`の設定を、`build`した時のイメージ名にすることが大切**

#nginx.conf
※nginxについて内容の精査がまだできていないので、一旦自分のローカルで動いたものの貼り付け。
`nginx.conf`内で`site.conf`をincludeする感じ。

```conf:nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;
events {
    worker_connections 1024;
}
http {
    include      /etc/nginx/mime.types;
    default_type application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}
```

```conf:site.conf
server {
    listen      80;
    listen      8080;
    server_name localhost;
    root        /var/www/html;
    location / {
        index  index.html index.htm;
        root   /var/www/html;
        try_files $uri $uri/ /index.html =404;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/html/error;
    }
}
```

#実際に立ち上がるか確認

###コマンドを打ってコンテナ起動・・・
```shell
$ cd docker-local
$ docker-compose up -d --remove-orphans
Creating nginx ... done
```

###作成されたのでアクセス！！！
![スクリーンショット 2021-03-29 10.55.06 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/d8a0b1d5-c683-a8c0-f8eb-1fe5337abd96.png)

###おお、ちゃんと表示された。

`localhost:80`で起動してるので、URLをみると`:80`が省略されて、
ちゃんと`localhost`で立ち上がった。

デフォルトのままなので、これから何かしらのアプリでも作ってみたいと思いまする。


#まとめ

今回の学びは、
**マルチステージビルド**と
**Dockerfileが参照できる範囲**
等でした。

やっぱりまだまだ、相対パスだったり、マウントの仕方だったりが、なかなか難しいので、
その感覚をもっと養っていけたらと思いました。

あとは、`nginx`のconfiguration周りがトーシロ(素人)レベルで全くわかっていないので、
そこら辺を次は追求していきたいなと思ったりしてます。


以上、ありがとうございました。