mcpField
C
cofounder-app-deploy

cofounder-app-deploy

github.com/locaweb云与 DevOps4109 次安装4 天前更新
代码仓库

关于 cofounder-app-deploy

cofounder-app-deploy 是用于指导 Web 应用部署的技能,面向配置 Locaweb Cloud 与 Kamal 部署流程的开发者。它涵盖环境配置、基础设施准备、密钥管理及平台约束。

主要功能

  1. 01生成按环境区分的部署与拆除工作流
  2. 02配置 Kamal 与环境密钥文件
  3. 03准备 Locaweb Cloud 基础设施
  4. 04检查容器、健康检查与存储约束
  5. 05提供 PostgreSQL 命令生成脚本

适用场景

  1. 01为应用设置预览和生产环境
  2. 02配置 GitHub Actions 部署及拆除流程
  3. 03排查部署前的 Dockerfile 与端口要求
  4. 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-common
secrets.preview
secrets.production
examples/config/
deploy.preview.yml
deploy.production.yml
deploy.yml
references/
env-vars.md
kamal.md
operations.md
postgres-recipe.md
recovery.md
scaling.md
teardown.md
workflows.md
scripts/
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 镜像出错(PostgreSQL initdb、Redis appendonly.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/postgres recipe——这是一个添加了多个扩展的 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_KEY GitHub 密钥保存(私钥)
  • 在本地用于通过 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 密钥

引导用户完成以下步骤:

  1. 打开 painel-cloud.locaweb.com.br
  2. 前往:Contas → (您的账户) → Visualizar usuarios → (您的用户)
  3. 最多等待 60 秒——密钥会异步加载,可能不会立即显示。它们会显示在用户页面的 “Criado”(创建日期)下方。

如果等待后密钥仍未显示:

  1. 点击用户页面右上角的 “Gerar novas chaves” 图标。
  2. 等待密钥显示。

等待用户确认已拿到两个密钥,然后继续执行 C。

C. 创建 GitHub 密钥

提供 GitHub 密钥页面的 URL:

echo "$(gh repo view --json url -q .url)/settings/secrets/actions"

请用户打开该 URL,并为每个密钥点击 “New repository secret”:

名称要粘贴的内容
CLOUDSTACK_API_KEYCopiar Chave da API
CLOUDSTACK_SECRET_KEYCopiar 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
  1. GitHub 密钥存储实际值(例如,POSTGRES_PASSWORD、API_KEY_PRODUCTION)
  2. 部署工作流的 env: 块会将每个 GitHub 密钥映射为运行器上的环境变量
  3. Kamal 使用 -d <destination> 运行时,会读取 .kamal/secrets-common 和 .kamal/secrets.<destination>,从 Shell 环境中解析密钥值
  4. 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 工具阅读示例工作流,然后进行调整:

  1. 部署工作流: 阅读 references/workflows.md 中的 预览工作流示例部分。复制完整的工作流 YAML 并进行调整:

    • 设置 accessories JSON,使其与 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
  2. 拆除工作流: 阅读 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),然后返回技术栈对应的本地开发反馈循环进行修复。

修改现有部署中的附加组件

  1. 更新 docs/INFRASTRUCTURE.md
  2. 更新 config/deploy.<env>.yml — 添加/移除附加组件配置块(镜像、主机 ERB、端口、cmd、env、目录;面向 Web 的组件需添加 proxy 块)
  3. 更新工作流中的 accessories JSON — disk_size_gb 为必填项;面向 Web 的组件需要 "ports": "80,443";name 必须与 Kamal 配置一致
  4. 根据需要更新环境变量 — 非 Secret 变量 → env.clear;Secret → env.secret + GitHub Secret + .kamal/secrets.<env> + 工作流 env: 块
  5. 提交、推送并重新部署。移除组件时,也要移除对其环境变量的代码引用。

运维(部署后)

与已部署基础设施交互的快速参考。完整详情请参阅 references/operations.md。

任务命令模式
获取部署 IPrm -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
进入应用容器的 ShellSSH 连接到 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。

为域名选择目标环境

用户要求配置自定义域名时,务必询问要将其配置到哪个环境 — 切勿悄悄采用默认环境。选项如下:

  1. 预览环境 — 每次推送都会立即上线。简单、快速。
  2. 生产环境 — 预览环境将变为预发布环境;只有带标签的版本才会上线。更安全,发布周期也更稳定。

请说明之后可以更改。然后继续进行 DNS 配置。如果生产环境尚不存在,请通过 步骤 9 创建。

自定义域名的 DNS 配置

首次部署完成前,无法得知 Web 虚拟机的公网 IP。要设置自定义域名:

  1. 先不配置域名进行部署(在目标配置中省略 proxy.host 域名)。应用将可通过 https://<web_ip>.nip.io 访问。
  2. 记下 web_ip,可从工作流输出或步骤摘要中获取。
  3. 创建 DNS A 记录,将域名指向该 IP:
    Type: A
    Name: myapp.example.com (or @ for apex)
    Value: <web_ip from step 2>
    TTL: 300
    
  4. 更新目标配置(config/deploy.<env>.yml),将 proxy.host 设置为该域名(SSL 已设置为 true)。
  5. 提交、推送并重新部署。 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 或更高值表示配置相应数量的工作进程虚拟机

启用工作进程时:

  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
  1. 将工作进程主机添加到 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'] %>
  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 标志从快照还原。

恢复步骤:

  1. 如果原始部署仍位于目标区域,请先将其拆除,或更改 zone 输入以在其他区域恢复(快照会跨区域复制,因此在任一区域都可以恢复)
  2. 在部署工作流的 infra 作业 with: 块中添加 recover: true(例如,.github/workflows/deploy-preview.yml)
  3. 提交、推送并监控工作流运行
  4. 恢复成功后,验证数据是否完整(运行健康检查、通过 SSH 登录虚拟机、检查数据库行和文件)
  5. 从工作流文件中移除 recover: true——如果保留该标志,后续运行将失败,因为网络和卷已经存在

完整步骤、预检要求和当前限制,请参阅 references/recovery.md。

无本地环境时的开发

没有本地运行时环境时,请将部署反馈循环作为主要工作周期:提交/推送 → 监控工作流 → 健康检查 → 必要时通过 SSH 调试 → 重复。先从预览环境开始(由推送触发、没有域名、通过 nip.io 使用 TLS)。之后再添加生产环境——没有本地开发环境时这一点尤为重要,因为预览环境是唯一的生产前检查。

参考资料

评论

支持 Markdown