From zeabur
Creates, edits, validates, and troubleshoots Zeabur template YAML files. Converts docker-compose.yml to Zeabur templates. Guides Docker image publishing for PREBUILT_V2 services.
npx claudepluginhub zeabur/agent-skills --plugin zeaburThis skill uses the workspace's default tool permissions.
> **Always use `npx zeabur@latest` to invoke Zeabur CLI.** Never use `zeabur` directly or any other installation method. If `npx` is not available, install Node.js first.
Deploys Zeabur marketplace templates for databases (MongoDB, PostgreSQL, MySQL, Redis) and self-hosted apps (n8n, WordPress, Uptime Kuma) via non-interactive CLI. Searches, fetches, customizes YAML, and deploys.
Deploys apps to Render by analyzing codebases, generating render.yaml blueprints, and providing dashboard deeplinks. For Git-backed services, Docker images, databases, and cron jobs.
Deploys Git-backed apps to Render by analyzing codebases, generating render.yaml blueprints, creating services via MCP, and providing dashboard deeplinks. Use for hosting on Render.
Share bugs, ideas, or general feedback.
Always use
npx zeabur@latestto invoke Zeabur CLI. Never usezeaburdirectly or any other installation method. Ifnpxis not available, install Node.js first.
This skill provides comprehensive knowledge for creating, debugging, and publishing Zeabur templates. It combines reference documentation with battle-tested patterns from real template development.
For the latest schema and detailed docs, fetch from https://raw.githubusercontent.com/zeabur/zeabur-template-doc/main/:
| User Need | Document to Fetch | Path |
|---|---|---|
| Create a template from scratch | Step-by-step guide | docs/GUIDE.md |
| Convert docker-compose.yml | Migration guide | docs/DOCKER_COMPOSE_MIGRATION.md |
| Look up YAML fields or built-in variables | Technical reference | docs/REFERENCE.md |
| Naming, design patterns, best practices | Best practices | docs/BEST_PRACTICES.md |
| Debug template errors | Troubleshooting | docs/TROUBLESHOOTING.md |
| Pre-deployment checklist | Checklist | docs/CHECKLIST.md |
| Quick all-in-one overview | Comprehensive 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.
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:
Dockerfile (usually at repo root)runner or production)docker-compose.yml or docker-compose.fullapp.yml for the correct startup command, env vars, and volumes — this is the battle-tested production configdocker buildx build --platform linux/amd64 --target runner -t org/image:tag --push .
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.When asked to create a template, always look for existing configuration first:
ghcr.io/org/repo), or the project's CI/CD for published images. If none exists, stop and inform the user.docker-compose.yml, docker-compose.yaml, or compose.ymlChart.yaml, values.yaml)Even experienced humans cannot create a working template in one shot. The workflow is an iterative loop:
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.
When your template needs a common service (PostgreSQL, Redis, MySQL, MongoDB, etc.), do not write the service definition yourself. Instead:
npx zeabur@latest template search postgres
npx zeabur@latest template get -c TEMPLATE_CODE --raw
How to judge template trustworthiness:
Deploy a template:
npx zeabur@latest template deploy -f YOUR_TEMPLATE.yaml
For non-interactive mode (automation):
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):
npx zeabur@latest service list --project-id PROJECT_ID
Check runtime logs:
npx zeabur@latest deployment log --service-id SERVICE_ID
Execute a command inside a running service (like docker exec):
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:
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):
npx zeabur@latest service restart --id SERVICE_ID -i=false -y
Delete the project and start over:
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:
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:
npx zeabur@latest template update -c TEMPLATE_CODE -f YOUR_TEMPLATE.yaml
# 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
portForwarding:
enabled: true # Auto-expose TCP/UDP ports externally (default: true)
healthCheck: # ensures port is ready before dependents start
type: TCP
port: web # references the port ID above
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: |
# ...
| Variable | Purpose |
|---|---|
${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 port forwarding (auto-set when enabled, usable in env vars and instructions) |
${[PORTID]_PORT_FORWARDED_PORT} | External forwarded port number (auto-set when enabled, usable in env vars and instructions) |
The ZEABUR_<PORT_ID>_URL pattern: for a port named web, it becomes ${ZEABUR_WEB_URL}; for console, it becomes ${ZEABUR_CONSOLE_URL}.
IMPORTANT: command MUST be inside source, alongside image. NOT at spec level.
# WRONG -- command at spec level (will be IGNORED, container uses default CMD)
spec:
source:
image: python:3.12-slim
command:
- /bin/sh
- -c
- /opt/app/start.sh
# CORRECT -- command inside source
spec:
source:
image: python:3.12-slim
command:
- /bin/sh
- -c
- /opt/app/start.sh
Note: The external docs may show
commandatspeclevel. This is incorrect. Always placecommandinsidesourceas confirmed by the JSON schema atschema.zeabur.app/prebuilt.json.
# RISKY -- @ at start of value is a YAML reserved indicator (may cause parse errors)
description: @BotFather token
# SAFE -- quote the value or avoid @ at start
description: "Token from @BotFather for Telegram bot"
description: Telegram bot token from BotFather
Some base images have ENTRYPOINT set, which conflicts with command.
| Image | ENTRYPOINT | Problem |
|---|---|---|
ghcr.io/astral-sh/uv:python3.12-* | uv | command becomes args to uv, container shows uv help and exits |
node:* | none | Safe to use |
python:* | none | Safe 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.
Services that are NOT web apps — such as HTTP proxies (Tinyproxy, Squid), databases, game servers, VPN servers, or any service that clients connect to directly via TCP/UDP — should NOT use HTTP port type or domainKey. Instead:
type: TCP (or UDP) — this prevents Zeabur's reverse proxy (Ingress) from intercepting trafficportForwarding: enabled: true — this auto-exposes the port externally so clients can connect directlydomainKey — TCP services don't need a domain; users connect via the forwarded hostname:port# CORRECT -- TCP service (e.g. HTTP proxy, database, game server)
spec:
source:
image: some/tcp-service:latest
ports:
- id: proxy
port: 8888
type: TCP # NOT HTTP — clients connect directly
portForwarding:
enabled: true # Auto-expose this port externally
env:
# ...
# WRONG -- using HTTP type for a TCP proxy
spec:
ports:
- id: proxy
port: 8888
type: HTTP # Zeabur's Ingress will intercept CONNECT requests
# Missing portForwarding — users must manually enable in Dashboard
Why not HTTP? Zeabur's HTTP port type routes traffic through a reverse proxy (Ingress/Envoy) that terminates TLS and performs Host-based routing. This breaks protocols like HTTP CONNECT (used by proxies), raw TCP streams (databases), and custom binary protocols (game servers).
Port Forwarding variables (for use in instructions or readme):
${PORT_FORWARDED_HOSTNAME} — the external hostname${PROXY_PORT_FORWARDED_PORT} — the external port (pattern: ${[PORTID]_PORT_FORWARDED_PORT})Post-deployment testing for TCP services:
After deploying a TCP service, verify port forwarding is working:
Check internal connectivity first (from inside the container):
npx zeabur@latest service exec --id SERVICE_ID -- curl -x http://127.0.0.1:8888 https://ifconfig.co
Get the forwarded host:port from the Dashboard's Networking tab, or use:
npx zeabur@latest service network --id SERVICE_ID
Test external connectivity:
curl -x http://FORWARDED_HOST:FORWARDED_PORT https://ifconfig.co
If port forwarding shows as DISABLED, enable it:
npx zeabur@latest service port-forward --id SERVICE_ID --enable
If a service does NOT listen on any HTTP port (502 Bad Gateway), see zeabur-port-mismatch skill for the fix.
# WRONG -- using :latest tag (may serve cached/stale image)
image: rajnandan1/kener:latest
# CORRECT -- pin to specific version
image: rajnandan1/kener:4.0.16
# WRONG -- hardcoded password
POSTGRES_PASSWORD:
default: mypassword123
# CORRECT -- use ${PASSWORD}
POSTGRES_PASSWORD:
default: ${PASSWORD}
expose: true
# WRONG -- PUBLIC_DOMAIN gives incomplete URL
APP_URL:
default: https://${PUBLIC_DOMAIN}
# CORRECT -- ZEABUR_WEB_URL gives full URL
APP_URL:
default: ${ZEABUR_WEB_URL}
readonly: true
# WRONG -- other services can't reference without expose
POSTGRES_HOST:
default: ${CONTAINER_HOSTNAME}
# CORRECT -- expose + readonly for connection info
POSTGRES_HOST:
default: ${CONTAINER_HOSTNAME}
expose: true
readonly: true
# WRONG -- referencing variables without declaring dependency
- name: app
spec:
env:
DB: ${POSTGRES_HOST}
# CORRECT -- declare dependency first
- name: app
dependencies:
- postgresql
spec:
env:
DB: ${POSTGRES_HOST}
# WRONG -- HTTP type for a TCP proxy/database/game server
ports:
- id: proxy
port: 8888
type: HTTP
# CORRECT -- TCP type + portForwarding for non-web services
ports:
- id: proxy
port: 8888
type: TCP
portForwarding:
enabled: true
Use domainKey on the service that needs a public domain. It maps to a variable defined in spec.variables with type: DOMAIN.
Single domain:
domainKey: PUBLIC_DOMAIN
Multiple domains (different ports):
domainKey:
- port: web
variable: ENDPOINT_DOMAIN
- port: console
variable: ADMIN_ENDPOINT_DOMAIN
See references/database-configs.md for copy-paste PostgreSQL, MySQL/MariaDB, Redis configs and standard volume paths.
See references/complexity-levels.md for the 5-level complexity guide (single service → large-scale multi-service platform).
Where to collect information (in priority order):
Key rules:
curl -s -o /dev/null -w "%{http_code}" "URL"
Common pitfall: wrong branch name in raw.githubusercontent.com URLs (develop vs main vs master).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
See references/hard-won-lessons.md for battle-tested patterns: wait-for-db, first-run init markers, OOM recovery, ImagePullBackOff, and more.