DockerToolboxで作るWindows用開発環境の構築

# はじめに
IDEを使う場合やデザイナさんのPCで開発をする場合にもDockerで手軽に開発環境を作りたい。
Docker+Virtualboxを使用するとコンテナとホストOSの間に簡単に共有フォルダが作れるので、
好きなエディタなどを使って開発を進めることができます。

# 環境

* DockerToolbox 1.9.1<br>
https://www.docker.com/products/docker-toolbox

インストーラをダウンロードして、インストールを進めるとGit, Dockerなど開発に必要なツール一式がまとめて入ります。

# ファイル構成

適当な場所に開発環境用ディレクトリを用意します。

```
+ source
 - Dockerfile
 + app
   - Gemfile
   - index.rb
```

```sh:Dockerfile
FROM ruby

RUN mkdir -p /srv/www/app
COPY app/Gemfile /srv/www/app
WORKDIR /srv/www/app
RUN bundle install
CMD ruby index.rb
```

```ruby:app/Gemfile
source "http://rubygems.org"

gem 'sinatra'
gem 'sinatra-contrib'
```

```ruby:app/index.rb
require 'sinatra'
require 'sinatra/reloader'

set :bind, '0.0.0.0'
set :port, 3000

get '/' do
  'hello'
end
```

# Dockerイメージ作成

DockerToolboxをインストールした時に入った、「Docker Qickstart Terminal」を起動します。

初回起動時は仮想マシンの作成が入るので少し時間が掛かります。
ターミナルの起動が完了したら、先ほど作ったフォルダに移動してDockerイメージを作成します。
(Cドライブへの移動は小文字のcなので注意)

```sh
$ cd /c/source
$ docker build -t web .
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
web                 latest              a8b38a36ca7d        About a minute ago   725.5 MB

```

# Dockerイメージの起動

作成したイメージを起動します。
WindowsのGitBashの場合は / スラッシュを前に付けないとうまく動かないようです。

```sh
$ cd /c/source/app
$ docker run -d -v /$(pwd):/srv/www/app -p 3000:3000 -t web
```

## 起動したイメージにアクセスしてみる

仮想マシンのIPを確認します。

```sh
$ docker-machine ip default
192.168.99.100
```

http://192.168.99.100:3000/ へアクセスして確認する

## ファイルを変更してみる

source/app/index.rbの中身を変更して反映されるか確認してみる。

```rb:app/index.rb
require 'sinatra'
require 'sinatra/reloader'

set :bind, '0.0.0.0'
set :port, 3000

get '/' do
  'hello world'
end
```

保存するとすぐに反映されているのがわかります。
このぐらいすぐに反映されるのであれば実際の開発で十分使用できると思います。

# さらに複雑な構成

実際の開発ではMySQLやNginxも含まれる構成が一般的です。
docker-composeを使って一括でコンテナの起動ができるようにします。

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/69294/0229e7ab-9db8-74a7-f122-35ee6ed3f4aa.png)

今回作成する構成は上図の様になっています。RubyプロセスがMySQLとNgnixと通信する形になります。

## ファイル構成

webに先ほど作った簡易版の構成をそのまま入れます。
注意するのはweb以下にappを配置しないとDockerイメージの構築に失敗してしまいます。

```
+ source
  - docker-compose.yml
  + web
    - Dockerfile
    + app
      - Gemfile
      - index.rb
  + ngnix
    - Dockerfile
    - nginx.conf
```

```docker-compose.yml
web:
  build: ./web
  volumes:
    - ./web/app:/srv/www/app
  links:
    - mysql
datastore:
  image: busybox
  volumes:
    - /data/nginx
    - /var/lib/mysql
nginx:
  build: ./nginx
  ports:
    - '80:80'
  volumes_from:
    - datastore
  links:
    - web
mysql:
  image: mysql
  environment:
    MYSQL_ROOT_PASSWORD: 'hoge'
  volumes_from:
    - datastore
```

MySQLのデータはDockerコンテナを終了すると消えてしまうので、専用のデータ永続化用のコンテナを用意します。
データだけ入れればいいのでbusyboxみたいな小規模を利用します。

```sh:Dockerfile
FROM ruby

RUN apt-get update && apt-get -y install mysql-client

RUN mkdir -p /srv/www/app
COPY app/Gemfile /srv/www/app
WORKDIR /srv/www/app
RUN bundle install
CMD ruby index.rb
```

先ほどと違うのはRubyでMySQLと接続するためmysql-clientをインストールする必要がある点です。

```rb:web/app/Gemfile
source "https://rubygems.org"

gem 'sinatra'
gem 'sinatra-contrib'
gem 'mysql2'
```

MySQL接続用ライブラリをインストールするようにします。

```rb:web/app/index.rb
require 'sinatra'
require 'sinatra/reloader'
require 'mysql2'

set :bind, '0.0.0.0'
set :port, '3000'

# MySQL接続
client = Mysql2::Client.new(
  :host => ENV['MYSQL_PORT_3306_TCP_ADDR'],
  :username => 'root',
  :password => ENV['MYSQL_ENV_MYSQL_ROOT_PASSWORD']
)

get '/' do
  results = client.query("SHOW DATABASES")
  tables = []
  results.each do |row|
    tables << row['Database']
  end
  
  "#{tables.join('<br>')}"
end
```

MySQLとの接続確認用の処理を追加しています。

```sh:nginx/Dockerfile
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf
```

Nginx設定ファイルを反映するためにビルドファイルを用意してからイメージ作成を行います。

```sh:nginx/nginx.conf
events {
  worker_connections  1024;
}

http {
  include       mime.types;
  default_type  application/octet-stream;
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

  sendfile        on;
  keepalive_timeout  65;
  
  access_log  /data/nginx/access.log  main;
  
  upstream backend {
    server web:3000;
  }

  server {
    listen       80;
    server_name  localhost;

    location / {
      if (-f $request_filename) { break; }
      proxy_set_header X-Real-IP  $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://backend;
    }
  }

  include /etc/nginx/conf.d/*.conf;
}
```

SinatraアプリのTPC:3000番ポートを80番ポートに流すように設定を行います。
docker-compose.yml に link: web　という名前で設定したので、
host名にwebという名前を使用することが出来るようになっています。

## Docker Composeによるイメージの起動

先ほど作ったのとは別環境ということを想定して、新しく仮想環境を作るとこから始めます。
docker-machine の createオプションを利用してdevという名前の環境を作ります。
evalコマンドを利用すると環境設定を読み込んでdockerコマンドなどが使えるようになります。

```sh
$ cd /c/source
$ docker-machine create --driver virtualbox dev
$ eval "$(docker-machine env dev)"
$ docker-compose build
$ docker-compose up
```

docker-composeコマンドを利用してイメージのビルドと起動をします。

## アクセスしてみる

先ほどと同じくIPの確認をしてから、ブラウザでアクセス。

```sh
$ docker-machine ip dev
192.168.99.100
```

http://192.168.99.100/ へアクセスして確認

# Docker環境構築をバッチ化

構築は2,3コマンドを叩くだけで作成できるようになりましたが、デザイナさんに毎回コマンドを叩いてもらうのは敷居が高いですね。
Windowsなのでバッチファイルを作成して、デスクトップなどに置いてもらって起動するだけで環境が立ち上がるようにしてみます。

```sh:start.sh
#!/bin/sh

docker-machine create --driver virtualbox dev
docker-machine start dev
eval "$(docker-machine env dev)"
docker-compose build
docker-compose up -d
```

先ほど作ったDocker構成のルート(docker-compose.ymlがある)に上記のバッチファイルを置きます。
ファイルの拡張子をshにしておくと、GitBashの関連付けファイルとなっているため、後はファイルをダブルクリックするだけで起動します。
上記のファイルは起動用なので、終了用など適宜用意しておくと便利です。

# おわりに
Docker+Virtualboxでは自動で共有フォルダを作ってくれるので、Dockerコンテナ起動にvolumeを指定するだけで、ホストOS側の好き場所のフォルダをコンテナ上に展開して、ファイルの編集ができます。

環境構築を簡易にして気持よく開発をしてもらえるようにしたいですね。