zeabur-template

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zeabur Template Knowledge Base

Zeabur模板知识库

This skill provides comprehensive knowledge for creating, debugging, and publishing Zeabur templates. It combines reference documentation with battle-tested patterns from real template development.
本技能提供了创建、调试和发布Zeabur模板的全面知识,结合了参考文档与实际模板开发中经过验证的实践模式。

External Documentation

外部文档

For the latest schema and detailed docs, fetch from
https://raw.githubusercontent.com/zeabur/zeabur-template-doc/main/
:
User NeedDocument to FetchPath
Create a template from scratchStep-by-step guide
docs/GUIDE.md
Convert docker-compose.ymlMigration guide
docs/DOCKER_COMPOSE_MIGRATION.md
Look up YAML fields or built-in variablesTechnical reference
docs/REFERENCE.md
Naming, design patterns, best practicesBest practices
docs/BEST_PRACTICES.md
Debug template errorsTroubleshooting
docs/TROUBLESHOOTING.md
Pre-deployment checklistChecklist
docs/CHECKLIST.md
Quick all-in-one overviewComprehensive prompt
prompt.md
The template YAML schema is also available at https://schema.zeabur.app/template.json and the prebuilt service schema at https://schema.zeabur.app/prebuilt.json.

如需获取最新的架构和详细文档,请从
https://raw.githubusercontent.com/zeabur/zeabur-template-doc/main/
获取:
用户需求需获取的文档路径
从零开始创建模板分步指南
docs/GUIDE.md
转换docker-compose.yml迁移指南
docs/DOCKER_COMPOSE_MIGRATION.md
查阅YAML字段或内置变量技术参考
docs/REFERENCE.md
命名、设计模式、最佳实践最佳实践
docs/BEST_PRACTICES.md
排查模板错误故障排除
docs/TROUBLESHOOTING.md
部署前检查清单检查清单
docs/CHECKLIST.md
快速概览所有内容综合提示
prompt.md

Core Principles

核心原则

0. All services MUST use PREBUILT_V2 with Docker images

0. 所有服务必须使用带Docker镜像的PREBUILT_V2

Every service in a template MUST be
template: PREBUILT_V2
with a Docker image as the source.
Never use
template: GIT
,
ARBITRARY_GIT
, or
GITHUB
source — these are NOT supported in templates.
If the project does not have a published Docker image (on Docker Hub, GHCR, etc.), tell the user they need to build and publish a Docker image first before a template can be created. Do not attempt to work around this.
If the user asks you to build the image, follow this workflow:
  1. Clone the project repo and find its
    Dockerfile
    (usually at repo root)
  2. Study the Dockerfile to understand build stages — use the production stage (often named
    runner
    or
    production
    )
  3. Study
    docker-compose.yml
    or
    docker-compose.fullapp.yml
    for the correct startup command, env vars, and volumes — this is the battle-tested production config
  4. Build for amd64 (Zeabur servers are amd64, local Macs are arm64):
    bash
    docker buildx build --platform linux/amd64 --target runner -t org/image:tag --push .
  5. After pushing, verify the Docker Hub repo is public:
    bash
    curl -s "https://hub.docker.com/v2/repositories/ORG/IMAGE/" | grep is_private
    New repos under org accounts often default to private. If
    is_private: true
    , the user must make it public on Docker Hub.
模板中的每个服务必须使用
template: PREBUILT_V2
,并以Docker镜像作为源。
绝不要使用
template: GIT
ARBITRARY_GIT
GITHUB
作为源——这些在模板中不受支持。
如果项目没有已发布的Docker镜像(在Docker Hub、GHCR等平台),请告知用户在创建模板前必须先构建并发布一个Docker镜像,不要尝试绕过这一步。
如果用户要求你构建镜像,请遵循以下流程:
  1. 克隆项目仓库并找到其
    Dockerfile
    (通常位于仓库根目录)
  2. 研究Dockerfile以了解构建阶段——使用生产阶段(通常命名为
    runner
    production
  3. 查看
    docker-compose.yml
    docker-compose.fullapp.yml
    以获取正确的启动命令、环境变量和卷配置——这是经过验证的生产环境配置
  4. amd64架构构建镜像(Zeabur服务器为amd64架构,本地Mac通常为arm64):
    bash
    docker buildx build --platform linux/amd64 --target runner -t org/image:tag --push .
  5. 推送完成后,验证Docker Hub仓库是否公开
    bash
    curl -s "https://hub.docker.com/v2/repositories/ORG/IMAGE/" | grep is_private
    组织账户下的新仓库通常默认设为私有。如果返回
    is_private: true
    ,用户必须在Docker Hub上将其设为公开。

1. Never start from scratch

1. 绝不要从零开始

When asked to create a template, always look for existing configuration first:
  • First check if a Docker image exists — search Docker Hub, GHCR (
    ghcr.io/org/repo
    ), or the project's CI/CD for published images. If none exists, stop and inform the user.
  • Search for the project's
    docker-compose.yml
    ,
    docker-compose.yaml
    , or
    compose.yml
  • Look for Helm charts (
    Chart.yaml
    ,
    values.yaml
    )
  • Check the project's GitHub repo for any deployment YAML files
  • Use these as the foundation to build the Zeabur template, not as a loose reference — they contain battle-tested environment variables, port mappings, volume mounts, and service dependencies
当被要求创建模板时,务必先查找现有的配置:
  • 首先检查是否存在Docker镜像——在Docker Hub、GHCR(
    ghcr.io/org/repo
    )或项目的CI/CD流程中搜索已发布的镜像。如果不存在,请停止操作并告知用户。
  • 搜索项目的
    docker-compose.yml
    docker-compose.yaml
    compose.yml
    文件
  • 查找Helm Chart(
    Chart.yaml
    values.yaml
  • 检查项目的GitHub仓库中是否有任何部署YAML文件
  • 以这些配置为基础构建Zeabur模板,而不是仅作为松散参考——它们包含了经过验证的环境变量、端口映射、卷挂载和服务依赖关系

2. Iterate via runtime logs — never expect one-shot success

2. 通过运行时日志迭代——不要期望一次成功

Even experienced humans cannot create a working template in one shot. The workflow is an iterative loop:
  1. Write/update the template YAML
  2. Deploy the template
  3. It will likely fail — check runtime logs to find the cause
  4. Fix the issue in the template
  5. Delete the project and redeploy from scratch
  6. Repeat until the template achieves one-click deployment success
This is the normal process, not a sign of failure. Do not try to get everything perfect before deploying — deploy early, read logs, and iterate.
即使是经验丰富的开发者也无法一次就创建出可用的模板。工作流程是一个迭代循环
  1. 编写/更新模板YAML
  2. 部署模板
  3. 很可能会失败——查看运行时日志以找出原因
  4. 在模板中修复问题
  5. 删除项目并重新部署
  6. 重复上述步骤,直到模板实现一键部署成功
这是正常流程,并非失败的标志。不要试图在部署前就把所有内容都弄完美——尽早部署、查看日志并迭代。

3. Reuse from existing templates — never write common services from scratch

3. 复用现有模板——绝不要从零开始编写通用服务

When your template needs a common service (PostgreSQL, Redis, MySQL, MongoDB, etc.), do not write the service definition yourself. Instead:
  1. Search for existing templates that already use that service:
    bash
    npx zeabur@latest template search postgres
  2. Find a template that includes the service you need
  3. Get the raw template YAML to see the exact service definition:
    bash
    npx zeabur@latest template get -c TEMPLATE_CODE --raw
  4. Copy the service definition directly from that template into yours
How to judge template trustworthiness:
  • Many templates are created by regular users and may not work correctly
  • Prefer templates with more deployments — higher deployment count = more battle-tested
  • Prefer official templates over user-submitted ones — official templates are vetted by Zeabur team

当你的模板需要通用服务(PostgreSQL、Redis、MySQL、MongoDB等)时,不要自己编写服务定义。请遵循以下步骤:
  1. 搜索已使用该服务的现有模板:
    bash
    npx zeabur@latest template search postgres
  2. 找到包含所需服务的模板
  3. 获取原始模板YAML以查看确切的服务定义:
    bash
    npx zeabur@latest template get -c TEMPLATE_CODE --raw
  4. 将该服务定义直接复制到你的模板中
如何判断模板的可靠性:
  • 许多模板由普通用户创建,可能无法正常工作
  • 优先选择部署次数多的模板——部署次数越多,经过的验证越充分
  • 优先选择官方模板而非用户提交的模板——官方模板经过Zeabur团队审核

CLI Commands for the Iteration Loop

迭代循环中的CLI命令

Deploy a template:
bash
npx zeabur@latest template deploy -f YOUR_TEMPLATE.yaml
For non-interactive mode (automation):
bash
npx zeabur@latest template deploy -i=false \
  -f YOUR_TEMPLATE.yaml \
  --project-id PROJECT_ID \
  --var PUBLIC_DOMAIN=myapp
List services (to get SERVICE_ID):
bash
npx zeabur@latest service list --project-id PROJECT_ID
Check runtime logs:
bash
npx zeabur@latest deployment log --service-id SERVICE_ID
Execute a command inside a running service (like
docker exec
):
bash
npx zeabur@latest service exec --id SERVICE_ID -- SHELL_COMMAND
This is extremely useful for debugging — check file paths, env vars, test connectivity, inspect the filesystem, etc. Examples:
bash
npx zeabur@latest service exec --id SERVICE_ID -- ls /app
npx zeabur@latest service exec --id SERVICE_ID -- env | grep DATABASE
npx zeabur@latest service exec --id SERVICE_ID -- nc -z localhost 5432
Restart a service (useful to clear ImagePullBackOff or force re-pull):
bash
npx zeabur@latest service restart --id SERVICE_ID -i=false -y
Delete the project and start over:
bash
npx zeabur@latest project delete --id PROJECT_ID
DANGEROUS OPERATION — Before deleting a project, you MUST ask the user for explicit confirmation, clearly stating the Project ID, Name, and createdAt timestamp. Never delete without confirmation.
Publish a new template:
bash
npx zeabur@latest template create -f YOUR_TEMPLATE.yaml
This returns a template URL like
https://zeabur.com/templates/XXXXXX
with a template code.
Update an existing template:
bash
npx zeabur@latest template update -c TEMPLATE_CODE -f YOUR_TEMPLATE.yaml

部署模板:
bash
npx zeabur@latest template deploy -f YOUR_TEMPLATE.yaml
非交互式模式(自动化):
bash
npx zeabur@latest template deploy -i=false \
  -f YOUR_TEMPLATE.yaml \
  --project-id PROJECT_ID \
  --var PUBLIC_DOMAIN=myapp
列出服务(获取SERVICE_ID):
bash
npx zeabur@latest service list --project-id PROJECT_ID
查看运行时日志:
bash
npx zeabur@latest deployment log --service-id SERVICE_ID
在运行中的服务内执行命令(类似
docker exec
):
bash
npx zeabur@latest service exec --id SERVICE_ID -- SHELL_COMMAND
这对调试非常有用——可用于检查文件路径、环境变量、测试连接性、查看文件系统等。示例:
bash
npx zeabur@latest service exec --id SERVICE_ID -- ls /app
npx zeabur@latest service exec --id SERVICE_ID -- env | grep DATABASE
npx zeabur@latest service exec --id SERVICE_ID -- nc -z localhost 5432
重启服务(用于解决ImagePullBackOff问题或强制重新拉取镜像):
bash
npx zeabur@latest service restart --id SERVICE_ID -i=false -y
删除项目并重新开始:
bash
npx zeabur@latest project delete --id PROJECT_ID
危险操作——删除项目前,必须向用户明确确认,清晰说明项目ID、名称和创建时间。绝不要未经确认就删除。
发布新模板:
bash
npx zeabur@latest template create -f YOUR_TEMPLATE.yaml
此命令会返回一个模板URL,例如
https://zeabur.com/templates/XXXXXX
,并附带一个模板代码。
更新现有模板:
bash
npx zeabur@latest template update -c TEMPLATE_CODE -f YOUR_TEMPLATE.yaml

Quick Reference: Template Skeleton

快速参考:模板骨架

yaml
undefined
yaml
undefined

yaml-language-server: $schema=https://schema.zeabur.app/template.json

yaml-language-server: $schema=https://schema.zeabur.app/template.json

apiVersion: zeabur.com/v1 kind: Template metadata: name: ServiceName spec: description: | English description (1-3 sentences) icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/service.svg coverImage: https://example.com/cover.webp tags: - Category variables: [] readme: | # Service Name English documentation... services: - name: service-name icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/service.svg template: PREBUILT_V2 spec: source: image: image:tag command: # MUST be inside source, alongside image - /bin/sh - -c - /opt/app/startup.sh ports: - id: web port: 8080 type: HTTP volumes: - id: data dir: /path/to/data configs: - path: /opt/app/startup.sh permission: 493 # 0755 envsubst: false template: | #!/bin/sh exec node server.js env: VAR_NAME: default: value expose: true localization: zh-TW: description: ... variables: [] readme: | # ... zh-CN: description: ... variables: [] readme: | # ... ja-JP: description: ... variables: [] readme: | # ... es-ES: description: ... variables: [] readme: | # ... id-ID: description: ... variables: [] readme: | # ...
undefined
apiVersion: zeabur.com/v1 kind: Template metadata: name: ServiceName spec: description: | 英文描述(1-3句话) icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/service.svg coverImage: https://example.com/cover.webp tags: - Category variables: [] readme: | # 服务名称 英文文档... services: - name: service-name icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/service.svg template: PREBUILT_V2 spec: source: image: image:tag command: # 必须与image一起放在source内 - /bin/sh - -c - /opt/app/startup.sh ports: - id: web port: 8080 type: HTTP volumes: - id: data dir: /path/to/data configs: - path: /opt/app/startup.sh permission: 493 # 0755 envsubst: false template: | #!/bin/sh exec node server.js env: VAR_NAME: default: value expose: true localization: zh-TW: description: ... variables: [] readme: | # ... zh-CN: description: ... variables: [] readme: | # ... ja-JP: description: ... variables: [] readme: | # ... es-ES: description: ... variables: [] readme: | # ... id-ID: description: ... variables: [] readme: | # ...
undefined

Quick Reference: Built-in Variables

快速参考:内置变量

VariablePurpose
${PASSWORD}
Auto-generated secure password
${ZEABUR_WEB_URL}
Full public URL (e.g.
https://app.zeabur.app
)
${ZEABUR_WEB_DOMAIN}
Domain only (e.g.
app.zeabur.app
)
${CONTAINER_HOSTNAME}
Internal hostname for inter-service communication
${[PORTID]_PORT}
Port value by port ID (e.g.
${DATABASE_PORT}
)
${PORT_FORWARDED_HOSTNAME}
External hostname (for
instructions
)
${[PORTID]_PORT_FORWARDED_PORT}
External forwarded port (for
instructions
)
The
ZEABUR_<PORT_ID>_URL
pattern: for a port named
web
, it becomes
${ZEABUR_WEB_URL}
; for
console
, it becomes
${ZEABUR_CONSOLE_URL}
.
变量用途
${PASSWORD}
自动生成的安全密码
${ZEABUR_WEB_URL}
完整的公共URL(例如
https://app.zeabur.app
${ZEABUR_WEB_DOMAIN}
仅包含域名(例如
app.zeabur.app
${CONTAINER_HOSTNAME}
服务间通信的内部主机名
${[PORTID]_PORT}
通过端口ID获取端口值(例如
${DATABASE_PORT}
${PORT_FORWARDED_HOSTNAME}
外部主机名(用于
instructions
${[PORTID]_PORT_FORWARDED_PORT}
外部转发端口(用于
instructions
ZEABUR_<PORT_ID>_URL
模式:对于名为
web
的端口,变量为
${ZEABUR_WEB_URL}
;对于
console
端口,变量为
${ZEABUR_CONSOLE_URL}

Quick Reference: command Placement

快速参考:command的位置

IMPORTANT:
command
MUST be inside
source
, alongside
image
. NOT at
spec
level.
yaml
undefined
重要提示:
command
必须放在
source
内,与
image
同级。不能放在
spec
层级。
yaml
undefined

WRONG -- command at spec level (will be IGNORED, container uses default CMD)

错误示例 -- command在spec层级(会被忽略,容器使用默认CMD)

spec: source: image: python:3.12-slim command: - /bin/sh - -c - /opt/app/start.sh
spec: source: image: python:3.12-slim command: - /bin/sh - -c - /opt/app/start.sh

CORRECT -- command inside source

正确示例 -- command在source内

spec: source: image: python:3.12-slim command: - /bin/sh - -c - /opt/app/start.sh

> **Note:** The external docs may show `command` at `spec` level. This is incorrect. Always place `command` inside `source` as confirmed by the JSON schema at `schema.zeabur.app/prebuilt.json`.
spec: source: image: python:3.12-slim command: - /bin/sh - -c - /opt/app/start.sh

> **注意:** 外部文档可能显示`command`在`spec`层级,这是错误的。请始终按照`schema.zeabur.app/prebuilt.json`中的JSON架构要求,将`command`放在`source`内。

Quick Reference: YAML Gotchas

快速参考:YAML注意事项

yaml
undefined
yaml
undefined

RISKY -- @ at start of value is a YAML reserved indicator (may cause parse errors)

风险 -- 值开头的@是YAML保留标记(可能导致解析错误)

description: @BotFather token
description: @BotFather token

SAFE -- quote the value or avoid @ at start

安全做法 -- 对值加引号或避免@开头

description: "Token from @BotFather for Telegram bot" description: Telegram bot token from BotFather
undefined
description: "来自@BotFather的Telegram机器人令牌" description: Telegram机器人令牌(来自BotFather)
undefined

Quick Reference: Docker Image ENTRYPOINT

快速参考:Docker镜像ENTRYPOINT

Some base images have ENTRYPOINT set, which conflicts with
command
.
ImageENTRYPOINTProblem
ghcr.io/astral-sh/uv:python3.12-*
uv
command
becomes args to
uv
, container shows
uv help
and exits
node:*
noneSafe to use
python:*
noneSafe to use
If using an image with ENTRYPOINT, switch to a plain base image (e.g.
python:3.12-slim-bookworm
) or one without ENTRYPOINT.
某些基础镜像设置了ENTRYPOINT,这会与
command
冲突。
镜像ENTRYPOINT问题
ghcr.io/astral-sh/uv:python3.12-*
uv
command
会成为
uv
的参数,容器会显示
uv help
并退出
node:*
可安全使用
python:*
可安全使用
如果使用的镜像有ENTRYPOINT,请切换到无ENTRYPOINT的基础镜像(例如
python:3.12-slim-bookworm
)。

Quick Reference: Headless Services (no HTTP)

快速参考:无头服务(无HTTP端口)

If a service does NOT listen on any HTTP port (502 Bad Gateway), see
zeabur-port-mismatch
skill for the fix.
如果服务不监听任何HTTP端口(出现502 Bad Gateway错误),请查看
zeabur-port-mismatch
技能获取解决方案。

Quick Reference: Critical Rules

快速参考:关键规则

yaml
undefined
yaml
undefined

WRONG -- hardcoded password

错误示例 -- 硬编码密码

POSTGRES_PASSWORD: default: mypassword123
POSTGRES_PASSWORD: default: mypassword123

CORRECT -- use ${PASSWORD}

正确示例 -- 使用${PASSWORD}

POSTGRES_PASSWORD: default: ${PASSWORD} expose: true
POSTGRES_PASSWORD: default: ${PASSWORD} expose: true

WRONG -- PUBLIC_DOMAIN gives incomplete URL

错误示例 -- PUBLIC_DOMAIN提供的URL不完整

APP_URL: default: https://${PUBLIC_DOMAIN}
APP_URL: default: https://${PUBLIC_DOMAIN}

CORRECT -- ZEABUR_WEB_URL gives full URL

正确示例 -- ZEABUR_WEB_URL提供完整URL

APP_URL: default: ${ZEABUR_WEB_URL} readonly: true
APP_URL: default: ${ZEABUR_WEB_URL} readonly: true

WRONG -- other services can't reference without expose

错误示例 -- 未暴露变量,其他服务无法引用

POSTGRES_HOST: default: ${CONTAINER_HOSTNAME}
POSTGRES_HOST: default: ${CONTAINER_HOSTNAME}

CORRECT -- expose + readonly for connection info

正确示例 -- 暴露并设为只读,用于连接信息

POSTGRES_HOST: default: ${CONTAINER_HOSTNAME} expose: true readonly: true
POSTGRES_HOST: default: ${CONTAINER_HOSTNAME} expose: true readonly: true

WRONG -- referencing variables without declaring dependency

错误示例 -- 未声明依赖就引用变量

  • name: app spec: env: DB: ${POSTGRES_HOST}
  • name: app spec: env: DB: ${POSTGRES_HOST}

CORRECT -- declare dependency first

正确示例 -- 先声明依赖

  • name: app dependencies:
    • postgresql spec: env: DB: ${POSTGRES_HOST}

---
  • name: app dependencies:
    • postgresql spec: env: DB: ${POSTGRES_HOST}

---

Domain Binding

域名绑定

Use
domainKey
on the service that needs a public domain. It maps to a variable defined in
spec.variables
with
type: DOMAIN
.
Single domain:
yaml
domainKey: PUBLIC_DOMAIN
Multiple domains (different ports):
yaml
domainKey:
    - port: web
      variable: ENDPOINT_DOMAIN
    - port: console
      variable: ADMIN_ENDPOINT_DOMAIN

在需要公共域名的服务上使用
domainKey
,它会映射到
spec.variables
中定义的
type: DOMAIN
类型的变量。
单域名:
yaml
domainKey: PUBLIC_DOMAIN
多域名(不同端口):
yaml
domainKey:
    - port: web
      variable: ENDPOINT_DOMAIN
    - port: console
      variable: ADMIN_ENDPOINT_DOMAIN

Common Database Configs

常见数据库配置

PostgreSQL

PostgreSQL

yaml
- name: postgresql
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg
  template: PREBUILT_V2
  spec:
    source:
      image: postgres:16-alpine
    ports:
      - id: database
        port: 5432
        type: TCP
    volumes:
      - id: data
        dir: /var/lib/postgresql/data
    env:
      POSTGRES_USER:
        default: postgres
        expose: true
      POSTGRES_PASSWORD:
        default: ${PASSWORD}
        expose: true
      POSTGRES_DB:
        default: mydb
        expose: true
      POSTGRES_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      POSTGRES_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true
      POSTGRES_CONNECTION_STRING:
        default: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
        expose: true
        readonly: true
yaml
- name: postgresql
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg
  template: PREBUILT_V2
  spec:
    source:
      image: postgres:16-alpine
    ports:
      - id: database
        port: 5432
        type: TCP
    volumes:
      - id: data
        dir: /var/lib/postgresql/data
    env:
      POSTGRES_USER:
        default: postgres
        expose: true
      POSTGRES_PASSWORD:
        default: ${PASSWORD}
        expose: true
      POSTGRES_DB:
        default: mydb
        expose: true
      POSTGRES_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      POSTGRES_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true
      POSTGRES_CONNECTION_STRING:
        default: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
        expose: true
        readonly: true

MySQL/MariaDB

MySQL/MariaDB

yaml
- name: mariadb
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/mariadb.svg
  template: PREBUILT_V2
  spec:
    source:
      image: mariadb:10.6
    ports:
      - id: database
        port: 3306
        type: TCP
    volumes:
      - id: data
        dir: /var/lib/mysql
    env:
      MYSQL_ROOT_PASSWORD:
        default: ${PASSWORD}
        expose: true
      MYSQL_DATABASE:
        default: mydb
        expose: true
      MYSQL_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      MYSQL_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true
yaml
- name: mariadb
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/mariadb.svg
  template: PREBUILT_V2
  spec:
    source:
      image: mariadb:10.6
    ports:
      - id: database
        port: 3306
        type: TCP
    volumes:
      - id: data
        dir: /var/lib/mysql
    env:
      MYSQL_ROOT_PASSWORD:
        default: ${PASSWORD}
        expose: true
      MYSQL_DATABASE:
        default: mydb
        expose: true
      MYSQL_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      MYSQL_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true

Redis

Redis

yaml
- name: redis
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/redis.svg
  template: PREBUILT_V2
  spec:
    source:
      image: redis:7-alpine
    ports:
      - id: database
        port: 6379
        type: TCP
    volumes:
      - id: data
        dir: /data
    env:
      REDIS_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      REDIS_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true
yaml
- name: redis
  icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/redis.svg
  template: PREBUILT_V2
  spec:
    source:
      image: redis:7-alpine
    ports:
      - id: database
        port: 6379
        type: TCP
    volumes:
      - id: data
        dir: /data
    env:
      REDIS_HOST:
        default: ${CONTAINER_HOSTNAME}
        expose: true
        readonly: true
      REDIS_PORT:
        default: ${DATABASE_PORT}
        expose: true
        readonly: true

Standard Volume Paths

标准卷路径

ServicePath
PostgreSQL
/var/lib/postgresql/data
MySQL/MariaDB
/var/lib/mysql
MongoDB
/data/db
Redis
/data
MinIO
/data

服务路径
PostgreSQL
/var/lib/postgresql/data
MySQL/MariaDB
/var/lib/mysql
MongoDB
/data/db
Redis
/data
MinIO
/data

Template Complexity Levels

模板复杂度等级

Level 1 -- Single prebuilt service (e.g., Memos, Uptime-Kuma, LobeChat):
  • Just one service with image, port, and optionally a volume
  • Simplest pattern, no cross-service wiring needed
Level 2 -- App + database (e.g., Ghost+MySQL, Linkwarden+PostgreSQL):
  • Database service exposes vars, app service references them
  • App service uses
    dependencies
    to ensure DB starts first
Level 3 -- App + database + init/migrator (e.g., Teable, Open Mercato):
  • First-run initialization or separate migrator service
  • Requires wait-for-db pattern and init marker files
Level 4 -- Multi-service with multiple domains (e.g., Logto):
  • Multiple ports on one service, each bound to a different domain variable
Level 5 -- Large-scale multi-service platform (e.g., Dify 12 services, Supabase 11 services):
  • Reverse proxy as entry point: nginx (Dify) or Kong (Supabase) as the single domain-bound service
  • Same image, different MODE: e.g., Dify runs
    api
    ,
    worker
    ,
    worker-beat
    from the same image
  • Internal-only services: no public domain, communicate via
    ${CONTAINER_HOSTNAME}
  • Heavy use of
    configs
    : Nginx conf, SQL init scripts, Kong config — all injected via
    configs
    field
  • PostgreSQL init SQL via configs: Mount to
    /docker-entrypoint-initdb.d/
    for auto execution
  • Shared secrets: Use
    ${PASSWORD}
    for all internal credentials

等级1 -- 单个预构建服务(例如Memos、Uptime-Kuma、LobeChat):
  • 仅包含一个服务,包含镜像、端口,可选卷
  • 最简单的模式,无需跨服务连接
等级2 -- 应用+数据库(例如Ghost+MySQL、Linkwarden+PostgreSQL):
  • 数据库服务暴露变量,应用服务引用这些变量
  • 应用服务使用
    dependencies
    确保数据库先启动
等级3 -- 应用+数据库+初始化/迁移服务(例如Teable、Open Mercato):
  • 首次运行初始化或独立的迁移服务
  • 需要等待数据库就绪的模式和初始化标记文件
等级4 -- 多服务+多域名(例如Logto):
  • 单个服务有多个端口,每个端口绑定到不同的域名变量
等级5 -- 大规模多服务平台(例如Dify的12个服务、Supabase的11个服务):
  • 反向代理作为入口:使用nginx(Dify)或Kong(Supabase)作为绑定域名的单一服务
  • 同一镜像,不同模式:例如Dify从同一镜像运行
    api
    worker
    worker-beat
  • 仅内部服务:无公共域名,通过
    ${CONTAINER_HOSTNAME}
    通信
  • 大量使用
    configs
    :Nginx配置、SQL初始化脚本、Kong配置——全部通过
    configs
    字段注入
  • 通过configs初始化PostgreSQL SQL:挂载到
    /docker-entrypoint-initdb.d/
    以自动执行
  • 共享密钥:所有内部凭证使用
    ${PASSWORD}

Writing name, description, readme, icon, and coverImage

编写name、description、readme、icon和coverImage

Where to collect information (in priority order):
  1. The project's GitHub repo README
  2. The project's official website
  3. Other public sources (blog posts, documentation sites, etc.)
Key rules:
  1. Do NOT copy-paste the original README. Write the introduction for this project's Zeabur template, not for the project itself. The readme should:
    • Briefly introduce what the project is
    • Focus on how to use this template (deployment steps, configuration, domain binding)
    • Include important caveats and troubleshooting tips specific to Zeabur deployment
  2. Always include licensing and attribution. If the original project has an MIT, Apache, or other license:
    • Mention the license in the readme
    • Link to the original repo
    • Link to the official website if available
    • This is legally required -- never skip it
  3. icon and coverImage: Find the project's logo from their GitHub repo or official website. Use a direct URL to an image (SVG, PNG, or WebP). Always verify the URL returns HTTP 200:
    bash
    curl -s -o /dev/null -w "%{http_code}" "URL"
    Common pitfall: wrong branch name in
    raw.githubusercontent.com
    URLs (
    develop
    vs
    main
    vs
    master
    ).
信息收集来源(按优先级排序):
  1. 项目的GitHub仓库README
  2. 项目的官方网站
  3. 其他公开来源(博客文章、文档站点等)
关键规则:
  1. 不要直接复制粘贴原始README。请为该项目的Zeabur模板编写介绍,而非项目本身的介绍。README应包含:
    • 简要介绍项目是什么
    • 重点说明如何使用该模板(部署步骤、配置、域名绑定)
    • 包含与Zeabur部署相关的重要注意事项和故障排除提示
  2. 始终包含许可证和归属信息。如果原始项目使用MIT、Apache或其他许可证:
    • 在README中提及许可证
    • 链接到原始仓库
    • 如有官方网站,也请链接
    • 这是法律要求——绝不要省略
  3. icon和coverImage:从项目的GitHub仓库或官方网站获取项目标志。使用指向图片的直接URL(SVG、PNG或WebP格式)。始终验证URL返回HTTP 200状态码
    bash
    curl -s -o /dev/null -w "%{http_code}" "URL"
    常见问题:
    raw.githubusercontent.com
    URL中的分支名称错误(
    develop
    main
    master
    混淆)。

Localization Requirements

本地化要求

6 languages required: en-US (in
spec
), zh-TW, zh-CN, ja-JP, es-ES, id-ID.
Translate:
description
,
variables[].name
,
variables[].description
,
readme
Do NOT translate:
key
,
type
,
${VARIABLE_NAMES}
, URLs

需要支持6种语言:en-US(在
spec
中)、zh-TW、zh-CN、ja-JP、es-ES、id-ID。
需要翻译的内容:
description
variables[].name
variables[].description
readme
不要翻译的内容:
key
type
${VARIABLE_NAMES}
、URL

Hard-Won Lessons (from real template challenges)

经验总结(来自实际模板开发挑战)

Wait for database readiness

等待数据库就绪

Zeabur's
dependencies
field only ensures services are deployed, not that they're ready to accept connections. A database container can take 5-15 seconds to initialize. Always add a wait loop before running migrations or init:
yaml
args:
    - -c
    - |
        echo "Waiting for PostgreSQL..."
        while ! nc -z ${POSTGRES_HOST} ${POSTGRES_PORT} 2>/dev/null; do sleep 2; done
        echo "PostgreSQL is ready!"
        echo "Waiting for Redis..."
        while ! nc -z ${REDIS_HOST} ${REDIS_PORT} 2>/dev/null; do sleep 2; done
        echo "Redis is ready!"
        # ... then run migrations/init
Note:
nc
(netcat) is available on most Alpine-based images. If not, use
wget --spider
or a node one-liner.
Zeabur的
dependencies
字段仅确保服务已部署,而非已准备好接受连接。数据库容器可能需要5-15秒才能初始化。在运行迁移或初始化前,务必添加等待循环:
yaml
args:
    - -c
    - |
        echo "等待PostgreSQL..."
        while ! nc -z ${POSTGRES_HOST} ${POSTGRES_PORT} 2>/dev/null; do sleep 2; done
        echo "PostgreSQL已就绪!"
        echo "等待Redis..."
        while ! nc -z ${REDIS_HOST} ${REDIS_PORT} 2>/dev/null; do sleep 2; done
        echo "Redis已就绪!"
        # ... 然后运行迁移/初始化
注意:
nc
(netcat)在大多数Alpine-based镜像中可用。如果不可用,可使用
wget --spider
或Node.js单行命令。

Study the project's own Dockerfile and docker-compose

研究项目自身的Dockerfile和docker-compose

Never guess startup commands. Instead:
  1. Read the project's
    Dockerfile
    to understand the build stages and the final
    CMD
    /
    ENTRYPOINT
  2. Read
    docker-compose.yml
    and especially
    docker-compose.fullapp.yml
    (or
    docker-compose.prod.yml
    ) for the production startup command -- these often override the Dockerfile's CMD with init/migrate logic
  3. Check the app's
    package.json
    scripts to understand what
    yarn start
    or
    npm start
    actually runs
  4. The startup command in docker-compose is battle-tested -- copy it, don't reinvent it
不要猜测启动命令。请遵循以下步骤:
  1. 阅读项目的
    Dockerfile
    以了解构建阶段和最终的
    CMD
    /
    ENTRYPOINT
  2. 阅读
    docker-compose.yml
    ,尤其是
    docker-compose.fullapp.yml
    (或
    docker-compose.prod.yml
    )以获取生产环境启动命令——这些命令通常会覆盖Dockerfile的CMD,包含初始化/迁移逻辑
  3. 查看应用的
    package.json
    脚本以了解
    yarn start
    npm start
    实际执行的内容
  4. docker-compose中的启动命令是经过验证的——直接复制,不要重新发明

Memory-heavy apps need lighter startup

内存密集型应用需要更轻量的启动方式

Apps that spawn workers, schedulers, and the web server all in one process may OOM on Zeabur's default allocation. Signs of OOM:
  • Container starts, runs for 1-2 minutes, then crashes with no error logs (just
    BackOff: Back-off restarting failed container
    )
  • No application-level crash message -- the kernel kills the process silently
Solutions:
  • Run the web server directly (e.g.,
    next start
    or
    node server.js
    ) instead of a CLI wrapper that spawns workers + scheduler + server
  • Disable non-essential background processes via env vars (e.g.,
    AUTO_SPAWN_WORKERS=false
    )
  • If workers are needed, consider splitting them into a separate service
同时启动worker、调度器和Web服务器的应用可能会在Zeabur的默认资源分配下出现OOM(内存不足)。OOM的迹象:
  • 容器启动后运行1-2分钟,然后无错误日志就崩溃(仅显示
    BackOff: Back-off restarting failed container
  • 无应用级崩溃信息——内核会静默终止进程
解决方案:
  • 直接运行Web服务器(例如
    next start
    node server.js
    ),而非启动worker+调度器+服务器的CLI包装器
  • 通过环境变量禁用非必要的后台进程(例如
    AUTO_SPAWN_WORKERS=false
  • 如果需要worker,考虑将其拆分为独立服务

First-run init with persistent marker

首次运行初始化与持久化标记

For apps that need first-time initialization (DB schema, seed data, admin users), use a marker file in a persistent volume:
yaml
args:
    - -c
    - |
        if [ ! -f /app/storage/.initialized ]; then
          echo "First run: initializing..."
          run-init-command && touch /app/storage/.initialized
        else
          echo "Subsequent run: migrations only..."
          run-migrate-command
        fi
        exec start-server-command
Key points:
  • The marker file MUST be in a persistent volume, not
    /tmp/
    (which is ephemeral)
  • Use
    &&
    after init so the marker is only created if init succeeds
  • Use
    exec
    for the final server process so it becomes PID 1 and receives signals properly
  • Read init logs carefully for the actual default credentials -- don't assume from docs
对于需要首次初始化(数据库架构、种子数据、管理员用户)的应用,在持久化卷中使用标记文件:
yaml
args:
    - -c
    - |
        if [ ! -f /app/storage/.initialized ]; then
          echo "首次运行:正在初始化..."
          run-init-command && touch /app/storage/.initialized
        else
          echo "后续运行:仅执行迁移..."
          run-migrate-command
        fi
        exec start-server-command
关键点:
  • 标记文件必须放在持久化卷中,而非
    /tmp/
    (临时目录)
  • 在初始化命令后使用
    &&
    ,确保只有初始化成功才创建标记
  • 最终的服务器进程使用
    exec
    ,使其成为PID 1并能正确接收信号
  • 仔细查看初始化日志以获取实际默认凭证——不要仅根据文档假设

Working directory matters

工作目录很重要

When overriding
command
/
args
, be aware of the Dockerfile's
WORKDIR
. In monorepo apps, different commands need to run from different directories:
yaml
args:
    - -c
    - |
        cd /app              # root workspace for yarn workspace commands
        yarn mercato init    # CLI from root package.json
        cd /app/apps/myapp   # app directory for next start
        exec next start -p 3000
覆盖
command
/
args
时,请注意Dockerfile的
WORKDIR
。在单体仓库应用中,不同命令需要在不同目录下运行:
yaml
args:
    - -c
    - |
        cd /app              # yarn workspace命令的根工作区
        yarn mercato init    # 来自根package.json的CLI命令
        cd /app/apps/myapp   # next start的应用目录
        exec next start -p 3000

ImagePullBackOff recovery

解决ImagePullBackOff问题

When a pod is stuck in
ImagePullBackOff
(e.g., after making a Docker Hub repo public), the Kubernetes backoff timer prevents immediate retries. Fix by restarting the service:
bash
npx zeabur@latest service restart --id SERVICE_ID -i=false -y
当Pod卡在
ImagePullBackOff
状态(例如将Docker Hub仓库设为公开后),Kubernetes的退避计时器会阻止立即重试。可通过重启服务解决:
bash
npx zeabur@latest service restart --id SERVICE_ID -i=false -y

Disable unused monitoring agents

禁用未使用的监控代理

Many production images ship with New Relic, Datadog, or similar APM agents. These require license keys and consume memory. Add
NEW_RELIC_ENABLED=false
or equivalent env vars to disable them cleanly. Check if the
start
script injects agents via
NODE_OPTIONS='-r newrelic'
-- these still run even if the agent errors out.
许多生产镜像内置了New Relic、Datadog或类似的APM代理。这些代理需要许可证密钥并消耗内存。添加
NEW_RELIC_ENABLED=false
或等效环境变量以干净地禁用它们。请检查
start
脚本是否通过
NODE_OPTIONS='-r newrelic'
注入代理——即使代理出错,它们仍会运行。

Verify all URLs before publishing

发布前验证所有URL

Before publishing a template, verify that ALL URLs return HTTP 200:
  • icon
    URL
  • coverImage
    URL
  • Links in
    readme
Common pitfall:
raw.githubusercontent.com
URLs with wrong branch name (
develop
vs
main
vs
master
). Always check:
bash
curl -s -o /dev/null -w "%{http_code}" "https://raw.githubusercontent.com/..."
发布模板前,请验证所有URL都返回HTTP 200状态码:
  • icon
    URL
  • coverImage
    URL
  • readme
    中的链接
常见问题:
raw.githubusercontent.com
URL中的分支名称错误(
develop
main
master
混淆)。请始终检查:
bash
curl -s -o /dev/null -w "%{http_code}" "https://raw.githubusercontent.com/..."