RailsとPostgreSQLのアプリをKamalでVPSにデプロイ
はじめに
KamalはRailsの作者であるDHHが開発したデプロイ自動化ツールです。Capistranoがサーバーに直接ソースコードをデプロイするのに対して、KamalはDockerコンテナを活用してアプリケーション環境ごとデプロイします。
37signals社はKamalを利用してクラウドからの移行を進めて、ランニングコストの大幅な削減をしたそうです。少し古い話にはなりますが、DropboxがAWSから自社データセンターに移行して、収益性を改善した話が思い出されます。ちょうど私がRailsアプリを開発してリリースすることがあったので、その運用にKamalを利用してみることにしました。
RailsとPostgreSQLはよくある構成だと思うのですが、意外と情報が見つからなかったです。 Kamal’s missing tutorial – how to deploy a Rails 8 app with Postgres にも以下のように記述されています。
たとえば、Railsアプリが使用するPostgresデータベースコンテナを設定する方法に関する適切なデフォルトの例やガイドはありません。物事を理解するには、Kamalのドキュメントを深く掘り下げ、Dockerの使い方をよく知る必要があります。
Kamalの情報はまだ少ないようなので、私がVPSにRailsアプリをデプロイした際のメモをまとめて、Zennに投稿することにしました。
VPSのセットアップ
国内のVPSを調べていると、1台のサーバーで運用するならXServer VPSの費用対効果が良いように思いました。さくらのVPSやConoHa VPSは複数のVPSでプライベート接続ができるようなので、システムを複数台構成にするときには良さそうです。
今回はXServerの無料VPSを使ってKamalのデプロイを試していきます。
XServer VPSの作成
OSはDebian 12で作成しました。SSH Keyにはキーを作成する
を選択肢して、公開鍵をインポートしておきます。
パケットフィルター設定を確認して、接続許可ポートにSSHとWebを追加します。
SSHの設定を確認
これまでのXServer VPSの設定で、SSHは以下のような設定になっています。
PasswordAuthentication no
PermitRootLogin prohibit-password
SSHでログインできることを確認します。
ssh root@<VPS_IP_ADDRESS>
VPSでの作業はこれで完了です。
DNSの設定
VPSのIPアドレスとアプリのURLが対応付けられるよう、DNSのレコードを設定します。
Docker Desktopのインストール
Docker Desktopをインストールして、Docker Hubにログインしておきます。
GitHub Container Registryのセットアップ
GitHub Container RegistryのPersonal Access Tokenを取得して、ログインします。
docker login ghcr.io -u <YOUR_GITHUB_USERNAME>
RailsアプリにKamalのデプロイ情報を設定
.kamal/github.key
にGitHub Container RegistryのPersonal Access Tokenを保存して、.kamal/postgres.key
にPostgreSQLのパスワードを保存しています。
KAMAL_REGISTRY_PASSWORD=$(cat .kamal/github.key)
# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/master.key)
POSTGRES_PASSWORD=$(cat .kamal/postgres.key)
私が開発しているアプリはRails 8.0なので上記のように設定しましたが、もうすぐリリースされるRails 8.1からは以下のような指定が可能になり、こちらの書き方のほうがよさそうです。
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
config/deploy.yml
を以下のように編集します。<>
で囲われている部分は環境に応じて変更してください。
# Name of your application. Used to uniquely configure containers.
service: <YOUR_APPNAME>
# Name of the container image.
image: ghcr.io/<YOUR_GITHUB_USERNAME>/<YOUR_APPNAME>
# Deploy to these servers.
servers:
web:
- <VPS_IP_ADDRESS>
# job:
# hosts:
# - 192.168.0.1
# cmd: bin/jobs
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
#
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
proxy:
ssl: true
host: <YOUR_APP_HOST_AND_DOMAIN>
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
server: ghcr.io
username: <YOUR_GITHUB_USERNAME>
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .kamal/secrets).
env:
secret:
- RAILS_MASTER_KEY
- POSTGRES_PASSWORD
clear:
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
# When you start using multiple servers, you should split out job processing to a dedicated machine.
SOLID_QUEUE_IN_PUMA: true
# Set number of processes dedicated to Solid Queue (default: 1)
# JOB_CONCURRENCY: 3
# Set number of cores available to the application on each server (default: 1).
# WEB_CONCURRENCY: 2
# Match this to any external database server to configure Active Record correctly
# Use <YOUR_APPNAME>-db for a db accessory server on same machine via local kamal docker network.
DB_HOST: <YOUR_APPNAME>-db
# Log everything from Rails
# RAILS_LOG_LEVEL: debug
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole"
# Use a persistent storage volume for sqlite database files and local Active Storage files.
# Recommended to change this to a mounted volume path that is backed up off server.
volumes:
- "<YOUR_APPNAME>_storage:/rails/storage"
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
asset_path: /rails/public/assets
# Configure the image builder.
builder:
arch: amd64
# # Build image via remote server (useful for faster amd64 builds on arm64 computers)
# remote: ssh://docker@docker-builder-server
#
# # Pass arguments and secrets to the Docker build process
# args:
# RUBY_VERSION: 3.4.5
# secrets:
# - GITHUB_TOKEN
# - RAILS_MASTER_KEY
# Use a different ssh user than root
# ssh:
# user: app
# Use accessory services (secrets come from .kamal/secrets).
accessories:
db:
image: postgres:17
host: <VPS_IP_ADDRESS>
port: 5432
env:
clear:
POSTGRES_USER: '<YOUR_APPNAME>'
POSTGRES_DB: '<YOUR_APPNAME>_production'
secret:
- POSTGRES_PASSWORD
#files:
# - config/postgresql/production.conf:/etc/postgresql/postgresql.conf
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
directories:
- data:/var/lib/postgresql/data
Kamalでデプロイ
最後にkamal
コマンドでデプロイします。初回のデプロイは以下のように実行します。
kamal setup
次回以降にデプロイする際は以下のコマンドになります。
kamal deploy
おわりに
RailsとPostgreSQLで構成されているアプリをKamalでVPSにデプロイできるようになりました。これまではVPSでのアプリの運用は管理ツールの設定やメンテナンスが面倒という印象でしたが、Kamalがあれば運用していけそうです。
最近は円安でクラウドの課金が厳しくなってきたり、ユーザーが増えてくるとクラウドの課金額が想定外に増えたりしています。工数と課金のバランスを考えて、VPSとKamalの導入を進めていきたいと考えています。
Discussion