Windows + VirtualBox + vagrant + Ubuntu + Docker + Docker Compose で Ruby on Rails + PostgreSQL の開発環境を構築する手順

タイトルにあるものを使って、Rails + PostgreSQL の環境を構築する手順を書きました。

Rails のソースコードは Windows のローカルにて管理する構成とします。
※Windowsのディレクトリを仮想環境へマウントし、Docker コンテナではそのマウント先を参照させる

「Docker Community Edition for Windows を入れる前に確認すること」
（https://qiita.com/lmatsul/items/9f05e76e22d862eb8620）
で書きましたが、Windows 上での Docker 実行が出来なかったので、
仕方なく仮想環境上で Docker を試すことにしました。

Windows 10 の Windows Subsystem for Linux (WSL) 上でも試そうかと思いましたが、
以下の記事のように出来ないこともあるようなので、無駄足を踏まないように最初から仮想環境を選びました…。
https://qiita.com/guchio/items/3eb0818df44fdbab3d14

# 仮想環境の構築

## VirtualBox をインストールする

https://www.virtualbox.org/wiki/Downloads
※今回試したのは バージョン 5.0.26 r108824

## vagrant をインストールする

https://www.vagrantup.com/downloads.html
※今回試したのは バージョン 2.2.4

## 今回の環境用のディレクトリを作成

C:\Users\\(ユーザー名)\dev_rails で作成したとします

最終的な構成は以下のようになります

```:構成
dev_rails
 ├ rails
 │  ├ src
 │  │  ├ Gemfile
 │  │  ├ Gemfile.lock
 │  │  └ （ここにRailsのソースコード一式が入る）
 │  ├ docker-compose.yml
 │  ├ Dockerfile
 │  └ entrypoint.sh
 └ vagrant
    └ Vagrantfile
```

## Ruby on Rails 環境構築用のディレクトリを作成

dev_rails 配下へ Ruby on Rails 環境構築用のディレクトリを作成します
今回は dev_rails\rails とします

## VagrantFile の作成

dev_rails 配下へ Vagrantfile を置くためのディレクトリを作成します
今回は dev_rails\vagrant とします

コマンドプロンプトを起動し、作成したディレクトリへ移動して `vagrant init` とコマンドを実行します

Vagrantfile が作成されるので、以下のように修正して保存します

```diff:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
-  config.vm.box = "base"
+  # Vagrant Box で 「ubuntu/bionic64（ubuntu-18.04）」を指定
+  config.vm.box = "ubuntu/bionic64"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
-  # config.vm.network "forwarded_port", guest: 80, host: 8080
+  # Port転送（Ruby on Rails 用）
+  config.vm.network "forwarded_port", guest: 3000, host: 3000

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
-  # config.vm.network "private_network", ip: "192.168.33.10"
+  # IPアドレスの固定
+  config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
-  # config.vm.synced_folder "../data", "/vagrant_data"
+  # Windows上の rails ディレクトリを /vagrant_data としてマウント
+  config.vm.synced_folder "../rails", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end
```

## 仮想環境の起動

コマンドプロンプトを起動し、Vagrantfile が置かれている場所で `vagrant up` コマンドを実行して仮想環境を起動します

```sh
$ cd dev_rails/vagrant
$ vagrant up
```

起動したらターミナルなどで `192.168.33.10` へ SSH 接続します
ログインID は `vagrant`
秘密鍵は `vagrant up` を実行した配下の .vagrant\machines\default\virtualbox にある private_key を使用してください

## Docker CE のインストール

https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-ce
を参考にインストールしていきます

> Docker には
CE（コミュニティエディション）[無償版]
と
EE（エンタープライズエディション）[有償版]
があり、Docker-CE は無償版となります。

### Docker リポジトリの登録

apt-get で docker-ce をインストールできるようにするために、以下の手順でリポジトリの設定を追加します。

```sh
# パッケージの更新
$ sudo apt-get update
# HTTPS利用のためのパッケージをインストール
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
# Docker公式の GPG を追加
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# 追加されたことを確認
$ sudo apt-key fingerprint 0EBFCD88
# リポジトリの設定
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
```

### Docker CE のインストール

以下のコマンドで Docker CE をインストールします

```sh
# パッケージの更新
$ sudo apt-get update
# Docker CE のインストール
$ sudo apt-get install docker-ce
# hello-world イメージを実行して動作確認
$ sudo docker container run hello-world
```

バージョンを指定してインストールしたい場合は、以下のようにバージョンを確認してから、バージョン指定してインストールする

```sh
# バージョン一覧を表示
$ apt-cache madison docker-ce
# バージョンを指定して Docker CE をインストール
$ sudo apt-get install docker-ce=<VERSION>
```

## Docker Compose のインストール

https://docs.docker.com/compose/install/
を参考にインストールしていきます

> Docker Compose とは、複数のコンテナを使う Docker アプリケーションを定義・実行するためのツールです。

### 
```sh
# Docker compose のダウンロード
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 実行権限の付与
sudo chmod +x /usr/local/bin/docker-compose
# 動作確認（バージョン情報が表示されればOK）
$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01
```

# Ruby on Rails 環境の構築

https://docs.docker.com/compose/rails/
を参考に進めます

### Dockerfile の作成

以下の内容のファイルをWindows側で dev_rails\rails へ作成します
※文字コードは `UTF-8`、改行コードは `LF` で作成する

```dockerfile:Dockerfile
# ベースイメージを Ruby 2.5 へ指定
# ベースイメージは https://github.com/docker-library/official-images/tree/master/library を参照
FROM ruby:2.5

# apt-get を更新し、Node.js、PostgreSQLクライアント、bundler をインストール
RUN apt-get update -qq && \
    apt-get install -y nodejs postgresql-client && \
    gem install bundler

# ./src をコンテナ内の /vagrant_data/src として追加し、作業ディレクトリを指定して bundle install する
ADD ./src /vagrant_data/src
WORKDIR /vagrant_data/src
RUN bundle install

# entrypoint.sh をコピーし、最初に実行されるものとして登録する
COPY entrypoint.sh /usr/bin/
ENTRYPOINT ["entrypoint.sh"]

# ポートの公開
EXPOSE 3000

# Rails コマンドでWebサーバを起動（ENTRYPOINTで指定された entrypoint.sh へ引き渡される）
CMD ["rails", "server", "-b", "0.0.0.0"]
```

### Rails のソースコード用のディレクトリを作成

今回は dev_rails\rails\src とします

### Gemfile の作成

上記で作成したディレクトリに以下の内容のファイルを `Gemfile` というファイル名で作成します
※文字コードは `UTF-8`、改行コードは `LF` で作成する

```gemfile:Gemfile
source 'https://rubygems.org'
gem 'rails', '~>5'
```

### Gemfile.lock の作成
Gemfile と同じ階層に `Gemfile.lock` というファイル名で空のファイルを作成します
※文字コードは `UTF-8`、改行コードは `LF` で作成する

### entrypoint.sh の作成

Rails固有の問題を修正するための entrypoint.sh を `Dockerfile と同じ階層` へ作成します。
このスクリプトはコンテナが起動されるたびに実行されます。
※文字コードは `UTF-8`、改行コードは `LF` で作成する

```sh:entrypoint.sh
#!/bin/bash
set -e

# Rails 用の server.pid が存在する場合は削除する
# （server.pid が存在したままだと起動中と判断されてしまうため）
rm -f /vagrant_data/src/tmp/pids/server.pid

# CMDで渡されたコマンドを実行
exec $@
```

### docker-compose.yml の作成

Docker Compose 用の設定ファイル（docker-compose.yml）を `Dockerfile と同じ階層` へ作成します。
データベースとWebサーバの構成設定を記述しています。
※文字コードは `UTF-8`、改行コードは `LF` で作成する

```yml:docker-compose.yml
version: '3'
services:
  db:
    # 使用するイメージを postgres に指定
    image: postgres
    # データベースのデータディレクトリを別のVolumeへ逃して永続化
    volumes:
      - data:/var/lib/postgresql/data
  web:
    # ビルドパスの指定（Dockerfileのある位置を指定する）
    build: .
    # Railsのソースコードディレクトリをマウント
    volumes:
      - ./src:/vagrant_data/src
    # ポートの設定
    ports:
      - "3000:3000"
    # dbが起動後に起動するように依存関係を設定する
    depends_on:
      - db
volumes:
  # データベースのデータ保存用Volume
  data:
    external: true
```

* ハマった点 (T_T)

最初、以下のようにWindows上のディレクトリへPostgreSQLのデータディレクトリをマウントしようとしたが、

```
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
```

とすると、以下のようなエラーにハマった。

```
FATAL:  data directory "/var/lib/postgresql/data" has wrong ownership
HINT:  The server must be started by the user that owns the data directory.
```

データの永続化のためには、今回のように Volume を作成してそこで保持するようにすることで対処可能でした。

### Rails プロジェクトの作成（まだRailsのソースコードを作成していない場合）

```sh
sudo docker-compose run web rails new . --force --no-deps --database=postgresql
```

### Rails の設定ファイルを編集

dev_rails\rails\src\config\database.yml にある Rails のデータベース設定ファイルを以下のように編集します。

```diff:database.yml
# PostgreSQL. Versions 9.1 and up are supported.
#
# Install the pg driver:
#   gem install pg
# On OS X with Homebrew:
#   gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On OS X with MacPorts:
#   gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
# On Windows:
#   gem install pg
#       Choose the win32 build.
#       Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem 'pg'
#
default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+  host: db
+  username: postgres
+  password:

development:
  <<: *default
  database: src_development

  # The specified database role being used to connect to postgres.
  # To create additional roles in postgres see `$ createuser --help`.
  # When left blank, postgres will use the default role. This is
  # the same name as the operating system user that initialized the database.
  #username: src

  # The password associated with the postgres role (username).
  #password:

  # Connect on a TCP socket. Omitted by default since the client uses a
  # domain socket that doesn't need configuration. Windows does not have
  # domain sockets, so uncomment these lines.
  #host: localhost

  # The TCP port the server listens on. Defaults to 5432.
  # If your server runs on a different port number, change accordingly.
  #port: 5432

  # Schema search path. The server defaults to $user,public
  #schema_search_path: myapp,sharedapp,public

  # Minimum log levels, in increasing order:
  #   debug5, debug4, debug3, debug2, debug1,
  #   log, notice, warning, error, fatal, and panic
  # Defaults to warning.
  #min_messages: notice

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: src_test

# As with config/secrets.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>
#
production:
  <<: *default
  database: src_production
  username: src
  password: <%= ENV['SRC_DATABASE_PASSWORD'] %>
```

## Docker Compose

### ビルド

192.168.33.10 へ SSH 接続しているターミナルで `docker-compose.yml` があるディレクトリへ移動し、以下のコマンドでビルドします。

```sh
$ cd /vagrant_data
$ sudo docker-compose build
```

### データベースのデータ保存用Volumeの作成

ビルドが完了したら以下のコマンドでデータベースのデータ保存用の Volume を作成をします

```sh
$ sudo docker volume create --name=data
```

### データベースとWebサーバを起動

Volume の作成が完了したら以下のコマンドで各サービスを起動します

```sh
# -d を付けることでバックグラウンドで実行されます
$ sudo docker-compose up -d
```

### データベースとWebサーバの起動確認

```sh
$ sudo docker-compose ps
# 以下のような表示で State が Up となっていたら起動成功
       Name                     Command               State           Ports
------------------------------------------------------------------------------------
vagrant_data_db_1    docker-entrypoint.sh postgres    Up      5432/tcp
vagrant_data_web_1   entrypoint.sh rails server ...   Up      0.0.0.0:3000->3000/tcp
```

### データベースの作成

```sh
sudo docker-compose run web rake db:create
```

### Rails の起動確認

ブラウザで `http://192.168.33.10:3000` へアクセスして Rails の画面が表示されれば、環境構築完了です。

## その他

- コンテナがうまく起動しない場合、`sudo docker-compose logs` でログを確認する
- コンテナを終了させたい場合、`sudo docker-compose down` で終了させる
- コンテナの中に入りたい場合、`sudo docker-compose exec web bash` で入ることができる  
（`web` のところは、docker-compose.yml で記述したサービス名）
- Volume の一覧を確認したい場合、`sudo docker volume ls` で確認ができる
- 特定の Volume を削除したい場合、`sudo docker volume rm [volume name]` で削除ができる