关于 cofounder-app-deploy
cofounder-app-deploy 是用于指导 Web 应用部署的技能,面向配置 Locaweb Cloud 与 Kamal 部署流程的开发者。它涵盖环境配置、基础设施准备、密钥管理及平台约束。
主要功能
- 01生成按环境区分的部署与拆除工作流
- 02配置 Kamal 与环境密钥文件
- 03准备 Locaweb Cloud 基础设施
- 04检查容器、健康检查与存储约束
- 05提供 PostgreSQL 命令生成脚本
适用场景
- 01为应用设置预览和生产环境
- 02配置 GitHub Actions 部署及拆除流程
- 03排查部署前的 Dockerfile 与端口要求
- 04为 PostgreSQL 配置 VM 资源参数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ locaweb/ cofounder --skill cofounder-app-deploy安装后在 Claude Code 中输入/cofounder-app-deploy确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/.kamal/secrets-commonsecrets.previewsecrets.productionexamples/config/deploy.preview.ymldeploy.production.ymldeploy.ymlreferences/env-vars.mdkamal.mdoperations.mdpostgres-recipe.mdrecovery.mdscaling.mdteardown.mdworkflows.mdscripts/generate_pg_cmd.py详细介绍
应用部署
概述
要部署 Web 应用,请为每个环境创建:
- 部署工作流:
.github/workflows/deploy-{env}.yml - 拆除工作流:
.github/workflows/teardown-{env}.yml - 环境专属的 Kamal 配置:
config/deploy.{env}.yml - 环境专属的密钥文件:
.kamal/secrets.{env}
所有环境通用的文件:
- Kamal 部署配置:
config/deploy.yml - 通用密钥文件:
.kamal/secrets-common - 仓库根目录下的单个
Dockerfile
部署工作流语法请参阅 references/workflows.md,拆除工作流语法请参阅 references/teardown.md。.kamal/(密钥)和 config/(Kamal 配置)的示例文件请参阅 examples/ 文件夹。.kamal/ 和 config/ 文件夹位于仓库根目录,以便 Kamal 部署作业访问它们。
部署工作流的工作方式
Caller workflow (.github/workflows/deploy-{env}.yml)
| |
provisioning job deploy job
| |
v v
+----------------------------------+ +----------------------------------+
| External workflow | | Kamal deployment |
|(locaweb/locaweb-cloud-provision)| | (runs in caller's deploy job) |
| | | |
| Provisions infrastructure: | | kamal setup / kamal deploy |
| VMs, networks, disks, IPs, | | Builds and pushes Docker image |
| firewalls, snapshots, DNS | | Deploys to provisioned VMs |
| | | Reboots scaled accessories |
| Outputs: infra_env, IPs, | | |
| infrastructure_changed, | | |
| scaled_accessories | | |
+----------------------------------+ +----------------------------------+
配置作业负责的内容
- 基础设施配置(VM、网络、磁盘、防火墙、DNS、快照)
- 输出:
infra_env、infrastructure_changed、scaled_accessories、web_ip、worker_ips、accessory_ips
平台限制(请先阅读)
这些限制适用于部署到此平台的所有应用。开始任何部署工作时,请提前说明这些限制:
- 仓库根目录只能有一个 Dockerfile,Web 应用必须监听端口 80,否则请调整
/config/deploy.yml中proxy块的app_port - 在
GET /up进行健康检查,健康时返回 HTTP 200 forward_headers: false是硬性要求——VM 直接暴露在互联网上,没有上游代理。Agent 绝不能将其设为true。- 只使用一个 Web VM:不进行横向扩展。通过更大的
web_plan进行纵向扩展(可用规格请参见 references/scaling.md -- VM Plans)。优先选择纵向扩展表现良好的运行时和框架。 - 工作进程使用相同的 Docker 镜像,但命令不同(
deploy.yml中的servers.workers.cmd)。 - 应用角色使用
volumes,附件服务使用directories——对于主要应用角色(Web、工作进程),使用 Kamal 的volumes关键字挂载持久化数据。对于附件服务,使用directories(支持mode和owner选项)。Kamal 会在主机上自动创建这两者,并将其直接映射到主机路径,使数据可见、可移植且可备份。绝不要使用具名 Docker 卷(myapp_data:/path)。有关directories语法(字符串和哈希格式)、mode/owner选项,以及volumes和directories的区别,请参见 references/kamal.md — Accessories。 - 主机路径必须是
/data/{subdir}——Web VM 和每个附件服务 VM 都有挂载在/data/的持久化磁盘。始终挂载/data/的子目录,绝不要直接挂载/data/根目录(Web 应用的用法请参见 references/env-vars.md -- Disk Storage Path,数据库示例请参见 references/postgres-recipe.md -- Volume Mount)。原因有二:(1)/data/是带有定期快照策略的附加磁盘,可确保灾难恢复——/data/之外的数据不会备份;(2)ext4 文件系统会在挂载根目录创建lost+found,这会导致首次启动时需要干净目录的 Docker 镜像出错(PostgreSQLinitdb、Redisappendonly.aof等)。 - 调用方工作流中不得构建 Docker 镜像:调用方的部署作业通过 Kamal 构建、推送并部署 Docker 镜像。调用方工作流不得包含任何单独的 Docker 构建或推送步骤(不得使用
docker/build-push-action、docker build、docker push,也不得登录 ghcr.io)。Kamal 会使用仓库根目录的 Dockerfile 处理完整的构建、推送和部署生命周期。 - 始终启用 TLS:在每个环境配置中设置
proxy.ssl: true——nip.io 和自定义域名都会通过 HTTP-01 验证自动获取 Let's Encrypt 证书。此前请求提高 nip.io 的速率限制时,Let's Encrypt 从未拒绝,因此可放心对 nip.io 子域名使用 TLS。 - 附件服务配置灵活——每个附件服务都有自己的 VM 和数据磁盘。适当时可通过 Kamal 层添加其他附件服务(Redis、WAHA、Meilisearch 等)。对于 PostgreSQL,请参见
supabase/postgresrecipe——这是一个添加了多个扩展的 Postgres 镜像,tech-stack 技能也推荐使用该镜像。通过 kamal-proxy 提供 HTTP/HTTPS 流量的附件服务需要在防火墙开放端口 80 和 443——在附件服务的 JSON 条目中传入"ports": "80,443"(端口 22/SSH 始终开放)。有关代理配置详情,请参见 references/kamal.md — Accessories。 - 每次部署都重启附件服务——
kamal deploy不会更新附件服务,但每次部署时,部署工作流都会在kamal setup后运行kamal accessory reboot all。这可确保始终应用附件服务配置更改(镜像标签、环境变量、卷、端口、命令)。重启期间附件服务会停机(不进行滚动部署)。详情请参见 references/kamal.md — Accessories。 - 命名规则——
env_name和附件服务的name值只能使用小写字母、数字和下划线([a-z0-9_])。不得使用连字符、大写字母或特殊字符。 如果应用当前的设计与其中任何一项冲突,请在继续进行部署设置之前解决冲突。
脚本
generate_pg_cmd.py
如果使用 supabase/postgres 配方,则输出针对 VM 方案优化的完整 PostgreSQL cmd 字符串。将其用于 config/deploy.<environment>.yml 中的 accessories.db.cmd 字段。
mise x -- python3 scripts/generate_pg_cmd.py --plan medium
# Output: postgres -D /etc/postgresql -c shared_buffers=1GB -c effective_cache_size=3GB -c work_mem=10MB -c maintenance_work_mem=256MB -c max_connections=100
有效方案:micro、small、medium、large、xlarge、2xlarge、4xlarge。
设置流程
按顺序执行以下步骤。每一步都是幂等的,可在多个 Agent 会话中安全地重新运行。
步骤 1:准备应用
- 满足所有 Dockerfile 要求——根目录下只有一个 Dockerfile、端口为 80、在
/up处进行健康检查 - 如果使用工作进程:确保同一个 Docker 镜像支持为工作进程运行单独的命令
步骤 2:确保已配置 GitHub 仓库
- 检查是否已配置 git 远程仓库:
git remote -v - 如果已设置
origin,则跳过此步骤 - 如果尚未配置远程仓库,请先使用 repo-setup 技能进行设置,然后再继续
步骤 3:生成 SSH 密钥
检查此仓库是否已有 SSH 密钥:
test -f ~/.ssh/<repo-name> && echo "Key exists" || echo "Key missing"
如果密钥已存在,请重复使用,不要覆盖。
如果密钥不存在,请生成新的 Ed25519 SSH 密钥:
ssh-keygen -t ed25519 -f ~/.ssh/<repo-name> -N "" -C "<repo-name>-deploy"
chmod 600 ~/.ssh/<repo-name>
此密钥将:
- 作为
SSH_PRIVATE_KEYGitHub 密钥保存(私钥) - 在本地用于通过 SSH 连接到预览环境 VM 进行调试
- 公钥将在部署工作流运行时自动派生
步骤 4:收集 CloudStack 凭据
gh secret list
如果 CLOUDSTACK_API_KEY 和 CLOUDSTACK_SECRET_KEY 都已显示 → 跳至步骤 5。
否则:
A. 检查是否拥有 Locaweb Cloud 账户
询问用户:“您已经拥有 Locaweb Cloud 账户了吗?”
- 没有 → 告知用户访问 locaweb.com.br/locaweb-cloud,然后点击 “Contratar” 注册。等待用户确认已拥有账户,然后继续执行 B。
- 有 → 继续执行 B。
B. 查找 API 密钥
引导用户完成以下步骤:
- 打开 painel-cloud.locaweb.com.br
- 前往:Contas → (您的账户) → Visualizar usuarios → (您的用户)
- 最多等待 60 秒——密钥会异步加载,可能不会立即显示。它们会显示在用户页面的 “Criado”(创建日期)下方。
如果等待后密钥仍未显示:
- 点击用户页面右上角的 “Gerar novas chaves” 图标。
- 等待密钥显示。
等待用户确认已拿到两个密钥,然后继续执行 C。
C. 创建 GitHub 密钥
提供 GitHub 密钥页面的 URL:
echo "$(gh repo view --json url -q .url)/settings/secrets/actions"
请用户打开该 URL,并为每个密钥点击 “New repository secret”:
| 名称 | 要粘贴的内容 |
|---|---|
CLOUDSTACK_API_KEY | Copiar Chave da API |
CLOUDSTACK_SECRET_KEY | Copiar Chave secreta |
等待用户确认两个密钥均已保存,然后继续执行步骤 5。
步骤 5:设置应用机密(数据库、API 密钥等)
完整的机密和环境变量参考,请参阅 references/env-vars.md。
发现所有应用环境变量: 阅读项目的 .env 文件,获取应用使用的完整环境变量列表。与应用的配置加载代码(例如,backend/internal/config/config.go)交叉核对,确认预期使用的变量。对每个变量进行分类:
- 跳过 — 仅用于本地开发(
DEV_MODE绝不能在生产环境中设置),或已直接设置在 Kamal 配置的env.clear或 Dockerfile 中(例如:PORT) - 派生 — 在
.kamal/secrets中由其他机密组合而成(DATABASE_URL由POSTGRES_PASSWORD派生) - 明文 — 非敏感,放入 Kamal 配置的
env.clear(无需 GitHub Secret) - 机密 — 敏感,需要 GitHub Secret、
.kamal/secrets.<env>中的一项、config/deploy.<env>.yml中env.secret的一项,以及工作流env:块中的一项
保留此分类列表——它将用于步骤 6 和 7。
使用 gh secret list 检查哪些机密已存在。
如果应用使用 supabase/postgres recipe,请设置数据库机密:
-
为每个环境生成随机密码:
# Preview password mise x -- python -c "import secrets; print(secrets.token_urlsafe(32))" # Production password (different from preview) mise x -- python -c "import secrets; print(secrets.token_urlsafe(32))" -
使用生成的密码通过
gh secret set --body设置POSTGRES_PASSWORD作为 GitHub Secret(Agent 直接执行此操作——绝不要要求用户设置生成的密码) -
DATABASE_URL不是单独的 GitHub Secret——它在.kamal/secrets文件中由POSTGRES_PASSWORD派生(参见 examples/ 中的模式) -
默认预览环境使用不带后缀的名称:
POSTGRES_PASSWORD -
其他环境使用与环境名称相匹配的带后缀名称:例如,
POSTGRES_PASSWORD_PRODUCTION
对于上述发现步骤中识别出的其他应用机密,请检查其值是否已存在于项目的 .env 文件中:
-
本地和部署环境可以使用相同的值(例如,SMTP 密码、OAuth 凭据、第三方 API 密钥,具体视情况而定)——Agent 直接从
.env设置 GitHub Secret(参见步骤 6)。 -
使用不同的值或未出现在
.env中——用户必须通过 GitHub UI 设置(参见步骤 6)。 -
绝不要通过聊天接收机密值
步骤 6:创建 GitHub Secrets
使用 gh secret list 检查哪些机密已存在——只创建缺失的机密。
Agent 直接设置的机密
# SSH private key for preview (skip if already set)
gh secret set SSH_PRIVATE_KEY < ~/.ssh/<repo-name>
# Postgres password for preview (skip if already set)
gh secret set POSTGRES_PASSWORD --body "<generated password from Step 5>"
对于其他环境,请使用带后缀的名称:
# SSH private key for production
gh secret set SSH_PRIVATE_KEY_PRODUCTION < ~/.ssh/<repo-name>-production
# Postgres password for production
gh secret set POSTGRES_PASSWORD_PRODUCTION --body "<generated password from Step 5>"
注意:DATABASE_URL 无需 GitHub Secret——它在 .kamal/secrets 文件中由 POSTGRES_PASSWORD 派生。
应用专属机密
对于只有用户知道的机密(应用 API 密钥、SMTP 凭据等),请先检查项目的 .env 文件。
从 .env 复用: 如果机密存在于 .env 中,且相同的值可用于部署环境,请直接设置——该值绝不会出现在聊天中:
# One-time setup (skip if already installed):
mise x -- pip install -q python-dotenv
# Set a secret from .env (value never appears in chat):
mise x -- python -c "from dotenv import dotenv_values; import sys; print(dotenv_values('.env')[sys.argv[1]], end='')" SECRET_NAME | gh secret set SECRET_NAME
改用 GitHub UI: 如果机密不在 .env 中,或部署环境应使用不同于本地的值,请向用户提供 GitHub secrets URL:
echo "$(gh repo view --json url -q .url)/settings/secrets/actions"
请用户点击 “New repository secret”,并告知他们要输入的确切 Name 以及如何获取该值。继续之前,等待用户确认所有手动添加的机密均已保存。
步骤 7:创建 Kamal 配置和机密文件
写入之前先阅读示例文件。 以下每个子步骤都会指定一个示例文件——先使用 Read 工具阅读该文件,然后根据项目进行调整。标记为 # do not change 的行必须原样保留。移除项目不需要的部分(例如,workers、volumes)。在注释指示的位置添加项目专属的值。
7a:通用配置(config/deploy.yml)
阅读 examples/config/deploy.yml 并据此调整:
- 添加项目专属的
env.clear变量(所有环境共享的非敏感配置) - 如果应用会在磁盘上存储文件,则添加
volumes条目(路径必须位于/data/下) - 如果项目没有 workers,则移除
servers.workers块 - 如果应用没有持久化存储,则移除
volumes块 - 保持所有标记为
# do not change的行原样不动——这些是平台约束(forward_headers: false、registry ERB 模板、builder 配置、SSH 用户) - 不要在此处设置
env.secret——目标文件会替换数组(而不是合并),因此此处列出的任何密钥都会被静默丢弃。将所有密钥放入每个config/deploy.<env>.yml文件中
对于 Postgres 附加组件,使用 generate_pg_cmd.py --plan <chosen_plan> 生成 cmd 字符串。请参阅 supabase/postgres 配方。
7b:环境专属配置(config/deploy.{env}.yml)
阅读 examples/config/deploy.preview.yml(非 preview 环境则阅读 examples/config/deploy.production.yml)并据此调整:
- 设置
env.secret,列出此环境的所有密钥(通用 + 环境专属)——密钥只在此处声明 - 设置
env.clear,用于环境专属的非敏感配置(例如,APP_ENV: preview) - 根据
docs/INFRASTRUCTURE.md设置附加组件——匹配镜像,通过 ERB 设置主机(<%= ENV['INFRA_<NAME>_IP'] %>),并配置端口、cmd、env 和directories(必须始终位于/data/下) - 设置
proxy.host——preview 环境使用 nip.io(<%= ENV['INFRA_WEB_IP'] %>.nip.io),production 环境使用自定义域名。始终设置ssl: true - 如果项目没有 workers,则移除
servers.workers块
重要——INFRA_*_IP 仅用于 Kamal 和外部 URL: 仅在 host: 字段、servers.*.hosts 和 proxy.host 中使用 INFRA_*_IP。不要在 env.clear 或 env.secret 中使用。组件间通信请使用 CloudStack 内部 DNS 主机名(即附加组件名称,例如 db、redis)。
7c:通用密钥(.kamal/secrets-common)
阅读 examples/.kamal/secrets-common 并据此调整:
- 保留
KAMAL_REGISTRY_PASSWORD行——它不是 GitHub Secret;它来自工作流中的secrets.GITHUB_TOKEN - 根据步骤 5 中识别的每个通用密钥,添加一行(格式:
SECRET_NAME=$SECRET_NAME)
7d:环境专属密钥(.kamal/secrets.{env})
阅读 examples/.kamal/secrets.preview(非 preview 环境则阅读 examples/.kamal/secrets.production)并据此调整:
- 根据步骤 5 中识别的每个环境专属密钥,添加一行
- Preview 使用不带后缀的环境变量名称:
POSTGRES_PASSWORD=$POSTGRES_PASSWORD - 非 preview 环境映射到带后缀的名称:
POSTGRES_PASSWORD=$POSTGRES_PASSWORD_PRODUCTION - 派生密钥(例如
DATABASE_URL)直接组合生成——无需为其单独创建 GitHub Secret
密钥如何从 GitHub 流向你的应用
GitHub Secrets --> Workflow env: block --> Shell environment --> .kamal/secrets* --> Kamal --> Container
- GitHub 密钥存储实际值(例如,
POSTGRES_PASSWORD、API_KEY_PRODUCTION) - 部署工作流的
env:块会将每个 GitHub 密钥映射为运行器上的环境变量 - Kamal 使用
-d <destination>运行时,会读取.kamal/secrets-common和.kamal/secrets.<destination>,从 Shell 环境中解析密钥值 config/deploy.yml(通用)和config/deploy.<env>.yml(特定环境)中的env.secret所列密钥会注入容器
密钥文件每行左侧是Kamal 密钥名称(在 env.secret 中引用)。右侧是 Shell 环境变量名称(与 GitHub 密钥名称一致)。对于非预览环境,GitHub 密钥会添加后缀(例如,POSTGRES_PASSWORD_PRODUCTION),因此右侧映射到带后缀的名称,而左侧保持不带后缀。
附属服务同步: 工作流的 accessories JSON、config/deploy.<env>.yml 和 docs/INFRASTRUCTURE.md 必须始终描述同一组服务。每个附属服务都需要在 JSON 中包含 name、plan、disk_size_gb;面向 Web 的服务还需要 "ports": "80,443"。双向同步时都以 INFRASTRUCTURE.md 为准——每当附属服务发生变化,都要更新该文件。
警告:每个附属服务都必须设置 disk_size_gb。 即使附属服务不需要持久化存储,也必须设置 disk_size_gb,值的范围为 10–4000 GB。例如:{"name":"nginx","plan":"small","disk_size_gb":10,"ports":"80,443"}。
命名:附属服务的 name 只能使用小写字母、数字和下划线([a-z0-9_])。 不得使用连字符、大写字母或特殊字符。
同步示例:
# In config/deploy.preview.yml:
accessories:
db:
image: supabase/postgres:17.11.0.007
# ... backend service, no proxy
n8n:
image: n8nio/n8n:latest
host: <%= ENV['INFRA_N8N_IP'] %>
proxy:
host: n8n.<%= ENV['INFRA_N8N_IP'] %>.nip.io
ssl: true
app_port: 5678
# ... web-facing, uses kamal-proxy
# In caller workflow (infra job):
with:
accessories: '[{"name":"db","plan":"medium","disk_size_gb":20},{"name":"n8n","plan":"small","disk_size_gb":10,"ports":"80,443"}]'
步骤 8:创建部署和拆除工作流
从预览环境开始。使用 Read 工具阅读示例工作流,然后进行调整:
-
部署工作流: 阅读 references/workflows.md 中的 预览工作流示例部分。复制完整的工作流 YAML 并进行调整:
- 设置
accessoriesJSON,使其与config/deploy.preview.yml和docs/INFRASTRUCTURE.md中的附属服务一致——每个附属服务都需要name、plan和disk_size_gb;面向 Web 的服务还需要"ports": "80,443" - 将项目密钥添加到部署任务的
env:块中——每个被.kamal/secrets文件引用的 GitHub 密钥都添加一项(DATABASE_URL等派生密钥无需添加) - 如果项目没有工作进程,移除
workers_replicas和workers_plan - 不要添加 Docker 构建/推送步骤或
secrets: inherit——Kamal 会处理整个构建、推送和部署流程 - 保存为
.github/workflows/deploy-preview.yml
- 设置
-
拆除工作流: 阅读 references/teardown.md 了解拆除模式。保存为
.github/workflows/teardown-preview.yml
第 9 步:添加其他环境(准备就绪时)
预览工作流(由推送触发)会针对主分支上的每次更改立即提供反馈,符合典型的开发者工作流程。团队可根据自身流程添加其他环境。
一种常见做法是添加由版本标签(v*)触发的**“生产”环境**,标签表示其指向的提交已准备好用于生产。你也可以根据需要创建其他环境,并设置不同的触发条件和工作流输入。
对于每个其他环境:
- 生成单独的 SSH 密钥:
~/.ssh/<repo-name>-<env_name>(步骤与第 3 步相同) - 将其存储为带环境名称后缀的 GitHub Secret,例如:
SSH_PRIVATE_KEY_PRODUCTION - 如果使用数据库,创建带相同后缀的单独 Secret,例如:
POSTGRES_PASSWORD_PRODUCTION - 如果应用有适用于该环境、且各环境值不同的自定义 Secret,也以相同方式添加后缀,例如:
API_KEY_PRODUCTION。 - 所有环境通用的 Secret(例如
CLOUDSTACK_API_KEY、CLOUDSTACK_SECRET_KEY)无需重新创建,只需在每个调用方工作流中传入即可 - 编写新的环境专属 Kamal 配置:
config/deploy.<env>.yml(结构与预览配置相同,并设置相应的主机、代理主机和附加组件) - 编写
.kamal/secrets.<env>文件,将 Kamal Secret 名称映射到带后缀的环境变量名称(例如,POSTGRES_PASSWORD=$POSTGRES_PASSWORD_PRODUCTION) - 创建部署工作流:
.github/workflows/deploy-<env>.yml(采用相同的两作业模式,并设置该环境的触发条件、Secret 和输入) - 创建拆除工作流:
.github/workflows/teardown-<env>.yml - 对于使用自定义域名的生产环境,请参阅 DNS 配置
部署反馈循环
在进入此循环前,所有更改(Kamal 配置、工作流、应用代码)都必须提交并推送,部署由 git push 触发。
+-----------------------------------------------------+
| |
| Push triggers GitHub Actions workflow |
| | |
| v |
| Monitor workflow run (gh run watch) |
| | |
| v |
| Workflow succeeds? --No--> Read logs, fix, |
| | commit/push, |
| Yes repeat |
| | |
| v |
| Health check: curl /up -> HTTP 200? |
| | |
| Yes --> Done (deployment verified) |
| | |
| No |
| | |
| v |
| SSH debug (logs, container state) |
| | |
| v |
| Return to tech-stack Local Development |
| Feedback Loop to diagnose and fix |
| |
+-----------------------------------------------------+
工作流监控
首先向用户提供可点击的链接:gh run list --limit=1 --json databaseId,url -q '.[0].url'。
醒目地展示:
部署正在运行!可在此实时查看:
<上述命令返回的 URL>
然后使用 gh run watch 进行监控。
如果失败:运行 gh run view <run-id> --log-failed。常见原因:缺少 Secret、Dockerfile 问题(端口/健康检查)、权限错误、输入无效。修复后提交并推送,预览环境会在推送时自动触发部署。
健康检查验证
curl -s -o /dev/null -w "%{http_code}" https://<web_ip>.nip.io/up(或 https://<domain>/up)。HTTP 200 = 完成。如果失败,请通过 SSH 调试(参阅 references/operations.md),然后返回技术栈对应的本地开发反馈循环进行修复。
修改现有部署中的附加组件
- 更新
docs/INFRASTRUCTURE.md - 更新
config/deploy.<env>.yml— 添加/移除附加组件配置块(镜像、主机 ERB、端口、cmd、env、目录;面向 Web 的组件需添加proxy块) - 更新工作流中的
accessoriesJSON —disk_size_gb为必填项;面向 Web 的组件需要"ports": "80,443";name必须与 Kamal 配置一致 - 根据需要更新环境变量 — 非 Secret 变量 →
env.clear;Secret →env.secret+ GitHub Secret +.kamal/secrets.<env>+ 工作流env:块 - 提交、推送并重新部署。移除组件时,也要移除对其环境变量的代码引用。
运维(部署后)
与已部署基础设施交互的快速参考。完整详情请参阅 references/operations.md。
| 任务 | 命令模式 |
|---|---|
| 获取部署 IP | rm -rf $HOME/tmp/provision-output && gh run download <run-id> --name provision-output --dir $HOME/tmp/provision-output |
| SSH 连接到虚拟机 | 先解析密钥:REPO_NAME=$(gh repo view --json name -q .name),然后运行 ssh -i ~/.ssh/$REPO_NAME[-<env_name>] root@<ip> — 始终使用 -i,切勿依赖默认 SSH 密钥 |
| 连接到附属服务(例如 Postgres) | SSH 连接到附属服务虚拟机 -> docker exec -it <repo-name>-db psql -U postgres |
| 查看应用日志 | SSH 连接到 Web 虚拟机 -> docker logs $(docker ps -q --filter "label=service=<repo-name>") --tail 100 |
| 检查应用健康状态 | curl -s https://<web_ip>.nip.io/up |
| 进入应用容器的 Shell | SSH 连接到 Web 虚拟机 -> docker exec -it $(docker ps -q --filter "label=service=<repo-name>") sh |
Dockerfile 要求
- 在仓库根目录放置单个
Dockerfile - Web 应用必须监听 80 端口
- 健康检查端点为
GET /up→ HTTP 200 - 如果使用工作进程,同一镜像必须支持通过
config/deploy.<env>.yml中的servers.workers.cmd执行独立命令 - 通过
DATABASE_URL环境变量连接数据库 — 如果该变量缺失,必须直接失败
对于 Go+React 应用,请参阅 tech-stack 技能中的 Dockerfile 部分。对于其他技术栈,请确保已配置 80 端口和健康检查端点。
数据库迁移
单个 Web 虚拟机 → 在容器启动时运行迁移(不会发生竞争条件)。在 Web 服务器启动前,将迁移纳入入口点。所有迁移依赖项都必须打包到 Docker 镜像中。
部署输出和 URL
部署后,可从以下位置获取信息:工作流输出(web_ip、worker_ips、accessory_ips、infrastructure_changed、scaled_accessories、infra_env)、Actions 步骤摘要,以及 provision-output 工件(保留 90 天)。
应用 URL: 无域名时:https://<web_ip>.nip.io。配置域名时:https://<domain>(需要 DNS A 记录)。两种方式都会通过 Let's Encrypt HTTP-01 获取 TLS。
为域名选择目标环境
用户要求配置自定义域名时,务必询问要将其配置到哪个环境 — 切勿悄悄采用默认环境。选项如下:
- 预览环境 — 每次推送都会立即上线。简单、快速。
- 生产环境 — 预览环境将变为预发布环境;只有带标签的版本才会上线。更安全,发布周期也更稳定。
请说明之后可以更改。然后继续进行 DNS 配置。如果生产环境尚不存在,请通过 步骤 9 创建。
自定义域名的 DNS 配置
首次部署完成前,无法得知 Web 虚拟机的公网 IP。要设置自定义域名:
- 先不配置域名进行部署(在目标配置中省略
proxy.host域名)。应用将可通过https://<web_ip>.nip.io访问。 - 记下
web_ip,可从工作流输出或步骤摘要中获取。 - 创建 DNS A 记录,将域名指向该 IP:
Type: A Name: myapp.example.com (or @ for apex) Value: <web_ip from step 2> TTL: 300 - 更新目标配置(
config/deploy.<env>.yml),将proxy.host设置为该域名(SSL 已设置为true)。 - 提交、推送并重新部署。 kamal-proxy 将自动配置 Let's Encrypt 证书。
Let's Encrypt HTTP-01 验证要求在颁发证书前,域名已解析到服务器。同一环境重新部署时 IP 保持不变 — 只有在环境被销毁并重新创建时才会变化。
顶级域名和 www
对于顶级域名(例如 example.com),主动添加 www。在代理配置中使用 hosts: 数组,并指示用户创建两条 DNS A 记录:
Type: A Name: @ Value: <web_ip> TTL: 300
Type: A Name: www Value: <web_ip> TTL: 300
目标配置使用 proxy.hosts(数组),而不是 proxy.host(字符串):
proxy:
hosts:
- example.com
- www.example.com
ssl: true
kamal-proxy 将为每个主机名分别配置 Let's Encrypt 证书。部署前,两条 DNS 记录都必须解析到该服务器。
规范重定向: BASE_URL 始终是裸域名。应用必须在应用层将 www 重定向到裸域名(HTTP 301)——kamal-proxy 不会执行主机到主机的重定向。这样可以避免 OAuth 回调、Cookie 和链接不一致。
工作进程
通过在调用方工作流中更改 workers_replicas 来水平扩展工作进程(所有输入请参阅 references/workflows.md -- Deploy Input Reference)。扩展详情请参阅 references/scaling.md -- Scaling Workers。工作进程命令在 config/deploy.yml 的 servers.workers.cmd 下设置,而不是作为工作流输入。
workers_replicas: 0表示不运行工作进程(工作流不会配置工作进程虚拟机)workers_replicas: 1或更高值表示配置相应数量的工作进程虚拟机
启用工作进程时:
- 在
config/deploy.yml中添加servers.workers.cmd和servers.workers.proxy: false:
# config/deploy.yml
servers:
workers:
cmd: "celery -A tasks worker --loglevel=info"
proxy: false
- 将工作进程主机添加到
config/deploy.{env}.yml。配置作业会导出INFRA_WORKER_IP_0、INFRA_WORKER_IP_1等变量——每个workers_replicas对应一个:
# config/deploy.preview.yml
servers:
web:
hosts:
- <%= ENV['INFRA_WEB_IP'] %>
workers:
hosts:
- <%= ENV['INFRA_WORKER_IP_0'] %>
- <%= ENV['INFRA_WORKER_IP_1'] %>
- 在调用方工作流的 infra 作业中设置
workers_replicas和workers_plan:
with:
workers_replicas: 2
workers_plan: "small"
扩展
通过更新工作流输入和/或 Kamal 配置,然后重新部署来扩展。有关虚拟机方案、磁盘大小和详细说明,请参阅 references/scaling.md。
Web(仅垂直扩展)
更改工作流中的 web_plan 并重新部署。无需更改配置文件。会造成短暂停机。
工作进程
- 垂直扩展:更改工作流中的
workers_plan并重新部署。无需更改配置文件。会造成短暂停机。 - 水平扩展:更改工作流中的
workers_replicas。还要更新config/deploy.<env>.yml中的主机列表,使其与新的副本数一致——详情请参阅 references/scaling.md -- Scaling Workers。提交、更改推送后重新部署。
附属服务(数据库、redis 等)
更改工作流中 accessories JSON 的 plan。对于使用 supabase/postgres 的数据库附属服务,还要重新生成 cmd——完整流程请参阅 references/scaling.md -- Scaling the Database。
其他附属服务也可能有依赖内存的配置(例如 Redis 的 maxmemory、Elasticsearch 的 JVM 堆)。扩展任何附属服务时,请检查 config/deploy.<env>.yml 中的 cmd、env 或容器配置,并调整参数以匹配新方案的资源。
拆除
有关拆除环境、从现有工作流推断 zone/env_name,以及读取上次运行输出的信息,请参阅 references/teardown.md。
灾难恢复
每个已部署环境都会自动对所有数据卷进行每日快照。如果环境丢失(拆除、虚拟机故障或数据损坏),可以通过运行 recover: true 的部署工作流来恢复。
当用户要求“恢复”“recuperar”“restore”“do DR”或“灾难恢复”时:关键操作是在部署工作流的 infra 作业输入中添加 recover: true。如果没有此标志,工作流会创建空磁盘,数据将丢失。仅更改区域不算恢复——恢复是指通过 recover: true 标志从快照还原。
恢复步骤:
- 如果原始部署仍位于目标区域,请先将其拆除,或更改
zone输入以在其他区域恢复(快照会跨区域复制,因此在任一区域都可以恢复) - 在部署工作流的 infra 作业
with:块中添加recover: true(例如,.github/workflows/deploy-preview.yml) - 提交、推送并监控工作流运行
- 恢复成功后,验证数据是否完整(运行健康检查、通过 SSH 登录虚拟机、检查数据库行和文件)
- 从工作流文件中移除
recover: true——如果保留该标志,后续运行将失败,因为网络和卷已经存在
完整步骤、预检要求和当前限制,请参阅 references/recovery.md。
无本地环境时的开发
没有本地运行时环境时,请将部署反馈循环作为主要工作周期:提交/推送 → 监控工作流 → 健康检查 → 必要时通过 SSH 调试 → 重复。先从预览环境开始(由推送触发、没有域名、通过 nip.io 使用 TLS)。之后再添加生产环境——没有本地开发环境时这一点尤为重要,因为预览环境是唯一的生产前检查。
参考资料
- references/operations.md -- 部署后的操作:查找 IP、SSH 访问、数据库访问、容器调试
- references/workflows.md -- 完整的调用方工作流示例(部署 + 拆除),包含所有输入的说明
- references/env-vars.md -- 环境变量和密钥配置
- references/scaling.md -- 虚拟机方案、工作进程扩缩容、磁盘大小
- references/teardown.md -- 拆除流程、推断参数、读取输出
- references/recovery.md -- 从快照进行灾难恢复:步骤、预检、限制
- references/postgres-recipe.md --
supabase/postgres镜像的配置方案;此 Postgres 镜像包含扩展,符合 tech-stack 技能的推荐 - references/kamal.md -- Kamal 部署概念:服务/工作进程/附属组件架构、配置语法、代理、环境变量、密钥、构建器、命令