Templates
A template is the declarative specification of a service — it describes the image, routes, storage, dependencies, secrets, configuration, and lifecycle in a single YAML file. Every DeployAlly deploy starts from a template.
Authors write the archetype first; everything else is a typed decoration on top of it. Defaults come from the archetype, and the template only overrides what's specific to the service.
Structure
A template is organized into blocks. The mandatory ones are few; the rest are optional and either generated or derived by the archetype when omitted.
spec: "3"
identity: # who the service is
slug: memos
name: Memos
archetype: application
app_version: "0.22.0"
family: notes
image: # which image and which variants
base: neosmemo/memos
variants:
- { id: stable, tag: "0.22.0", default: true }
- { id: latest, tag: stable }
routes: # HTTP / TCP exposure
- id: web
kind: http
port: 5230
host: ${input.WEB_HOSTNAME}
tls: { cert_resolver: letsencrypt }
storage: # volumes, binds, config files
- id: data
kind: volume
mount: /var/opt/memos
secrets: {} # generated/persisted credentials
config: # user inputs
required:
WEB_HOSTNAME:
type: hostname
help: "Public hostname (e.g. memos.example.com)."
runtime: # env, network, restart policy
env:
MEMOS_MODE: ${input.MEMOS_MODE}
healthcheck: # how to measure health
mode: external_http
path: /healthz
actions: # on-demand operations
- { slug: restart, type: docker_op, operation: restart }Required Blocks
| Block | Purpose |
|---|---|
spec |
Schema version. Always "3". |
identity |
Slug, name, archetype, family, and version. |
image |
Base image and variants (supported tags). |
config |
Inputs the user provides at deploy time. |
healthcheck |
How DeployAlly measures whether the service is alive. |
The routes and storage blocks are required for most archetypes (a web app needs a route, a database needs a volume), but the worker archetype accepts both empty.
Optional Blocks
| Block | Purpose |
|---|---|
secrets |
Locally generated and persisted credentials (passwords, hex tokens). |
needs |
Dependencies on other services (see Ecology). |
provides |
What this service offers to others (functions, exports). Used by assets. |
runtime |
Env vars, restart policy, network mode. |
resources |
Memory/CPU limits and requests. |
actions |
On-demand operations (restart, dump, etc.). |
lifecycle |
post_deploy and pre_decommission hooks. |
profiles |
Declarative variants (development, production, etc.). |
Archetypes
identity.archetype is a closed enum — only six values are valid. Each archetype carries its own defaults and exposure/scope rules.
| Archetype | Typical use | Default healthcheck | Default scope |
|---|---|---|---|
application |
Single app with an HTTP route (CMS, panel, web tool) | external_http |
shared |
asset |
Shared infrastructure service (database, cache, queue) | external_tcp |
singleton |
static |
Static site served by nginx/caddy | external_http on / |
shared |
worker |
Headless process without HTTP (queues, jobs, cron) | stable_state (settle 30s) |
shared |
multi_component_saas |
App with multiple containers (web + worker + …) | per component | shared |
network_appliance |
Network proxy/ingress (traefik) | exit or external_tcp |
singleton |
Details on each archetype and how they relate are in Ecology.
Image: Single or Multi-Variant
The image block accepts two shapes:
Single ref — the service has one fixed image:
image: traefik:v3.5Multi-variant — the service offers several versions (LTS, latest, alpine, etc.) and the user picks one at deploy time:
image:
base: mariadb
variants:
- { id: "11.4", tag: "11.4", default: true, description: "11.4 LTS (recommended)" }
- { id: "10.11", tag: "10.11", description: "10.11 LTS (legacy)" }
- { id: latest, tag: latest, description: "Rolling release" }The variant flagged with default: true wins when none is specified. The CLI accepts --variant <id> to pick one explicitly.
Storage: Kinds
The storage block declares where the service persists data. Supported kinds:
| Kind | Description | Example use |
|---|---|---|
volume |
Managed bind mount, automatically resolved by the engine under /data/ (see below) |
Database, uploads |
bind |
Bind mount of a fixed system path (e.g. /var/run/docker.sock, /etc/...) |
Docker socket, live host configs |
config_file |
Single file rendered from a template and bind-mounted, inside the same resolved base as volume |
nginx.conf, redis.conf |
tmpfs |
In-memory volume, discarded on stop | Ephemeral caches, sockets |
Resolving `kind: volume` (client >= 0.45.0)
Templates don't declare the host path — only id and mount (the mount point
inside the container). The client resolves the bind automatically from the
template's taxonomy:
identity.archetype: application→/data/apps/{instance_uid}/{id}- Any other archetype (
asset,infrastructure,network_appliance, etc.) →/data/assets/{species}/{instance_uid}/{id}
The directory is created by the client at deploy time. If the volume declares
owner: (uid/gid), the client attempts a chown — a chown failure only
produces a warning, it does not abort the deploy.
kind: config_file follows the same resolved base as volume: the rendered file
lands at {base}/config/{filename} (e.g. /data/assets/mysql/{instance_uid}/config/custom.cnf).
Nothing is written to disk when the deploy runs with --dry-run.
kind: bind remains reserved for system paths — the validator rejects any
host_path declared under /opt/ (reserved for CCS tools: runner, deployally,
backupally, etc.).
Named Docker volumes: current templates no longer create named volumes. Older deploys (pre client 0.45.0) that still use named volumes keep working until the next redeploy — the client does not migrate the data automatically. Migration is manual: copy the data out of the named volume into the resolved
/data/...path before redeploying with the updated template.
Example combining a volume and a config file:
storage:
- id: data
kind: volume
mount: /var/lib/mysql
owner:
uid: 999
gid: 999
- id: my-cnf
kind: config_file
mount: /etc/mysql/conf.d/custom.cnf
content: |
[mysqld]
max_connections = ${input.MAX_CONNECTIONS}Before (named volume era, deprecated) / After (resolved under /data/):
# BEFORE — named Docker volume declared directly in compose (don't use in new templates)
volumes:
- mysql_data:/var/lib/mysql
# AFTER — kind: volume, path resolved by the engine
storage:
- id: data
kind: volume
mount: /var/lib/mysql
# archetype: asset, species: mysql -> resolves to:
# /data/assets/mysql/{instance_uid}/dataRoutes
Routes declare how the service is reached. kind: http makes DeployAlly emit Traefik labels with TLS via Let's Encrypt; kind: tcp exposes an L4 port. Assets usually have routes: [] (they're only reachable by other containers on a dedicated network).
routes:
- id: web
kind: http
port: 80
host: ${input.WEB_HOSTNAME}
tls:
cert_resolver: letsencryptReflang: Typed References
Every dynamic value in a template uses the ${namespace.path} syntax. The namespaces:
| Namespace | Source | Example |
|---|---|---|
${input.X} |
Input provided by the user | ${input.WEB_HOSTNAME} |
${secrets.X} |
Generated/persisted secret | ${secrets.WP_DB_PASSWORD} |
${self.X} |
Self-reference to the container | ${self.container.name} |
${instance.X} |
Instance metadata | ${instance.uid} |
${needs.X} |
Output of a dependent asset | ${needs.database.host} |
${asset.X.Y} |
Asset introspection by class | ${asset.database.host} |
${tenant.X} |
Multi-tenant context | ${tenant.slug} |
${context.X} |
Host state (containers, ports) | ${context.port_in_use} |
${system.X} |
System info | ${system.ip}, ${system.memory_mb} |
${env.X} |
Host environment variable | ${env.DOCKER_HOST} |
Resolution is typed and checked at preflight — a ${needs.database.host} fails if the asset doesn't declare that export.
Profiles
profiles allows declarative variants of the same template. The user picks a profile at deploy time (--profile development) and the template applies overrides:
profiles:
development:
description: "Reduced memory + dev mode."
overrides:
resources.memory.limit: 128M
config.optional.MEMOS_MODE.default: dev
production:
description: "Standard sizing for production."
overrides:
resources.memory.limit: 256M
config.optional.MEMOS_MODE.default: prodNext Steps
- Ecology — The six archetypes in detail and how they relate.
- Taxonomy — How kingdom/family/species organize the catalog.
- Definitions and Instances — How a template becomes a running container.
- Manifests — Where the instance persists state and secrets.