Kamalデプロイ
Rails公式デプロイツール — Dockerベースのゼロダウンタイムデプロイ
Kamal 2はRails 8の公式デプロイツールで、Dockerコンテナベースのゼロダウンタイムデプロイを提供します。HerokuのようなPaaSなしでも、どんなVPS(DigitalOcean、Hetzner、AWS EC2等)にもデプロイできます。
Kamalとは?
Kamal(旧MRSK)は37signalsがBasecampとHEYをデプロイするために作ったツールです。Rails 8からrails newでプロジェクトを作成するとKamal設定がデフォルトで含まれます。
主要な特徴:
Dockerコンテナベースのデプロイ
ゼロダウンタイム: 新コンテナがヘルスチェックを通過した後にトラフィック切替
kamal-proxy: Traefikを置き換える軽量リバースプロキシ(Kamal 2から)
SSL自動発行(Let's Encrypt)
ロールバック:
kamal rollback一行で前バージョン復元マルチサーバーデプロイ対応
deploy.yml設定
# config/deploy.yml
service: my-app
image: my-dockerhub-user/my-app
servers:
web:
hosts:
- 123.456.789.10
options:
network: "private"
proxy:
ssl: true
host: my-app.com
registry:
username: my-dockerhub-user
password:
- KAMAL_REGISTRY_PASSWORD
builder:
local:
arch: amd64
host: unix:///var/run/docker.sock
デプロイフロー
kamal deploy 実行
↓
1. ローカルでDockerイメージビルド
↓
2. Docker Hub(またはGHCR)にイメージPush
↓
3. サーバーでイメージPull
↓
4. 新コンテナ起動 + ヘルスチェック
↓
5. kamal-proxyがトラフィックを新コンテナに切替
↓
6. 旧コンテナ削除
⚠️ DigitalOcean + Kamal: ローカルビルド推奨
DigitalOceanの小規模Droplet(1〜2GB RAM)でサーバー上で直接Dockerビルドすると、CPU/メモリ負荷が極めて高くなります。RailsアプリのDockerビルドはgemコンパイル、アセットプリコンパイル等で多くのリソースを消費するためです。
推奨パターン: ローカルビルド → レジストリPush → サーバーPull
builder:
local:
arch: amd64
host: unix:///var/run/docker.sock
builder.local設定でローカルマシンでイメージをビルドし、Docker HubやGitHub Container Registryにpush後、サーバーでは完成したイメージをpullするだけです。
なぜサーバービルドが問題か?
bundle install: 数十のgemコンパイル → CPU 100% + メモリ1GB以上rails assets:precompile: Tailwind CSS + Viteビルド → 追加メモリ消費1GB Droplet: OOM Killerがプロセスを終了、稼働中のアプリにも影響
2GB Droplet: ビルド中のレスポンスタイムが10倍以上遅延
💡 実戦経験: Solid Queueのメモリ問題
Solid Queueはデフォルトでワーカーが3つ起動します。各ワーカーは独立プロセスで約178MBずつ占有し、Rails本体よりメモリを多く消費します。
プロセス メモリ
─────────────────────────────────────────
ruby(Rails/Puma) 357MB
bundle(SolidQueue worker x3) 178MB x 3 = 534MB
supervisord 18MB
─────────────────────────────────────────
合計 約910MB
1GBサーバーでOOMが発生するのは当然です。2GB Dropletでも、OS + Dockerオーバーヘッドを考慮すると通常運用自体がメモリ限界に近い状態です。特にデプロイ時に新コンテナ起動 + 旧コンテナdrain過程でメモリ不足が顕著になり、OOMが多発しました。
production設定でprocesses: 1, threads: 1にしてもbundleプロセスが3つ起動する点に注意が必要です。ワーカー数を減らせばメモリ節約は可能ですが、根本的に小規模VPSでは厳しいです。
メモリ節約方法:
1. YJIT無効化(RUBY_YJIT_ENABLE=0)— プロセスあたり30〜50MB節約(性能は若干低下)
2. MALLOC_ARENA_MAX=2 — Rubyのメモリ断片化を削減(プロセスあたり50〜100MB節約、効果大)
3. Sidekiqに切替 — スレッドベースで1プロセスで動作、メモリ大幅節約。ただしRedis必要
4. Solid Queueを別マシンに分離 — SQLite使用時は不可能(ファイル共有問題)
筆者は最もシンプルな環境変数追加を選択しました:
MALLOC_ARENA_MAX = "2" # Rubyメモリ断片化防止
RUBY_YJIT_ENABLE = "0" # YJIT無効化でメモリ節約
これだけでプロセスあたり80〜150MB程度の節約が可能ですが、根本的な解決策ではありませんでした。
筆者は結局2GB Dropletでの不安定さを解消できずFly.ioに移行しました。より大きなDroplet(4GB+)にすれば解決する可能性はありますが、コスト対比でFly.ioがより良い選択でした。
ローカルビルドの利点:
開発マシンの豊富なCPU/RAMを活用(M1/M2 Macの高速ビルド)
サーバーはpull + コンテナ起動のみ → ほぼ負荷なし
稼働中のアプリに影響なくデプロイ可能
主要コマンド
kamal setup # 初回サーバー設定(Docker、kamal-proxyインストール)
kamal deploy # ビルド → Push → デプロイ
kamal redeploy # 既存イメージで再デプロイ(設定変更時)
kamal rollback # 前バージョンにロールバック
kamal app logs # アプリログ確認
kamal app exec 'bin/rails console' # リモートRailsコンソール
kamal app exec 'bin/rails db:migrate' # マイグレーション実行
⚠️ Vite autoBuild: プロダクションでは必ずfalse
Viteを使うRailsアプリをデプロイする際、config/vite.jsonのプロダクション環境でautoBuild: trueに設定してはいけません。autoBuild: trueだとユーザーアクセスのたびにサーバーでvite buildが実行され、膨大なCPU/メモリ負荷が発生します。
筆者はこの設定を見落として、アクセスのたびにサーバーリソースが枯渇する問題を経験しました。
// config/vite.json
{
"development": { "autoBuild": true }, // ✅ 開発環境のみtrue
"test": { "autoBuild": false },
"production": { "autoBuild": false } // ⚠️ 必ずfalse!
}
プロダクションではデプロイ時にvite buildを事前実行(DockerfileのRUN bin/rails assets:precompile)し、ランタイムではビルド済みアセットのみ配信すべきです。
Kamal vs 他のデプロイ方式
| 項目 | Kamal + VPS | Heroku | Fly.io | Capistrano |
|---|---|---|---|---|
| 月額費用 | $5〜12(VPS) | $25+(Eco) | $5+ | VPS費用のみ |
| Docker必須 | ✅ | ❌(Buildpack) | ✅ | ❌(サーバー直接) |
| ゼロダウンタイム | ✅(kamal-proxy) | ✅(有料) | ✅ | △(設定必要) |
| SSL自動 | ✅(Let's Encrypt) | ✅ | ✅ | 手動 |
| SQLite対応 | ✅(Volume) | ❌ | ✅(Litestream) | ✅ |
| サーバー制御 | 完全制御 | 制限的 | 制限的 | 完全制御 |
| 学習コスト | 中 | 低 | 中 | 高 |
SQLite + Kamal
KamalでSQLiteベースのアプリをデプロイするには、Docker Volumeを使用してデータベースファイルをコンテナ外部に保存する必要があります。
servers:
web:
volumes:
- data:/rails/storage # SQLite DBファイル永続化
注意: SQLiteは単一サーバーでのみ使用可能です。マルチサーバーデプロイ時はPostgreSQL等のクライアント-サーバーDBを使用してください。
構造ダイアグラム
キーポイント
gem install kamal — Kamal CLIインストール
kamal init — config/deploy.yml生成
deploy.ymlにサーバーIP、Dockerレジストリ、環境変数を設定
builder.local設定 — ローカルビルド + レジストリPush(小規模サーバー必須)
kamal setup — 初回サーバー設定(Docker、kamal-proxyインストール)
kamal deploy — ビルド → Push → ゼロダウンタイムデプロイ
メリット
- ✓ Rails 8公式 — rails newですぐ使用可能
- ✓ 月$5〜12でプロダクション運用可能(PaaS比で安価)
- ✓ ゼロダウンタイムデプロイが標準搭載
- ✓ SSL自動発行(Let's Encrypt)
- ✓ サーバー完全制御 — ベンダーロックインなし
- ✓ kamal rollbackで即座にロールバック
- ✓ SQLite運用可能 — Render/Herokuは永続ボリューム未対応でSQLite不可
- ✓ DigitalOcean等のVPSはお名前.com VPSより全般的に安価で、サードパーティ連携も充実しUIもシンプル
デメリット
- ✗ サーバー管理を自分で行う必要(セキュリティパッチ、モニタリング)
- ✗ Dockerの基本知識が必要
- ✗ 初期設定がPaaSより複雑
- ✗ 小規模サーバーでサーバービルド時の負荷問題(ローカルビルドで解決)
- ✗ SSHアクセス可能なサーバーが必要(サーバーレス不可)
- ✗ VPS運用負担 — Docker/アプリログが蓄積しディスク100%問題、定期的なログ整理・容量・リソース監視等PaaSでは不要な作業が増える
- ✗ Dockerイメージレジストリ管理が必要 — Render/Fly.ioはコードpushで自動ビルドされるが、KamalはDocker Hub等にイメージを自分で保存する必要あり。Privateリポ使用時は追加費用発生の可能性、古いイメージの蓄積で容量管理も必要
- ✗ SQLite運用時バックアップを自前で実装する必要あり — Kamal自体の問題というよりSQLite + VPSの組み合わせの問題。マネージドDB(RDS等)は自動バックアップ提供だが、SQLiteファイルはcron + Litestream等で自分でバックアップパイプラインを構築する必要あり
- ✗ デプロイ時のトラブルが頻発 — インフラ知識のある筆者でもデプロイのたびに大小の問題が発生。インフラ経験がない場合、問題解決がかなり困難になりうる。Render/Fly.ioはgit pushだけでデプロイ完了だが、KamalはDocker・SSH・ネットワーク・プロキシ等複数レイヤーで問題が起こりうるためデバッグ範囲が広い