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.5

Multi-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}/data

Routes

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: letsencrypt

Reflang: 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: prod

Next 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.
By Borlot.com.br on 05/06/2026