【完全ガイド】Kamal 2 で Rails 8 をデプロイする方法|設定・SSL・アクセサリー・トラブル対応を徹底解説
- 作成日 2026.07.06
- その他
Rails 8 で標準採用され、Ruby on Rails のデプロイを劇的にシンプルにした Kamal 2:
# たった2コマンドで、Rails アプリを本番環境にデプロイ
kamal setup # 初回セットアップ
kamal deploy # デプロイ
# → Docker + SSH + kamal-proxy でゼロダウンタイム
# → Let's Encrypt SSL 自動対応
# → 数千円/月の VPS 1台で運用可能
Basecamp / 37signals が開発し、Heroku や AWS 依存からの脱却を可能にした画期的ツール。1台のVPS(Hetzner/DigitalOcean/Linode/Vultr等)で、月額数千円で本格的な Rails 本番運用が実現できます。
しかし、いざ導入しようとすると:
- Kamal 2 って何が変わったの?(Traefik → kamal-proxy)
config/deploy.ymlの書き方が分からない- Let’s Encrypt SSL はどう有効化?
- サーバーの初期設定は?
- PostgreSQL や Redis はどう配置?
- マイグレーションはどう実行?
- ゼロダウンタイムって本当?
- M1/M2 Mac からデプロイでハマる
- Cloudflare を挟むには?
- Heroku と比べてどうなの?
など、公式ドキュメントだけでは埋められない知識ギャップが多くあります。
本記事では、Kamal 2 による Rails 8 デプロイの完全ガイドを、実践的な設定例とトラブル対応を含めてリファレンスとして整理します。基礎概念、Kamal 1 との違い、サーバー準備、config/deploy.yml 全解説、Docker レジストリ、SSL 自動化、アクセサリー、マイグレーション戦略、マルチアプリ、Cloudflare 対応、実践パターン、トラブル対応、FAQまで完全網羅。この1本で Rails 8 の本番運用に自信を持って踏み出せます。
- 1. 結論:Kamal 2 が Rails 8 のデプロイをこう変える
- 2. Kamal とは何か
- 3. Kamal のアーキテクチャ
- 4. 事前準備
- 5. サーバー初期設定(セキュリティ)
- 6. Rails アプリ側の準備
- 7. config/deploy.yml の完全解説
- 8. .kamal/secrets
- 9. kamal コマンド一覧
- 10. Docker レジストリの設定
- 11. SSL / Let’s Encrypt 自動化
- 12. アクセサリー(PostgreSQL / Redis)
- 13. マイグレーション戦略
- 14. Solid Queue / Solid Cache / Solid Cable
- 15. 実践シナリオ
- 16. トラブルシューティング
- 17. よくある質問(FAQ)
- 17.1. Q1. Kamal 2 と Kamal 1、どっちを使うべき?
- 17.2. Q2. Kubernetes より Kamal?
- 17.3. Q3. Capistrano から Kamal 移行の壁
- 17.4. Q4. Docker Hub 有料化?
- 17.5. Q5. マイグレーションで長時間ダウンする
- 17.6. Q6. コンテナのログが消える
- 17.7. Q7. 環境変数の反映
- 17.8. Q8. VPS が落ちたら?
- 17.9. Q9. Kamal で S3 / R2 に静的アセット配信
- 17.10. Q10. 秘密情報のローテーション
- 17.11. Q11. Multi-tenant アプリ
- 17.12. Q12. Rails 以外にも使える?
- 18. 参考リンク・関連資料
- 19. まとめ
結論:Kamal 2 が Rails 8 のデプロイをこう変える
時間がない方向けに、最重要ポイントを先に示します。
Kamal 2 の 5つの特徴
- Rails 8 で標準:
rails newするだけで Kamal 設定が生成される - Docker + SSH ベース: 独自プロトコル不要、Linux があれば動く
- ゼロダウンタイムデプロイ: kamal-proxy が新旧コンテナを切り替え
- Let’s Encrypt SSL 自動:
proxy.ssl: trueだけで HTTPS 化 - 単一サーバー〜複数サーバー対応: VPS 1台からスケールアップ可能
最短デプロイ手順
# 1. Rails 8 アプリ生成(Kamal 標準搭載)
rails new myapp --database=postgresql
cd myapp
# 2. Docker Hub の credential 設定
kamal init # skeleton 生成(Rails 8 なら不要)
# .kamal/secrets を編集
# config/deploy.yml を編集
# 3. 初回セットアップ + デプロイ
bin/kamal setup
# 4. 以降はデプロイのみ
bin/kamal deploy
Heroku との比較
| 観点 | Heroku | Kamal 2 + VPS |
|---|---|---|
| 月額(小規模) | $25〜$50 | $5〜$20 |
| セットアップ難度 | ワンクリック | 中程度 |
| カスタマイズ | 制限あり | 自由 |
| バックアップ | 自動 | 自分で設定 |
| 障害対応 | Heroku 任せ | 自分で対応 |
| スケール | ボタン1つ | 手動 or 手作業 |
中小規模のアプリでは Kamal 2 + VPS が圧倒的にコスパ良好。詳細は以下で解説します。
Kamal とは何か
背景
- 2019年: Basecamp が MRSK(後の Kamal 1)を開発
- 2023年: Kamal 1 公開、Traefik を内部で使用
- 2024年10月: Kamal 2 リリース、kamal-proxy 導入
- 2024年11月: Rails 8 リリース、Kamal 2 を標準採用
37signals は自社サービス(Basecamp、HEY等)を Kamal で運用し、AWS から離脱。“Cloud Exit” と呼ばれるトレンドを牽引。
Kamal の思想
- 1台のサーバーで多くのアプリが動く時代に戻ろう
- Kubernetes は多くの Rails アプリには過剰
- Docker + SSH で十分
- PaaSの利便性を、自前サーバーで
Kamal 1 → Kamal 2 の主な変更
| 観点 | Kamal 1 | Kamal 2 |
|---|---|---|
| リバースプロキシ | Traefik(外部) | kamal-proxy(自社製) |
| SSL | Traefik 経由 Let’s Encrypt | kamal-proxy 直接 |
| マルチアプリ | 難しい | 1サーバーに複数アプリ簡単 |
| 設定 | 複雑 | シンプル化 |
| 切り替え | 数百ms〜 | 瞬時(インスタント) |
Rails 8 では Kamal 2 が標準。1 → 2 のアップグレードも比較的スムーズ。
詳細はRails 8 アップグレードガイドの記事も参照。
Kamal のアーキテクチャ
全体像
[ローカルマシン]
↓ SSH + Docker build/push
[Docker レジストリ(Docker Hub 等)]
↑ pull
[本番サーバー]
├─ kamal-proxy(コンテナ、80/443)
│ ↓ ルーティング
├─ myapp:web(Rails アプリコンテナ)
├─ myapp:job(Solid Queue ワーカー)
├─ postgres(アクセサリー)
└─ redis(アクセサリー)
デプロイの流れ
1. ローカルで docker build
2. Docker レジストリに push
3. サーバー側で docker pull
4. 新コンテナ起動
5. /up ヘルスチェック
6. kamal-proxy が新コンテナへ切り替え
7. 旧コンテナ停止
この間、リクエストは一切失われない(ゼロダウンタイム)。
主要コンポーネント
- kamal (CLI): ローカルで動く Ruby gem
- kamal-proxy: サーバー側の reverse proxy コンテナ
- アプリコンテナ: あなたの Rails アプリ
- アクセサリー: PostgreSQL / Redis 等の付属コンテナ
事前準備
必要なもの
- VPS: Ubuntu 22.04 / 24.04 LTS 推奨(1GB RAM 以上)
- DNS: A レコードでサーバーIP を指す
- Docker Hub アカウント(または GitHub Container Registry / プライベートレジストリ)
- SSH 鍵ペア
- Rails 8 アプリ
VPS 選び
| プロバイダ | 特徴 |
|---|---|
| Hetzner | 最安(€4/月〜)、性能高 |
| DigitalOcean | 使いやすい、$4/月〜 |
| Linode(Akamai) | 老舗、$5/月〜 |
| Vultr | 選択肢多い、$2.50/月〜 |
| AWS Lightsail | AWS 統合、$5/月〜 |
Kamal 公式は Hetzner を推奨(性能・価格比)。
DNS の準備
A レコード: myapp.example.com → 203.0.113.42
デプロイ前にDNS を有効化。Let’s Encrypt SSL 取得時に必要。
SSH 鍵の準備
# 鍵生成(既にあれば省略)
ssh-keygen -t ed25519 -C "deploy@myapp"
# サーバーに登録
ssh-copy-id root@your_server_ip
詳細はSSH host key verification failed の記事も参照。
サーバー初期設定(セキュリティ)
⚠️ 公開されたVPS は数分でスキャン開始。デプロイ前にセキュリティ設定を。
deploy ユーザーの作成
# root で SSH ログイン
ssh root@your_server_ip
# deploy ユーザー作成
adduser deploy
usermod -aG sudo deploy
usermod -aG docker deploy # Docker はまだ未インストール
# SSH 鍵をコピー
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
SSH 設定強化
# /etc/ssh/sshd_config を編集
sudo vim /etc/ssh/sshd_config
# 以下を変更
PermitRootLogin no
PasswordAuthentication no
# Port 22 → Port 12444 (任意、ポート変更でセキュリティ向上)
# 再起動
sudo systemctl restart sshd
詳細はsystemctl vs service の記事も参照。
ファイアウォール(UFW)
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH(変更してれば 12444)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
⚠️ 重要な注意: Docker が公開するポートは UFW をバイパスします。アクセサリのポートは 127.0.0.1 にバインドすること(後述)。
Docker のインストール
Kamal が自動的にインストールしますが、手動でも可:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker deploy
詳細はdocker daemon 接続エラーの記事も参照。
スワップ設定(小さいVPS の場合)
# 2GB スワップ作成
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
デプロイ中の一時的なメモリ増大に備える。
Rails アプリ側の準備
Kamal の確認
Rails 8 なら Gemfile にすでに kamal が含まれる:
# Gemfile
gem "kamal", require: false
gem "thruster", require: false
古い Rails なら追加してから:
bundle install
bundle exec rails kamal:install # または bin/kamal init
生成されるファイル
config/
├─ deploy.yml ← メイン設定
.kamal/
├─ secrets ← シークレット
└─ hooks/ ← カスタムフック
Dockerfile ← 自動生成
bin/kamal ← 実行スクリプト
Dockerfile の確認
Rails 8 のrails new は本番向けの Dockerfile を自動生成:
# syntax=docker/dockerfile:1
# check=error=true
ARG RUBY_VERSION=3.3.6
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
WORKDIR /rails
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_WITHOUT="development"
# ...省略...
# ヘルスチェック用
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
thruster は Rails 8 の HTTP/2 対応リバースプロキシ。X-Sendfile も対応。
ローカルでの動作確認
# ビルド
docker build -t myapp .
# 起動テスト
docker run --rm -e RAILS_MASTER_KEY=$(cat config/master.key) -p 3000:80 myapp
エラーなく起動すればOK。Kamal を使う前にこの確認は必須。
config/deploy.yml の完全解説
最小限の設定例
# config/deploy.yml
service: myapp
image: yourdockerhub/myapp
servers:
web:
- 203.0.113.42
proxy:
ssl: true
host: myapp.example.com
registry:
username: yourdockerhub
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
builder:
arch: amd64
実践的な完全設定例
# config/deploy.yml
service: myapp
image: yourdockerhub/myapp
servers:
web:
hosts:
- 203.0.113.42
options:
memory: 512m # メモリ制限(OOM 対策)
job:
hosts:
- 203.0.113.42
cmd: bin/jobs
ssh:
user: deploy
port: 12444 # SSH ポート変更してるなら
proxy:
ssl: true
hosts:
- myapp.example.com
- www.myapp.example.com
forward_headers: true # Cloudflare 使うなら
healthcheck:
path: /up
interval: 3
timeout: 30 # 起動が遅い場合は伸ばす
registry:
username: yourdockerhub
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: "1"
RAILS_SERVE_STATIC_FILES: "1"
SOLID_QUEUE_IN_PUMA: "true"
HOST: myapp.example.com
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
accessories:
db:
image: postgres:16
host: 203.0.113.42
port: "127.0.0.1:5432:5432" # ⚠️ localhost バインド!
env:
clear:
POSTGRES_USER: myapp
POSTGRES_DB: myapp_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
host: 203.0.113.42
port: "127.0.0.1:6379:6379"
directories:
- data:/data
volumes:
- "myapp_storage:/rails/storage"
builder:
arch: amd64
cache:
type: gha
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
migrate: app exec --primary "bin/rails db:migrate"
dbc: app exec --interactive --reuse "bin/rails dbconsole"
主要設定の意味
service / image
service: myapp # コンテナ・ネットワーク名の prefix
image: yourdockerhub/myapp # Docker イメージのタグ
servers
servers:
web:
hosts:
- 203.0.113.42
options:
memory: 512m
job:
hosts:
- 203.0.113.42
cmd: bin/jobs
role(web / job / worker等)を任意で作成。同じサーバーに複数役割を配置できる。
options.memory: Docker のメモリ制限。必ず設定推奨。小さい VPS で OOM を防ぐ。
ssh
ssh:
user: deploy
port: 12444
proxy: bastion.example.com # ジャンプサーバー
proxy
proxy:
ssl: true # Let's Encrypt 自動取得
hosts: # 複数ホスト対応
- myapp.example.com
- www.myapp.example.com
forward_headers: true # Cloudflare 対応
healthcheck:
path: /up # ヘルスチェック URL
interval: 3 # 秒
timeout: 30 # タイムアウト(起動遅い場合は伸ばす)
ssl: true で Let’s Encrypt が自動で取得。魔法のような便利さ。
⚠️ DNS が正しく設定されていないと ACME challenge 失敗。デプロイ前に確認。
registry
# Docker Hub
registry:
username: yourdockerhub
password:
- KAMAL_REGISTRY_PASSWORD
# GitHub Container Registry
registry:
server: ghcr.io
username: your-github-user
password:
- KAMAL_REGISTRY_PASSWORD
# ローカル registry(サーバー上)
registry:
server: localhost:5555
Docker Hub は無料でも十分。プライベートリポジトリの制限があるので注意。
env(環境変数)
env:
clear: # 平文(Docker image に埋め込まれない)
RAILS_ENV: production
RAILS_LOG_TO_STDOUT: "1"
secret: # .kamal/secrets から取得
- RAILS_MASTER_KEY
- DATABASE_URL
clear は平文でOKなもの、secret はパスワード等。必ず区別。
builder
builder:
arch: amd64 # ターゲットアーキ(M1 Mac からデプロイ時必須)
cache:
type: gha # GitHub Actions キャッシュ
M1/M2 Mac から Intel/AMD サーバーへデプロイなら arch: amd64 必須。忘れると起動しない。
aliases
aliases:
console: app exec --interactive --reuse "bin/rails console"
logs: app logs -f
migrate: app exec --primary "bin/rails db:migrate"
kamal console、kamal migrate で使える便利ショートカット。
.kamal/secrets
シークレット管理の設定ファイル:
# .kamal/secrets
# 直接記述
KAMAL_REGISTRY_PASSWORD=your_docker_hub_token
RAILS_MASTER_KEY=$(cat config/master.key)
POSTGRES_PASSWORD=your_postgres_password
DATABASE_URL="postgres://myapp:${POSTGRES_PASSWORD}@myapp-db:5432/myapp_production"
# 環境変数から
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# 1Password から取得(推奨)
SECRETS=$(kamal secrets fetch --adapter 1password \
--account YOUR_1PASSWORD_ACCOUNT_ID \
--from YOUR_VAULT/SECURE_NOTE \
RAILS_MASTER_KEY POSTGRES_PASSWORD)
RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD $SECRETS)
.gitignore に追加
.kamal/secrets
⚠️ 絶対にGit に含めない。事故防止。
secrets fetch アダプター
1password: 1Passwordbitwarden: Bitwardenaws-secrets-managerlastpass
kamal コマンド一覧
初回セットアップ
# サーバーに Docker インストール、初回デプロイまで一括
kamal setup
# 実質、以下を全部やる:
# - Docker インストール
# - kamal-proxy 起動
# - accessories 起動
# - image build & push
# - app コンテナ起動
通常のデプロイ
# ビルド + プッシュ + 切り替え(ゼロダウンタイム)
kamal deploy
# ビルド済みイメージで再デプロイ
kamal redeploy
# 特定のホストだけ
kamal deploy --hosts 203.0.113.42
# 特定の role だけ
kamal deploy --roles web
アプリ操作
kamal app boot # 起動
kamal app stop # 停止
kamal app start # 開始
kamal app restart # 再起動
kamal app remove # コンテナ削除
kamal app logs # ログ
kamal app logs -f # tail -f
kamal app logs -n 100 # 直近100行
kamal app exec "bin/rails db:migrate" # コマンド実行
kamal app exec --primary "bin/rails db:migrate" # プライマリのみ
kamal app exec --interactive --reuse "bin/rails console" # 対話
kamal app details # コンテナ詳細
アクセサリー
kamal accessory boot db # 起動
kamal accessory stop db # 停止
kamal accessory logs db # ログ
kamal accessory exec db "psql -U myapp" # 実行
プロキシ操作
kamal proxy boot # kamal-proxy 起動
kamal proxy reboot # 再起動
kamal proxy logs # ログ
kamal proxy details # 詳細
ロールバック
kamal rollback # 直前のバージョンへ
kamal rollback abc123def # 特定コミット
削除・クリーンアップ
kamal remove # 全て削除
kamal prune all # 古いイメージ・コンテナ削除
kamal prune images # イメージのみ
秘密情報関連
kamal envify # env → .env 変換
kamal secrets print # secrets 内容表示(デバッグ用)
Docker レジストリの設定
Docker Hub(推奨・簡単)
# アクセストークン生成
# https://hub.docker.com/settings/security
# .kamal/secrets
KAMAL_REGISTRY_PASSWORD=dckr_pat_xxxxxxxxxxxxx
# config/deploy.yml
registry:
username: your_dockerhub_user
password:
- KAMAL_REGISTRY_PASSWORD
GitHub Container Registry
registry:
server: ghcr.io
username: your_github_user
password:
- KAMAL_REGISTRY_PASSWORD
GitHub Personal Access Token(write:packages 権限)を使用。
ローカルレジストリ(サーバー上)
メリット: 外部依存なし、通信高速、無料
registry:
server: localhost:5555
セットアップは kamal が自動化。ただしイメージがそのサーバーのみに存在するため、マルチサーバー時は注意。
SSL / Let’s Encrypt 自動化
基本設定
proxy:
ssl: true
host: myapp.example.com
これだけで kamal-proxy が Let’s Encrypt から証明書を自動取得。
複数ホスト
proxy:
ssl: true
hosts:
- myapp.example.com
- www.myapp.example.com
前提条件(重要)
- DNS が正しく設定されている(A レコード)
- ポート 80 と 443 が開いている
- サーバーがインターネットから到達可能
これらが揃わないと ACME challenge 失敗 → 証明書取得できない。
証明書の確認
# 証明書取得ログ
kamal proxy logs
# 実際に SSL 動作確認
curl -v https://myapp.example.com
Cloudflare を使う場合
proxy:
ssl: true
host: myapp.example.com
forward_headers: true # ⚠️ 実IPを取得するため必須
Cloudflare の SSL/TLS 設定を Full に:
- Flexible: NG(無限リダイレクトループ)
- Full: ✅ 推奨
- Full (Strict): ✅ 動くが更新時に注意
アクセサリー(PostgreSQL / Redis)
PostgreSQL アクセサリー
accessories:
db:
image: postgres:16
host: 203.0.113.42
port: "127.0.0.1:5432:5432" # ⚠️ 必ず localhost バインド
env:
clear:
POSTGRES_USER: myapp
POSTGRES_DB: myapp_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
⚠️ 127.0.0.1:5432:5432 の意味:
- 外側(ホスト側)を
127.0.0.1:5432にバインド - 公開ネットワークから見えない
- Docker bridge network 経由でアプリコンテナからのみアクセス可能
DATABASE_URL
# .kamal/secrets
DATABASE_URL="postgres://myapp:${POSTGRES_PASSWORD}@myapp-db:5432/myapp_production"
myapp-db は Kamal が作るDocker network 内のホスト名。
Redis アクセサリー
accessories:
redis:
image: redis:7-alpine
host: 203.0.113.42
port: "127.0.0.1:6379:6379"
directories:
- data:/data
アクセサリー管理コマンド
# 起動
kamal accessory boot db
# 全部起動
kamal accessory boot all
# 停止
kamal accessory stop db
# ログ
kamal accessory logs db
# 内部実行
kamal accessory exec db "psql -U myapp -d myapp_production"
マネージド DB を使う場合
AWS RDS、Digital Ocean Managed DB 等を使うなら、アクセサリー不要:
env:
secret:
- DATABASE_URL # マネージドDB の URL
accessories: から db を削除。
マイグレーション戦略
Kamal は自動でマイグレーションを実行しない。3つの選択肢:
オプションA:post-deploy hook(推奨)
# .kamal/hooks/post-deploy
#!/usr/bin/env bash
set -e
kamal app exec --primary "bin/rails db:migrate"
chmod +x .kamal/hooks/post-deploy
# config/deploy.yml
# post-deploy は自動的に実行される
オプションB:コンテナ起動時
# Dockerfile または entrypoint スクリプト
CMD ["sh", "-c", "bundle exec rails db:migrate && bin/thrust ./bin/rails server"]
⚠️ 非推奨: 再起動のたびに migrate 実行、複数サーバーで競合。
オプションC:手動(最も安全)
kamal app exec --primary "bin/rails db:migrate"
kamal deploy
破壊的なマイグレーションでは C 推奨。
エイリアス活用
aliases:
migrate: app exec --primary "bin/rails db:migrate"
kamal migrate
詳細はrails db:migrate 使い方の記事も参照。
Solid Queue / Solid Cache / Solid Cable
Rails 8 の Solid 三銃士との連携:
Solid Queue(バックグラウンドジョブ)
servers:
web:
- 203.0.113.42
job:
hosts:
- 203.0.113.42
cmd: bin/jobs # Solid Queue ワーカー
env:
clear:
SOLID_QUEUE_IN_PUMA: "false" # 別コンテナで動かす
別プロセスで実行する場合は SOLID_QUEUE_IN_PUMA: "false" にし、job role を追加。
Puma と同居させる場合は "true" で job role 不要。
詳細はSolid Queue 使い方の記事も参照。
Solid Cache / Solid Cable
こちらは Rails アプリ内で動く(別コンテナ不要):
env:
clear:
RAILS_ENV: production
# Solid Cache/Cable の設定は database.yml で
詳細はSolid Cache 使い方の記事、Solid Cable 使い方の記事も参照。
実践シナリオ
シナリオ1:単一サーバーでのフルスタック
servers:
web:
- 203.0.113.42
accessories:
db:
image: postgres:16
host: 203.0.113.42
# ...
redis:
image: redis:7-alpine
host: 203.0.113.42
# ...
月$5-10 で本格 Rails 運用。中小規模には十分。
シナリオ2:Web + DB 分離
servers:
web:
- 203.0.113.42
accessories:
db:
host: 203.0.113.43 # 別サーバー
port: "203.0.113.43:5432:5432" # ⚠️ 外部到達可
セキュリティ強化のため VPN or private network 推奨。
シナリオ3:複数 Web サーバー
servers:
web:
- 203.0.113.42
- 203.0.113.43
job:
hosts:
- 203.0.113.44
cmd: bin/jobs
proxy:
ssl: true
host: myapp.example.com
ロードバランサーが別途必要(Kamal 自身は LB 機能なし)。
シナリオ4:マルチアプリ(1サーバーに複数)
# app1: config/deploy.yml
service: app1
proxy:
ssl: true
host: app1.example.com
# app2: config/deploy.yml
service: app2
proxy:
ssl: true
host: app2.example.com
同じサーバーに複数アプリを配置可能。kamal-proxy が Host ヘッダで振り分け。
シナリオ5:GitHub Actions で自動デプロイ
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
- name: Deploy
run: bin/kamal deploy
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.DOCKER_HUB_TOKEN }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
詳細はgit push rejected の記事も参照。
シナリオ6:DB バックアップ
accessories:
db-backup:
image: kartoza/pg-backup:latest
host: 203.0.113.42
env:
clear:
CRON_SCHEDULE: "@daily"
REMOVE_BEFORE: 30
STORAGE_BACKEND: S3
secret:
- POSTGRES_USER
- POSTGRES_PASSWORD
- ACCESS_KEY_ID
- SECRET_ACCESS_KEY
- BUCKET
S3 に定期バックアップ。
詳細はcrontab 書き方 例の記事、scp vs rsync の記事も参照。
シナリオ7:ロールバック
# 直前のバージョンへ
kamal rollback
# 特定 commit
kamal rollback abc123def
デプロイ直後の障害復旧に。
シナリオ8:モニタリング
# ログ追跡
kamal app logs -f
# 状態
kamal details
# サーバーのリソース
ssh deploy@server "iostat -xm 1"
ssh deploy@server "ss -tulnp"
詳細はiostat 見方 使い方の記事、ss コマンドの使い方の記事も参照。
トラブルシューティング
Health check timeout
DEBUG [7fd3] Command: (...) not yet healthy
ERROR (...) not passing health check
→ Rails の起動が遅い(大規模アプリ、重い初期化)。timeout を伸ばす:
proxy:
healthcheck:
path: /up
timeout: 60 # デフォルトは 7 秒
interval: 3
Architecture mismatch(M1 Mac → x86 VPS)
exec /usr/bin/bash: exec format error
→ M1/M2 Mac の ARM で作ったイメージが x86 で動かない:
builder:
arch: amd64 # 必須
または GitHub Actions で amd64 ビルド。
DNS 未設定で証明書失敗
Failed to obtain certificate: acme: error
→ DNS A レコードがサーバーIP を指していない。
dig myapp.example.com # 確認
# → サーバーIP が返ってくるべき
DNS 伝播を待ってから kamal proxy reboot。
Docker daemon 接続エラー
Cannot connect to the Docker daemon
→ サーバー側の Docker が起動していない:
ssh deploy@server "sudo systemctl start docker"
ssh deploy@server "sudo systemctl enable docker"
詳細はdocker daemon 接続エラーの記事も参照。
ディスク不足
Error response from daemon: no space left on device
→ 古い Docker イメージが溜まっている:
kamal prune all
# または
ssh deploy@server "docker system prune -a"
詳細はDocker no space left on device の記事も参照。
メモリ不足(OOM)
Killed
container exited with code 137
→ VPS のメモリ不足:
servers:
web:
options:
memory: 512m # コンテナに制限
- スワップ設定 or VPS グレードアップ。
DATABASE_URL 接続エラー
PG::ConnectionBad: could not connect to server
→ アクセサリーとアプリコンテナが同じ Docker network にいない、または DATABASE_URL 間違い:
# 確認
kamal accessory logs db
kamal app exec "env | grep DATABASE_URL"
# ネットワーク確認
kamal app exec "ping myapp-db"
Kamal コマンドが遅い
→ SSH の Multiplexing で高速化:
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
詳細はscp vs rsync の記事、SSH host key verification failed の記事も参照。
アセットが 404
ActionController::RoutingError No route matches [GET] "/assets/..."
→ Propshaft 設定確認:
env:
clear:
RAILS_SERVE_STATIC_FILES: "1"
詳細はPropshaft 使い方の記事も参照。
初回セットアップ失敗
# デバッグ
kamal setup --verbose
# 手動で段階的に
kamal registry login
kamal build push
kamal accessory boot all
kamal proxy boot
kamal deploy
よくある質問(FAQ)
Q1. Kamal 2 と Kamal 1、どっちを使うべき?
新規は絶対に Kamal 2。既存 Kamal 1 も 2 へのアップグレードを推奨。
Q2. Kubernetes より Kamal?
- 中小規模: Kamal が圧倒的にシンプル
- 数十サーバー以上: Kubernetes 検討
- 複雑な要件: Kubernetes
Kamal は「1台〜数台」の Rails 運用に最適化。
Q3. Capistrano から Kamal 移行の壁
- コンテナ化必要: Dockerfile 作成
- 設定様式が違う: YAML ベース
- DB は accessories or managed
慣れれば Capistrano より楽。
Q4. Docker Hub 有料化?
無料枠でも十分(月 200 pull まで)。心配なら:
- GitHub Container Registry(無料)
- ローカルレジストリ
Q5. マイグレーションで長時間ダウンする
→ zero-downtime migration の手法を採用:
- カラム追加 → コード変更 → デプロイ → 古い列削除 の分離
- pg_bulk 等の高速化ツール
Q6. コンテナのログが消える
Docker のログドライバ設定:
# ~/.docker/daemon.json(サーバー側)
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
Q7. 環境変数の反映
# secrets 変更後
kamal env push
kamal app boot # 再起動
Q8. VPS が落ちたら?
Kamal 自体はフェイルオーバー機能なし:
- 複数サーバー + LB
- 監視ツール(UptimeRobot 等)
- 自動再起動スクリプト
真の HA には Kubernetes / マネージド。
Q9. Kamal で S3 / R2 に静的アセット配信
env:
clear:
RAILS_SERVE_STATIC_FILES: "0" # Rails は配信せず
CDN 側で asset を持つ設定を Rails に。
Q10. 秘密情報のローテーション
# .kamal/secrets 更新
# デプロイ
kamal deploy
環境変数を更新してデプロイ。
Q11. Multi-tenant アプリ
kamal-proxy が host ヘッダで振り分けるので、複数ドメイン対応可:
proxy:
ssl: true
hosts:
- customer1.example.com
- customer2.example.com
- customer3.example.com
Q12. Rails 以外にも使える?
はい。Node.js、Python、Go 等どんな Docker アプリでも Kamal でデプロイ可能。
bin/kamal 相当のツールを設定すれば動作。
参考リンク・関連資料
Kamal 公式
- Kamal Deploy – Kamal 公式サイト
- kamal GitHub – Kamal リポジトリ
- kamal-proxy GitHub – proxy リポジトリ
- Kamal Documentation – 公式ドキュメント
参考ツール
- Hetzner Cloud – コスパ良 VPS
- DigitalOcean – 使いやすい VPS
- Let’s Encrypt – 無料 SSL
関連記事(本サイト)
- Rails 8 アップグレードガイド – Rails 8 全般
- Solid Queue 使い方 – バックグラウンドジョブ
- Solid Cache 使い方 – キャッシュ
- Solid Cable 使い方 – WebSocket
- Propshaft 使い方 – アセット
- rails db:migrate 使い方 – マイグレーション
- docker daemon 接続エラー – Docker 管理
- Docker no space left on device – ディスク管理
- systemctl vs service – サービス管理
- scp vs rsync – ファイル転送
- SSH host key verification failed – SSH 接続
- git push rejected – Git エラー
- Linuxでプロセスをバックグラウンド実行する方法 – プロセス管理
- Linux kill vs pkill vs killall – プロセス終了
- iostat 見方 使い方 – I/O 監視
- ss コマンドの使い方 – ネットワーク監視
- crontab 書き方 例 – 定期実行
- Address already in use – ポート占有
まとめ
Kamal 2 による Rails 8 デプロイ、要点を再整理します。
Kamal 2 の価値
- Rails 8 標準:
rails newで自動生成 - Heroku 脱却: 月額数千円で本格運用
- ゼロダウンタイム: kamal-proxy による切り替え
- Let’s Encrypt SSL 自動:
ssl: trueだけ - シンプル: Docker + SSH + YAML
最短デプロイ手順
# 1. Rails 8 アプリ準備(既に Kamal 統合済み)
rails new myapp --database=postgresql
# 2. config/deploy.yml と .kamal/secrets を編集
# 3. サーバー準備(Ubuntu 24.04、SSH 鍵、DNS)
# 4. デプロイ
bin/kamal setup
主要コマンド
kamal setup # 初回セットアップ
kamal deploy # ビルド + デプロイ
kamal redeploy # 既存イメージで再デプロイ
kamal rollback # ロールバック
kamal app logs -f # ログ追跡
kamal console # Rails console
kamal migrate # マイグレーション
kamal proxy reboot # proxy 再起動
kamal prune all # クリーンアップ
必須の落とし穴回避
builder.arch: amd64: M1/M2 Mac からデプロイ時- DNS を事前設定: SSL 取得のため
options.memory: OOM 防止127.0.0.1:5432:5432: DB を外部露出しない.kamal/secretsは Git 除外- 本番でのマイグレーション戦略を事前決定
事故防止のチェックリスト
- [ ] DNS の A レコード設定済み
- [ ] SSH 鍵で deploy ユーザーログイン可能
- [ ] UFW でポート制限
- [ ]
.kamal/secretsは .gitignore に - [ ] メモリ制限(
options.memory) - [ ] ローカルで
docker build && run成功 - [ ] マイグレーション戦略決定済み
- [ ] バックアップ体制構築
- [ ] モニタリング設定
スケールの考え方
【Phase 1】単一 VPS
- $5-10/月、web + job + db + redis
- MVP、初期ユーザー数千人
【Phase 2】DB 分離
- $15-30/月、web + job + 別 DB サーバー
- ユーザー数万人
【Phase 3】複数 Web + LB
- $50-100/月、複数 web + LB + managed DB
- ユーザー数十万人
【Phase 4】Kubernetes 検討
- 大規模な複雑要件
- 通常の Web アプリはここまで来ない
Kamal で運用する意義
- サーバー1台の力を最大限に活用
- PaaS の便利さと自前運用の自由度の両立
- 月額コスト削減(Heroku 比較で 1/5 〜 1/10)
- 技術理解の深化(Docker/SSH/DNS の実践)
- Cloud Exit トレンドへの参加
これらの知識は、Rails 8 の本番運用・スタートアップの初期インフラ・個人開発の実運用・技術負債の削減など、あらゆる場面で活用できます。本記事をブックマークしておけば、Rails アプリのデプロイに自信を持って踏み出せるようになります。
本記事は2026年6月時点の情報をもとに、Kamal 2.6+、Rails 8.0+、Docker 24+、Ubuntu 22.04/24.04 LTS での動作確認・公式ドキュメントに基づき作成しています。Kamal のバージョンによって設定形式が異なる場合があるため、最新の情報はKamal 公式ドキュメントもあわせてご確認ください。
-
前の記事
【完全ガイド】iostat の見方・使い方|ディスクI/Oのボトルネックを特定する方法を徹底解説 2026.07.06
-
次の記事
【完全ガイド】MySQL EXPLAIN の見方|type/key/rows/Extraの読み方とインデックス最適化を徹底解説 2026.07.07
コメントを書く