Dockerを使ってRails開発のOSやバージョンに起因するエラーを根絶しよう！

## 背景

プログラミングスクールではよく、OSやバージョンの違いによるエラーにどう対処するかがしばしば問題となります。この対応にメンターの時間やリソースを多く費やしてしまうと、肝心のプロダクトの作り込みに時間を割けないで終わってしまうという事態が発生します。

GeekSalonも例外ではなく、Webコース(Ruby on rails)では特にWindowsに特有の様々なエラーの対処に多くの時間を費やしてきました。そんな環境の違いによるエラーを減らすために有効な解決作の一つが「仮想環境」の導入です。その中でも今回はDockerを紹介していきたいと思います。


## Dockerとは
Docker社が開発している、コンテナ型の仮想環境を作成、配布、実行するためのプラットフォームです。MacやWindowsといったホストマシーンのOS上に、仮想で別のOSを立てることができるので、RubyやRailsといった、言語/フレームワークレベルのバージョンの差を気にすることなく開発が可能となります。

## 準備

### install
Dockerにサインアップしてinstallしてください。

* windowsの方 https://docs.docker.com/docker-for-windows/install/
* Macの方 https://hub.docker.com/editions/community/docker-ce-desktop-mac

terminal(command prompt)で、

```terminal:terminal
$ docker version
$ docker-compose version
```
と入力して、バージョンが表示されたら準備完了です。

## 使い方

早速、仮想環境(=dockerではこの実行単位を**コンテナ**と呼びます)を立ち上げてみましょう。まずは、以下のコマンドを入力してみてください。

```terminal:terminal
$ docker run centos:latest /bin/echo 'Hello World' # => Hello World
```

|内容|概念・役割|
|---|---|
|docker|コマンド名|
|run|コンテナ実行|
|centos|イメージ名|
|latest|イメージのバージョン名|
|/bin/echo|仮想OS上で実行するコマンド|
|'Hello World'|コマンドラインに送る引数|

#### 解説
このコマンドによってcentosの最新(latest)バージョンのコンテナが立ち上がります。`centos`には仮想実行環境に関する情報が詰め込まれており、これらは通称イメージと呼ばれます。`run`によってイメージを読み込み、そこで指示されたコマンドを実行します。イメージはホストマシーン上になければ、リモートのdocker hubというところからコピーして来るため、最初に立ち上げる時は少し時間がかかリます。


もう一つ例を見てみましょう

```terminal:terminal
$ docker run -it ruby:2.6.5
irb(main):001:0> 
```

今度は`-it`オプションをつけることでコンテナ上でコマンドを一度実行するだけでなく、立ち上がった実行環境の中に入ることができます。今回はruby(2.6.5)イメージを指定しており、rubyを対話的に実行できるirbを利用できます。


## Railsの環境を用意する

いよいよ本題です。適当なディレクトリを作成し、以下の4つファイルを作り、以下のコードをそれぞれコピペしてください。

* Gemfile
* Gemfile.lock(空のママでOK)
* Dockerfile
* docker-compose.yml

```Dockerfile:Dockerfile
FROM ruby:2.6.5
ENV LANG C.UTF-8

RUN apt-get update -qq && apt-get install -y build-essential nodejs && rm -rf /var/lib/apt/lists/*

# install nodejs
RUN curl -s -L git.io/nodebrew | perl - setup
ENV PATH /root/.nodebrew/current/bin:$PATH
RUN nodebrew install-binary v12.1.0
RUN nodebrew use v12.1.0

RUN bundle config --delete frozen \
 && apt-get update -qq && apt-get install -y build-essential \
 && curl -sL https://deb.nodesource.com/setup_9.x | bash - \
 && apt-get install -y nodejs \
 && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
 && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
 && apt-get update && apt-get install yarn \
 && rm -rf /var/lib/apt/lists/*

RUN gem install bundler

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

ENV APP_HOME /myapp
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME
```

```docker-compose.yml
version: '3'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    environment:
      RAILS_ENV: development
    volumes:
      - .:/myapp
      - bundle:/usr/local/bundle
    stdin_open: true
volumes:
  bundle:
```

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.0'
```


Dockerfileは先ほど実行したコマンドに様々な設定を加えてスクリプトにまとめたものです。また、Dockerでは立ち上げたコンテナは全て独立したものとして扱われますが、docker-composeを使えば、それらを繋げて利用できます。(ただし、今回は簡単のため、データベースとアプリケーションは分けていません。)


## ビルドしてサーバを起動
これらを準備したら、以下のコマンドを順番に実行してください。(Macだと全部で5~6分かかります。)

```terminal:terminal
$ docker-compose build # コンテナのbuild
$ docker-compose run web bundle exec rails new . --force --skip-bundle # railsアプリを作成
$ docker-compose run web bundle update
$ docker-compose run web bundle exec rails webpacker:install
$ docker-compose run web bundle exec rails db:create
```
(基本的に普段のRailsのコマンドに`docker-compose run web`がつくだけです。)
Serverの起動および停止には以下のコマンドを用います。

```terminal:terminal
$ docker-compose up      # serverの起動
$ docker-compose down    # serverの終了(別のshellから行なってください。)
```


起動に関して、他のコマンドと同様に`docker-compose run web rails s`で動かせそうな気もしますが、実はそれではうまく行きません。仮想環境というのはあくまでホストマシーンとは別の場所で動いているため、そこでlocalhostのサーバを起動しても、ホストマシンからはアクセスできないためです。(コマンド自体はもちろん実行できます。)

`docker-compose up`では、コンテナのhttpポートをホストマシーンのポートと紐付ける設定(ポートフォワーディングと言います)も一緒に行なっているため、ホストマシンからのアクセスが可能になるのです。

セットアップできればあとは簡単なのでぜひ試してみてください！!