Template file
Every field of a template YAML file, its expressions, limits and the problems nebula template validate reports.
A template is one YAML document with schemaVersion: 1. It names a set of services (images, public Git repositories or database engines), their processes, volumes and variables, and the inputs a person answers at install time. Unknown fields are rejected at every level. To install or share one, follow Install and share templates.
Example
The example installs n8n with its own Postgres database, an install-time hostname and exposure, and a generated encryption key.
schemaVersion: 1
name: n8n
description: Workflow automation with a visual editor and 400+ integrations.
category: automation
tags: [workflows]
links:
website: https://n8n.io
docs: https://docs.n8n.io
source: https://github.com/n8n-io/n8n
inputs:
- key: HOSTNAME
label: Hostname
description: Where n8n is served, for example n8n.example.com.
required: true
- key: EXPOSURE
label: Exposure
default: public
options: [public, tailnet]
services:
- name: n8n
image: docker.n8n.io/n8nio/n8n:1.80.0
processes:
- name: web
kind: http
port: 5678
replicas: 1
memory: {request: 256Mi, limit: 1Gi}
volumes:
- name: data
mountPath: /home/node/.n8n
size: 1Gi
domain:
hostname: ${{ inputs.HOSTNAME }}
exposure: ${{ inputs.EXPOSURE }}
variables:
N8N_ENCRYPTION_KEY: ${{ secret(32) }}
DB_POSTGRESDB_HOST: ${{ host(db) }}
DB_POSTGRESDB_PASSWORD: ${{ db.POSTGRES_PASSWORD }}
WEBHOOK_URL: https://${{ inputs.HOSTNAME }}/
- name: db
database: {engine: postgres}The database's password never appears in the file. ${{ db.POSTGRES_PASSWORD }} stays a reference and resolves from the database's generated password when a release is frozen, and ${{ host(db) }} becomes svc-db.
Validate a file
nebula template validate checks one or more files without a database or a running control plane. It reads the files only.
nebula template validate templates/n8n.yamlA problem prints to standard output as <file>:<line>: <path>: <message>. The <line>: part is omitted when the problem is not tied to a line. A file that has no problems prints a summary:
templates/n8n.yaml: ok (n8n, 2 services)The command exits with status 1 when any file has a problem. It also requires the file name, without .yaml or .yml, to equal the template's slug, and reports a slug problem when it does not. That check runs only on a file that otherwise parses. A file in the community repository must pass it.
The community repository runs the same check with the published image:
docker run --rm -v "$PWD:/w:ro" ghcr.io/nebulactrl/nebula:latest template validate /w/templates/*.yamlSections
| Section | Describes |
|---|---|
| Template fields | Name, category, links and the top-level lists |
| Inputs | Questions asked at install time |
| Services | The source of each service and its variables |
| Processes | What each service runs |
| Volumes | Persistent storage of a process |
| Domain | The hostname of an http process |
| Expressions | ${{ ... }} in variables and domains |
Template fields
schemaVersion
Type integer · Required · Constraints must be 1
The only schema version.
name
Type string · Required · Constraints 1 to 60 bytes after trimming
The display name.
slug
Type string · Default the name in lowercase, with each run of characters other than a-z and 0-9 replaced by one -, trimmed, cut to 40 characters · Constraints ^[a-z][a-z0-9-]{0,39}$
The template's identifier. A name that starts with a digit or has no ASCII letters or digits derives an invalid slug, so set slug for it.
description
Type string · Required · Constraints 1 to 280 bytes
category
Type string · Required · Constraints one of analytics, automation, cms, communication, database, development, media, monitoring, productivity, security, storage, other
tags
Type array of strings · Default none · Constraints at most 8; each matches ^[a-z0-9][a-z0-9-]{0,23}$
links
Type table of website, docs and source · Default none · Constraints each an absolute https:// URL
readme
Type string of Markdown · Default none · Constraints at most 16 KiB
Shown on the template's page in the console. The console renders headings, paragraphs, ordered and unordered lists, inline code, bold text and links that start with https://. Nothing else becomes markup.
inputs
Type array · Default none · Constraints at most 20 entries
See Inputs.
services
Type array · Required · Constraints 1 to 20 entries
See Services.
A template cannot set file mounts, a release command, a deploy-after list, a storage class or process metrics. Exporting a project leaves them out, and warns about file mounts, a release command and a deploy-after list.
Inputs
Each entry of inputs is one question. The install form shows them in order.
key
Type string · Required · Constraints ^[A-Z][A-Z0-9_]{0,63}$; unique among inputs
The name a variable or domain uses to read the answer: ${{ inputs.KEY }}.
label
Type string · Required · Constraints 1 to 60 bytes after trimming
description
Type string · Default none · Constraints at most 280 bytes
default
Type string · Default "" · Constraints one of options when options is set
The answer used when the person gives none.
required
Type boolean · Default false
A required input whose answer is empty is a problem at install time. An empty answer to a required input is a problem even when the input has a default, because an explicit answer replaces the default.
secret
Type boolean · Default false
The install form masks the field, and every variable built from the input is stored as a secret.
options
Type array of strings · Default none
The answer must be one of these. The form shows a segmented control for up to four options and a select for more.
Services
Each entry of services names its source with exactly one of image, git or database.
name
Type string · Required · Constraints ^[a-z][a-z0-9]*(-[a-z0-9]+)*$, at most 30 characters; unique in the template; inputs is reserved
The service's name in the template. Its slug in the project is derived from it, and a person can rename it at install time.
image
Type string · Constraints an explicit registry host, a repository and a tag or digest
An image reference such as docker.io/library/nginx:1.27. nginx:1.27 is refused because it has no registry host, and latest is never implied. An explicit :latest is accepted.
git
Type table of url, dockerfile and context
A service built from a public repository. The first deploy builds the environment's tracked branch, and the template cannot name a branch.
git.url: string, required,https://only, with no embedded user name or password.git.dockerfile: string, defaultDockerfile.git.context: string, default..
database
Type table of engine and version
A database service.
database.engine: one ofpostgres,valkey,mysql,mongodb.database.version: a major version the control plane runs, defaulting to the newest. The accepted majors are Postgres 18 and 16, Valkey 9 and 7, MySQL 8, and MongoDB 7.
processes
Type array · Default one http process named web on port 8080 with 1 replica, 100m to 500m CPU and 128Mi to 512Mi memory · Constraints at most 10; image and git services only
See Processes. A database service declares no processes.
variables
Type table of string to string · Default none · Constraints keys match ^[A-Z_][A-Z0-9_]*$; at most 100; image and git services only
The service's variables. Values may contain expressions. A scalar such as 8080 is read as the text "8080".
Processes
Each entry of a service's processes is one process.
name
Type string · Required · Constraints ^[a-z][a-z0-9-]{0,29}$; unique within the service
kind
Type string · Required · Constraints http, worker, cron or job
A proxy process cannot be templated.
port
Type integer · Constraints 1 to 65535; required for http, optional for worker, omitted for cron and job
command
Type array of strings · Default the image's ENTRYPOINT and CMD
Replaces both the image's ENTRYPOINT and its CMD.
args
Type array of strings · Default the image's CMD
Replaces only the image's CMD.
replicas
Type integer · Default 1 for an http or worker process that sets none of cpu.request, cpu.limit and memory.request, otherwise 0 · Constraints a whole number that is not negative
Set replicas on every process that sets a resource.
schedule
Type string · Constraints required for cron; forbidden for every other kind
A five-field cron expression, evaluated in UTC. Descriptors such as @daily and TZ= prefixes are refused.
runAsUser
Type integer · Default a non-root user is enforced · Constraints at least 0
0 runs the process as root.
cpu and memory
Type table of request and limit · Default the control plane's defaults · Constraints Kubernetes quantities; either key may be left out
Quantities use Kubernetes notation, such as 100m or 256Mi. The validator does not compare request with limit. An install is refused when a request exceeds its limit.
startupPath, readinessPath and livenessPath
Type string · Default none · Constraints an absolute HTTP path; http only
volumes
Type array · Default none · Constraints at most 5 per process
See Volumes.
domain
Type table · Default none · Constraints http only
See Domain.
Volumes
Each entry of a process's volumes is one persistent volume.
name
Type string · Required · Constraints ^[a-z][a-z0-9-]{0,29}$; unique within the process
mountPath
Type string · Required · Constraints an absolute path
size
Type Kubernetes quantity · Required · Constraints greater than zero, at most 1Ti
Domain
A domain is rendered at install time, so both fields may hold ${{ inputs.KEY }}. No other expression is allowed in them.
hostname
Type string · Default empty · Constraints after rendering, empty or a bare hostname
An empty hostname creates no domain. A hostname has no scheme, port or path.
exposure
Type string · Default public · Constraints after rendering, public or tailnet
An empty rendered value means public.
Expressions
An expression is written ${{ EXPR }}, and spaces around EXPR are optional. Text may surround any number of expressions, as in https://${{ inputs.HOSTNAME }}/. Expressions are allowed in variable values and in domain.hostname and domain.exposure. An unterminated ${{, an empty expression, a ${{ inside another and an expression the parser does not know are problems.
inputs.KEY
Resolved at install time · Constraints KEY names a declared input
The install answer, else the input's default, else an empty string.
secret() and secret(N)
Resolved at install time · Constraints N is 16 to 128; the default is 32
N random characters from A-Za-z0-9. Each occurrence draws its own value, and each environment draws its own.
host(NAME)
Resolved at install time · Constraints NAME names a service of the template
The in-cluster host name of that service, svc-<slug>, where <slug> is the service's final slug. A service may use host() of itself.
NAME.KEY
Resolved when a release is frozen · Constraints NAME names a service of the template other than the service itself; KEY matches ^[A-Z_][A-Z0-9_]*$
A reference that stays ${{ <slug>.KEY }} and resolves when a release is frozen. For a database service, KEY is one of the variables its engine generates. For an image or git service, KEY is one of that service's own variables.
| Engine | Keys |
|---|---|
postgres | DATABASE_URL, DATABASE_READ_URL, POSTGRES_DB, POSTGRES_PASSWORD, POSTGRES_USER |
valkey | REDIS_URL, REDIS_PASSWORD |
mysql | MYSQL_URL, MYSQL_DATABASE, MYSQL_ROOT_PASSWORD |
mongodb | MONGODB_URL, MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD |
Rules
- Renaming. Renaming a service at install time changes its final slug. Every
host(NAME)andNAME.KEYthat names it follows the rename. - Empty inputs. A variable that uses an
inputs.KEYwhose answer is empty is not set at all, even when it also holds other text, so the application keeps its own default. With an optionalHOSTNAME,BASE_URL: https://${{ inputs.HOSTNAME }}/is set when a hostname is given and left out when it is not. - Secrets. A variable is stored as a secret when its value contains
secret(, uses an input withsecret: true, or uses aNAME.KEYthat names a database service. Every other variable is stored in plain text.
A variable that a person types into a service understands secret(N), host(slug) and slug.KEY, but not inputs.KEY. See Variable expressions.
Limits
| Limit | Value |
|---|---|
| File size | 256 KiB |
name, label | 60 bytes |
description | 280 bytes |
slug | 40 characters |
tags | 8, each at most 24 characters |
readme | 16 KiB |
inputs | 20 |
Input key | 64 characters |
services | 1 to 20 |
Service name | 30 characters |
processes per service | 10 |
volumes per process | 5 |
variables per service | 100 |
Volume size | at most 1Ti |
secret(N) | 16 to 128, default 32 |
Problems
The parser collects every problem in one pass. A problem is {path, line, message}. path locates the field, such as services[1].processes[0].port. line is the 1-based source line, and 0 when the problem is not tied to one, as for an install answer. message says what is wrong and what is allowed.
A file that is not valid YAML, has a second document or is not a mapping at the top level stops at one problem with no path. The API reports problems as a 422 whose detail lists path (line N): message. The preview endpoint answers 200 with a problems list instead.
nebula.toml project file
Every table and key of the nebula.toml that declares a project's services, databases and connections, with limits, wiring rules and the problems the parser reports.
Compose mapping
How each Docker Compose file, service and key becomes a NebulaCtrl service, database or variable, what blocks an import, and the limits of one import.