Dockerによる開発環境を立ち上げるまで【Rails + Nginx + Unicorn + MySQL】

Dockerを導入し、コンテナを立ち上げVSCodeで開発を行うところまで環境構築ができました。
備忘として導入した流れを簡単にまとめておきたいと思います。

# インストール
[Docker公式サイト](https://www.docker.com/)からDocker Desctopをダウンロード。
以下の記事を参考にしてインストールしました。

参考記事：[Dockerインストール手順＜macOS向け＞](https://sukkiri.jp/technologies/virtualizers/docker/docker-mac_install.html)

# フォルダ構成
Dockerfileは記事によって配置場所が異なりますが、個人的にはこの構成が一番綺麗にまとまってると思いました。
Dockerfileやdocker-compose.yml、それにNginxやUnicornの設定ファイルは自動生成されないので自分で作ります。

```
app
├ config
│　├ database.yml
│　└ unicorn_development.conf.rb
├ docker
│　├ web
│　│　└ Dockerfile
│　└ nginx
│　 　├ Dockerfile
│　 　└ revorite.conf
└ docker-compose.yml
```

# Docker関連ファイルの記述

以下記事を参考にさせていただきました。
参考記事：[DockerでNginx+unicorn+rails+Mysqlの開発環境を作ってみた](https://qiita.com/E6YOteYPzmFGfOD/items/509dbabeb20bf2487283)

## docker/web/Dockerfile

```dockerfile
FROM ruby:2.7.1
# dockerizeパッケージダウンロード用環境変数
ENV DOCKERIZE_VERSION v0.6.1

# パッケージの取得
RUN apt-get update && \
    apt-get install -y --no-install-recommends\
    nodejs  \
    vim \
    mariadb-client  \
    build-essential  \
    wget \
    && wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock

RUN echo "alias cp='cp -i'" >> /root/.bashrc
RUN echo "alias mv='mv -i'" >> /root/.bashrc
RUN echo "alias rm='rm -i'" >> /root/.bashrc
RUN echo "alias la='ls -al'" >> /root/.bashrc
RUN echo "alias ll='ls -l'" >> /root/.bashrc

RUN gem install bundler
RUN bundle install

COPY . /app
```

## docker/nginx/Dockerfile

```dockerfile
FROM nginx:stable
# デフォルトのNginxの設定ファイルを削除し、作成しておいた設定ファイルをコピー
RUN rm -f /etc/nginx/conf.d/*
COPY ./docker/nginx/revorite.conf /etc/nginx/conf.d/revorite.conf

RUN echo "alias cp='cp -i'" >> /root/.bashrc
RUN echo "alias mv='mv -i'" >> /root/.bashrc
RUN echo "alias rm='rm -i'" >> /root/.bashrc
RUN echo "alias la='ls -al'" >> /root/.bashrc
RUN echo "alias ll='ls -l'" >> /root/.bashrc

# -c以降の設定ファイルを指定して起動 daemon offでフォアグラウンドで起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
```

## docker/nginx/revorite.conf （Nginx設定ファイル）

```docker/nginx/revorite.conf
# log directory
error_log  /var/log/nginx.error.log;
access_log /var/log/nginx.access.log;
# max body size
client_max_body_size 2G;
upstream unicorn {
  # for UNIX domain socket setups
  server unix:/app/tmp/sockets/.unicorn.sock fail_timeout=0; 
}
server {
  listen 80;
  server_name localhost; 
  # nginx so increasing this is generally safe...
  keepalive_timeout 5;
  # path for static files
  root /app/public; 
  location @unicorn {
    # HTTP headers
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }
  location / {
    try_files $uri/index.html $uri.html $uri @unicorn;
    include /etc/nginx/mime.types;
  }
  location ~ ^/assets/(.*) {
    alias /app/public/assets/$1;
  }
  # Rails error pages
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /app/public; 
  }
}
```


## docker-compose.yml

```docker-compose.yml
version: '3'
services:
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    # dockerizeを使い、DBの起動を待ってからUnicornを起動する。
    command: dockerize -wait tcp://db:3306 -timeout 20s bundle exec unicorn -p 3000 -c /app/config/unicorn_development.conf.rb
    environment:
      TZ: Asia/Tokyo
    tty: true         # binding.pryを利用可能にするための2行
    stdin_open: true  # binding.pryを利用可能にするための2行
    depends_on:
      - db 
    ports:
      - "3000:3000"
    volumes:
      - .:/app:cached 
      # ソケット通信用ファイルをnginxコンテナと共有
      - tmp-data:/app/tmp/sockets
      # アセットファイルをnginxと共有
      - public-data:/app/public

  db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    ports: 
      - "4306:3306"
    environment:
      MYSQL_DATABASE: revorite_development
      MYSQL_ROOT_PASSWORD: password
    # dbのデータを永続化しておく
    volumes:
      - mysql-data:/var/lib/mysql

  nginx:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
    ports:
      - 80:80
    restart: always #明示的にstopさせるまでリスタートする。（失敗するたび遅延あり）
    volumes:
      - tmp-data:/app/tmp/sockets
      - public-data:/app/public
    depends_on:
      - web 

volumes:
  public-data:
  tmp-data:
  mysql-data:
```

## config/database.yml
```database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  host: db
  username: root
  password: password
  database: revorite_development
```

## config/unicorn_development.conf.rb

```config/unicorn_development.conf.rb
# set lets
$worker  = 2
$timeout = 30
$app_dir = "/app"
$listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
$std_log = File.expand_path 'log/unicorn.log', $app_dir
# set config
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid
# loading booster
preload_app true
# before starting processes
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end
# after finishing processes
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end
```

## コンテナ作成・起動
```bash
$ docker-compose build  # イメージの作成
$ docker-compose up -d  # コンテナの作成・起動
```

# 詰まった点
他の記事ではあまり語られておらず、自分が詰まった点をいくつか。

## Unicornの起動で失敗する
関連ファイルを作り終え、いざ```docker-compose build```と```docker-compose up -d```を叩く。
そして```docker ps```を叩くと・・・

```
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                               NAMES
11e605b70c51   revorite_nginx   "/docker-entrypoint.…"   21 minutes ago   Up 21 minutes   0.0.0.0:80->80/tcp                  revorite_nginx_1
313d53cbbc27   mysql:5.7        "docker-entrypoint.s…"   21 minutes ago   Up 21 minutes   33060/tcp, 0.0.0.0:4306->3306/tcp   revorite_db_1
``` 

あれ？**アプリサーバ**は？？？
で、```docker-compose logs```でログを見てみると

```
web_1    | 2021/03/04 11:01:27 Command exited with error: exit status 1
```

```exit status 1```とは一体・・・
exitしたということは一度起動して即落ちた、と読めるので、落ちたコンテナに入ってログも見てみたのですがそれらしいログは出ていない。
（落ちたコンテナに入る方法は[こちら](https://qiita.com/mom0tomo/items/35dfacb628df1bd3651e)を参考にしました）

最終的に、```database.yml```に```host: db```の記述が漏れているためだということが判明。一文追加し、無事起動しました。
こんな単純なミスなのですが一日使ってしまったので、Dockerfileとdocker-composeだけを書いて満足しないように注意です。

## コンテナ内でGitが使えない
GitをインストールしSSH認証もできるようにしたのですが、どうしてもgit pushだけが弾かれてしまうんですよね。（commitまではできる）

```
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
```

仕方ないのでgit関連の操作はホスト側で実行しています（ホスト側でコンテナ内のソースコードに対しマウントしている）。git操作もコンテナ内で完結できたらベストだったのですが。
　
# コンテナ内で開発を行う（VSCode: Remote Development）

コンテナ内で開発を行うには、VSCodeでコンテナ内のソースを参照・開発できるRemote Developmentという拡張機能を使います。
参考記事：[VS Code Remote Development で Docker 開発環境を利用する](https://qiita.com/yito/items/1edd82414dfc2ab2a7bc)

前述の起動コマンドで起動できていれば、参考記事の通りに起動するだけで特に問題は起きないはず。

# その他補足

docker関連のコマンドは長いので、~/.bashrcにエイリアスを設定すると便利。
またエラーが起きる度にコンテナやイメージの削除コマンドを打つのが大変のため、こちらも設定しておくと快適です。

```bash
# 例
alias dco='docker-compose'
alias docker-purge='docker stop $(docker ps -q) && docker rm $(docker ps -a -q) && docker rmi $(docker images -q -a) -f'
```

改善点や誤りなどありましたらコメントで指摘いただけると幸いです。