【完全ガイド】Kamal 2 で Rails 8 をデプロイする方法|設定・SSL・アクセサリー・トラブル対応を徹底解説

【完全ガイド】Kamal 2 で Rails 8 をデプロイする方法|設定・SSL・アクセサリー・トラブル対応を徹底解説

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 の本番運用に自信を持って踏み出せます。


目次

結論:Kamal 2 が Rails 8 のデプロイをこう変える

時間がない方向けに、最重要ポイントを先に示します。

Kamal 2 の 5つの特徴

  1. Rails 8 で標準: rails new するだけで Kamal 設定が生成される
  2. Docker + SSH ベース: 独自プロトコル不要、Linux があれば動く
  3. ゼロダウンタイムデプロイ: kamal-proxy が新旧コンテナを切り替え
  4. Let’s Encrypt SSL 自動: proxy.ssl: true だけで HTTPS 化
  5. 単一サーバー〜複数サーバー対応: 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 との比較

観点HerokuKamal 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 1Kamal 2
リバースプロキシTraefik(外部)kamal-proxy(自社製)
SSLTraefik 経由 Let’s Encryptkamal-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 LightsailAWS 統合、$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 consolekamal 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: 1Password
  • bitwarden: Bitwarden
  • aws-secrets-manager
  • lastpass

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

前提条件(重要)

  1. DNS が正しく設定されている(A レコード)
  2. ポート 80 と 443 が開いている
  3. サーバーがインターネットから到達可能

これらが揃わないと 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 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 公式ドキュメントもあわせてご確認ください。