最近我用 Kamal 部署了我的第一个 Rails 应用。之前一直用 Docker Compose 和 Caddy 管理博客,起初我以为部署 Rails 也会遵循类似的模式。结果过程中遇到了一些摩擦点,主要集中在反向代理设置和 Docker 存储配置上。
以下是这次搭建过程中的笔记和解决方案。
架构决策:避免代理冲突
我最初的计划是把所有东西部署到现有的服务器上,那里已经有 Caddy 在运行我的博客。
然而,Kamal 默认使用 kamal-proxy。在同一台服务器上让两个反向代理(kamal-proxy 和 Caddy)竞争很快就会变得混乱,除非你要么让所有流量都经过 Caddy,要么把所有东西都迁移到 kamal-proxy。
与其增加配置复杂度,或者升级那台服务器来承载多个应用,我选择了更直接的路线:单独开一台规格匹配的 VPS 专门跑 Rails 应用。这样两个环境都保持干净和隔离。
可用的 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 错误:directories 与 volumes
在第一次 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/data3. 通过 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 卷挂载使用命名卷,而非未初始化的主机目录。

正在加载评论…