cubeship
Templates

The template file

Every block a template may contain, and the defaults for the ones it leaves out

A template is one YAML file, template.yaml inside its directory in the shared repository — see Publishing. This is the worked example the validator's own tests run against — Umami, one app and one database:

version: 1
name: Umami
minCubeship: "0.6.0"
project: umami            # suggested slug; the installer may change it

inputs:                   # what the template cannot know
  - key: domain
    type: domain
    label: Where the dashboard answers
  - key: appSecret
    type: secret
    label: The app's session secret
    generate: 32          # the instance generates it and shows it once

databases:
  - key: db                # template-local; what references name
    name: umami-db         # suggested instance name, renamable at install
    engine: postgres
    version: "18"
    database: umami        # the password is generated, never written here

apps:
  - key: web
    name: web
    image: ghcr.io/umami-software/umami
    tag: postgresql-v2
    port: 3000
    health: /api/heartbeat
    domains:
      - host: ${input.domain}
    attach:
      - database: db      # contributes DATABASE_URL and friends
        prefix: ""
    limits: { cpu: 1, memory: 1Gi }
    scale: 1
    env:
      APP_SECRET: ${input.appSecret}
      DATABASE_TYPE: postgresql

Every key not shown below is optional and takes the default listed. Unknown keys are refused — see schema.unknown-key.

Top level

KeyDefaultMeaning
version—Always 1. This is the file format, not a template release.
name—Display name in the catalog. Required in templates/.
minCubeshipnoneA semver range the installing instance must satisfy, e.g. ">=0.6.0".
project—The suggested project slug. The installer may rename it.
environmentproductionThe suggested environment inside that project.
inputs[]What the installer is asked. See Inputs.
databases[]The managed databases this template needs.
stores[]The managed object stores this template needs.
apps—At least one. What actually runs.

Keys and names are two different things

Every input, database, store and app has a key — lowercase or mixed case, letters/digits/dash/underscore, starting with a letter — which never leaves the file. It is what a reference like ${db.db.host} names, and what attach.database or attach.store names, and it is how one part of the template points at another. See References.

A database, store or app also has a name, which defaults to its key and is what appears on the instance. The installer may change any name, and for databases and stores it sometimes has to: their names are unique across the whole instance, so two installs of one template would collide on the first one otherwise.

Databases

KeyDefaultMeaning
key—Required. What attach and ${db.<key>.…} name.
namethe keyThe suggested instance name.
engine—Required. One of postgres, mysql, mariadb, redis, mongodb.
versionthe newest offeredPinning it is advised but not required.
extensionsnonePostgres only. A list of extension names the database is created with.
usernamethe engine's defaultRedis is always default; MySQL and MariaDB refuse root.
databasenoneThe database name inside the engine. Ignored on engines with none, like Redis.
exposenot exposednull (the default) keeps it internal only; 0 asks for a port picked for you; a number asks for that port.
limitsnoneSee Limits.

There is no password field. A database's password is generated by the instance and handed to an app through an attachment — a template that could write one down would be a template that could steal one.

Extensions

A Postgres database can be created with extensions:

databases:
  - key: db
    engine: postgres
    version: "16"
    extensions:
      - pgvector
      - vectorchord

Names, not packages. Cubeship accepts only the ones it has reviewed, and each one picks a fixed statement — and, for two of them, a specific image pinned by digest. Nothing in a template reaches a Dockerfile, a package manager or a SQL string.

The name you write is Cubeship's, and it is not always the name the extension has in SQL: pgvector creates the extension vector, and vectorchord creates vchord. Your migrations use the SQL name.

The list is normalized: duplicates go, it is sorted, and what an extension requires is added — vectorchord brings pgvector, earthdistance brings cube. So [vectorchord] and [pgvector, vectorchord] are the same template.

Most of them are PostgreSQL's own contrib modules, which every image Cubeship runs already carries: pg_trgm, pgcrypto, hstore, citext, ltree, unaccent, uuid-ossp, btree_gin, btree_gist, bloom, cube, earthdistance, fuzzystrmatch, intarray, isn, seg, tablefunc, tsm_system_rows, dblink, postgres_fdw, pg_prewarm, pgstattuple, pg_buffercache and pg_stat_statements. Two are not — pgvector and vectorchord — and those run an image of their own. GET /datastores/engines on an instance is the list it will actually accept, per version.

A template with extensions needs a minCubeship that excludes releases before them. An older instance installs the template happily and creates the database without them, which is an app that comes up and fails on its first query.

A later release of a template that changes a database's extensions does not change a database that already exists — the preview says so, the same way it does for an engine or a version. Extensions can be installed on the database itself afterwards, from its own page.

Stores

KeyDefaultMeaning
key—Required. What attach names.
namethe keyThe suggested instance name.
versionnoneThe SeaweedFS version, unvalidated here — the instance decides what a missing one means.
buckets[]The buckets to create inside it.
limitsnoneSee Limits.

A template cannot declare a linked S3 endpoint — that would be somebody's credential. A template that wants one asks for an input of type store instead, and the installer picks a store that already exists on the instance.

Apps

Source

An app is exactly one of an image or a build — never neither, never both:

KeyDefaultMeaning
imagenoneA public image, with no tag in the string — tag is its own field.
tagnoneThe tag for image. Refused on a build: a built app is tagged by its deploy, not by you.
repononeAn http, https or git URL, with the branch in ref, never after a #.
refthe repo's default branchThe branch or tag to build.
buildnonedockerfile or railpack. Required once repo is set.
dockerfileDockerfileOnly meaningful with build: dockerfile.

Network

KeyDefaultMeaning
portnoneThe port the app listens on. Also the default for a domain that names none.
healthoffA path starting with /, no ? or #, at most 255 characters. Strongly advised — see advice.no-health.
domains[]See below.

Each entry under domains is { host, port }. host must come from exactly one input of type domain — ${input.<key>} and nothing else, never a literal hostname. port defaults to the app's own port, and beneath that to 8080.

Attach

Each entry under attach wires one database or one store into the app's environment — exactly one of database or store per entry, naming its key:

KeyDefaultMeaning
database—A database's key, exclusive with store.
store—A store's key, or the key of an input of type store, exclusive with database.
bucket—Required with store: which bucket the app gets.
prefix""Upper case, ending in _, like ANALYTICS_. Distinguishes two attachments that would otherwise write the same variable names.

A database attachment contributes <PREFIX><STEM>_URL, _HOST, _PORT, _USER, _PASSWORD, and _NAME unless the engine has no named databases — STEM is DATABASE for postgres, mysql and mariadb, REDIS for redis, MONGO for mongodb. A store attachment contributes <PREFIX>S3_ENDPOINT, _REGION, _BUCKET, _ACCESS_KEY_ID, _SECRET_ACCESS_KEY, _PATH_STYLE.

Environment

env is a map of names to values, merged into the app on top of whatever attach already wrote — an app's own env wins on a collision, which is how you point at a different target without detaching anything. A value may itself be a reference, like ${input.appSecret} above.

Limits and scaling

KeyDefaultMeaning
limits.cpunoneCores, at least 0.01.
limits.memorynoneA size — 512Mi, 2Gi, 1500M — at least 6Mi.
scalenoneHow many copies, at least 1.
spreadfalseSpread copies across machines rather than pack them onto one.
autoscale.min1The floor, at least 1 and at most max.
autoscale.max—Required with autoscale. At most 100.
autoscale.cpu—Required with autoscale. A target above 0, where 100 means one whole core.

limits and stores/databases' own limits share the same shape.

Volumes

apps:
  - key: broker
    image: rabbitmq
    volumes:
      - path: /var/lib/rabbitmq
KeyDefaultMeaning
volumes[].path—An absolute path inside the container whose contents survive deploys and restarts. Not /, not under /proc, /sys or /dev.

A volume is for software that keeps its state in files — a queue, a search index. An app with a volume runs as one copy on one machine, so scale, spread and autoscale are refused beside it, and each deploy stops the old container before starting the new one: the app is briefly unavailable. A template with a volume needs minCubeship: "0.7.0" or later, so an older instance refuses it instead of installing it without its data surviving. Uninstalling keeps volume data unless whoever uninstalls says otherwise.

TCP ports

inputs:
  - key: sshPort
    type: number
    label: The port SSH answers on
    default: 2222
    min: 1024
    max: 65535
apps:
  - key: git
    image: gitea/gitea
    tcp:
      - port: 22
        host: ${input.sshPort}
KeyDefaultMeaning
tcp[].port—The port the app listens on inside its container, 1-65535.
tcp[].hostpickedWhere the instance publishes it: 1024-65535, or ${input.<key>} naming a number input. Left out, the instance picks a free one from 17000-17999.

A TCP port is for a protocol that is not HTTP — SSH into a Git server, a game server, a broker. It is published on the instance's own address with no TLS and no proxy in front of it, so the app's own authentication is what protects it. An app with a TCP port runs as one copy, so scale, spread and autoscale are refused beside it, and each deploy stops the old container before starting the new one.

Name the host port through an input rather than as a number: a port another installation already holds is refused, and the person installing is the one who knows which is free. A template with a TCP port needs minCubeship: "0.7.2" or later.

On this page