軽量なDocker Imageを作成する

# 目的
この記事では、軽量のDocker Imageを生成するDockerfileを作成する手順を書いていきます。

# 余談
有名な仮想化ソフトとして、Virtual Box(+ Vagrant)がありますが、以下のような問題点があります。

* 仮想マシン起動が遅い
* イメージの容量が大きい
  * 2GBとか

勿論、Virtual Boxは、ホストOS(PC)の中にそのままOSを立ち上げるため、ネットワーク周り等扱いやすいと言えば扱いやすいのですが、仮想マシン立ち上げ時に時間がかかったり、ほかの人と同じ環境を共有する際、2GBのイメージを交換したりするのは大変です。

そこで、Dockerという仮想化ソフトの出番ですが、実際に使ってみると初心者は特に、以下の課題が発生するかと思います。

* Docker Imageが1GBを超える

仮想環境のイメージを共有する際、Virtual Boxとは異なり、Imageを生成するためのテキストで記述されたDockerfileを使用することに関しては、イメージ共有を効率化するのですが、何も考えずイメージを作成していしまうと、1GBのイメージになってしまい、それなりにリソースを食います。

やはり、かっこいいのは、軽量化されたイメージだと思うので、今回は自分で目的に沿ったイメージを作成したいと思います。

# 開発環境
今回のDocker Imageは、以下の環境で作成/テストを行います。

* OS: Windows 10
* Docker: Docker Toolbox version 19.03.1

# Dockerfileの作成
## ゴール
Ruby on RailsのWebアプリケーション(rails newした初期アプリ)が入っているDocker Imageを生成するDockerfileを作成します。  
ハードウェア容量が少ないサーバー等に需要があるかと思います。  
また、Docker Imageを共有するときも、軽い方がImageの生成が素早くていいと思います。

## 作成するDocker Image
Ruby公式の軽量イメージの`alpineイメージ`を使用します。  
このalpineイメージ、最小のソフトしかインストールされていない為、Ruby on Railsのアプリケーション起動に必要なソフトをインストールしながらDockerfileを作成します。  
インストールするアプリケーションのバージョンは、以下です。

* Ruby: 2.6.5
* Ruby on Rails: 6.0.1

## Dockerfileの作成手順
Dockerfileを作成するの手順は以下の様に行います。

1. ベースイメージのPull
1. ベースコンテナの立ち上げ
1. コンテナ内で必要ソフトのインストール
1. Dockerfileの作成
1. Dockerfileの実行

地道な作業になりますが、やる気をそがれず、後戻りせず行うためには、上記のような手順になるかと思います。

それでは、実際にイメージを作成していきます。  
Dockerはインストールするされていることが前提で解説をします。

## 1.ベースイメージのPull
以下のコマンドでベースとなるDocker Imageをローカルにダウンロードします。  
どのバージョンのRubyイメージがあるかは、[公式HP](https://hub.docker.com/_/ruby)の`Description`タブに記述があります。  

```
docker pull ruby:2.6.5-alpine3.10
```

## 2.ベースコンテナの立ち上げ
以下のコマンドで、Pullしたイメージを使用し、コンテナを立ち上げます。

```
docker run -it ruby:2.6.5-alpine3.10 /bin/ash
```
alpineイメージでは、`bash`は使用できないため、`/bin/ash`を指定して実行します。

## 3.コンテナ内で必要ソフトのインストール
コンテナ内でRailsアプリケーションを立ち上げるために必要なソフトをインストールします。  
インストールしたソフトやコマンドに関しては、Dockerfileまたはメモに書き残します。  
エラーが発生した場合、エラー文を読んだり、検索したりして対処を行います。  
また、コマンド実行後に、`y/n`の選択肢が出る場合は、記録を行い、Dockerfileでは`-y`などのオプションをつけて記述していきます。  

今回は、railsをインストールして、必要なパッケージをインストールする方法をとりました。  
コンテナ内で実行したコマンド

```
/ # apk update
/ # apk add yarn nodejs
/ # gem install bundler
/ # gem install rails <- C compilerなどがなく エラー
/ # apk add gcc
/ # apk add g++
/ # apk add make
/ # gem install rails <- 成功
/ # mkdir app
/ # cd app
/ # rails new . <- sqlite3がなくエラー
/ # apk add sqlite-dev
/ # rails s
... <- rails sができるまで試す
```

```text:メモ
apk update
apk add yarn nodejs
gem install bundler
apk gcc g++ make
apk add sqlite-dev
...
```

## 4.Dockerfileの作成
3でメモしたコマンドを元に、Dockerfileを作成します。

作成したDockerfileが以下です。

```text:Dockerfile
FROM ruby:2.6.5-alpine3.10

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
ADD . $APP_HOME

RUN apk update \
    && apk add  --no-cache gcc g++ make sqlite-dev yarn nodejs \
    && gem install bundler \
    && bundle install \
    && yarn install

EXPOSE 3000
CMD ["rails", "s", "-b", "0.0.0.0"]
```

## 5.Dockerfileの実行
4.で作成したDockerfileを実行します。  
railsのアプリケーションディレクトリの中に、Dockerfileを置き、イメージを生成します。  
ディレクトリは以下のような構成になります。

```
app/
   ├ Dockerfile
   ├ bin/
   ├ config.ru
   ├ Gemfile
   ├ Gemfile.lock
   ├ lib/
   ├ node_modules/
   ├ postcss.config.js
   ├ Rakefile
   ├ storage/
   ├ tmp/
   ├ yarn.lock
   ├ babel.config.js
   ├ config/
   ├ db/
   ├ log/
   ├ package.json
   ├ public/
   ├ README.md
   ├ test/
   └ vendor/
```

また、4.で`rails s`を実行した際に、`Please add gem 'tzinfo-data' to your Gemfile and run bundle install`といったエラーが出たので、ホストOSのGemfileを変更します。

```
- (修正前) gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+ (修正後) gem 'tzinfo-data'
```

以下のコマンドでDockerfileをビルドします。  
`docker build -t rails-app .`

作成したイメージを使用して上手くコンテナが起動できない場合は、以下のコマンドを使用して、ログを確認しつつ、エラーを解消していきます。  
`docker logs <コンテナID>`

作成したイメージからコンテナを立ち上げ、ブラウザなどから、Railsアプリケーションの起動が確認できれば終了です。  
`docker run -d -p 3000:3000 rails-app`  
`curl http://192.168.99.100:3000`

## 公式イメージとの比較
Rubyの公式イメージにrailsをインストールしたDocker Imageと比較しました。

|イメージ名|容量|
|------------|----------|
|rails-app|649MB|
|ruby(公式)|1.29GB|

容量はあくまでも参考にしてください。
そもそも、Rubyの公式イメージ自体、840MBあるので、今回作成したイメージが軽量化されていることがわかります。

docker-composeを使用して、ホストOS側のアプリケーションディレクトリをマウントすると、もっとイメージ自体の容量が少なくなります。
Macを使用して、ホストOSからエディタで開発をする場合はdocker-composeを使用した方がいいかもしれません。

ポイントとしては、`rails new .`をするまでの過程の中で、必要最低限のものしかインストールしない事だと思います。
例えば、`alpine-sdk`をインストールすれば、gccやmakeがインストールできるのですが、その他gitやcurlなど必要のない物もインストールされてしまいます。
なので、`gem install rails`や`bundle install`した際にエラーが出て、インストールが必要なものだけ入れるのが良いと思います。

また、今回使用した`ruby:2.6.5-alpine3.10`には、`vi`が入っていたので、この辺りも削除するとよいと思います。