このページは原文を AI が翻訳したものです。

最近、Kamal を使って初めて Rails アプリケーションをデプロイしました。これまで Docker Compose と Caddy でブログを管理してきた私は、Rails のデプロイも似たような流れになるだろうと最初は考えていました。しかし実際には、リバースプロキシの設定や Docker のストレージ構成を中心に、いくつか摩擦ポイントがありました。

以下が、そのセットアップで得たメモと解決策です。

アーキテクチャの決定:プロキシ競合の回避

当初の計画は、ブログがすでに Caddy の背後で動いている既存サーバーにすべてをデプロイすることでした。

しかし、Kamal はデフォルトで kamal-proxy を使用します。同じサーバー上で 2 つのリバースプロキシ(kamal-proxy と Caddy)が競合すると、すべてのトラフィックを Caddy 経由にするか、すべてを kamal-proxy に移行しない限り、すぐに複雑になってしまいます。

設定の複雑さを増やしたり、複数のアプリを処理するためにその単一サーバーをアップグレードする代わりに、私はシンプルな方法を取りました:Rails アプリ専用に同等スペックの別 VPS を立ち上げるのです。これにより、両方の環境をクリーンで分離された状態に保てました。

動作する deploy.yml

以下が最終的な config/deploy.yml 設定です:

service: <your-application-name>
image: <your-name>/<your-application-name>

ssh:
  user: <your-ssh-user-name>

servers:
  web:
    - <server ip address>
  job:
    hosts:
      - <server ip address>
    cmd: bin/jobs

# Critical if behind Cloudflare
proxy:
  ssl: true
  host: <your-host-name>
  forward_headers: true

registry:
  username: <docker-hub-username>
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  secret:
    - RAILS_MASTER_KEY
  clear:
    HOST: <your-host-name>
    RAILS_SERVE_STATIC_FILES: true
    RAILS_LOG_TO_STDOUT: true
    DB_HOST: <same-as-server-name>-db

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"

asset_path: /rails/public/assets

volumes:
  - "pulse_storage:/rails/storage"

builder:
  arch: amd64

accessories:
  db:
    image: postgres:18
    host: <your-server-ip>
    env:
      clear:
        POSTGRES_USER: <application-name>
        POSTGRES_DB: <application-name>_production
      secret:
        - POSTGRES_PASSWORD
    files:
      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql
    volumes:
      - <application>_db_data:/var/lib/postgresql/data

主要な設定の詳細

1. Cloudflare と forward_headers

Cloudflare でプロキシを有効(オレンジ色の雲)にしている場合、proxy ブロック内に forward_headers: true を設定する必要があります:

proxy:
  ssl: true
  host: example.com
  forward_headers: true

この設定がないと、kamal-proxy はクライアントの実際の IP を Rails に渡さず、request.remote_ip が Cloudflare のエッジ IP を報告することになります。

2. Docker エラー:directoriesvolumes の違い

最初の kamal deploy の際、データベースコンテナが以下のエラーで起動に失敗しました:

docker: Error response from daemon: failed to create task for container:
failed to create shim task: OCI runtime create failed: runc create failed:
unable to start container process: error during container init:
error mounting "/home/<username>/<application>-db/data" to rootfs at
"/var/lib/postgresql/data": change mount propagation through procfd:
open o_path procfd: open /var/lib/docker/overlay2/[...]/merged/var/lib/postgresql/data:
no such file or directory: unknown

Kamal のアクセサリ設定では:

  • directories はホストパスを直接バインドします(ホストファイルシステム上に存在する必要があります)。
  • volumes は名前付き Docker ボリュームを作成・管理します。

多くのチュートリアルではアクセサリを directories で設定していますが、名前付き volumes に切り替えることで、ホストディレクトリの権限やパス作成の問題を完全に回避できました:

accessories:
  db:
    volumes:
      - <application>_db_data:/var/lib/postgresql/data

3. init.sql による複数データベースの初期化

Rails 8 で Solid Queue、Solid Cache、Solid Cable を使用する場合、本番環境でデータベースを分離するのが標準です。SSH で手動でデータベースを作成する代わりに、PostgreSQL では初期化スクリプトを /docker-entrypoint-initdb.d/ にマウントできます:

-- config/init.sql
CREATE DATABASE <application>_production;
CREATE DATABASE <application>_production_cable;
CREATE DATABASE <application>_production_cache;
CREATE DATABASE <application>_production_queue;

このファイルを deploy.yml でマッピングします:

accessories:
  db:
    files:
      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql

データベースコンテナが初めて起動する際、必要なすべてのデータベースが自動的に作成されます。

まとめ

適切に設定すれば、Kamalは非常にスムーズなデプロイワークフローを提供してくれます。主な摩擦ポイントは、kamal-proxyがCloudflareのヘッダーとどう連携するかを理解することと、アクセサリー用のDockerボリュームマウントが未初期化のホストディレクトリではなく名前付きボリュームを使用するようにすることでした。

Epona
執筆Epona

There's nothing wrong with having a little fun

x.com/simura_epona

コメントを読み込み中…