Rails 7.1 の Dockerfile を試してみる
Ruby on Rails 7.1が正式公開されたので触ってみます。
今回は、新たに rails new
で作成されるようになった Dockerfile
について見てみます。
$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]
$ gem install rails -v 7.1.1 --no-document
$ rails -v
Rails 7.1.1
$ rails new moblog
$ Code moblog
Rails 7.1 の Dockerfile
Rails 7.1 で rails new
したときの Dockerfile
は以下の内容です。開発用ではなく本番環境用ですね。
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here
WORKDIR /rails
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base as build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libvips pkg-config
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Copy built artifacts: gems, application
COPY /usr/local/bundle /usr/local/bundle
COPY /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER rails:rails
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
ローカルで起動してみる
試しにこれを使って作成したアプリケーションを起動してみます。
Rails 7.1ではslite3データベースファイルは /db
でなく /storage
に作成されるようになっています。(コミット:Use storage/ instead of db/ for sqlite3 db files (#46699))
$ bin/rails g scaffold Post body:text
$ bin/setup
$ docker build -t moblog .
$ docker volume create moblog-storage
$ docker run --rm -it -v moblog-storage:/rails/storage -p 3000:3000 --env RAILS_MASTER_KEY=`cat config/master.key` moblog
W, [2023-10-13T07:13:34.531872 #7] WARN -- : You are running SQLite in production, this is generally not recommended. You can disable this warning by setting "config.active_record.sqlite3_production_warning=false".
I, [2023-10-13T07:13:34.543185 #7] INFO -- : Migrating to CreatePosts (20231013071205)
== 20231013071205 CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0004s
== 20231013071205 CreatePosts: migrated (0.0005s) =============================
=> Booting Puma
=> Rails 7.1.1 application starting in production
=> Run `bin/rails server --help` for more startup options
W, [2023-10-13T07:13:34.937148 #1] WARN -- : You are running SQLite in production, this is generally not recommended. You can disable this warning by setting "config.active_record.sqlite3_production_warning=false".
[1] Puma starting in cluster mode...
[1] * Puma version: 6.4.0 (ruby 3.2.2-p53) ("The Eagle of Durango")
[1] * Min threads: 5
[1] * Max threads: 5
[1] * Environment: production
[1] * Master PID: 1
[1] * Workers: 10
[1] * Restarts: (✔) hot (✔) phased
[1] * Listening on http://0.0.0.0:3000
[1] Use Ctrl-C to stop
[1] - Worker 0 (PID: 11) booted in 0.01s, phase: 0
[1] - Worker 1 (PID: 13) booted in 0.01s, phase: 0
[1] - Worker 2 (PID: 15) booted in 0.01s, phase: 0
[1] - Worker 3 (PID: 19) booted in 0.01s, phase: 0
[1] - Worker 4 (PID: 35) booted in 0.01s, phase: 0
[1] - Worker 5 (PID: 51) booted in 0.01s, phase: 0
[1] - Worker 6 (PID: 64) booted in 0.01s, phase: 0
[1] - Worker 7 (PID: 70) booted in 0.0s, phase: 0
[1] - Worker 8 (PID: 84) booted in 0.0s, phase: 0
[1] - Worker 9 (PID: 97) booted in 0.0s, phase: 0
起動時に bin/docker-entrypoint
により bin/rails db:prepare
が実行されていますね。
これはデータベースが存在しない場合はデータベースを作成してスキーマを読み込みseedを実行し、存在するときはmigrateを実行するものです。
このまま http://localhost:3000 にアクセスしても、 config.force_ssl = true
のため https://localhost:3000 にリダイレクトされてしまい puma のログに 2023-10-13 06:54:45 +0000 HTTP parse error, malformed request: #<Puma::HttpParserError: Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?>
と記録されてしまいます。本番では前段にロードバランサーやリバースプロキシを配置するので問題にならないのですが、ローカルではちょっと面倒ですね。
今回はNgrokを使って転送します。
$ ngrok http 3000 --region jp
ngrok (Ctrl+C to quit)
Introducing Always-On Global Server Load Balancer: https://ngrok.com/r/gslb
Session Status online
Account Yuichi Takeuchi (Plan: Pro)
Update update available (version 3.3.5, Ctrl-U to update)
Version 3.1.1
Region Japan (jp)
Latency -
Web Interface http://127.0.0.1:4040
Forwarding https://f6a50b7d8fda.ngrok.app -> http://localhost:3000
転送URLを使ってアクセスしてみます。
Render.com で起動してみる
Rander.comでRailsを起動するには通常 Native Runtime を使いますが、Dockerfileからビルド&デプロイして使うこともできます。
Web Service を作成
Render.comにログインして New => Web Service をクリック
デプロイ元はGitリポジトリを使います。
設定
Web Serviceの設定します。
/rails/storage
は SQLiteデータベースやActiveStorageに使うので永続化しますが、これには Render Disks を使います。サービス作成時に設定することで自動でマウントしてくれるようになります。
- Runtime
Docker
- Advanced 設定を追加
- Environemt Variable
-
RAILS_MASTER_KEY
を設定(Automatic translation of environment variables to Docker build args)
-
- Add Disk
- Name: moblog-storage
- Mount Path:
/rails/storage
- Health Check Path
-
/up
を設定(Rails 7.1 では標準でヘルスチェック用のControllerが追加されたのでこれを使う)
-
- Environemt Variable
ビルド・起動
Web Serviceを作成すると自動でビルドが始まるので、しばらく待ちます。
無事起動しました。
Kamalでデプロイしてみる
せっかくなのでコンテナ用のCapistranoというKamalも試してみます。
デプロイ先の準備
Dockerが利用できるサーバなら何でもよさそうですが、今回はさくらのクラウド上の Ubuntu Server 22.04 を使ってみます。
SSHキーペアの作成
$ ssh-keygen -t ed25519 -C "yuichi.takeuchi@takeyuweb.co.jp" -f ~/.ssh/moblog-demo.id_rsa
公開鍵をサーバ側においておきます。
$ cat ~/.ssh/moblog-demo.id_rsa.pub
SSH接続設定
$ vi ~/.ssh/config
Host moblog-demo
HostName 153.120.18.91
User ubuntu
Port 22
IdentityFile ~/.ssh/moblog-demo.id_rsa
IdentitiesOnly yes
接続できることを確認します。
$ ssh moblog-demo
The authenticity of host '153.120.18.91 (153.120.18.91)' can't be established.
ED25519 key fingerprint is SHA256:UHXXcBzWBaDwhtToerB8NMndWRzgZYXYyPLfbbmswHo.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '153.120.18.91' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-78-generic x86_64)
bootstrap実行
kamalはデフォルトでrootを使いますが、今回はrootでのssh接続はできません。その場合は事前に手動でDockerのインストールなどをしておく必要があるようです。
リモートで
ubuntu@xxxxx:~$ sudo apt update
ubuntu@xxxxx:~$ sudo apt upgrade -y
ubuntu@xxxxx:~$ sudo apt install -y docker.io curl git
ubuntu@xxxxx:~$ sudo usermod -a -G docker ubuntu
コンテナレジストリの作成
Docker HubやECRなどコンテナイメージのアップロード先を作成します。
今回はさくらのクラウドのコンテナレジストリを試してみます。
さくらのクラウド > LAB > コンテナレジストリ
作成したコンテナレジストリを選択 > ユーザ でアクセスに使用するユーザ名とパスワードを登録
Kamalのセットアップ
まずはkamalコマンドをインストールします。
$ gem install kamal
Railsプロジェクトのルートに移動し、 kamal init --bundle
すると、設定ファイルなどが作成されます。
$ cd RAILS_ROOT
$ kamal init --bundle
reated configuration file in config/deploy.yml
Created .env file
Created sample hooks in .kamal/hooks
Adding Kamal to Gemfile and bundle...
INFO [f3ee60bf] Running /usr/bin/env bundle add kamal as yuichi@localhost
INFO [f3ee60bf] Finished in 2.273 seconds with exit status 0 (successful).
INFO [d59db3cb] Running /usr/bin/env bundle binstubs kamal as yuichi@localhost
INFO [d59db3cb] Finished in 0.109 seconds with exit status 0 (successful).
Created binstub file in bin/kamal
Kamalの設定
config/deploy.yml
を開き、デプロイ先などの設定を変更します。
- デプロイ先のサーバー名は
~/.ssh/config
に記載したホスト名にしました。 - イメージ保存先のコンテナレジストリはさくらのクラウドにしました。
-
storage
ディレクトリ
service: moblog-demo
# Name of the container image.
image: rails-moblog-demo/app
# Deploy to these servers.
servers:
- moblog-demo
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
server: moblog-demo.sakuracr.jp
username: moblogdemouser
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
# env:
# clear:
# DB_HOST: 192.168.0.2
# secret:
# - RAILS_MASTER_KEY
env:
secret:
- RAILS_MASTER_KEY
# Use a different ssh user than root
ssh:
user: ubuntu
# サーバ上のディレクトリをマウント(事前に作っておく)
volumes:
- "./moblog-demo-storage:/rails/storage"
ローカルで使う環境変数
Docker Hubのパスワード(アクセストークン)は .env
に書いておきます。
Docker Hub上で Account Settings > Security > Access Tokens で作成できます。
# .env
KAMAL_REGISTRY_PASSWORD=change-this
RAILS_MASTER_KEY=another-env
リモートに送る環境変数
RAILS_MASTER_KEY は env:
に RAILS_MASTER_KEY
と記載した上で、 .env
に書いておきます。
こうすることで、kamal によってサーバ上の .kamal/env/roles/moblog-demo-web.env
に、 RAILS_MASTER_KEY が設定されます。(ローカルに作った .env
がそのまま配置されるわけではない)
ボリュームのマウント
コンテナ内の /rails/storage
ディレクトリは永続化する必要があるので、 volumes:
で指定しています。
このように指定することでコンテナ起動時のオプションが追加されます。
Kamalの実行
setup
bin/kamal setup
を実行すると、
- ローカルでのappイメージの build と push
- リモートへのenvファイルの配置(ローカルの.envとは別)
- リモートでのリバースプロキシ traefik コンテナの作成
- appイメージを pull して起動しヘルスチェック実行
などが行われます。
ヘルスチェックに成功すると完了します。
$ bin/kamal setup
INFO [fe523bf0] Running /usr/bin/env mkdir -p .kamal on moblog-demo
INFO [fe523bf0] Finished in 1.080 seconds with exit status 0 (successful).
Acquiring the deploy lock...
Ensure Docker is installed...
INFO [2cdd9cb3] Running docker -v on moblog-demo
INFO [2cdd9cb3] Finished in 0.155 seconds with exit status 0 (successful).
INFO [ba1ed433] Running /usr/bin/env mkdir -p .kamal on moblog-demo
INFO [ba1ed433] Finished in 0.112 seconds with exit status 0 (successful).
Push env files...
INFO [d5d2607b] Running /usr/bin/env mkdir -p .kamal/env/roles on moblog-demo
INFO [d5d2607b] Finished in 0.111 seconds with exit status 0 (successful).
INFO Uploading .kamal/env/roles/moblog-demo-web.env 100.0%
INFO [c9bbd2ca] Running /usr/bin/env mkdir -p .kamal/env/traefik on moblog-demo
INFO [c9bbd2ca] Finished in 0.113 seconds with exit status 0 (successful).
INFO Uploading .kamal/env/traefik/traefik.env 100.0%
Log into image registry...
中略
INFO [bb3423c3] Finished in 3.463 seconds with exit status 0 (successful).
Finished all in 100.3 seconds
Releasing the deploy lock...
Finished all in 102.7 second
http://サーバのIPアドレス でアクセスできました。
deploy
変更を反映するには kamal deploy
を使います。env:
の変更がある場合は、事前に kamal env push
が必要とのこと。
$ bin/kamal env push
$ bin/kamal deploy
INFO [5cc64564] Running /usr/bin/env mkdir -p .kamal on moblog-demo
INFO [5cc64564] Finished in 1.271 seconds with exit status 0 (successful).
Acquiring the deploy lock...
Log into image registry..
中略
INFO [469f18d4] Finished in 0.285 seconds with exit status 0 (successful).
Releasing the deploy lock...
Finished all in 109.0 seconds
起動中のコンテナにログイン
bin/kamal app exec -i --reuse bash
--reuse
をつけない場合は新しくコンテナを起動してログインします。
トラブルシューティング
アーキテクチャが一致しない
Gemfile.lock
記載のCPUアーキテクチャと bundle install
した環境のアーキテクチャの不一致らしい。
エラーメッセージにあるように bundle lock --add-platform アーキテクチャ名
して再試行。
#20 45.07 Your bundle only supports platforms ["x86_64-linux"] but your local platform is
#20 45.07 aarch64-linux. Add the current platform to the lockfile with
#20 45.07 `bundle lock --add-platform aarch64-linux` and try again.
------
Dockerfile:26
--------------------
25 | COPY Gemfile Gemfile.lock ./
26 | >>> RUN bundle install && \
27 | >>> rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
28 | >>> bundle exec bootsnap precompile --gemfile
29 |
--------------------
ERROR: failed to solve: process "/bin/sh -c bundle install && rm -rf ~/.bundle/ \"${BUNDLE_PATH}\"/ruby/*/cache \"${BUNDLE_PATH}\"/ruby/*/bundler/gems/*/.git && bundle exec bootsnap precompile --gemfile" did not complete successfully: exit code: 16
本番環境にHTTPでアクセスしたい
rails new で作成したままだと config.force_ssl = true
なので、 kamal(traefik)の80番ポート(HTTP)では 443番へのリダイレクトが発生してしまいますが、自前でHTTPSアクセスできるようにするのは面倒です。
手っ取り早くアクセスできるようにするには
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
# config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false
とします。
まとめ
これまで本番用に各々自前の Dockerfile
や関連ファイルを構成していましたが、標準ができたことで今後はそちらに寄せる判断ができ、プロジェクト固有の秘伝のタレを減らすことができそうです。
Dockerfile
自体は特別なことはしていないので、Kamalの他、さまざまなツールやプラットホームで動作させることができます。
弊社としてはRailsを使う以上はなるべく標準に寄せた方がよいと考えているので、今後はうまく活用していきたいと思います。
Discussion