Rails7 の本番環境(Docker)を楽々に作成してSSL化する
目的
WEBサーバー(nginx,apacheなど)やアプリケーションサーバー(unicorn,pumaなど)、その他のSSL対応手続きに必要な、面倒なセットアップを大幅にカットあるいは簡略化することで、ローカル環境で実装したRailsとDockerのアプリケーションの本番環境作成を容易にすること。
ステージングに関しては別の記事で書いているので、かなり作業は似ていますが、こちらも見ておくとタメになると思います。
ステージング環境作成
前提条件
- ローカル環境でアプリケーションが正常に動いている (http://localhost:3000)
- 本番サーバーの契約済み
- 本番サーバーのインフラ設定(ネットワーク、セキュリティなど含む)が完了している
- 本番サーバー内にdockerとdocker-composeがインストールされている
- 独自ドメインを取得済み
- DNS設定にて、ドメインとパプリックIPを紐付け済み
別に一緒でなくて全然構いませんが、筆者の環境は以下のようになっていますので、参考までに。
- ドメイン取得 => ムームードメインで購入
- ステージングサーバー => EC2でインスタンス作成
- ドメインとパプリックIPを紐付け => Route53で設定済み
バージョン情報
- 手元の作業PC: Apple M1 Pro
- Rails: 7.0.2
- Ruby: 3.1.1
- PostgreSQL: 14.3
- Docker: 20.10.17
- Docker-compose.yml: 2.10.1
ゴール
ブラウザに 独自ドメインでアクセスできること。
今回は、例として、https://www.mysite.work で アクセスできるようにします。
ディレクトリ構成
プロジェクトルート
├── data (postgresqlのデータマウント用)
├── docker-compose.production.yml
├── https-portal
└── web
├── .env
├── Dockerfile.prod
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── app
├── bin
├── config
├── config.ru
├── db
├── entrypoint.sh
├── lib
├── log
├── public
├── storage
├── test
├── tmp
└── vendor
「Rails7.0.2 + PostgreSQL14.3 開発環境(docker)を構築する」の構成をベースにしています。
手順1: 各ファイルの作成・編集
.envを作成
秘匿したい情報はこのファイルに記載
DB_PROD_DATABASE="xxxxxxxxxx"
DB_PROD_HOST="db" # 今回は、docker-composeで立ち上げるdbサービスを利用
DB_PROD_USER="xxxxxxxxxx"
DB_PROD_PASSWORD="xxxxxxxxxx"
DB_PROD_PORT="xxxxx" # デフォルトポート(5432)から変えるのを推奨
docker-commpose.production.yml作成
version: '3'
services:
db:
image: postgres:14.3
ports:
- ${DB_PROD_PORT}:5432
volumes:
- ./data:/var/lib/postgresql/data
# - ./db/initdb.d:/docker-entrypoint-initdb.d
environment:
POSTGRES_USER: ${DB_PROD_USER}
POSTGRES_PASSWORD: ${DB_PROD_PASSWORD}
restart: always
networks:
dragon-network:
ipv4_address: 192.168.1.2
web:
build:
context: web
dockerfile: Dockerfile.prod
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0' -e production"
restart: always
volumes:
- ./web:/app
environment:
DB_PROD_DATABASE: ${DB_PROD_DATABASE}
DB_PROD_HOST: ${DB_PROD_HOST}
DB_PROD_USER: ${DB_PROD_USER}
DB_PROD_PASSWORD: ${DB_PROD_PASSWORD}
EDITOR: vim
RAILS_ENV: production
depends_on:
- db
networks:
dragon-network:
ipv4_address: 192.168.1.3
https-portal:
image: steveltn/https-portal:1
ports:
- '80:80'
- '443:443'
restart: always
environment:
DOMAINS: 'mysite.work => www.mysite.work, www.mysite.work -> http://web:3000'
STAGE: "production"
WORKER_PROCESSES: 2
CLIENT_MAX_BODY_SIZE: 10M
volumes:
- ./https-portal:/var/lib/https-portal
networks:
dragon-network:
ipv4_address: 192.168.1.4
networks:
dragon-network:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1
https-portal の詳細に関しては以下をご覧ください
- 今回のポイント
- mysite.workにアクセスするとwww.mysite.workにリダイレクトするように設定
- STAGE: "production"により、local,stagingのオレオレ証明書とは違って、let's encryptによる正式なSSL証明書が自動発行される
- 注意点
- サービス名webのポート3000番をdockerネットワークの外部にまで公開しない。
ports: 3000:3000
などでポートフォワーディングしてしまうとブラウザから、[server_public_ip]:3000でアクセスできてしまいます。 - インフラでネットワーク設定がある場合は、インバウンドルールで、DB_PROD_PORTの指定ポートを開けておく
- サービス名webのポート3000番をdockerネットワークの外部にまで公開しない。
environments/production.rbの強制SSLをオフにしておく
プロジェクトルート/web/config/environments/production.rb
...
config.force_ssl = false
...
https-portalサービスにて、SSL証明書を発行するときに、httpの通信が発生するため、docker-compose.production.ymlにて80番ポートが開いていなかったり、force_sslをtrueにしてしまっていると、SSL証明書の発行に失敗してしまう。
publicディレクトリ内の静的ファイルのアクセスを許可する
プロジェクトルート/web/config/environments/production.rb
...
config.public_file_server.enabled = true
...
上記設定に変更しないと、publicディレクトリ内の静的ファイルのアクセスが全て404 Not Foundになる。
(たとえば、ドメイン/ads.txtやドメイン/robots.txtなど)
Dockerfile.prodを作成
# gemインストールのみに使用
FROM ruby:3.1.1-alpine as builder
ENV ROOT="/app"
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo
WORKDIR ${ROOT}
COPY Gemfile Gemfile.lock ${ROOT}
RUN apk add \
alpine-sdk \
build-base \
# sqlite-dev \
# mysql-client \
# mysql-dev \
postgresql-dev \
postgresql-client \
tzdata \
git
# M1のRails(Docker環境)起動時にnokogiriがLoadErrorとなる問題の解決方法
RUN apk add --no-cache gcompat
RUN apk add --no-cache --virtual .build-deps \
build-base \
ruby-dev
RUN gem install bundler && bundle install
RUN bundle update webdrivers selenium-webdriver
RUN bundle exec rails assets:precompile RAILS_ENV=production
# マルチステージビルド
FROM ruby:3.1.1-alpine AS runner
ENV ROOT="/app"
ENV LANG=C.UTF-8
ENV TZ=Asia/Tokyo
WORKDIR ${ROOT}
RUN apk update && \
apk add \
postgresql-dev \
tzdata \
bash \
gcompat \
vim
RUN apk add --no-cache nodejs
COPY --from=builder /usr/local/bundle /usr/local/bundle
COPY . ${ROOT}
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
- ポイント
- docker-composeでビルドしたときに、アセットのプリコンパイルを行うようにしている
- これをしないと、jsとcssが本番に反映されない
- docker-composeでビルドしたときに、アセットのプリコンパイルを行うようにしている
- 注意点
- EXPOSE 3000などで、外部に公開しない
web/config/database.ymlを編集
production:
<<: *default
database: <%= ENV["DB_PROD_DATABASE"] %>
host: <%= ENV["DB_PROD_HOST"] %>
username: <%= ENV["DB_PROD_USER"] %>
password: <%= ENV["DB_PROD_PASSWORD"] %>
(uglifierを使っている人) terserに置き換え
2年前にRails5.2で動かしていた頃は、自分は、uglifierというgemを使って、javascriptのコードを軽量化していたが、Rails7.0で同じように使用すると、precompile時にエラーを吐いていた。ES6構文まではuglifierが使えるけど、ES2020とかになるとterserに置き換えないといけないらしい。以下のようにしてterserに置き換えることで解決した。
- gem 'uglifier'
+ gem 'terser'
- config.assets.js_compressor = :uglifier
+ config.assets.js_compressor = :terser
手順2: アプリケーションを本番サーバーに転送
それぞれのやり方で転送してください。自分は、fileZillaを愛用しております。
手順3: 本番環境で起動
ビルド
$ docker-compose -f docker-compose.production.yml build
DB作成とマイグレーションファイルの実行
$ docker-compose -f docker-compose.production.yml run --rm web bundle exec rails db:migrate:reset RAILS_ENV=production
起動
$ docker-compose -f docker-compose.production.yml up -d
すると、https-portalサービスからlet's encryptが自動で起動し、正式なSSL証明書が自動作成される。
ログで確認する。
docker-compose -f docker-compose.production.yml logs -f
証明書が発行されたら、ブラウザにて以下3点を確認する。
- https://www.mysite.workにアクセスして、Webページが表示される
- https://mysite.workにアクセスして、https://www.mysite.workにリダイレクトされる
- public内の静的ファイルにアクセスして、表示できる (https://www.mysite.work/robots.txt やhttps://www.mysite.work/ads.txtなど)
確認できたら、手順4に入る。
手順4: environments/production.rbの強制SSLをオンにしておく
プロジェクトルート/web/config/environments/production.rb
...
config.force_ssl = true
...
設定を変えたら、立ち上げ直す
$ docker-compose -f docker-compose.production.yml down
$ docker-compose -f docker-compose.production.yml up -d --build
変更のデプロイ
アプリケーションのコードを変更したときは、イメージを再構築し、アプリケーションのコンテナを作り直す必要があります。web というサービスを再デプロイするには、次のようにします:
$ docker-compose -f docker-compose.production.yml build web
$ docker-compose -f docker-compose.production.yml up --no-deps -d web
これは、まず web イメージの再構築するため、停止、破棄をしてから、web サービスのみ再作成します。Compose に --nodeps フラグを使うことで、web に依存するサービスの再作成をしません。
終わりに
今回はRailsとDockerで作成したローカル環境を容易に本番に作成する方法を紹介いたしました。
https-portalは、wordpressで使っている記事はよく見るものの、Railsで使っている記事は、自分が探した限りでは、ただの1件もなかったので、非常に価値のある記事が、公開できたのではないかと思っています。
ログに関しては、標準出力で出すように設定するなり、マウントして確認できるようにするなり、各自のやり方で設定してください。
docker + Railsの本番環境作成にぜひ役に立てば幸いです。
Discussion