【Docker】複数のコンテナ（サービス）を1サーバーで起動させてサブドメインで80ポートにアクセスする

# Background
1サーバーに複数のコンテナ（サービス）を綺麗に乗せられるDockerってすごい便利です。こんなことがしたい。
![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254216/82827602-d150-a1de-190c-14ffd63c979a.png)
四角で囲まれている部分が1サーバー、例えばlocalhostだったりAWSやGCPの1インスタンスだったりということです。
service-a.hoge.comにアクセスするとcontainer Aのサービスを利用できて、service-b.hoge.comにアクセスするとcontainer Bのサービスが利用できるという世界。

# nginx-proxy
[GitHub - jwilder/nginx-proxy: Automated nginx proxy for Docker containers using docker-gen](https://github.com/jwilder/nginx-proxy)
こちらのDocker imageを使わせてもらいます。
上の図でいうところのcontainer A、container Bにそれぞれ`VIRTUAL_HOST`を設定することでサブドメインに応じてアクセスできるようになります。

使い方はこんな感じ。

```yaml:docker-compose.yml
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

  service-a:
    image: xxxxxxxxxx
    environment:
      - VIRTUAL_HOST=service-a.hoge.com

  service-b:
    image: xxxxxxxxxx
    environment:
      - VIRTUAL_HOST=service-b.hoge.com
```
基本の型はこんな感じです。

# 実際はサービスごとにdocker-compose.ymlつくりますよね。
例えば、「[Rocket.Chat](https://rocket.chat/)」と「[Wekan](https://wekan.github.io/)」をlocalhostで展開することを考えます。それぞれのサービスのdocker-compose.yml自体が複雑になったりもするので、それらを一つのyamlファイルとして管理するのは抵抗がありました。上のyamlファイルの例では1つのyamlファイルに`nginx-proxy`、`service-a`、`service-b`を記載していますが、別々のyamlファイルに宣言することも可能です。ただし、`nginx-proxy`と`service-a`、`service-b`が同じネットワークに存在していないと互いに通信ができないのでその設定をしてあげることが注意点です。

ちょうど、「Rocket.Chat」と「Wekan」を例に出したので、これをサンプルに`http://rocketchat.localhost`に接続したら「Rocket.Chat」、`http://wekan.localhost`に接続したら「Wekan」にアクセスできるようにしてみます。

## Directory structure
```
/
├ nginx-proxy
|  └ docker-compose.yml
├ rocketchat
|  └ docker-compose.yml
└ wekan
   └ docker-compose.yml
```

## nginx-proxy/docker-compose.yml
```yaml:nginx-proxy/docker-compose.yml
version: "3.5"

services:
  nginx-proxy:
    container_name: nginx-proxy
    image: jwilder/nginx-proxy
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - proxy-network

networks:
  proxy-network:
    name: proxy_network
```
最初の例と大きく違うところは`networks`を指定したところです。ちょっと上の方で注意点としてあげましたが、この後に宣言する「Rocket.Chat」と「Wekan」のアプリケーションのコンテナとこの「nginx-proxy」のコンテナは同じネットワークに存在しなければなりません。同じネットワークに存在させるために`proxy_network`という名前のネットワークに所属することを宣言しています。
ちなみに`networks`の方で宣言している`name`というプロパティですが、versionが3.xと宣言されていないと使えないらしいので、今回の例ではversionを3.5としています。

## rocketchat/docker-compose.yml
```yaml:rocketchat/docker-compose.yml
version: "3.5"

services:
  app:
    container_name: rocketchat_app
    image: rocketchat/rocket.chat:latest
    command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 sec..."; sleep 5; done; (exit $$s)'
    restart: unless-stopped
    volumes:
      - ./uploads:/app/uploads
    environment:
      - PORT=3000
      - ROOT_URL=http://rocketchat.localhost
      - MONGO_URL=mongodb://mongo:27017/rocketchat
      - MONGO_OPLOG_URL=mongodb://mongo:27017/local
      - VIRTUAL_HOST=rocketchat.localhost
    depends_on:
      - mongo
    networks:
      - proxy-network
      - rocketchat-network

  mongo:
    container_name: rocketchat_db
    image: mongo:4.0
    restart: unless-stopped
    volumes:
      - ./data/db:/data/db
      - ./data/dump:/dump
    command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
    networks:
      - rocketchat-network

  mongo-init-replica:
    image: mongo:4.0
    command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
    depends_on:
      - mongo
    networks:
      - rocketchat-network

networks:
  proxy-network:
    name: proxy_network
  rocketchat-network:
    name: rocketchat_network
```
Rocket.Chatを起動させるための基本的なdocker-compose.ymlについては説明を省略します。（「[大人の事情でSlackが使えないのでRocket.ChatをBasic認証ありでdocker-composeで起動した - Qiita](https://qiita.com/at-946/items/26cd4e829e557b3a8a4e)」で書いてますのでよろしければ参考に〜）

ここで肝になる部分をいくつかご紹介。

### `services.app.environment`の`VIRTUAL_HOST`
ここがRocket.Chat用のURLを宣言する箇所です。今回は`http://rocketchat.localhost`を指定します。

### `services.app.networks`
`app`コンテナは`nginx-proxy`と同一ネットワークに存在する必要があるので、`proxy-network`を指定しています。`proxy-network`は`networks.proxy-network.name`で`nginx-proxy`で作成した`proxy_network`と同じであると定義しています。
また、`app`コンテナは`rocketchat-network`にも所属するようにしています。これは後ろの方で宣言している`mongo`コンテナと接続するためのネットワークです。`mongo`コンテナは`nginx-proxy`と直接接続する必要がないので敢えてネットワークを切り離しています。![Untitled Diagram-Page-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254216/772bce8f-87cf-b455-99ff-d5c0b808dae2.png)

## wekan/docker-compose.yml
```yaml:wekan/docker-compose.yml
version: "3.5"

services:
  app:
    container_name: wekan_app
    image: wekanteam/wekan:latest
    links:
      - mongo
    environment:
      - MONGO_URL=mongodb://wekandb/wekan
      - ROOT_URL=http://wekan.localhost
      - VIRTUAL_HOST=wekan.localhost
    depends_on:
      - mongo
    networks:
      - proxy-network
      - wekan-network

  mongo:
    container_name: wekan_db
    image: mongo:3.2.14
    volumes:
      - ./data:/data/db
    networks:
      - wekan-network

networks:
  proxy-network:
    name: proxy_network
  wekan-network:
    name: wekan_network
```
注目ポイントはRocket.Chatの宣言と同じなので省略。

# Check
全てのdocker-compose.ymlを起動してみて、サブドメインでアクセスするアプリケーションが制御されているか確認してみましょう！
`nginx-proxy/`、`rocketchat/`、`wekan/`のそれぞれのディレクトリで`$ docker-compose up -d`コマンドを実行し、各コンテナが完全に立ち上がったら`http://rocketchat.localhost`と`http://wekan.localhost`にアクセスしてみましょう！

# あとがき
実際にドメインを取得してマネージドクラウドなんかにアプリをたてる場合も、`VIRTUAL_HOST`の設定をそのサブドメインに宣言するだけでいける！！

# References
- [GitHub - jwilder/nginx-proxy: Automated nginx proxy for Docker containers using docker-gen](https://github.com/jwilder/nginx-proxy)
- [docker-composeで作成されるネットワークに名前をつける | blog.kamiazya.tech](http://blog.kamiazya.tech/post/docker-compose-network-name/)
- [大人の事情でSlackが使えないのでRocket.ChatをBasic認証ありでdocker-composeで起動した - Qiita](https://qiita.com/at-946/items/26cd4e829e557b3a8a4e)