文档 / 核心概念 / 堆栈

堆栈

Stack 用于归组一个项目的应用和服务。其成员共享一个私有网络和一组变量,并可按指定顺序一起部署。

什么是 Stack?

在 API 和 CLI 中,Stack 称为项目(project):由 /api/v1/projects 和 sh0 projects 管理。Stack 为其应用提供:

  • 私有网络:每个应用可通过 <app-name> 和 <app-name>.internal 访问,重新部署后主机名不变。
  • 共享变量,Stack 中所有应用都会读取。
  • 启动顺序,在一次性部署整个 Stack 时使用。

不同 Stack 中的应用无法通过名称互相访问。数据库服务器不属于任何 Stack:任何应用都可以通过 <server-name>.internal 访问它们。

创建 Stack

在控制面板中打开 Stacks,点击 Create Stack,填写 Stack Name(1 到 100 个字符)和可选的 Description。通过 CLI:

Terminal
$ sh0 projects create my-shop
Project created: my-shop (5aee8eea-9f77-4d10-ac21-2fe4a565e666)
Note
sh0 push --project 需要这里输出的 Stack ID,不接受名称。

Stack 页面

打开 Stack 后会按类型分组显示其服务,并提供:

  • Add App(打开该 Stack 的部署页面)、Stack Settings、Export Snapshot 和 Import Snapshot。
  • Internal network & shared settings 面板:显示每个服务的内部主机名、Shared variables、Start order,以及按该顺序部署所有应用的 Deploy the project。

Stack 变量

共享变量会在下一次部署时注入 Stack 中的每个应用。如果应用定义了同名键,以应用的值为准。详情、API 和优先级参见环境变量。

Stack 设置

Stack Settings 可编辑名称、描述、颜色和成员,并提供 Delete Stack。

Warning
Stack 只有为空时才能删除。只要其中还有应用、数据库或服务,删除就会被拒绝,并在消息中列出剩余内容:请先删除它们——真正丢失数据的是这一步。

常见用法

  • 每个产品一个 Stack:需要通过名称互相访问的前端、API 和 worker。
  • 每个环境一个 Stack:预发布与生产分开,各自拥有共享变量。
  • 每个客户一个 Stack:借助 Stack 成员机制,每个客户的应用只对指定人员可见。

通过 API 管理 Stack

路由位于 /api/v1/projects 下。更新使用 PATCH,PUT 返回 405。不带 /v1 的请求不会到达 API,而是返回控制面板页面。以下输出取自一个测试实例:

Terminal
# Create a stack
$ curl -s -X POST https://panel.example.com/api/v1/projects \
    -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
    -d '{"name":"my-staging","description":"Staging environment"}'
{"data":{"color":null,"created_at":"2026-10-02 02:53:41","description":"Staging environment","id":"b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e","member_count":1,"name":"my-staging","owner_id":"50159570-e8ad-46a8-8065-e6c9096edde6"}}

# List stacks (paginated: page, per_page)
$ curl -s https://panel.example.com/api/v1/projects -H "Authorization: Bearer $SH0_TOKEN"
{"data":[{"color":null,"created_at":"2026-10-02 02:53:41","description":"Staging environment","id":"b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e","member_count":1,"name":"my-staging","owner_id":"50159570-e8ad-46a8-8065-e6c9096edde6"}, ...],"page":1,"pages":1,"per_page":20,"total":3}

# Get one stack
$ curl -s https://panel.example.com/api/v1/projects/b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e -H "Authorization: Bearer $SH0_TOKEN"
{"data":{"color":null,"created_at":"2026-10-02 02:53:41","description":"Staging environment","id":"b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e","member_count":1,"name":"my-staging","owner_id":"50159570-e8ad-46a8-8065-e6c9096edde6"}}

# Update a stack
$ curl -s -X PATCH https://panel.example.com/api/v1/projects/b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e \
    -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
    -d '{"description":"Staging, rebuilt nightly"}'
{"data":{"color":null,"created_at":"2026-10-02 02:53:41","description":"Staging, rebuilt nightly","id":"b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e","member_count":1,"name":"my-staging","owner_id":"50159570-e8ad-46a8-8065-e6c9096edde6"}}

# Delete a stack that still holds an app: refused (HTTP 400)
$ curl -s -X DELETE https://panel.example.com/api/v1/projects/5aee8eea-9f77-4d10-ac21-2fe4a565e666 -H "Authorization: Bearer $SH0_TOKEN"
{"error":{"code":"BAD_REQUEST","key":"project_not_empty","message":"Cannot delete stack: it still has 1 app (my-api). Delete these services first.","params":{"remaining":[{"count":1,"family":"apps","names":"my-api"}]}}}

# Delete an empty stack (HTTP 204, no body)
$ curl -s -X DELETE https://panel.example.com/api/v1/projects/b9b14ce5-7aa4-4c0d-85fe-b6c4b3977e8e -H "Authorization: Bearer $SH0_TOKEN" -o /dev/null -w "%{http_code}\n"
204
Note
全部端点参见 API 参考。