Docker ComposeでApache+Rails環境を構築

# 最終的なフォルダ構成

```:最終的なフォルダ構成
myapp/
    |--docker-compose.yml
    |--apache/
        |--html/
    |--rails/
        |--app/
        |--bin/
        |--config/
        |--db/
        |--lib/
        |--log/
        |--public/
        |--storage/
        |--test/
        |--tmp/
        |--vendor/
        |--config.ru
        |--Dockerfile
        |--entrypoint.sh
        |--Gemfile
        |--Gemfile.lock
        |--Rakefile
    |--README.md
    |--.env
    |--.gitignore
    |--.git

```

# .git

Gitの初期化を行う

```bash:bash
git init
```

# .gitignore

- Gitの管理下に置きたくないファイルやディレクトリを管理するためのファイル

```:.gitigrore
/.env
```

# .env

- 環境変数を管理するために使われるファイル

```:.env
# commons
WORKDIR=app
CONTAINER_PORT=3000
RAILS_PORT=3000
APACHE_PORT=8080

# db
# POSTGRES_PASSWORD=password
```

# docker-compose.yml

まずプロジェクトフォルダを作成し、そこにrailisフォルダ、apacheフォルダとdockercompose.ymlファイルを作成する。

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

services:
  
  apache:
    image: httpd:alpine
    container_name: apache
    volumes:
      - ./apache/html:/usr/local/apache2/htdocs
    # httpdイメージでは80番ポートを公開している
    ports:
      - "$APACHE_PORT:80"
    depends_on:
      - rails

  # railイメージを構築、railsコンテナを構築、起動
  rails:
    # railsフォルダの中にあるDockerfileをビルド
    build:
      context: ./rails
      # Dockerイメージをビルドする際にDockerfileに渡す値を指定
      args:
        WORKDIR: $WORKDIR
    container_name: rails
    environment:
      # rack-corsで使用する
      ACCESS_CONTROL_ALLOW_ORIGIN: "localhost:$APACHE_PORT"
    # サーバの起動フラッグを削除(起動されていると勘違いしてしまわないように)
    # ポートを$CONTAINER_PORTとしてサーバ起動(Dockerfile内でも起動しているが)
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p $CONTAINER_PORT -b '0.0.0.0'"
    # コンテナ中のappフォルダをローカルのappフォルダに結びつける（コンテナを起動する際にホストマシン上のデータがコンテナにマウントされる）
    volumes:
      - ./rails:/$WORKDIR
    # コンテナのポートとホストのポートを対応付ける
    ports:
      - "$RAILS_PORT:$CONTAINER_PORT"
    # attachに必要,ターミナルの入出力がコンテナのbashとつながる？,attachしないとブラウザは一生読み込み中に
    stdin_open: true
    # これがないとattachしてもすぐ切れてしまう？
    tty: true
```

## CORSの設定
- CORSとは、異なるオリジン感の通信を許可する仕組み
    1. Gemのインストール
        1. railsディレクトリのGemfileを開く
        2. 26行目付近の`gem 'rack-cors'`のコメントを外す
        3. Dockerイメージを再ビルド
    2. 設定ファイルの編集
        1. api/config/initializers直下にあるcors.rbが設定ファイル
        2. 下記のように編集

            ```cors.rb
            # Be sure to restart your server when you modify this file.

            # Avoid CORS issues when API is called from the frontend app.
            # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

            # Read more: https://github.com/cyu/rack-cors

            Rails.application.config.middleware.insert_before 0, Rack::Cors do
              allow do
                # docker-compose.ymlで指定した値かその値がNullの場合は、空文字を入れる（エラーにならないように）
                origins ENV["ACCESS_CONTROL_ALLOW_ORIGIN"] || ""

                # 許可したいリソースファイル
                resource '*',
                  headers: :any,
                  # getのみ許可
                  methods: [:get]
              end
            end
            ```

# Dockerfile

railsイメージはDockerfileから作成するためDockerfileとentrypoint.shをrailsフォルダの下に作成する。

```:現在のフォルダ構成
myapp/
    |--docker-compose.yml
		|--apache/
    |--rails/
      |--Dockerfile
      |--entrypoint.sh

```

```Dockerfile:Dockerfile
FROM ruby:2.7.2

RUN apt update -qq && \
    apt install -y build-essential \
    libpq-dev

# Dockerfile内で使用する変数名を指定    
ARG WORKDIR
ENV APP_ROOT /$WORKDIR
RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT
# このファイル（Dockerfile）と同じ階層にあるGemfile等をコンテナにコピー
COPY ./Gemfile $APP_ROOT/Gemfile
COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock
# コンテナのGemfileを参考にGemをインストール
RUN bundle install
# このファイルが含まれているディレクトリをコンテナのルートディレクトリにコピー
COPY . $APP_ROOT

COPY ./entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

CMD ["rails", "server", "-b", "0.0.0.0"]
```

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

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

```

# GemfileとGemfile.lock

```:現在のフォルダ構成
myapp/
    |--docker-compose.yml
		|--apache/
    |--rails/
      |--Dockerfile
      |--entrypoint.sh
      |--Gemfile
      |--Gemfile.lock

```

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

GemfileはRubyのライブラリをまとめたもので、Dockerfile内の`bundle install`ではこのファイルを元にgemたちをインストールしている。
Gemfile.lockはGemfileをもとに実際にインストールされたgemの一覧とバージョンが記載されたファイルで、ここでは空のファイルを作成しておく。

# イメージの構築、コンテナの作成・起動

ここまででとりあえず必要なファイルが揃ったので、イメージの構築等を行っていく。

## rails new —api

`rails new`はrailsアプリケーションの雛形を作成してくれるコマンドで、railsサーバを立ち上げるのに必要なgem等を作成してくれる。今回railsはAPIサーバとして用いるためAPIモードでアプリを作成するように`--api`をオプションとして付けている。

```bash:bash
docker-compose run rails rails new . --api --force --skip-bundle

```

`docker-compose run`は引数で指定したサービスについて、イメージの構築から、コンテナの作成・起動までを行ってくれるコマンドで、それプラスサービス名の後にコマンドを打つとコンテナ内で実行してくれる。
ここではサービスにDockerfileで指定したrails:を指定しており、プラスアルファのコマンドとして`rails new . `を行っている。

`--force`は既存のGemfileを上書きするためのオプションである。
後で、イメージを構築する際に`bundle install`は行われるので、ここでは`--skip-bundle`としている。

## イメージの構築

```bash:bash
docker-compose build

```

このとき、Dockerfileよりrailsのイメージが作成される。

## コンテナの作成・起動

```bash:bash
docker-compose up

```

httpdのイメージはこのときに、Docker Hubからローカルに持ってきている。

localhost:8080で`apache/html`にアクセスできる。localhost:3000でrailsサーバにアクセスできる。

## 補足

`docker-compose build`で`bundle install`は行われるが、volumeとは紐付かないらしく、コンテナを起動する際にエラーが出る恐れがある。そのときは、volumeと紐づけられる`run`コマンドを実行する。

```bash
$ docker-compose run rails bundle install
```

または、`rails new`で`--skip-bundle`を付けなければ問題ない。

[Docker Compose + Railsでイメージ内でbundle installしているはずなのにgemが無いとエラーがでる。 - Qiita](https://qiita.com/hokita222/items/49f4ca54835e08fdd6b2)

# 参考

[DockerインストールからRails + Docker + MySQLで環境構築までの手順](https://masanoriblog.org/construct_rails/)
[【Mac編】DockerでRuby on Railsの開発環境を作ってみよう](https://blog.codecamp.jp/docker-ruby-on-rails-mac)
[Docker ComposeでNode.jsの環境構築 - Qiita](https://qiita.com/Keitaroooo/items/0c7995d8d7d33f99e9cd)
[Rails6・Nuxt.js・PostgreSQLを動かすdocker-compose.ymlファイルを作成する - 独学プログラマ](https://blog.cloud-acct.com/posts/u-create-docker-compose/)