Dockerで最低限のrails開発環境をコツコツ構築してみる〜第1章〜

## はじめに
コツコツと環境構築する様をアウトプットしていきます。

## ゴール
いつものやつが表示されるまで
![rails.png](https://qiita-image-store.s3.amazonaws.com/0/383351/e7c8035d-96cf-2d15-7de5-dedc3abcb1e0.png)

## 環境
- OS : macOS Mojave 10.14.3
- Docker : 18.09.2

## 準備
### ディレクトリ作成
```bash
$ mkdir rails_minimum
$ cd rails_minimum
```

### Dockerfile作成
```bash
$ touch Dockerfile
```
今回は元となるイメージとしてruby:2.5.1を指定します。

```dockerfile:Dockerfile
# 元となるイメージの指定
FROM ruby:2.5.1
```

|FROM [イメージ名]:[タグ]|元となるイメージの指定|
|---|---|

### イメージをビルドする
まずは今あるイメージを表示してみます。

```bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
```

何もしていないので、何もありません。
minimumという名前を付けてDockerfileを元にイメージをビルドします。
タグを指定することでイメージのバージョンを管理することができますが、今回は特にタグの指定はしないのでデフォルトのlatestになります。

```bash
# docker build [Dockerfile配置ディレクトリ]
$ docker build . -t minimum
```
|オプション|説明|
|---|---|
|-t [イメージ名]:[タグ]|イメージ名、タグの指定|

イメージがビルドされました！

```bash
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ruby                2.5.1               3c8181e703d2        5 months ago        869MB
minimum             latest              3c8181e703d2        5 months ago        869MB
```

### コンテナを起動する
今あるコンテナを表示してみます。

```bash
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```
|オプション|説明|
|---|---|
|-a|全てのコンテナを表示|

何もしていないので、何もありません。
先ほどビルドしたminimumというイメージからminimumという名前をつけてコンテナを作成します。

```bash
# docker create [イメージ名]
$ docker create --name minimum -it minimum
```

|オプション|説明|
|---|---|
|--name [コンテナ名]|コンテナ名の指定|

コンテナができました！

```bash
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
fed23d01ed83        minimum             "irb"               8 seconds ago       Created                                 minimum
```

が、STATUSがCreatedでは起動はしていないので起動させます。

```bash
# docker start [コンテナ名]
$ docker start minimum
```

STATUSがUpになりコンテナが起動しました！

```bash
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
fed23d01ed83        minimum             "irb"               35 seconds ago      Up 3 seconds                            minimum
```

なお、docker runでコンテナ作成+コンテナ起動もできます。

```bash
# docker run [コンテナ名]
$ docker run -d --name minimum -it minimum
```
|オプション|説明|
|---|---|
|-d|バックグラウンドで起動|
|-i|起動時にコンテナの標準出力にアタッチ|
|-t|擬似ターミナル割り当て|

コンテナ内で操作をする場合には-tが必須なようです。

### コンテナを操作する

bashに入ってみます。

```bash
# docker exec [コンテナ名] [実行コマンド]
$ docker exec -it minimum bash
root@fed23d01ed83:/# 
```

プロジェクト用のディレクトリを作成してみます。

```bash
root@fed23d01ed83:/# mkdir app
```

作成したディレクトリに移動します。

```bash
root@fed23d01ed83:/# cd app
root@fed23d01ed83:/app#
```

railsプロジェクトを初期化してみます。

```
root@fed23d01ed83:/# rails new .
bash: rails: command not found
```

「railsなんてコマンドないよー」って怒られました。
ということでrailsに必要なgemをコンテナにインストールしなければなりません。

### コンテナ内にrailsを導入する
railsに必要なgemをインストールするために、Gemfileを作成します。

```bash
root@fed23d01ed83:/app# touch Gemfile
```

Gemfileを編集します。
が、vimが使えませんね...。

```bash
root@fed23d01ed83:/app# vim Gemfile
bash: vim: command not found
```

コンテナ内にvimをインストールします。

```bash
root@fed23d01ed83:/app# apt-get update
root@fed23d01ed83:/app# apt-get -y install vim
```

vimが使えるので、Gemfileを編集します。
今回のrailsのバージョンは5.1.6にします。

```bash
root@fed23d01ed83:/app# vim Gemfile
```

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

bundle経由で、Gemfileを元にrailsに必要なgemをインストールします。

```bash
root@fed23d01ed83:/app# bundle install
```

railsに必要なgemがインストールされたようです。
railsコマンドが使えるので、バージョンを確かめておきます。

```bash
root@fed23d01ed83:/app# rails -v
Rails 5.1.6
```

### railsプロジェクトを初期化する
railsプロジェクトを初期化します。
すでにGemfileが存在するため、Gemfileが競合します。
上書いていいかを聞かれた場合は'y'を入力して下さい。
※-fオプションで強制的に上書くこともできます。

```bash
root@fed23d01ed83:/app# rails new .
```

無事作成できました。色々とできていますね。

```bash
root@fed23d01ed83:/app# ls
Gemfile  Gemfile.lock  README.md  Rakefile  app  bin  config  config.ru  db  lib  log  package.json  public  test  tmp  vendor
```

これでコンテナ内でrailsプロジェクトを初期化することができましたが、これではコンテナを削除してしまえば全て消えてしまいますし、なにより毎回こんなことをするのは面倒です。
ここからはDockerfileを改善していきます。

### Dockerfileを改善する
まずは作成したコンテナを思い切って削除してしまいましょう。

```bash
root@fed23d01ed83:/app# exit

# docker stop [コンテナ名]
$ docker stop minimum

# docker rm [コンテナ名]
$ docker rm minimum
```

イメージも削除します。

```bash
# docker rmi [イメージ名]
$ docker rmi minimum
```

ここで、今存在するコンテナとイメージを表示してみます。

```bash
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ruby                2.5.1               3c8181e703d2        5 months ago        869MB
```

minimumイメージを削除しただけなので、rubyイメージは残っていますが、元となるrubyイメージ自体には変更を加えないので、消す必要はありません。
イメージをビルドする際にすでにローカルに存在している場合はダウンロードせず、ローカルのイメージを利用してくれます。

処理をDockerfileに記述していきます。
が、全てをDockerfileに記述するわけではなく、一部はホスト側準備をします。

まずはGemfileを作成します。内容は先ほどと同じです。

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

空のGemfile.lockも作っておきましょう。

Dockerfileを編集します。

```dockerfile:Dockerfile
# FROM 元となるイメージ
FROM ruby:2.5.1

ENV APP_ROOT /app

# 作業ディレクトリを指定
# 指定したディレクトリが存在しない場合には作成してくれる
WORKDIR $APP_ROOT

# Gemfile、Gemfile.lockコピー
COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

# Gemfileを元にgemをインストール
RUN bundle install
```

||説明|
|---|---|
|ENV [変数名] [値]|環境変数を定義する|
|WORKDIR [コンテナ内ディレクトリ]|コンテナ内の作業ディレクトリを指定|
|RUN [実行コマンド]|イメージビルド時にコマンドを実行|
|COPY [ホスト側コピー元] [コンテナ内コピー先]|ホスト側からコンテナ内にコピー|

一度イメージを作成してみます。

```bash
$ docker build . -t minimum
```

このタイミングで、先ほどのgemがインストールされた状態のイメージが作成されます。
コンテナを起動させます。
このとき、ボリュームの指定をすることで、指定したディレクトリでの編集が共有されます。

```bash
$ docker run -d --name minimum -it -v $PWD:/app minimum
```

|オプション|説明|
|---|---|
|-v [ホスト側絶対パス] [コンテナ側絶対パス]|ボリュームの指定|

コンテナに入ります。

```bash
$ docker exec -it minimum bash
```

先ほどWORKDIRで指定したディレクトリがカレントディレクトリになっていることがわかります。
railsプロジェクトを初期化します。

```bash
root@888d61342746:/app# rails new -f .
```

ボリューム指定をしているため、ホスト側にもrailsのファイル群が作成されています。

```bash
$ ls
Dockerfile      Gemfile.lock    Rakefile        bin             config.ru       lib             package.json    test            vendor
Gemfile         README.md       app             config          db              log             public          tmp
```

サーバーを立ち上げてみます。

```bash
root@888d61342746:/app# rails server -d
```

エラーが出たかと思います。
railsの実行環境にはサーバーサイドでのJavaScript実行環境が必要(assetまわりのコンパイルで？)なようなのでnodejsをインストールします。

```bash
root@888d61342746:/app# apt-get update
root@888d61342746:/app# apt-get install -y nodejs
```

再度、サーバーを立ち上げる

```bash
root@888d61342746:/app# rails server -d
```

これで、晴れてサーバーが起動しました。
なにも考えずいつも通りブラウザからlocalhost:3000を表示してみると...アクセスできません！！
ブラウザからlocalhost:3000にアクセスした際にコンテナ内に立ち上がっているサーバーにアクセスできるように設定する必要があります。

### localhost:3000でサーバーにアクセスできるようにする

一旦サーバーを止めます。
バックグラウンドでサーバーを起動している場合、tmp/pids/server.pidにサーバーが利用しているプロセス番号が書かれているので確認し、直接killします。

```bash
root@888d61342746:/app# cat tmp/pids/server.pid
863
root@888d61342746:/app# kill 863
```

コンテナを止め、削除します。

```bash
root@888d61342746:/app# exit
$ docker stop minimum
$ docker rm minimum
```

3000番ポートの開放、ポートの割り当て設定をしつつ、コンテナを起動します。

```bash
$ docker run -d --name minimum -it -v $PWD:/app -p 3000:3000 --expose 3000 minimum
```

|オプション|説明|
|---|---|
|-p [ホスト側ポート番号]:[コンテナ側ポート番号]|ポートの割り当て|
|--expose [コンテナ側ポート番号]|ポートの開放|

コンテナに入り、先ほどrails new実行時に自動的に実行されたbundle installによってインストールされたgemが現在のコンテナには存在しないのでgemのインストールをします。
nodejsも消えてしまっているのでインストールし直します。

```bash
root@41febcaa682c:/app# bundle install
root@41febcaa682c:/app# apt-get update
root@41febcaa682c:/app# apt-get install -y nodejs
```

コンテナのbashに入り、外部からアクセスできる0.0.0.0を指定してサーバーを起動しなおします。

```bash
root@41febcaa682c:/app# rails server -d -b 0.0.0.0
```
ブラウザからlocalhost:3000にアクセスしてみると...できた！

![rails.png](https://qiita-image-store.s3.amazonaws.com/0/383351/6fb518d4-906e-4185-64e2-e92975b4e02a.png)

ひとまず、ゴールは達成。

もう少しだけDockerfileを改善していきます。

nodejsのインストールをイメージビルド時に行います。

```dockerfile:Dockerfile
# FROM 元となるイメージ
FROM ruby:2.5.1

# 必要なものをインストール
RUN apt-get update
RUN apt-get install -y nodejs

ENV APP_ROOT /app

# 作業ディレクトリを指定
# 指定したディレクトリが存在しない場合には作成してくれる
WORKDIR $APP_ROOT

# Gemfile、Gemfile.lockコピー
COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

# Gemfileを元にgemをインストール
RUN bundle install
```

これで、イメージをビルドした段階でnodejsのインストールホスト側のGemfileに記述してあるgemのインストールが行われます。

docker-composeを利用したさらなる改善は第2章で。
[Dockerで最低限のrails開発環境をコツコツ構築してみる〜第2章〜](https://qiita.com/ii_km/items/84bbe3e82c2f1bde40b3)