
Docker To Sealos
- 68 installs
- 1 repo stars
- Updated June 18, 2026
- zjy365/sealos-skills
Converts Docker Compose files or install docs into production-grade Sealos/Kubernetes templates end-to-end without prompting for missing fields.
About
Analyzes Docker Compose services, volumes, ports, env vars, and secrets, then generates and validates a Sealos template automatically. A developer uses it to migrate a docker-compose.yml or installation doc to Sealos format.
- Extracts app vs database services, object storage, and startup credential rules
- Runs analysis, conversion, validation, and output automatically without user input
Docker To Sealos by the numbers
- 68 all-time installs (skills.sh)
- Ranked #622 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zjy365/sealos-skills --skill docker-to-sealosAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 68 |
|---|---|
| repo stars | ★ 1 |
| Last updated | June 18, 2026 |
| Repository | zjy365/sealos-skills ↗ |
What it does
Converts Docker Compose files or install docs into production-grade Sealos/Kubernetes templates end-to-end without prompting for missing fields.
Files
Docker to Sealos Template Converter
Overview
Convert Docker Compose files or installation docs into production-grade Sealos templates. Execute end-to-end automatically (analysis, conversion, validation, output) without asking users for missing fields.
Governance and Rule Priority
Use the following precedence to prevent rule drift:
1. SKILL.md MUST rules (this file) 2. references/sealos-specs.md and references/database-templates.md 3. references/conversion-mappings.md and references/example-guide.md
If lower-priority references conflict with higher-priority MUST rules, update the lower-priority files. Do not keep conflicting examples.
Workflow
Step 1: Analyze input
Extract from Docker Compose/docs:
- application services vs database services
- volumes/config mounts/object storage requirements
- ports, dependencies, service communication
- env vars and secret usage
- startup-time validation rules for bootstrap credentials, API keys, salts, secrets, and feature flags
- multi-service web roles: browser entry, REST API, OpenAI/API gateway, docs, workers, and one-shot jobs
- resource limits/requests and health checks
- if official Kubernetes installation docs/manifests are available, also extract app-runtime behavior from them (bootstrap admin fields, external endpoint/protocol assumptions, health probes, startup/init flow, migration ordering)
Step 2: Infer metadata
Infer and normalize:
- app name, title, description, categories
- official URL, gitRepo, icon source (prefer square/circular icon-first assets such as app icons, favicons, or avatars; avoid rectangular wordmark/text logos)
- locale/i18n metadata
Step 3: Plan resources in strict order
Generate resources in this order:
1. Template CR 2. ObjectStorageBucket (if needed) 3. Database resources (ServiceAccount → Role → RoleBinding → Cluster → Job if needed) 4. App workload resources (ConfigMap/Secret → Deployment/StatefulSet → Service → Ingress) 5. App resource (last)
Step 4: Apply conversion rules
Apply field-level mappings from references/conversion-mappings.md, including:
- image pinning and annotation mapping
- port/service/ingress conversion
- env var conversion and dependency ordering
- storage conversion and vn naming (
scripts/path_converter.py) - service-name to Kubernetes FQDN conversion
- for DB URL/DSN envs (for example
*_DATABASE_URL,*_DB_URL), when Kubeblocksendpointis host:port, injecthost/port/username/passwordvia approvedsecretKeyRefenvs and compose the final URL with$(VAR)expansion - edge gateway normalization: when Compose includes Traefik-like edge proxy plus business services, skip the proxy workload and expose business services via Sealos Ingress directly
- TLS offload normalization for Sealos Ingress: when a business service exposes both 80 and 443, drop 443 from workload/service ports and remove in-container TLS certificate mounts (for example
/etc/nginx/ssl,/etc/ssl,/certs) unless official Kubernetes docs explicitly require HTTPS backend-to-service traffic - multi-service web normalization: expose the verified browser entry in the App resource, expose API/gateway/docs only when they are intended public surfaces, and keep workers private with no Service/Ingress
- URL topology: browser-facing env vars must use public HTTPS URLs, while server-to-server env vars must use Kubernetes Service FQDNs unless the app explicitly requires public callbacks
- prefer
scripts/compose_to_template.py --kompose-mode alwaysas deterministic conversion entrypoint (requirekomposefor reproducible workload shaping) - when official Kubernetes installation docs/manifests exist, perform a dual-source merge: use Compose as baseline topology, then align app-runtime semantics with official Kubernetes guidance
Step 5: Apply database strategy
- Database services must be generated as KubeBlocks
Clusterresources. Do not convert PostgreSQL/MySQL/MongoDB/Redis/Kafka Compose database services into raw KubernetesDeploymentorStatefulSetworkloads. - PostgreSQL must follow the pinned version and structure requirements.
- MySQL/MongoDB/Redis/Kafka must use templates and secret naming from
references/database-templates.md. - Add DB init Job/initContainer when application database bootstrap requires it.
- For PostgreSQL custom databases (non-
postgres), the init Job must wait for PostgreSQL readiness before execution and create the target database idempotently. - Critical application compatibility objects must be verified in live database state. Use idempotent initContainer self-healing for compatibility views, legacy tables/views, indexes, extensions, search paths, and bootstrap state that the app requires on every cold start.
- One-shot init Jobs may create initial databases or seed state, but app startup gates must verify the final database objects directly. Treat TTL-expired Jobs as historical evidence and rely on database state for acceptance.
- Worker, gateway, and background services that depend on app migrations must wait for the required tables, migration markers, or app-specific readiness objects, not only for the database port.
- Redis readiness probes or initContainers must tolerate authenticated Redis responses such as
NOAUTHorAuthentication requiredwhen credentials are not needed for readiness. - PostgreSQL bootstrap shell must use safe quoting patterns. Prefer shell-level existence checks plus simple SQL statements when possible. Use single-quoted heredocs or SQL files for psql variable interpolation, and avoid PL/pgSQL
DO $$blocks in inline shell commands when a guard query can express the same logic. - Do not use
psql -c "..."for:'var'variable interpolation. Usepsql -v name=value <<'SQL' ... :'name' ... SQLor pass already-safe literal SQL.
Step 6: Generate output files
Always produce:
template/<app-name>/index.yamltemplate/<app-name>/logo.<ext>when official icon is resolvable, prioritizing square/circular icon-first artwork and avoiding rectangular wordmark/text logos
Never create:
template/<app-name>/README.mdtemplate/<app-name>/README_zh.md
README authoring is out of scope for this skill. If the Template CR requires README URLs, populate URL fields in index.yaml only and leave file creation to a dedicated README skill.
Step 7: Validate before output
Run validator and self-tests before delivering template output. If validation fails, fix template/rules/examples first.
MUST Rules (Condensed)
Naming and metadata
- Template
metadata.namemust be hardcoded lowercase; do not use${{ defaults.app_name }}. - Template CR folder name must match
metadata.name. - Template CR must include required metadata fields (
title,url,gitRepo,author,description,icon,templateType,locale,i18n,categories). - Template
spec.readmemust point tohttps://raw.githubusercontent.com/labring-actions/templates/kb-0.9/template/<app-name>/README.md. - Template
spec.i18n.zh.readmemust point tohttps://raw.githubusercontent.com/labring-actions/templates/kb-0.9/template/<app-name>/README_zh.md. - These README fields are URL references in
index.yamlonly; this skill must not create or update the referenced README files. iconURL must point to template repo raw path for this app onkb-0.9branch.template/<app-name>/logo.<ext>must use square/circular icon-first artwork (for example app icon/favicon/avatar), and must not use rectangular wordmark/text logos.i18n.zh.descriptionmust be written in Simplified Chinese.- Omit
i18n.zh.titlewhen it is identical totitle. categoriesmust only use predefined values (tool,ai,game,database,low-code,monitor,dev-ops,blog,storage,frontend,backend).
App resource
- App resource must use
spec.data.url. - App resource
spec.displayTypemust benormal. - App resource
spec.typemust belink. - App resource
spec.data.urlmust be the browser entry URL that succeeds from a fresh Sealos launch. For apps with safe-path, setup-path, or entrance-path behavior, verify the configured path and root path, then choose the URL that supports login or first-run setup without hidden prior navigation. - SSR/Next.js/React server apps must not use a path that renders a server-side exception as the App URL or HTTP probe. Treat visible
Application error,server-side exception,Internal Server Error, orUnhandled Runtime Errortext as a failed entry path even if the HTTP status is 2xx/3xx. - Never use
spec.templatein App resource. cloud.sealos.io/app-deploy-managerlabel value must equal resourcemetadata.name.metadata.labels.applabel value must equal resourcemetadata.namefor managed app workloads.- The primary business container name must equal workload
metadata.namefor managed app workloads; sidecar/helper containers may use distinct descriptive names. - Application
Serviceresources must definemetadata.labels.appandmetadata.labels.cloud.sealos.io/app-deploy-manager, and both labels must matchspec.selector.app. - Runtime component-scoped
ConfigMapresources must definemetadata.labels.appandmetadata.labels.cloud.sealos.io/app-deploy-manager, and both labels must matchmetadata.name; bootstrap-only ConfigMaps used only by init containers to copy initial config into persistent storage must not define either label. - Application
Serviceresources must use the same component name acrossmetadata.name,metadata.labels.app,metadata.labels.cloud.sealos.io/app-deploy-manager, andspec.selector.app. - Root-path
Ingressresources (pathType: Prefix,path: /) must use the same component name acrossmetadata.name,metadata.labels.cloud.sealos.io/app-deploy-manager, and backendservice.name; non-root or non-Prefix Ingress rules may route to a different backend service. - Service
spec.ports[*].namemust be explicitly set (required for multi-port services). - HTTP Ingress must include required nginx annotations (
kubernetes.io/ingress.class,nginx.ingress.kubernetes.io/proxy-body-size,nginx.ingress.kubernetes.io/server-snippet,nginx.ingress.kubernetes.io/ssl-redirect,nginx.ingress.kubernetes.io/backend-protocol,nginx.ingress.kubernetes.io/client-body-buffer-size,nginx.ingress.kubernetes.io/proxy-buffer-size,nginx.ingress.kubernetes.io/proxy-send-timeout,nginx.ingress.kubernetes.io/proxy-read-timeout,nginx.ingress.kubernetes.io/configuration-snippet) with expected defaults. - CronJob resources must define labels
cloud.sealos.io/cronjob,cronjob-launchpad-name, andcronjob-type;cloud.sealos.io/cronjobmust equalmetadata.name,cronjob-launchpad-namemust be"", andcronjob-typemust beimage. - When official application health checks are available, managed workloads must define
livenessProbe,readinessProbe, and (for slow bootstrap apps)startupProbe, aligned with official endpoints/commands.
Official Kubernetes alignment
- If official Kubernetes installation docs/manifests are available, conversion must reference them and align critical runtime settings before emitting template artifacts.
- When official Kubernetes docs/manifests and Compose differ, prefer official Kubernetes runtime semantics for app behavior (bootstrap admin fields, external endpoint/env/protocol, health probes), unless doing so violates higher-priority Sealos MUST/security constraints.
Images and pull policy
- Do not use
:latest. - Resolve versions with
crane: prefer an explicit version tag (for examplev2.2.0), and fallback to digest pin only when a deterministic version tag is unavailable. - Avoid floating tags (for example
:v2,:2.1,:stable); use an explicit version tag or digest. - Managed workload image references must be concrete and must not contain Compose-style variable expressions (for example
${VAR},${VAR:-default}); resolve to explicit tag or digest before emitting template artifacts. - Application
originImageNamemust match container image. - Public-image managed app workloads must omit
template.spec.imagePullSecrets; private-registry workloads may reference only the app-scoped pull Secret${{ defaults.app_name }}. - The registry pull Secret is runtime-managed by
sealos-deployusing localghCLI credentials for private GHCR images; do not expose raw registry credential inputs in generated templates. - All containers must explicitly set
imagePullPolicy: IfNotPresent.
Storage
- Do not use
emptyDir. - Use persistent storage patterns (
volumeClaimTemplates) where storage is needed. - StatefulSet resources with
volumeClaimTemplatesmust setmetadata.labels.cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}and everyvolumeClaimTemplates[].metadata.labels.cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}so Template can track and clean PVCs. - PVC request must be
<= 1Giunless source spec explicitly requires less. - ConfigMap data keys must follow vn naming (
scripts/path_converter.py), including/,-,., and other special characters. - ConfigMaps mounted by managed Deployment/StatefulSet workloads must use
metadata.name == workload.metadata.name. - ConfigMap workload volumes must use
<workload-name>-cm, and every ConfigMapdatakey must be mounted as its ownvolumeMountwithsubPathexactly equal to that key. - Avoid long inline startup scripts or heredocs in
command/args; place initialization/start scripts in ConfigMap files and invoke them with a short command.
Env and secrets
- Non-database sensitive values/inputs use direct
env[].value. - Business containers must source database connection fields (
endpoint,host,port,username,password) from approved Kubeblocks database secrets viaenv[].valueFrom.secretKeyRef; exception: Redishost/portmay use Sealos Redis Service FQDN and6379when the Redis secret only exposes credentials, and MongoDB connection URLs may use the Sealos MongoDB Service FQDN plus27017when the MongoDB secret exposes credentials only. - Business containers must not use custom env/volume
Secretreferences except approved Kubeblocks database secrets and object storage secrets. - A dedicated app-scoped registry pull Secret is allowed only for private-registry images and must be referenced only through
template.spec.imagePullSecrets; public images must not add pull secrets. - Database connection/bootstrap may use Kubeblocks-provided secrets, and reserved Kubeblocks database secret names must not be redefined by custom
Secretresources. - Env vars must be declared before referenced (for example password before URL composition).
- Follow official app env var naming; do not invent prefixes.
- For split frontend/API/gateway apps, keep public browser URLs and internal service URLs separate. Frontend/browser callback variables use
https://${{ defaults.<host> }}.${{ SEALOS_CLOUD_DOMAIN }}; backend-to-backend variables usehttp://<service>.$(SEALOS_NAMESPACE).svc.cluster.local:<port>or the fully rendered Service FQDN. - When the application requires its public URL configured via a file-based config system (e.g., node-config
config/default.json, PHP config files), create a ConfigMap containing the config file with the public URL set tohttps://${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}, and mount it to the application's config directory. The ConfigMap must follow standard naming and label conventions. - For PostgreSQL custom databases (non-
postgres), include${{ defaults.app_name }}-pg-initJob and implement startup-safe/idempotent creation logic (readiness wait + existence check before create). - For application-specific database compatibility, include an initContainer or startup gate that idempotently creates or repairs required views, aliases, indexes, extensions, privileges, role search paths, and legacy compatibility objects before the business container starts.
- Managed app main container
command/argsmust stay close to the image's official entrypoint. Keep only official startup commands, Compose-native args, or a short exec wrapper; move file preparation, permission repair, database bootstrap, and compatibility self-healing into initContainers, Jobs, or ConfigMap scripts. - Shell wrappers in the main business container must
execthe final process so signal handling remains correct. - Database bootstrap SQL must be safe under shell execution: prefer shell-level guard queries plus simple SQL, use single-quoted heredocs for psql variables, and avoid unguarded inline
DO $$blocks. psql -cmust not contain:'var'psql variable syntax; use heredocs for SQL that needs-vinterpolation.
Database-specific constraints
- Database services must use KubeBlocks
Clusterresources, not applicationDeploymentorStatefulSetworkloads.StatefulSetis allowed for stateful application components only, never for PostgreSQL/MySQL/MongoDB/Redis/Kafka database services. - PostgreSQL version:
postgresql-16.4.0. - PostgreSQL API:
apps.kubeblocks.io/v1alpha1. - PostgreSQL RBAC unified naming:
${{ defaults.app_name }}-pg. - PostgreSQL RBAC requires
app.kubernetes.io/instanceandapp.kubernetes.io/managed-bylabels. - Every KubeBlocks database
Clustermust includekb.io/database,sealos-db-provider-cr, andclusterdefinition.kubeblocks.io/namelabels;sealos-db-provider-crmust equalmetadata.nameso dbprovider can list and classify the database. Related Pods, Services, and OpsRequests should carryapp.kubernetes.io/instance=<database name>for detail views. - PostgreSQL role wildcard permission requirement remains as defined in current spec.
- PostgreSQL cluster must include required labels/fields (
kb.io/database: postgresql-16.4.0,clusterdefinition.kubeblocks.io/name: postgresql,clusterversion.kubeblocks.io/name: postgresql-16.4.0,clusterVersionRef: postgresql-16.4.0,disableExporter: true,enabledLogs: [running],switchPolicy.type: Noop,serviceAccountName). - MongoDB cluster must follow upgraded structure (
componentDef: mongodb,serviceVersion: 8.0.4, labelskb.io/databaseandapp.kubernetes.io/instance). - MySQL cluster must follow upgraded structure (
kb.io/database: ac-mysql-8.0.30-1,clusterDefinitionRef: apecloud-mysql,clusterVersionRef: ac-mysql-8.0.30-1,tolerations: []). - Redis cluster must follow upgraded structure (
componentDef: redis-7,componentDef: redis-sentinel-7,serviceVersion: 7.2.7, main data PVC1Gi, topologyreplication). - Database cluster component resources must use
limits(cpu=500m,memory=512Mi)andrequests(cpu=50m,memory=51Mi)unless source docs explicitly require otherwise. - All managed workload container resources must use the Sealos resource ladder:
limits.cpuonly100m/200m/500m/1/2/3/4/8,limits.memoryonly128Mi/256Mi/512Mi/1024Mi/2048Mi/4096Mi/8192Mi/16384Mi, andrequestsmust be derived fromlimitsby dropping the last numeric digit (500m→50m,512Mi→51Mi,1→100m,1024Mi→102Mi,4096Mi→409Mi). Do not invent non-ladder values, and never use2G/4G/8G/16Gbecause Sealos Template API quota preview can parse bareGmemory as 0. - Secret naming:
- MongoDB:
${{ defaults.app_name }}-mongo-mongodb-account-root(or${{ defaults.app_name }}-mongodb-mongodb-account-rootwhen the MongoDB cluster name uses-mongodb) - Redis:
${{ defaults.app_name }}-redis-redis-account-default(legacy${{ defaults.app_name }}-redis-account-defaultmay be accepted for backward compatibility) - Kafka:
${{ defaults.app_name }}-broker-account-admin - Do not use legacy naming outside supported exceptions.
Baseline runtime defaults
Unless source docs explicitly require otherwise, use the lightweight app ladder entry:
- container limits:
cpu=200m,memory=256Mi - container requests:
cpu=20m,memory=25Mi revisionHistoryLimit: 1automountServiceAccountToken: falseby default; set it totrueonly when the application has explicit Kubernetes API/service account token requirements, evidenced by Kubernetes integration settings,serviceAccountName, or asealos.io/service-account-token-reasonworkload annotation.
For higher resource needs, move only to another allowed limits ladder entry and recompute requests from that limits value.
Browser / remote desktop resource validation
For browser, VNC, WebRTC desktop, Xvfb, Selkies, noVNC, Kasm, or remote-desktop-style containers:
- Do not treat a short smoke test as proof of a stable minimum memory value.
- Validate memory with a fresh deployment, not only a patched warm pod.
- Exercise cold start until readiness, a lightweight page, a real/medium page, an interactive/search page, and a 60s post-smoke stability check.
- If observed cgroup memory reaches more than 80% of the limit during smoke, move to the next allowed Sealos memory ladder value.
- Keep requests derived from limits according to the Sealos resource ladder.
Example:
- Bad: Chrome passes a short smoke at
512Mibut reaches503Mi; shipping512Mias the stable minimum is unsafe. - Good: raise to
1024Mi, set request to102Mi, rerun smoke and stability checks.
For Chrome + Xvfb + Selkies with 4K max display, use at least:
- limits:
cpu=200m,memory=1024Mi - requests:
cpu=20m,memory=102Mi
Defaults vs inputs
defaultsfor generated values (app_name,app_host, random passwords/keys).inputsonly for truly user-provided operational values (email/SMTP/external API keys, etc.).- When the user explicitly asks to enter application administrator credentials, declare the administrator username/password in
spec.inputs, pass them as direct env values, and apply them through the application's documented bootstrap or initialization path. Keep database credentials on KubeBlocks secrets. - Every
${{ inputs.<name> }}reference in a template artifact must have a matchingspec.inputs.<name>declaration in the same Template CR. inputs.descriptionmust be in English.- Startup-critical
inputs[*].defaultvalues must satisfy the application's documented startup validation. For admin/bootstrap passwords with complexity rules, do not use'', weak examples, or bare${{ random(n) }}because generated characters may not include required classes; include deterministic required classes around the random segment, for example"AppName@${{ random(16) }}!1". - If an application exits when a required input is weak or empty, treat the input default as part of the runtime contract. Live validation must include the first boot logs and login/setup path with the generated default value.
- For binary object storage choices, use a boolean input (for example
enable_s3_storage) and test withinputs.<name> === 'true'.
Validation Commands
Run all checks before final response:
1. python scripts/path_converter.py --self-test 2. python scripts/test_check_consistency.py 3. python scripts/test_compose_to_template.py 4. python scripts/test_check_must_coverage.py 5. python scripts/check_consistency.py --skill SKILL.md --references references --rules-file references/rules-registry.yaml 6. python scripts/check_consistency.py --skill SKILL.md --references references --rules-file references/rules-registry.yaml --artifacts template/<app-name>/index.yaml 7. python scripts/check_must_coverage.py --skill SKILL.md --mapping references/must-rules-map.yaml --rules-file references/rules-registry.yaml 8. (CI / one-shot) python scripts/quality_gate.py (requires template/*/index.yaml by default; set DOCKER_TO_SEALOS_ALLOW_EMPTY_ARTIFACTS=1 only for dev/debug without artifacts) 9. Live deploy acceptance: after sealos-deploy creates the app, verify the actual App URL, login/setup flow for web apps, recent logs, expected database objects, and full resource footprint before reporting success.
check_consistency.py is registry-driven. Keep references/rules-registry.yaml in sync with implemented rules. Registry rule entries support severity and optional scope.include_paths metadata.
Output Contract
When conversion is complete, provide:
1. brief conversion summary 2. target file path (template/<app-name>/index.yaml) 3. complete template YAML 4. key decisions only where ambiguity existed
Do not create or output README content in this skill. README generation is delegated to another skill.
Reference Navigation (Progressive Loading)
Load only needed references for current task:
references/sealos-specs.md- authoritative ordering, labels, App/Ingress/ConfigMap conventions
references/conversion-mappings.md- Docker→Sealos field-level mappings and edge conversions
references/database-templates.md- database templates, RBAC structures, secret naming patterns
references/frappe-bench.md- Frappe/ERPNext/HRMS/bench conversion patterns, init resources, idempotent site bootstrap, and common failure signatures
references/example-guide.md- examples and pattern walkthroughs (non-authoritative)
references/rules-registry.yaml- machine-readable validation scope/rules list
references/must-rules-map.yaml- MUST bullet to enforcement mapping (
ruleormanual) for drift control
Script Utilities
scripts/path_converter.py- convert paths to vn names
- self-test support for regression checks
scripts/compose_to_template.py- deterministic compose/docs-to-template generator entrypoint
- supports
--kompose-mode auto|always|never(alwaysis default) to reusekompose convertworkload shapes - emits
template/<app-name>/index.yaml scripts/test_compose_to_template.py- regression tests for compose conversion behavior
scripts/check_consistency.py- registry-driven consistency validator
scripts/test_check_consistency.py- regression tests for validator behavior
scripts/check_must_coverage.py- validate MUST bullet coverage mapping against registry rules
scripts/test_check_must_coverage.py- regression tests for MUST coverage validator
Edge Policies
- Never ask users for missing fields; infer from compose/docs and platform conventions.
- Keep App resource in
spec.data.urlformat; never usespec.template. - Keep App resource
spec.displayType: normalandspec.type: link; do not infer alternative enum values. - Keep business-env, object storage, and DB-secret policy consistent with MUST rules.
- Prefer square/circular icon-first logo assets (app icon/favicon/avatar) and avoid rectangular wordmark/text logos.
- Prefer Sealos-managed ingress over bundled edge proxies: if a Traefik gateway is only acting as ingress/front-proxy and at least one business service exists, do not emit Traefik workload resources.
- Prefer gateway TLS termination in Sealos Ingress over in-container TLS: for dual-port HTTP/HTTPS workloads, keep HTTP service port and remove redundant HTTPS/certificate mounts unless official docs require HTTPS backend.
- Never create
template/<app-name>/README.mdortemplate/<app-name>/README_zh.md; only keep README URL references insideindex.yamlwhen required by the template schema. - Prefer fixing references/examples over adding exceptions when conflicts appear.
- If official Kubernetes installation docs/manifests exist for the target app, do not ignore them; use them to refine runtime semantics beyond Compose defaults.
- If the project mentions Frappe, ERPNext, HRMS, or
bench, loadreferences/frappe-bench.mdbefore generating app workloads.
Docker to Sealos Conversion Mapping Guide
This document provides detailed mapping rules from Docker Compose configuration to Sealos templates.
Dual-Source Input Merging (Compose + Official Kubernetes)
When an application provides both a Docker Compose file and an official Kubernetes installation method, the conversion must use dual-source merging rather than single-source inference.
Merging Principles
1. Sealos specifications and SKILL MUST rules take priority (security/platform constraints must not be violated) 2. The official Kubernetes installation method takes priority over Compose for application runtime semantics 3. Compose serves as the baseline for service topology and dependencies 4. Generic default values are only used when the above sources are absent
Key Alignment Fields
- First-time initialization and admin bootstrap fields (bootstrap admin/org/user/password)
- External access related fields (domain/port/secure/tls termination assumption)
- Protocol and gateway behavior (Ingress backend protocol, service appProtocol, path routing)
- Health checks and startup ordering (liveness/readiness/startup probe)
- Officially recommended startup parameters and commands
Conflict Resolution
When the official Kubernetes method conflicts with Compose:
- Preserve Sealos MUST and security rules
- For all other application behavior, default to aligning with the official Kubernetes method
- Record key decisions in the output (only record items with ambiguity)
Core Concept Mapping
Docker Compose Service → Sealos Resources
A single service in Docker Compose needs to be converted into multiple Sealos resources:
# Docker Compose
services:
app:
image: myapp:1.0.0
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
- DB_HOST=postgresConverts to:
# Sealos Template
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${{ defaults.app_name }}
---
# Service
apiVersion: v1
kind: Service
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
---
# Ingress (if public access is required)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${{ defaults.app_name }}Image Mapping
Warning: Example images must use a pinned version, preferring an exact version tag (e.g., v2.2.0); only use a digest when a stable version tag cannot be determined. Using :latest is prohibited. Warning: Compose variable image expressions (e.g., ${IMAGE}, ${IMAGE:-ghcr.io/example/app}) must not be retained in the final template; they must be resolved to concrete image references during the conversion phase.
Docker Compose
services:
app:
image: nginx:1.27.2
# or
build: ./appSealos Template
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
originImageName: nginx:1.27.2 # Must be added
spec:
revisionHistoryLimit: 1
template:
spec:
automountServiceAccountToken: false
containers:
- name: ${{ defaults.app_name }}
image: nginx:1.27.2
imagePullPolicy: IfNotPresent # Must be setNotes:
- Omit
imagePullSecretsfor public images. For private-registry images, reference only the app-scoped image pull Secret${{ defaults.app_name }}. sealos-deployshould create or refresh that Secret automatically from localghCLI credentials when deploying private GHCR images.- Reusable templates should not expose raw registry credential inputs as user-facing form fields.
Port Mapping
Docker Compose
services:
app:
ports:
- "3000:3000"
- "8080:80"The Sealos gateway terminates TLS at the Ingress layer by default. If Compose exposes both80and443, and the backend service does not require HTTPS, the conversion should preferentially keep the HTTP port and remove443, while also not mounting in-container certificate directories (e.g.,/etc/nginx/ssl,/etc/ssl,/certs).
Sealos Template
Container Port Configuration
spec:
revisionHistoryLimit: 1
template:
spec:
automountServiceAccountToken: false
containers:
- name: ${{ defaults.app_name }}
ports:
- containerPort: 3000
- containerPort: 80Service Configuration
apiVersion: v1
kind: Service
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
spec:
ports:
- name: tcp-3000
port: 3000
targetPort: 3000
- name: tcp-8080
port: 8080
targetPort: 80
selector:
app: ${{ defaults.app_name }}Ingress Configuration (Public Access)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${{ defaults.app_name }}
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager-domain: ${{ defaults.app_host }}
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/proxy-body-size: 32m
nginx.ingress.kubernetes.io/server-snippet: |
client_header_buffer_size 64k;
large_client_header_buffers 4 128k;
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
nginx.ingress.kubernetes.io/backend-protocol: HTTP
nginx.ingress.kubernetes.io/client-body-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-send-timeout: '300'
nginx.ingress.kubernetes.io/proxy-read-timeout: '300'
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($request_uri ~* \.(js|css|gif|jpe?g|png)) {
expires 30d;
add_header Cache-Control "public";
}
spec:
rules:
- host: ${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: ${{ defaults.app_name }}
port:
number: 3000
tls:
- hosts:
- ${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}
secretName: ${{ SEALOS_CERT_SECRET_NAME }}TLS Offload Normalization (80/443 Dual-Port Scenario)
# Docker Compose
services:
app:
ports:
- "80:80"
- "443:443"
volumes:
- certs:/etc/nginx/ssl
# After conversion (Sealos)
# - workload/service only retains port 80
# - Ingress continues to use the platform certificate
# - /etc/nginx/ssl is no longer converted to a PVC mountEnvironment Variable Mapping
Docker Compose
services:
app:
environment:
- NODE_ENV=production
- API_KEY=secret123
- DB_HOST=postgresSealos Template
Plain Environment Variables
spec:
revisionHistoryLimit: 1
template:
spec:
automountServiceAccountToken: false
containers:
- name: ${{ defaults.app_name }}
env:
- name: NODE_ENV
value: productionSensitive Values in Business Containers (Non-Database Connection Fields)
# Deployment
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
env:
- name: API_KEY
value: ${{ defaults.api_key }}Notes:
- Sensitive values for non-database connection fields use
env[].value(fromdefaultsorinputs). - Database connection fields (
endpoint/host/port/username/password) must usesecretKeyRef. - Only Kubeblocks database Secrets and object storage Secrets are allowed.
Referencing Database Connections
env:
- name: DB_ENDPOINT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: endpoint
- name: DB_HOST
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: host
- name: DB_PORT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: port
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: passwordURL/DSN Variable Composition (When endpoint Is Only host:port)
env:
- name: SEALOS_DATABASE_POSTGRES_HOST
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: host
- name: SEALOS_DATABASE_POSTGRES_PORT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: port
- name: SEALOS_DATABASE_POSTGRES_USERNAME
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: username
- name: SEALOS_DATABASE_POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: password
- name: DATABASE_URL
value: postgres://$(SEALOS_DATABASE_POSTGRES_USERNAME):$(SEALOS_DATABASE_POSTGRES_PASSWORD)@$(SEALOS_DATABASE_POSTGRES_HOST):$(SEALOS_DATABASE_POSTGRES_PORT)/postgresNotes:
- This pattern should only be used when the source value is a URL/DSN pointing to a recognized database service.
- URL fields such as
DATABASE_URLare allowed to reference component variables injected by approved DBsecretKeyRefvia$(VAR). - Assembling database URLs by referencing non-secret source variables is not allowed.
Volume Mapping
Docker Compose Volumes → Sealos VolumeClaimTemplates
Important: Sealos does not support emptyDir; all storage must be persistent.
Docker Compose
services:
app:
volumes:
- ./data:/app/data
- ./config:/app/configSealos Template (Using StatefulSet)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}
spec:
revisionHistoryLimit: 1
template:
spec:
automountServiceAccountToken: false
containers:
- name: ${{ defaults.app_name }}
volumeMounts:
- name: vn-appvn-data
mountPath: /app/data
- name: vn-appvn-config
mountPath: /app/config
volumeClaimTemplates:
- metadata:
annotations:
path: /app/data
value: '1'
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}
name: vn-appvn-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- metadata:
annotations:
path: /app/config
value: '1'
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}
name: vn-appvn-config
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiDocker Compose ConfigMap → Sealos ConfigMap
Docker Compose
services:
nginx:
volumes:
- ./nginx.conf:/etc/nginx/nginx.confSealos Template
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
data:
vn-etcvn-nginxvn-nginxvn-conf: |
server {
listen 80;
...
}
---
# Deployment
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
volumeMounts:
- name: ${{ defaults.app_name }}-cm
mountPath: /etc/nginx/nginx.conf
subPath: vn-etcvn-nginxvn-nginxvn-conf
volumes:
- name: ${{ defaults.app_name }}-cm
configMap:
name: ${{ defaults.app_name }}
defaultMode: 493Database Service Mapping
Docker Compose
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/dataSealos Template
Use the full Kubeblocks Cluster configuration (refer to database-templates.md):
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
name: ${{ defaults.app_name }}-pg
labels:
kb.io/database: postgresql-16.4.0
clusterdefinition.kubeblocks.io/name: postgresql
clusterversion.kubeblocks.io/name: postgresql-16.4.0
spec:
clusterDefinitionRef: postgresql
clusterVersionRef: postgresql-16.4.0
# ... full configuration see database-templates.mdService Dependency Mapping
Docker Compose
services:
app:
depends_on:
- postgres
- redis
environment:
- DB_HOST=postgres
- REDIS_HOST=redisSealos Template
Inter-Service Communication Using FQDN
env:
- name: DB_HOST
value: ${{ defaults.app_name }}-pg-postgresql.${{ SEALOS_NAMESPACE }}.svc.cluster.local
- name: REDIS_HOST
value: ${{ defaults.app_name }}-redis-redis-redis.${{ SEALOS_NAMESPACE }}.svc.cluster.localOr Using Secret
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: password
- name: DB_URL
value: postgresql://postgres:$(POSTGRES_PASSWORD)@${{ defaults.app_name }}-pg-postgresql.${{ SEALOS_NAMESPACE }}.svc:5432/mydbResource Limits Mapping
Compose resource values must be normalized to the Sealos ladder. Use Compose limits only to choose the nearest allowed limits tier. Normalize 1G-class memory to 1024Mi; normalize higher GiB classes to Mi values such as 2048Mi, 4096Mi, 8192Mi, or 16384Mi. Never emit bare 2G/4G/8G/16G limits because the Sealos Template API quota preview can parse them as 0. Ignore Compose reservations for requests; Sealos requests are derived from the selected limits by dropping the last numeric digit, so 1024Mi maps to 102Mi and 4096Mi maps to 409Mi.
Docker Compose
services:
app:
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.5'
memory: 512MSealos Template
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
resources:
limits:
cpu: 1
memory: 1024Mi
requests:
cpu: 100m
memory: 102MiHealth Check Mapping
Conversion priority: 1. When Docker Compose has a healthcheck, convert it to livenessProbe + readinessProbe 2. When Compose does not provide one but the official documentation clearly specifies a health endpoint/command, livenessProbe + readinessProbe must still be generated 3. For applications with slow initial startup (e.g., those that need to initialize a database), a startupProbe must also be generated to avoid premature failure during startup
Docker Compose
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3Official Health Check Example (authentik)
containers:
- image: ghcr.io/goauthentik/server:2025.12.3
imagePullPolicy: IfNotPresent
startupProbe:
httpGet:
path: /-/health/ready/
port: 9000
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 90
livenessProbe:
httpGet:
path: /-/health/live/
port: 9000
readinessProbe:
httpGet:
path: /-/health/ready/
port: 9000Sealos Template
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10Command and Arguments Mapping
Main business containers should keep startup behavior close to the image's official entrypoint. Use command/args only for official startup commands, Compose-native parameters, or a short wrapper that fixes one local precondition and then execs the final process. Move file preparation, permission repair, database/bootstrap SQL, compatibility views, package installs, and generated config into initContainers, one-shot Jobs, or ConfigMap-mounted scripts.
Docker Compose
services:
app:
command: ["npm", "start"]
# or
entrypoint: /app/start.sh
command: arg1 arg2Sealos Template
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
command: ["npm", "start"]
# or
command: ["/app/start.sh"]
args: ["arg1", "arg2"]Main Container Startup Contract
Use this decision flow before emitting a business container command/args:
1. If the image already has a valid ENTRYPOINT/CMD, omit command and args unless the upstream docs explicitly require parameters. 2. If Compose provides a simple command or args that are the application entrypoint, keep them. 3. If a small runtime precondition is required, use workingDir plus a short shell wrapper that ends with exec, for example:
workingDir: /opt/billionmail/core
command:
- /bin/sh
- -ec
- mkdir -p template && exec ./billionmail4. If the startup block copies files, changes ownership/permissions, writes config, runs database clients, creates compatibility objects, installs packages, or spans multiple lines, move that logic out of the main container.
Bad main-container startup:
command:
- /bin/sh
- -ec
- |
cp -r /defaults/* /data/
chmod -R 777 /data
psql -c 'CREATE VIEW ...'
exec ./appGood split:
- Config/data preparation: initContainer or ConfigMap script.
- Database bootstrap/compatibility: idempotent Job or initContainer.
- Main container: official entrypoint or short
execwrapper only.
Volume-Dependent Arguments (Important!)
Docker Compose command: or args may reference paths that only exist because of a host volume mount in Compose. These paths may not exist inside the container image itself.
Example — compose mounts a host dir for log output:
# Docker Compose
services:
app:
command: --log-dir /app/logs
volumes:
- ./logs:/app/logs # host mount creates /app/logsIf the Sealos template does not provision a matching volume, the /app/logs directory will not exist and the container will crash at startup (e.g., mkdir /app/logs: no such file or directory).
Resolution — check before converting: 1. For each path referenced in command:/args, check whether it comes from a Compose volumes: mount. 2. If the path is a log/data output directory that only exists via host mount:
- Option A (preferred): Drop the argument entirely — let the app use its built-in defaults (most apps log to stdout by default).
- Option B: Add a matching
volumeClaimTemplates(StatefulSet) oremptyDir-equivalent PVC to ensure the path exists.
3. If the path is an essential config/script file mounted from host → convert to ConfigMap mount instead. 4. Paths to executables or tools already inside the image (e.g., npm start, /app/start.sh from Dockerfile COPY) are safe to keep.
Network Mode Mapping
Built-in Edge Gateway (Traefik) Handling
When Compose includes both Traefik and business services, prefer using the Sealos platform Ingress capability and do not retain Traefik as an in-template workload.
Handling rules:
- If a service name or image is identifiable as Traefik, and at least one non-database business service exists, skip Traefik resource generation.
- The primary access entry point should target the business service (typically the first business service) via its Service, with the public domain exposed through Sealos Ingress.
- Only when the application contains only Traefik (no other business services) should Traefik be retained as a fallback, to avoid generating empty workloads.
Motivation:
- Avoid the additional forwarding complexity introduced by a dual-gateway setup (Traefik + Sealos Ingress).
- Reduce the risk of port, routing, and TLS configuration drift, making the template better aligned with Sealos platform capabilities.
Docker Compose
services:
app:
network_mode: host
# or
ports:
- "3000:3000"Sealos Template
Sealos does not support host network mode; all access uses Service + Ingress:
# Service (cluster-internal access)
apiVersion: v1
kind: Service
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
spec:
ports:
- name: tcp-3000
port: 3000
selector:
app: ${{ defaults.app_name }}
---
# Ingress (public access)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${{ defaults.app_name }}
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager-domain: ${{ defaults.app_host }}
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/proxy-body-size: 32m
nginx.ingress.kubernetes.io/server-snippet: |
client_header_buffer_size 64k;
large_client_header_buffers 4 128k;
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
nginx.ingress.kubernetes.io/backend-protocol: HTTP
nginx.ingress.kubernetes.io/client-body-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-send-timeout: '300'
nginx.ingress.kubernetes.io/proxy-read-timeout: '300'
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($request_uri ~* \.(js|css|gif|jpe?g|png)) {
expires 30d;
add_header Cache-Control "public";
}
spec:
rules:
- host: ${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: ${{ defaults.app_name }}
port:
number: 3000Object Storage Mapping
When docs offer local file storage and S3-compatible object storage as a binary choice, model the S3 branch with a boolean input. Use type: boolean and conditionals that test inputs.<name> === 'true'; do not model the binary local/S3 choice as a choice input.
Docker Compose (Using Minio)
services:
minio:
image: minio/minio
command: server /data
volumes:
- minio-data:/dataSealos Template (Optional Object Storage)
inputs:
enable_s3_storage:
description: "Enable S3 object storage"
type: boolean
default: "false"
required: false
---
${{ if(inputs.enable_s3_storage === 'true') }}
apiVersion: objectstorage.sealos.io/v1
kind: ObjectStorageBucket
metadata:
name: ${{ defaults.app_name }}
spec:
policy: private
${{ endif() }}
---
# Using object storage in the application
spec:
template:
spec:
containers:
- name: ${{ defaults.app_name }}
env:
- name: S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }}
key: accessKey
- name: S3_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }}
key: secretKey
- name: S3_BUCKET
valueFrom:
secretKeyRef:
name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }}
key: bucketBucket-scoped object-storage secrets may append an additional lowercase suffix when one app needs multiple bucket values, for example object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }}-public. Env names ending in _BUCKET may reference those bucket-scoped secrets.
CronJob Mapping
Any generated CronJob must include Sealos cron labels:
metadata:
labels:
cloud.sealos.io/cronjob: <metadata.name>
cronjob-launchpad-name: ""
cronjob-type: imageCommon Patterns Summary
Single-Container Application
- Docker Service → Deployment + Service + Ingress
Multi-Container Application
- Each Docker Service → Independent Deployment + Service
- The main application uses
${{ defaults.app_name }} - Other components use
${{ defaults.app_name }}-<component>
Database Services
- Docker postgres/mysql/mongo/redis → Kubeblocks Cluster + ServiceAccount + Role + RoleBinding
Persistent Storage
- Docker volumes → StatefulSet + volumeClaimTemplates
Configuration Files
- Docker config files → ConfigMap (using vn- naming convention)
Public URL Configuration
Many web apps need their external URL configured to avoid hardcoded localhost references. Without this, frontend API calls, OAuth callbacks, and webhook URLs will break in production.
Detection
Check source code/docs for:
- Env vars:
BASE_URL,SITE_URL,APP_URL,NEXTAUTH_URL,PUBLIC_URL,EXTERNAL_URL,HOSTNAME - Config files: node-config (
config/default.json), PHP config, Railsconfig/environments/production.rb - Code patterns:
getConfig(.*[Uu]rl,homeUrl,baseUrl,siteUrl, fallback tohttp://localhost
Strategy A: Env Var (preferred when supported)
When the app reads its public URL from an environment variable:
- name: APP_URL # use the app's actual env var name
value: https://${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}Strategy B: ConfigMap (for file-based config systems)
When the app reads its public URL from a config file (e.g., node-config, PHP config):
1. Create ConfigMap with the minimal config override containing only the public URL 2. Mount to the app's config directory using subPath to avoid overwriting other files 3. Follow standard ConfigMap naming/label conventions
# ConfigMap — only include the minimal config needed for public URL
apiVersion: v1
kind: ConfigMap
metadata:
name: ${{ defaults.app_name }}
labels:
app: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
data:
<config-filename>: |
<minimal config content with public URL set to
https://${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}>
# Deployment volumeMount — use subPath to mount single file
volumeMounts:
- name: app-config
mountPath: <app-config-dir>/<config-filename>
subPath: <config-filename>
# Deployment volume
volumes:
- name: app-config
configMap:
name: ${{ defaults.app_name }}Real-world examples: see skills/sealos-deploy/knowledge/lessons-learned.md (EverShop case study)
Sensitive Information
- Docker business env vars →
env[].value(defaults/inputs)
Database Template Reference
This document contains complete Sealos template configurations for various databases, intended as a reference during conversion.
Database Workload Rule
Database services must be represented by KubeBlocks Cluster resources. Do not translate Compose database services such as PostgreSQL, MySQL, MongoDB, Redis, or Kafka into raw Kubernetes Deployment or StatefulSet workloads. StatefulSet remains valid for stateful application components, but not for managed database services.
PostgreSQL Full Template
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
labels:
kb.io/database: postgresql-16.4.0
clusterdefinition.kubeblocks.io/name: postgresql
clusterversion.kubeblocks.io/name: postgresql-16.4.0
name: ${{ defaults.app_name }}-pg
spec:
affinity:
podAntiAffinity: Preferred
tenancy: SharedNode
clusterDefinitionRef: postgresql
clusterVersionRef: postgresql-16.4.0
componentSpecs:
- componentDefRef: postgresql
disableExporter: true
enabledLogs:
- running
name: postgresql
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
serviceAccountName: ${{ defaults.app_name }}-pg
switchPolicy:
type: Noop
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: openebs-backup
terminationPolicy: Delete
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-pg
app.kubernetes.io/instance: ${{ defaults.app_name }}-pg
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-pg
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-pg
app.kubernetes.io/instance: ${{ defaults.app_name }}-pg
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-pg
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-pg
app.kubernetes.io/instance: ${{ defaults.app_name }}-pg
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-pg
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${{ defaults.app_name }}-pg
subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-pgPostgreSQL Database Initialization Job
apiVersion: batch/v1
kind: Job
metadata:
name: ${{ defaults.app_name }}-pg-init
spec:
backoffLimit: 3
template:
spec:
containers:
- name: pgsql-init
image: postgres:16-alpine
imagePullPolicy: IfNotPresent
env:
- name: PG_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: password
- name: PG_ENDPOINT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: endpoint
- name: PG_DATABASE
value: <dbname>
command:
- /bin/sh
- -c
- |
set -eu
for i in $(seq 1 60); do
if pg_isready -h "${PG_ENDPOINT%:*}" -p "${PG_ENDPOINT##*:}" -U postgres -d postgres >/dev/null 2>&1; then
break
fi
sleep 2
done
pg_isready -h "${PG_ENDPOINT%:*}" -p "${PG_ENDPOINT##*:}" -U postgres -d postgres >/dev/null 2>&1
if ! psql "postgresql://postgres:$(PG_PASSWORD)@$(PG_ENDPOINT)/postgres" -tAc "SELECT 1 FROM pg_database WHERE datname='$(PG_DATABASE)'" | grep -q 1; then
psql "postgresql://postgres:$(PG_PASSWORD)@$(PG_ENDPOINT)/postgres" -v ON_ERROR_STOP=1 -c "CREATE DATABASE \"$(PG_DATABASE)\";"
fi
restartPolicy: OnFailure
ttlSecondsAfterFinished: 300MySQL Full Template
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
labels:
kb.io/database: ac-mysql-8.0.30-1
clusterdefinition.kubeblocks.io/name: apecloud-mysql
clusterversion.kubeblocks.io/name: ac-mysql-8.0.30-1
name: ${{ defaults.app_name }}-mysql
spec:
affinity:
nodeLabels: {}
podAntiAffinity: Preferred
tenancy: SharedNode
topologyKeys:
- kubernetes.io/hostname
clusterDefinitionRef: apecloud-mysql
clusterVersionRef: ac-mysql-8.0.30-1
componentSpecs:
- componentDefRef: mysql
monitor: true
name: mysql
noCreatePDB: false
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
serviceAccountName: ${{ defaults.app_name }}-mysql
switchPolicy:
type: Noop
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: openebs-backup
terminationPolicy: Delete
tolerations: []
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mysql
app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mysql
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mysql
app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mysql
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mysql
app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mysql
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${{ defaults.app_name }}-mysql
subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-mysqlMongoDB Full Template
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
labels:
kb.io/database: mongodb-8.0.4
app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo
name: ${{ defaults.app_name }}-mongo
spec:
affinity:
podAntiAffinity: Preferred
tenancy: SharedNode
topologyKeys:
- kubernetes.io/hostname
componentSpecs:
- componentDef: mongodb
name: mongodb
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
serviceAccountName: ${{ defaults.app_name }}-mongo
serviceVersion: 8.0.4
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: openebs-backup
terminationPolicy: Delete
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mongo
app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mongo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mongo
app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mongo
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-mongo
app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-mongo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${{ defaults.app_name }}-mongo
subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-mongoRedis Full Template
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
labels:
kb.io/database: redis-7.2.7
app.kubernetes.io/instance: ${{ defaults.app_name }}-redis
app.kubernetes.io/version: 7.2.7
clusterversion.kubeblocks.io/name: redis-7.2.7
clusterdefinition.kubeblocks.io/name: redis
name: ${{ defaults.app_name }}-redis
spec:
affinity:
podAntiAffinity: Preferred
tenancy: SharedNode
topologyKeys:
- kubernetes.io/hostname
clusterDefinitionRef: redis
componentSpecs:
- componentDef: redis-7
name: redis
replicas: 1
enabledLogs:
- running
env:
- name: CUSTOM_SENTINEL_MASTER_NAME
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
serviceAccountName: ${{ defaults.app_name }}-redis
serviceVersion: 7.2.7
switchPolicy:
type: Noop
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: openebs-backup
- componentDef: redis-sentinel-7
name: redis-sentinel
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
serviceAccountName: ${{ defaults.app_name }}-redis
serviceVersion: 7.2.7
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
terminationPolicy: Delete
topology: replication
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-redis
app.kubernetes.io/instance: ${{ defaults.app_name }}-redis
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-redis
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-redis
app.kubernetes.io/instance: ${{ defaults.app_name }}-redis
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-redis
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-redis
app.kubernetes.io/instance: ${{ defaults.app_name }}-redis
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-redis
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${{ defaults.app_name }}-redis
subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-redisKafka Full Template
apiVersion: apps.kubeblocks.io/v1alpha1
kind: Cluster
metadata:
finalizers:
- cluster.kubeblocks.io/finalizer
labels:
kb.io/database: kafka-3.3.2
clusterdefinition.kubeblocks.io/name: kafka
clusterversion.kubeblocks.io/name: kafka-3.3.2
annotations:
kubeblocks.io/extra-env: >-
{"KB_KAFKA_ENABLE_SASL":"false","KB_KAFKA_BROKER_HEAP":"-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64","KB_KAFKA_CONTROLLER_HEAP":"-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64","KB_KAFKA_PUBLIC_ACCESS":"false"}
name: ${{ defaults.app_name }}-broker
spec:
terminationPolicy: Delete
componentSpecs:
- name: broker
componentDef: kafka-broker
tls: false
replicas: 1
affinity:
podAntiAffinity: Preferred
topologyKeys:
- kubernetes.io/hostname
tenancy: SharedNode
tolerations:
- key: kb-data
operator: Equal
value: 'true'
effect: NoSchedule
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
volumeClaimTemplates:
- name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- name: metadata
spec:
storageClassName: null
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- name: controller
componentDefRef: controller
componentDef: kafka-controller
tls: false
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
volumeClaimTemplates:
- name: metadata
spec:
storageClassName: null
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- name: metrics-exp
componentDef: kafka-exporter
replicas: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 51Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-broker
app.kubernetes.io/instance: ${{ defaults.app_name }}-broker
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-broker
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-broker
app.kubernetes.io/instance: ${{ defaults.app_name }}-broker
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-broker
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
sealos-db-provider-cr: ${{ defaults.app_name }}-broker
app.kubernetes.io/instance: ${{ defaults.app_name }}-broker
app.kubernetes.io/managed-by: kbcli
name: ${{ defaults.app_name }}-broker
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ${{ defaults.app_name }}-broker
subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-brokerDatabase Connection Configuration
Upgrade Baseline (Database Upgrade Documentation)
The following specifications are consistent with the database upgrade documentation:
- Database connection fields (
endpoint/host/port/username/password) in application containers must be obtained viasecretKeyRef; Redis host/port may use the Sealos Redis Service FQDN plus6379when the secret only exposes credentials, and MongoDB URLs may use the Sealos MongoDB Service FQDN plus27017when the secret only exposes credentials - PostgreSQL Cluster uses
postgresql-16.4.0and includeskb.io/database,disableExporter: true,enabledLogs: [running] - Secret naming upgrades:
xxx-redis-conn-credential->xxx-redis-redis-account-defaultxxx-mongo-conn-credential->xxx-mongo-mongodb-account-root(orxxx-mongodb-mongodb-account-rootwhen the Cluster name usesxxx-mongodb)xxx-conn-credential(kafka) ->xxx-broker-account-admin
Secret Naming Conventions
- PostgreSQL:
${{ defaults.app_name }}-pg-conn-credential - MySQL:
${{ defaults.app_name }}-mysql-conn-credential - MongoDB:
${{ defaults.app_name }}-mongo-mongodb-account-root(or${{ defaults.app_name }}-mongodb-mongodb-account-rootwhen the MongoDB Cluster name uses${{ defaults.app_name }}-mongodb) - Redis:
${{ defaults.app_name }}-redis-redis-account-default(legacy${{ defaults.app_name }}-redis-account-defaultmay be accepted for backward compatibility) - Kafka:
${{ defaults.app_name }}-broker-account-admin
Important — Redis naming pattern: The Redis secret and service names contain a "double redis" because Kubeblocks follows the pattern <cluster>-<component>-account-default for secrets and <cluster>-<component>-<component> for ClusterIP services:
- Cluster name:
${{ defaults.app_name }}-redis - Component name:
redis(defined incomponentSpecs[].name) - Secret:
${{ defaults.app_name }}-redis+-redis-account-default=...-redis-redis-account-default - ClusterIP Service:
${{ defaults.app_name }}-redis+-redis+-redis=...-redis-redis-redis - Service FQDN:
${{ defaults.app_name }}-redis-redis-redis.${{ SEALOS_NAMESPACE }}.svc
This same pattern applies to other databases (e.g., PostgreSQL service is <app>-pg-postgresql, MySQL is <app>-mysql-mysql).
Keys Included in Secrets
PostgreSQL/MySQL/MongoDB/Kafka secrets usually contain:
endpoint: Full connection endpoint (host:port)host: Hostnamepassword: Passwordport: Port numberusername: Username
Redis default account secrets usually contain:
usernamepassword
Redis and MongoDB account secrets can appear after the first component pods report progress. During deployment validation, wait for the KubeBlocks Cluster to reach Running/Ready and then poll for the expected account Secret before judging application initialization. For Redis, the Sentinel component may become ready before the primary redis component and ${{ defaults.app_name }}-redis-redis-account-default; validate the final Redis Service FQDN ${{ defaults.app_name }}-redis-redis-redis.${{ SEALOS_NAMESPACE }}.svc.cluster.local plus business registration/login behavior. For MongoDB, poll ${{ defaults.app_name }}-mongo-mongodb-account-root or the matching ${{ defaults.app_name }}-mongodb-mongodb-account-root name when the Cluster uses the mongodb suffix.
Environment Variable Configuration Examples
env:
# PostgreSQL
- name: POSTGRES_ENDPOINT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: endpoint
- name: POSTGRES_HOST
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: host
- name: POSTGRES_PORT
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: port
- name: POSTGRES_USERNAME
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-pg-conn-credential
key: password
# MySQL
- name: MYSQL_HOST
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-mysql-conn-credential
key: host
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-mysql-conn-credential
key: password
# MongoDB (credential secret + fixed Service FQDN)
- name: MONGO_USERNAME
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-mongo-mongodb-account-root
key: username
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-mongo-mongodb-account-root
key: password
- name: MONGODB_URI
value: mongodb://$(MONGO_USERNAME):$(MONGO_PASSWORD)@${{ defaults.app_name }}-mongo-mongodb.${{ SEALOS_NAMESPACE }}.svc:27017/app?authSource=admin
# Redis
- name: REDIS_HOST
value: ${{ defaults.app_name }}-redis-redis-redis.${{ SEALOS_NAMESPACE }}.svc.cluster.local
- name: REDIS_PORT
value: "6379"
- name: REDIS_USERNAME
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-redis-redis-account-default
key: username
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ${{ defaults.app_name }}-redis-redis-account-default
key: passwordTemplate Guide
!FastGPT Page
Using FastGPT as an example, this guide explains how to create a template with code. This example assumes you already have some understanding of Kubernetes resource files and only explains parameters specific to templates. The template file is mainly divided into two parts.
!structure!structure
Part 1: Metadata CR
apiVersion: app.sealos.io/v1
kind: Template
metadata:
name: fastgpt
spec:
title: 'FastGpt'
url: 'https://fastgpt.run/'
gitRepo: 'https://github.com/labring/FastGPT'
author: 'sealos'
description: 'Fast GPT allows you to use your own openai API KEY to quickly call the openai interface, currently integrating Gpt35, Gpt4 and embedding. You can build your own knowledge base.'
readme: 'https://raw.githubusercontent.com/labring-actions/templates/kb-0.9/template/fastgpt/README.md'
icon: 'https://avatars.githubusercontent.com/u/50446880?s=96&v=4'
templateType: inline
defaults:
app_name:
type: string
value: fastgpt-${{ random(8) }}
app_host:
type: string
value: ${{ random(8) }}
inputs:
root_passowrd:
description: 'Set root password. login: username: root, password: root_passowrd'
type: string
default: ${{ SEALOS_NAMESPACE }}
required: true
openai_key:
description: 'openai api key'
type: string
default: ''
required: true
database_type:
description: 'type of database'
required: false
type: choice
default: 'mysql'
options:
- sqlite
- mysqlAs shown in the code, the Metadata CR is a standard Kubernetes custom resource type. The table below lists the fields that need to be filled in.
| Field | Description |
|---|---|
templateType | inline indicates this is an inline template where all yaml files are integrated into a single file. |
defaults | Defines default values to be populated into the resource files, such as the application name (app_name), domain (app_host), etc. |
inputs | Defines some parameters that users need when deploying the application, such as email, API-KEY, etc. If there are none, this can be omitted. |
Explanation: Variables
Any characters surrounded by ${{ }} are variables. Variables are divided into the following types:
1. SEALOS_ all-uppercase predefined system built-in variables, such as ${{ SEALOS_NAMESPACE }}, are variables provided by Sealos itself. For all currently supported system variables, see System Variables. 2. functions() functions, such as ${{ random(8) }}, are functions provided by Sealos itself. For all currently supported functions, see Functions. 3. defaults is a list of names and values that are resolved when populating random values. 4. inputs are filled in by the user when deploying the application, and the inputs will be rendered as a frontend form.
Explanation: Defaults
spec.defaults is a mapping of names, types, and values that are populated as default values when the template is parsed.
| Name | Description |
|---|---|
type | string or number indicates the type of the variable. The only difference is that string types will be quoted during rendering, while number types will not. |
value | The value of the variable. If the value is a function, it will be rendered. |
In the current version implementation, `defaults` must have an `app_name` field, and it must contain a `${{ random(8) }}` random number as the unique name for the application, otherwise an error will occur.
Explanation: Inputs
spec.defaults is a defined object mapping that is parsed and displayed as form inputs for user interaction.
| Name | Description |
|---|---|
description | The description of the input. It will be rendered as the input placeholder. |
default | The default value of the input. |
required | Whether the input is required. |
type | Must be one of string \ |
options? | When the type is choice, sets the list of available options. |
if? | A JavaScript expression that controls whether this option is enabled. |
The inputs shown above will be rendered as form inputs on the frontend:
<table> <tr> <td> Template </td> <td> View </td> </tr> <tr> <td width="50%">
inputs:
root_passowrd:
description: 'Set root password. login: username: root, password: root_passowrd'
type: string
default: ''
required: true
openai_key:
description: 'openai api key'
type: string
default: ''
required: true</td> <td>
!render inputs
</td> </tr> </table>
Usage of the if Parameter in Inputs
- The form supports dynamic rendering, controlling whether a form item is enabled through the
ifparameter. - The content of the parameter is an expression; do not wrap it with
${{ }}. - When the expression result is
true, the parameter is rendered; when the result isfalse, the parameter is not rendered, and the correspondingrequiredparameter will not take effect either. - If the result is not a boolean value, it will be coerced to a boolean value.
Built-in System Variables and Functions
The Sealos template engine uses the ${{ expression }} syntax to parse expressions.
expressionis a valid JavaScript expression.- Built-in Sealos variables and functions can be accessed within the expression.
Sealos provides some built-in system variables and functions for convenient use in templates.
Built-in System Variables
${{ SEALOS_NAMESPACE }}The namespace where the Sealos user deploys.${{ SEALOS_CLOUD_DOMAIN }}The domain suffix of the Sealos cluster.${{ SEALOS_CERT_SECRET_NAME }}The secret name used by Sealos to store TLS certificates.${{ SEALOS_SERVICE_ACCOUNT }}The SA of the Sealos user.
Built-in System Functions
${{ random(length) }}Generates a random string of the specifiedlength.${{ base64(expression) }}Encodes the expression result into base64 format.${{ base64('hello world') }}will returnaGVsbG8gd29ybGQ=.- You can also reference variables
${{ base64(inputs.secret) }}.
Note
>
You cannot use${{ inputs.enabled }}to determine whether an option is enabled, becauseenabledis a string, not a boolean value.
>
You need to use ${{ inputs.enabled === 'true' }} to determine whether an option is enabled.Conditional Rendering
The Sealos template engine supports conditional rendering using ${{ if(expression) }}, ${{ elif(expression) }}, ${{ else() }}, and ${{ endif() }}.
- Conditional rendering is a special type of built-in system function.
- Conditional statements must occupy a line by themselves and cannot be on the same line as other content.
- Conditional expressions must return a boolean value (
trueorfalse); otherwise, they will be coerced to a boolean value. - Cross-YAML-list rendering is allowed.
Template CRdoes not support conditional rendering.
Example:
${{ if(inputs.enableIngress === 'true') }}
apiVersion: networking.k8s.io/v1
kind: Ingress
...
${{ endif() }}This code means that the Ingress resource will only be rendered when inputs.enableIngress is true.
<details>
<summary>A relatively complete example</summary>
apiVersion: app.sealos.io/v1
kind: Template
metadata:
name: chatgpt-next-web
spec:
title: 'chatgpt-next-web'
url: 'https://github.com/Yidadaa/ChatGPT-Next-Web'
gitRepo: 'https://github.com/Yidadaa/ChatGPT-Next-Web'
author: 'Sealos'
description: 'One-click free deployment of your cross-platform private ChatGPT application'
readme: 'https://raw.githubusercontent.com/labring-actions/templates/kb-0.9/template/chatgpt-next-web/README.md'
icon: 'https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/docs/images/icon.svg'
templateType: inline
categories:
- ai
defaults:
app_host:
type: string
value: ${{ random(8) }}
app_name:
type: string
value: chatgpt-next-web-${{ random(8) }}
inputs:
DOMAIN:
description: "Custom domain, need to CNAME to: ${{ defaults.app_host + '.' + SEALOS_CLOUD_DOMAIN }}"
type: string
default: ''
required: false
OPENAI_API_KEY:
description: 'This is your API key obtained from the OpenAI account page. Separate multiple keys with commas to enable random rotation among these keys'
type: string
default: ''
required: true
HIDE_USER_API_KEY:
description: 'Check this if you do not want users to fill in their own API Key'
type: boolean
default: 'false'
required: false
AUZRE_ENABLE:
description: 'Enable Azure'
type: boolean
default: 'false'
required: false
AZURE_API_KEY:
description: 'Azure Key'
type: string
default: ''
required: true
if: inputs.AUZRE_ENABLE === 'true'
AZURE_URL:
description: 'Azure Deployment URL'
type: string
default: 'https://{azure-resource-url}/openai/deployments/{deploy-name}'
required: true
if: inputs.AUZRE_ENABLE === 'true'
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${{ defaults.app_name }}
annotations:
originImageName: yidadaa/chatgpt-next-web:v2.12.4
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
app: ${{ defaults.app_name }}
spec:
replicas: 1
revisionHistoryLimit: 1
selector:
matchLabels:
app: ${{ defaults.app_name }}
template:
metadata:
labels:
app: ${{ defaults.app_name }}
spec:
automountServiceAccountToken: false
containers:
- name: ${{ defaults.app_name }}
image: yidadaa/chatgpt-next-web:v2.12.4
env:
- name: OPENAI_API_KEY
value: ${{ inputs.OPENAI_API_KEY }}
${{ if(inputs.HIDE_USER_API_KEY === 'true') }}
- name: HIDE_USER_API_KEY
value: '1'
${{ endif() }}
${{ if(inputs.AUZRE_ENABLE === 'true') }}
- name: AZURE_URL
value: ${{ inputs.AZURE_URL }}
- name: AZURE_API_KEY
value: ${{ inputs.AZURE_API_KEY }}
${{ endif() }}
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: ${{ defaults.app_name }}
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
spec:
ports:
- port: 3000
selector:
app: ${{ defaults.app_name }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${{ defaults.app_name }}
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
cloud.sealos.io/app-deploy-manager-domain: ${{ defaults.app_host }}
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/proxy-body-size: 32m
nginx.ingress.kubernetes.io/server-snippet: |
client_header_buffer_size 64k;
large_client_header_buffers 4 128k;
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
nginx.ingress.kubernetes.io/backend-protocol: HTTP
nginx.ingress.kubernetes.io/client-body-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-buffer-size: 64k
nginx.ingress.kubernetes.io/proxy-send-timeout: '300'
nginx.ingress.kubernetes.io/proxy-read-timeout: '300'
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($request_uri ~* \.(js|css|gif|jpe?g|png)) {
expires 30d;
add_header Cache-Control "public";
}
spec:
rules:
- host: ${{ inputs.DOMAIN || defaults.app_host + '.' + SEALOS_CLOUD_DOMAIN }}
http:
paths:
- pathType: Prefix
path: /()(.*)
backend:
service:
name: ${{ defaults.app_name }}
port:
number: 3000
tls:
- hosts:
- ${{ inputs.DOMAIN || defaults.app_host + '.' + SEALOS_CLOUD_DOMAIN }}
secretName: "${{ inputs.DOMAIN ? defaults.app_name + '-cert' : SEALOS_CERT_SECRET_NAME }}"
---
${{ if(inputs.DOMAIN !== '') }}
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: ${{ defaults.app_name }}
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@sealos.io
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
serviceType: ClusterIP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ${{ defaults.app_name }}-cert
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
spec:
secretName: ${{ defaults.app_name }}-cert
dnsNames:
- ${{ inputs.DOMAIN }}
issuerRef:
name: ${{ defaults.app_name }}
kind: Issuer
${{ endif() }}</details>
Part 2: Application Resource Files
This part typically consists of a set of resource types:
- Application
Deployment,StatefulSet,Service - External Access
Ingress - Underlying Dependencies
Database,Object Storage
Each resource can be repeated any number of times, in no particular order.
Explanation: Application
An application is a list composed of multiple Deployment, StatefulSet, Service and/or Job, Secret, ConfigMap, Custom Resource.
<details>
<summary>Code</summary>
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${{ defaults.app_name }}
annotations:
originImageName: c121914yu/fast-gpt:v1.0.0
deploy.cloud.sealos.io/minReplicas: '1'
deploy.cloud.sealos.io/maxReplicas: '1'
labels:
cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}
app: ${{ defaults.app_name }}
spec:
replicas: 1
revisionHistoryLimit: 1
selector:
matchLabels:
app: ${{ defaults.app_name }}
template:
metadata:
labels:
app: ${{ defaults.app_name }}
spec:
apiVersion: apps/v1 kind: Deployment metadata: name: ${{ defaults.app_name }} annotations: originImageName: c121914yu/fast-gpt:v1.0.0 deploy.cloud.sealos.io/minReplicas: '1' deploy.cloud.sealos.io/maxReplicas: '1' labels: cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }} app: ${{ defaults.app_name }} spec: replicas: 1 revisionHistoryLimit: 1 selector: matchLabels: app: ${{ defaults.app_name }} template: metadata: labels: app: ${{ defaults.app_name }} spec: containers:
- name: ${{ defaults.app_name }}
image: c121914yu/fast-gpt:v1.0.0 env:
- name: MONGO_PASSWORD
valueFrom: secretKeyRef: name: ${{ defaults.app_name }}-mongodb-account-root key: password
- name: PG_PASSWORD
valueFrom: secretKeyRef: name: ${{ defaults.app_name }}-pg-conn-credential key: password
- name: ONEAPI_URL
value: ${{ defaults.app_name }}-key.${{ SEALOS_NAMESPACE }}.svc.cluster.local:3000/v1
- name: ONEAPI_KEY
value: sk-xxxxxx
- name: DB_MAX_LINK
value: 5
- name: MY_MAIL
value: ${{ inputs.mail }}
- name: MAILE_CODE
value: ${{ inputs.mail_code }}
- name: TOKEN_KEY
value: fastgpttokenkey
- name: ROOT_KEY
value: rootkey
- name: MONGODB_URI
value: >- mongodb://root:$(MONGO_PASSWORD)@${{ defaults.app_name }}-mongo-mongo.${{ SEALOS_NAMESPACE }}.svc:27017
- name: MONGODB_NAME
value: fastgpt
- name: PG_HOST
value: ${{ defaults.app_name }}-pg-pg.${{ SEALOS_NAMESPACE }}.svc
- name: PG_USER
value: postgres
- name: PG_PORT
value: '5432'
- name: PG_DB_NAME
value: postgres resources: requests: cpu: 100m memory: 102Mi limits: cpu: 1000m memory: 1024Mi command: [] args: [] ports:
- containerPort: 3000
imagePullPolicy: IfNotPresent volumeMounts: [] volumes: []
--- apiVersion: v1 kind: Service metadata: name: ${{ defaults.app_name }} labels: cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }} spec: ports:
- port: 3000
selector: app: ${{ defaults.app_name }}
</details>
The frequently changed fields are as follows:
| Field | Description |
| :--------------------------- | :----------------------------------------------------------- |
| `metadata.annotations`<br/>`metadata.labels` | Change to match Launchpad's requirements, such as `originImageName`, `minReplicas`, `maxReplicas`. |
| `spec.containers[].image` | Change to your Docker image. |
| `spec.containers[].env` | Configure environment variables for the container. |
| `spec.containers[].ports.containerPort` | Change to the port corresponding to your Docker image. |
| `${{ defaults.app_name }}` | You can use `${{ defaults.xxxx }}`\|`${{ inputs.xxxx }}` variables to set parameters defined in the `Template CR`.
### Explanation: `External Access`
If the application needs to be accessed externally, you need to add the following code:
<details>
<summary>Code</summary>
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ${{ defaults.app_name }} labels: cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }} cloud.sealos.io/app-deploy-manager-domain: ${{ defaults.app_host }} annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/proxy-body-size: 32m nginx.ingress.kubernetes.io/server-snippet: | client_header_buffer_size 64k; large_client_header_buffers 4 128k; nginx.ingress.kubernetes.io/ssl-redirect: 'true' nginx.ingress.kubernetes.io/backend-protocol: HTTP nginx.ingress.kubernetes.io/client-body-buffer-size: 64k nginx.ingress.kubernetes.io/proxy-buffer-size: 64k nginx.ingress.kubernetes.io/proxy-send-timeout: '300' nginx.ingress.kubernetes.io/proxy-read-timeout: '300' nginx.ingress.kubernetes.io/configuration-snippet: | if ($request_uri ~* \.(js|css|gif|jpe?g|png)) { expires 30d; add_header Cache-Control "public"; } spec: rules:
- host: ${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}
http: paths:
- pathType: Prefix
path: /()(.*) backend: service: name: ${{ defaults.app_name }} port: number: 3000 tls:
- hosts:
- ${{ defaults.app_host }}.${{ SEALOS_CLOUD_DOMAIN }}
secretName: ${{ SEALOS_CERT_SECRET_NAME }}
</details>
Please note that for security purposes, the `host` field needs to be set randomly. You can set `${{ random(8) }}` as `defaults.app_host`, and then use `${{ defaults.app_host }}`.
### Explanation: `NodePort Type Service`
If the application needs to expose services through a NodePort type Service, the following naming convention must be followed: the Service name should have `-nodeport` as a suffix. For example:
<details>
<summary>Demo</summary>
apiVersion: v1 kind: Service metadata: name: ${{ defaults.app_name }}-nodeport labels: cloud.sealos.io/app-deploy-manager: ${{ defaults.app_name }}-nodeport spec: type: NodePort ports:
- protocol: UDP
port: 21116 targetPort: 21116 name: "rendezvous-udp"
- protocol: TCP
port: 21116 targetPort: 21116 name: "rendezvous-tcp"
- protocol: TCP
port: 21117 targetPort: 21117 name: "relay"
- protocol: TCP
port: 21115 targetPort: 21115 name: "heartbeat" selector: app: ${{ defaults.app_name }}
</details>
This naming convention (`${{ defaults.app_name }}-nodeport`) is required for NodePort type Services so that the system can correctly identify and handle this type of resource.
### Explanation: `Underlying Dependencies`
Almost all applications require underlying dependencies, such as `database`, `cache`, `object storage`, etc. You can add the following code to deploy some of the underlying dependencies we provide:
#### `Database`
We use [`kubeblocks`](https://kubeblocks.io/) to provide database resource support. You can directly use the following code to deploy databases:
<details>
<summary>MongoDB</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: finalizers:
- cluster.kubeblocks.io/finalizer
labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mongo kb.io/database: mongodb-8.0.4 app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo clusterdefinition.kubeblocks.io/name: mongodb annotations: {} name: ${{ defaults.app_name }}-mongo generation: 1 spec: affinity: podAntiAffinity: Preferred tenancy: SharedNode topologyKeys:
- kubernetes.io/hostname
componentSpecs:
- componentDef: mongodb
name: mongodb replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi serviceAccountName: ${{ defaults.app_name }}-mongo serviceVersion: 8.0.4 volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi storageClassName: openebs-backup terminationPolicy: Delete
--- apiVersion: v1 kind: ServiceAccount metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mongo app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mongo
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mongo app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mongo rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mongo app.kubernetes.io/instance: ${{ defaults.app_name }}-mongo app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mongo roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${{ defaults.app_name }}-mongo subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-mongo
</details>
<details>
<summary>PostgreSQL</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: finalizers:
- cluster.kubeblocks.io/finalizer
labels: kb.io/database: postgresql-16.4.0 clusterdefinition.kubeblocks.io/name: postgresql clusterversion.kubeblocks.io/name: postgresql-16.4.0 annotations: {} name: ${{ defaults.app_name }}-pg spec: affinity: nodeLabels: {} podAntiAffinity: Preferred tenancy: SharedNode topologyKeys: [] clusterDefinitionRef: postgresql clusterVersionRef: postgresql-16.4.0 componentSpecs:
- componentDefRef: postgresql
disableExporter: true enabledLogs:
- running
name: postgresql replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi serviceAccountName: ${{ defaults.app_name }}-pg switchPolicy: type: Noop volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi storageClassName: openebs-backup terminationPolicy: Delete
--- apiVersion: v1 kind: ServiceAccount metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-pg app.kubernetes.io/instance: ${{ defaults.app_name }}-pg app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-pg
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-pg app.kubernetes.io/instance: ${{ defaults.app_name }}-pg app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-pg rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-pg app.kubernetes.io/instance: ${{ defaults.app_name }}-pg app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-pg roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${{ defaults.app_name }}-pg subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-pg
</details>
<details>
<summary>MySQL</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: finalizers:
- cluster.kubeblocks.io/finalizer
labels: kb.io/database: ac-mysql-8.0.30-1 clusterdefinition.kubeblocks.io/name: apecloud-mysql clusterversion.kubeblocks.io/name: ac-mysql-8.0.30-1 annotations: {} name: ${{ defaults.app_name }}-mysql spec: affinity: nodeLabels: {} podAntiAffinity: Preferred tenancy: SharedNode topologyKeys: [] clusterDefinitionRef: apecloud-mysql clusterVersionRef: ac-mysql-8.0.30-1 componentSpecs:
- componentDefRef: mysql
monitor: true name: mysql noCreatePDB: false replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi serviceAccountName: ${{ defaults.app_name }}-mysql switchPolicy: type: Noop volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi storageClassName: openebs-backup terminationPolicy: Delete tolerations: [] --- apiVersion: v1 kind: ServiceAccount metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mysql app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mysql
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mysql app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mysql rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-mysql app.kubernetes.io/instance: ${{ defaults.app_name }}-mysql app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-mysql roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${{ defaults.app_name }}-mysql subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-mysql
</details>
<details>
<summary>Redis</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: finalizers:
- cluster.kubeblocks.io/finalizer
labels: kb.io/database: redis-7.2.7 app.kubernetes.io/instance: ${{ defaults.app_name }}-redis app.kubernetes.io/version: 7.2.7 clusterversion.kubeblocks.io/name: redis-7.2.7 clusterdefinition.kubeblocks.io/name: redis annotations: {} name: ${{ defaults.app_name }}-redis spec: affinity: podAntiAffinity: Preferred tenancy: SharedNode topologyKeys:
- kubernetes.io/hostname
clusterDefinitionRef: redis componentSpecs:
- componentDef: redis-7
name: redis replicas: 1 enabledLogs:
- running
env:
- name: CUSTOM_SENTINEL_MASTER_NAME
resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi serviceAccountName: ${{ defaults.app_name }}-redis serviceVersion: 7.2.7 switchPolicy: type: Noop volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi storageClassName: openebs-backup
- componentDef: redis-sentinel-7
name: redis-sentinel replicas: 1 resources: limits: cpu: 100m memory: 100Mi requests: cpu: 10m memory: 10Mi serviceAccountName: ${{ defaults.app_name }}-redis serviceVersion: 7.2.7 volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi terminationPolicy: Delete topology: replication --- apiVersion: v1 kind: ServiceAccount metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-redis app.kubernetes.io/instance: ${{ defaults.app_name }}-redis app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-redis
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-redis app.kubernetes.io/instance: ${{ defaults.app_name }}-redis app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-redis rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-redis app.kubernetes.io/instance: ${{ defaults.app_name }}-redis app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-redis roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${{ defaults.app_name }}-redis subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-redis
</details>
<details>
<summary>Kafka</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: labels: kb.io/database: kafka-3.3.2 clusterdefinition.kubeblocks.io/name: kafka clusterversion.kubeblocks.io/name: kafka-3.3.2 name: ${{ defaults.app_name }}-kafka annotations: kubeblocks.io/extra-env: >- {"KB_KAFKA_ENABLE_SASL":"false","KB_KAFKA_BROKER_HEAP":"-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64","KB_KAFKA_CONTROLLER_HEAP":"-XshowSettings:vm -XX:MaxRAMPercentage=100 -Ddepth=64","KB_KAFKA_PUBLIC_ACCESS":"false"} spec: terminationPolicy: Delete componentSpecs:
- name: broker
componentDef: kafka-broker tls: false replicas: 1 affinity: podAntiAffinity: Preferred topologyKeys:
- kubernetes.io/hostname
tenancy: SharedNode tolerations:
- key: kb-data
operator: Equal value: 'true' effect: NoSchedule resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- name: metadata
spec: storageClassName: null accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- name: controller
componentDefRef: controller componentDef: kafka-controller tls: false replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi volumeClaimTemplates:
- name: metadata
spec: storageClassName: null accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- name: metrics-exp
componentDef: kafka-exporter replicas: 1 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi
</details>
<details>
<summary>Milvus</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: labels: clusterdefinition.kubeblocks.io/name: milvus name: ${{ defaults.app_name }}-milvus spec: affinity: podAntiAffinity: Preferred tenancy: SharedNode clusterDefinitionRef: milvus clusterVersionRef: milvus-2.2.4 terminationPolicy: Delete componentSpecs:
- componentDefRef: milvus
name: milvus disableExporter: true serviceAccountName: ${{ defaults.app_name }}-milvus replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- componentDefRef: etcd
name: etcd disableExporter: true serviceAccountName: ${{ defaults.app_name }}-milvus replicas: 1 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- componentDefRef: minio
name: minio disableExporter: true serviceAccountName: ${{ defaults.app_name }}-milvus replicas: 1 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi resources: cpu: '0' memory: '0' storage: size: '0'
</details>
<details>
<summary>ClickHouse</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: labels: kb.io/database: clickhouse-24.8.3 clusterdefinition.kubeblocks.io/name: clickhouse clusterversion.kubeblocks.io/name: clickhouse-24.8.3 name: ${{ defaults.app_name }}-clickhouse spec: affinity: podAntiAffinity: Preferred tenancy: SharedNode topologyKeys:
- cluster
clusterDefinitionRef: clickhouse componentSpecs:
- componentDefRef: zookeeper
disableExporter: true name: zookeeper replicas: 1 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi serviceAccountName: ${{ defaults.app_name }}-clickhouse volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- componentDefRef: clickhouse
disableExporter: true name: clickhouse replicas: 1 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 100m memory: 102Mi serviceAccountName: ${{ defaults.app_name }}-clickhouse volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi
- componentDefRef: ch-keeper
disableExporter: true name: ch-keeper replicas: 1 resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi serviceAccountName: ${{ defaults.app_name }}-clickhouse volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi terminationPolicy: Delete
</details>
<details>
<summary>Weaviate</summary>
apiVersion: apps.kubeblocks.io/v1alpha1 kind: Cluster metadata: finalizers:
- cluster.kubeblocks.io/finalizer
labels: clusterdefinition.kubeblocks.io/name: weaviate clusterversion.kubeblocks.io/name: weaviate-1.18.0 name: ${{ defaults.app_name }}-weaviate spec: affinity: podAntiAffinity: Preferred tenancy: SharedNode clusterDefinitionRef: weaviate clusterVersionRef: weaviate-1.18.0 componentSpecs:
- componentDefRef: weaviate
monitor: false name: weaviate noCreatePDB: false replicas: 1 resources: limits: cpu: "1" memory: 1Gi requests: cpu: "1" memory: 1Gi rsmTransformPolicy: ToSts serviceAccountName: ${{ defaults.app_name }}-weaviate volumeClaimTemplates:
- name: data
spec: accessModes:
- ReadWriteOnce
resources: requests: storage: 1Gi monitor: {} resources: cpu: "0" memory: "0" storage: size: "0" terminationPolicy: Delete --- apiVersion: v1 kind: ServiceAccount metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-weaviate app.kubernetes.io/instance: ${{ defaults.app_name }}-weaviate app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-weaviate
--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-weaviate app.kubernetes.io/instance: ${{ defaults.app_name }}-weaviate app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-weaviate rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: sealos-db-provider-cr: ${{ defaults.app_name }}-weaviate app.kubernetes.io/instance: ${{ defaults.app_name }}-weaviate app.kubernetes.io/managed-by: kbcli name: ${{ defaults.app_name }}-weaviate roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ${{ defaults.app_name }}-weaviate subjects:
- kind: ServiceAccount
name: ${{ defaults.app_name }}-weaviate
</details>
When deploying a database, you only need to focus on the resources used by the database:
| Field | Description |
| ----------- | --------------- |
| `replicas` | Number of instances |
| `resources` | Allocate CPU and memory |
| `storage` | Volume size |
#### How to Access the Application's Database
The database username/password is set as a secret for future use. It can be added to environment variables through the following code. Once added, you can read the MONGODB password in the container via $(MONGO_PASSWORD).
... spec: containers:
- name: ${{ defaults.app_name }}
... env:
- name: MONGO_PASSWORD
valueFrom: secretKeyRef: name: ${{ defaults.app_name }}-mongodb-account-root key: password ...
#### `Object Storage`
Use a boolean input when object storage has only enabled and disabled states:
inputs: enable_s3_storage: description: "Enable S3 object storage" type: boolean default: "false" required: false
--- ${{ if(inputs.enable_s3_storage === 'true') }} apiVersion: objectstorage.sealos.io/v1 kind: ObjectStorageBucket metadata: name: ${{ defaults.app_name }} spec: policy: private ${{ endif() }}
The policy has three types: private (private bucket, not open), publicRead (shared bucket, open for public read), and publicReadwrite (shared bucket, open for public read and write).
#### How to Access the Application's Bucket
The bucket access key and access address are stored in a secret. They can be added to environment variables through the following code.
... spec: containers:
- name: ${{ defaults.app_name }}
... env:
- name: ACCESS_KEY
valueFrom: secretKeyRef: name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }} key: accessKey
- name: SECRET_KEY
valueFrom: secretKeyRef: name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }} key: secretKey
- name: EXTERNAL_ENDPOINT
valueFrom: secretKeyRef: name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }} key: external
- name: INTERNAL_ENDPOINT
valueFrom: secretKeyRef: name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }} key: internal
- name: BUCKET_NAME
valueFrom: secretKeyRef: name: object-storage-key-${{ SEALOS_SERVICE_ACCOUNT }}-${{ defaults.app_name }} key: bucket ...
### Note: System Underlying Processing Logic
#### Template Instance
To facilitate user management and modification of applications deployed through templates, the system deploys an `app.sealos.io/v1, Kind=Instance` CRD as the application instance during actual deployment.
The CRD itself will be fully migrated according to the template format and fields of `app.sealos.io/v1, Kind=Template`, with the following processing logic:
1. Replace all variables/functions in the template with definite values
2. Change the kind from `Template` to `Instance`
3. Apply this template instance to the user's namespace
#### Resource Labels
For all resources deployed through the template marketplace, including system resources such as `deploy`, `service` as well as custom resources such as `app`, `kb database`, etc., a unified label will be added to all of them: `cloud.sealos.io/deploy-on-sealos: $app_name`.
Where `app_name` is the name of the application deployed by the user, which by default ends with a random number, such as `fastgpt-zu1n048s`.
For application `StatefulSet` resources that define `spec.volumeClaimTemplates`, also set `cloud.sealos.io/deploy-on-sealos: ${{ defaults.app_name }}` on every `volumeClaimTemplates[].metadata.labels`. Preserve component labels such as `app` so legacy component-level PVC cleanup remains possible.
## Part 3: `Rendering Process Details`
The Sealos template engine follows a specific order during the rendering process to ensure that variables and conditional statements can be correctly parsed.
<details>
<summary>The following flowchart details the entire rendering process</summary>
graph TB subgraph father[ ] style A fill:#FFD700,stroke:#333,stroke-width:2px,color:#000 style B fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style C fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style D fill:#FF6347,stroke:#333,stroke-width:2px,color:#FFF style E fill:#FFD700,stroke:#333,stroke-width:2px,color:#000 style F fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style G fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style H fill:#FF6347,stroke:#333,stroke-width:2px,color:#FFF style I fill:#FFD700,stroke:#333,stroke-width:2px,color:#000 style J fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style K fill:#1E90FF,stroke:#333,stroke-width:2px,color:#FFF style L fill:#FF6347,stroke:#333,stroke-width:2px,color:#FFF
subgraph sub1[ ] A[1. Get Template CR file] --> B[Parse defaults] B -- Only built-in system variables and functions allowed --> C[Parse inputs] C -- Built-in system variables, functions, and defaults allowed --> D[Template CR parsing complete] end subgraph sub2[ ] E[2. Parse application resource files] --> F[Conditional rendering] F -- Selectively render code blocks based on expression truth values --> G[Variable resolution] G -- Replace placeholders using defaults, inputs, and built-in variables/functions --> H[Application resource file parsing complete] end subgraph sub3[ ] I[3. Render Form and YAML file list] --> J[Form conditional rendering] J -- Selectively render form items based on expression truth values --> K[Form changes trigger re-rendering] K -- Re-execute step 2 --> L[Rendering complete] end
sub1 --> sub2 sub2 --> sub3 end
</details>
- Parse Template CR
- First, the system reads the `Template CR` file.
- Then, it parses the `spec.defaults` field, which defines the default values for the template.
- In the `defaults` field, only predefined [built-in system variables](#built-in-system-variables) and [built-in system functions](#built-in-system-functions) are allowed.
- Next, it parses the `spec.inputs` field, which defines the parameters that users need to fill in.
- In the `inputs` field, in addition to built-in system variables and functions, variables defined in `defaults` can also be referenced.
- Parse application resource files
- At this stage, expressions can reference `built-in system variables`, `built-in system functions`, as well as `defaults` and `inputs`.
- First, [conditional rendering](#conditional-rendering) is performed, selectively rendering code blocks based on the truth values of conditional expressions.
- Then, [variable resolution](#built-in-system-variables) is performed, replacing placeholders in resource files using `defaults`, `inputs`, and built-in variables/functions.
- Render Form and YAML file list
- Finally, the system renders the Form based on the parsed `inputs` field, where users can fill in custom parameters.
- At this stage, expressions can reference `built-in system variables`, `built-in system functions`, as well as `defaults` and `inputs`.
- When the `Form` changes, it triggers re-rendering of the `YAML` file list.
> Note:
>
> When users enter information in input fields, the `Template CR` content will not be re-parsed,
> meaning the original expressions will not be re-evaluated, such as `value: ${{ random(8) }}`.
Frappe / Bench Conversion Notes
Use this reference when a project mentions Frappe, ERPNext, HRMS, bench, frappe/bench, frappe_docker, or commands such as bench new-site, bench start, bench set-mariadb-host, or bench set-redis-*.
Runtime Model
Frappe repositories are often app modules, not standalone web servers. Treat docker/ compose files and init scripts as authoritative startup evidence.
Two common models:
- Development bench model:
frappe/bench:*runsbench init,bench get-app,bench new-site,bench install-app, thenbench start. - Prebuilt production image model:
frappe/erpnext:*orghcr.io/frappe/<app>:<tag>already contains apps and normally uses separate web, frontend, websocket, worker, scheduler processes.
Do not mix these models blindly. If a repo's docker/docker-compose.yml uses frappe/bench:latest plus a mounted init.sh, do not assume a detected prebuilt GHCR image has the same startup contract.
Template Rules
- Database services from Compose (
mariadb,mysql,redis) should follow the Sealos database strategy. Redis must use the KubeBlocks RedisClusterand its generated Secret (${{ defaults.app_name }}-redis-redis-account-default) unless the user explicitly asks for raw containers. - If using a prebuilt Frappe image with mounted
sitesandlogsPVCs, set podsecurityContext.fsGroup: 1000so thefrappeuser can write mounted volumes. - Init containers that run
bench init,bench new-site,bench migrate, or app install steps must set explicit resources from the Sealos ladder. Do not rely on namespace defaults;64Mimemory is too small. Use at least: - light config init:
limits.memory: 256Mi,requests.memory: 25Mi bench new-site/ app install / migrate:limits.memory: 2048Mi,requests.memory: 204Mi- choose matching ladder CPU values and derive CPU requests the same way, for example
limits.cpu: 500m→requests.cpu: 50m - Bootstrap scripts must be idempotent and recover from partial initialization:
- create
sites/common_site_config.jsonif a fresh PVC hides the image's bundled file - use
bench new-site --forcefor first-site creation when the database may contain residue from a previous failed attempt - check both filesystem site state and database readiness; a
sites/<site>directory alone does not prove the Frappe database is valid - Prefer the source docs' site name when it is part of the documented flow (
hrms.localhostin development docs). For public Sealos access, ensure the frontend's site-name/header behavior matches the generated Ingress host or an intentionally configured default site.
Administrator Credentials
Frappe reserves Administrator and Guest as built-in account names. Use admin as the recommended default login name when a template asks the deployer to choose an administrator username.
bench new-site --admin-password sets the built-in Administrator user's password. It does not change the login name.
When the user explicitly asks to enter ERPNext/Frappe administrator credentials:
- Declare
admin_usernameandadmin_passwordin the Template CRspec.inputs. - Pass both values to init scripts as direct env values. Keep MariaDB, Redis, and PostgreSQL connection credentials on KubeBlocks secrets.
- Run
bench new-sitewith the deployer-provided password for the built-inAdministratoruser. - After the site exists, set the built-in user's
User.usernameto the deployer-provided login name through Frappe ORM orbench execute. - Enable
allow_login_using_user_namefor the site so username login works. - Clear Frappe cache after changing user/login settings.
- Write the init ready marker only after the username, login setting, cache clear, migrations, and app installs have completed.
Example bootstrap sequence:
bench new-site "$SITE_NAME" \
--mariadb-root-username "$DB_ROOT_USER" \
--mariadb-root-password "$DB_ROOT_PASSWORD" \
--admin-password "$ADMIN_PASSWORD" \
--force
bench --site "$SITE_NAME" execute frappe.client.set_value \
--kwargs "{'doctype':'User','name':'Administrator','fieldname':'username','value':'${ADMIN_USERNAME}'}"
bench --site "$SITE_NAME" set-config allow_login_using_user_name 1
bench --site "$SITE_NAME" clear-cache
touch "$READY_MARKER"Failure Signatures
Permission deniedwritingsites/apps.txt: mounted PVC ownership is wrong; addfsGroup: 1000or a volume-permission init.OOMKilled/ exit137increate-site: init resources are too small.pymysql.err.ProgrammingError: ('DocType', 'Patch Log'): a prior failed init left a site directory or database residue; reset the failed site/database or rerunbench new-site --force.- Ingress returns
no healthy upstream: usually not an Ingress problem; check Service endpoints, Pod readiness, and init container state first. - Login with the configured username fails while
Administratorworks: the template only set theAdministratorpassword. SetUser.username, enable username login, clear cache, and recreate the ready marker after those steps succeed.
#!/usr/bin/env python3
"""Context builder layer for consistency checks."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence, Tuple
from check_consistency_models import ScanContext, Violation
from check_consistency_parser import build_context
@dataclass(frozen=True)
class ContextBuilder:
skill_path: Path
references_dir: Path
include_paths: Sequence[str]
def build(self) -> Tuple[ScanContext, list[Violation]]:
return build_context(
skill_path=self.skill_path,
references_dir=self.references_dir,
include_paths=self.include_paths,
)
#!/usr/bin/env python3
"""Violation construction helpers for consistency rules."""
from __future__ import annotations
from typing import Any, Callable, List, Mapping, Optional
from check_consistency_models import ScanContext, Violation, YamlDocument
from check_consistency_parser import find_line
from check_consistency_helpers_workload import is_managed_app_workload_document
def add_doc_violation(
violations: List[Violation],
*,
rule_id: str,
doc: YamlDocument,
pattern: str,
message: str,
default_pattern: Optional[str] = None,
) -> None:
default_line = find_line(doc, default_pattern) if default_pattern else None
line = find_line(doc, pattern, default=default_line)
violations.append(
Violation(
rule_id=rule_id,
path=doc.path,
line=line,
message=message,
)
)
def check_managed_workload_setting(
context: ScanContext,
*,
rule_id: str,
value_extractor: Callable[[Mapping[str, Any]], Any],
expected: Any,
value_pattern: str,
fallback_pattern: str,
missing_message: str,
mismatch_message: str,
) -> List[Violation]:
violations: List[Violation] = []
for doc in context.yaml_documents:
if doc.skip_checks or not is_managed_app_workload_document(doc):
continue
if not isinstance(doc.data, dict):
continue
value = value_extractor(doc.data)
if value == expected:
continue
add_doc_violation(
violations,
rule_id=rule_id,
doc=doc,
pattern=value_pattern,
default_pattern=fallback_pattern,
message=mismatch_message if value is not None else missing_message,
)
return violations