【Docker】Rails+MySQLの環境をDockerで構築中「ポートの衝突」に遭遇してから解決するまで

## はじめに
Dockerで**Ruby on Rails + MySQLの環境**を構築中にいわゆる**「ポートの衝突」**に遭遇したので解決までを記載します。

同じような箇所でハマった方の参考になればと思います。

## この記事が役に立つ方
- DockerでRails+MySQLの開発環境を行いたいと考えている
- Docker初心者

## この記事のメリット
- ポートの衝突を回避する方法が分かる

## 環境
- macOS Mojave バージョン10.14.6
- シェル：zsh
- Ruby:2.6.5
- Rails:5.2.2
- MySQL:5.7
- Docker:19.03.5

## Dockerfile

こちらを参考に、rubyのバージョンのみ、現行安定版である**2.6.5**にしています。
[丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac) - Qiita](https://qiita.com/azul915/items/5b7063cbc80192343fc0)


```:Dockerfile
FROM ruby:2.6.5

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libpq-dev \
                       nodejs

RUN mkdir /app_name
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
ADD . $APP_ROOT
```

## docker-compose.yml

```:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db
    tty: true
```

## エラー内容

```zsh
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
上記コマンドで`rails new`したかったのですが、以下エラーが発生してしまいました。

```zsh
Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use
```

`address already in use`
 とあるので、既にポートが使われているらしい。

ここから解決していきます。
## １．ポート番号の変更（これだけでは解決せず）

過去、DockerでMySQLを使用したときもポートは3306を使用したことはあったなと思ったので、ホスト側のポートをずらしてみて検証します。

```:docker-compose.yml
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3316:3306" # ここを修正。左側はホスト、右側はコンテナを表す。
      # コンテナ間の通信は3306が使われるようなので、右側はそのまま。
```

しかし、同様のエラーが発生。

```zsh
Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use
```
ポートを変えたのに、なぜ？

※今思えば、ここで**ホスト側で同じポートが使われていないか**を以下コマンドで調べることがベストだったかもしれません。無駄撃ちが減ったはず。

```zsh
$ sudo lsof -i -P | grep "LISTEN"
```

次の策に移ります。

## ２．`docker-compose down` （これで無事解決）

以下記事をstack overflowで見つけました。（phpですがエラーは同じ）
[php - Error starting userland proxy: listen tcp0.0.0.0:3306: bind: address already in use - Stack Overflow](https://stackoverflow.com/questions/37896369/error-starting-userland-proxy-listen-tcp0-0-0-03306-bind-address-already-in)

> Probably you have already a MySQL service running in port 3306. You should close it first.
Then try to end `docker-compose down`

すでにMySQLがポート**3306**で起動している可能性があるので、とりあえず`docker-compose down`してからもう一度コマンドを実行してみろとのこと。

エラー出てたのに起動してるの？と疑問を抱きつつも、やってみます。

```zsh
$ docker-compose down
```
↓

```zsh
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
↓

無事`rails new`出来ました！

一回目のコマンドでエラーが出ながらも起動してしまっていたのか？
スッキリしませんが一応解決です。

## おわりに

これまであまりポートを意識することがなかったため、いい勉強になりました:writing_hand:

docker-compose.ymlについてもまだ理解不十分なため、次は以下記事を見て勉強しようと思います。
[Docker Compose - docker-compose.yml リファレンス - Qiita](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa)

## 参考にさせて頂いたサイト（いつもありがとうございます）
[mysql-Dockerを使用するときに「ポートの衝突」を回避する方法 - コードログ](https://codeday.me/jp/qa/20190910/1582627.html)
[mysql - How do I avoid ‘port collision’ when using docker? - Stack Overflow](https://stackoverflow.com/questions/48281970/how-do-i-avoid-port-collision-when-using-docker)
[Docker Compose - docker-compose.yml リファレンス - Qiita](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa)