~/journal/2025/10/my-first-rails-deployment-journey-with-kamal

用 Kamal 完成我的第一次 Rails 部署之旅

首次使用 Kamal 部署 Rails 应用的笔记:与 Caddy 的代理冲突、Cloudflare 的 forward_headers,以及调试 Docker 卷挂载错误。

发布
阅读时间
2 分钟
标签
CloudflareDockerPostgreSQLRails

本页由 AI 从原文翻译。

最近我用 Kamal 部署了我的第一个 Rails 应用。之前一直用 Docker Compose 和 Caddy 管理博客,起初我以为部署 Rails 也会遵循类似的模式。结果过程中遇到了一些摩擦点,主要集中在反向代理设置和 Docker 存储配置上。

以下是这次搭建过程中的笔记和解决方案。

架构决策:避免代理冲突

我最初的计划是把所有东西部署到现有的服务器上,那里已经有 Caddy 在运行我的博客。

然而,Kamal 默认使用 kamal-proxy。在同一台服务器上让两个反向代理(kamal-proxy 和 Caddy)竞争很快就会变得混乱,除非你要么让所有流量都经过 Caddy,要么把所有东西都迁移到 kamal-proxy。

与其增加配置复杂度,或者升级那台服务器来承载多个应用,我选择了更直接的路线:单独开一台规格匹配的 VPS 专门跑 Rails 应用。这样两个环境都保持干净和隔离。

可用的 deploy.yml

以下是最终的 config/deploy.yml 配置:

yaml
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:

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

没有这个设置,kamal-proxy 不会把客户端的真实 IP 传给 Rails,导致 request.remote_ip 报告的是 Cloudflare 边缘 IP。

2. Docker 错误:directories 与 volumes

在第一次 kamal deploy 期间,数据库容器启动失败,报了这个错误:

text
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 完全避免了主机目录权限和路径创建的问题:

yaml
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/ 中:

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

在 deploy.yml 中映射这个文件:

yaml
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

相关文章

正在加载评论…