Reflang — Reference Language

Reflang is the declarative substitution language used in DeployAlly templates. It lets you reference values from multiple contexts (user inputs, secrets, deploy context, system metadata), apply functions to generate or transform values, and perform indirection via nested substitution.

Basic syntax: ${namespace.path}. Anything between ${...} is resolved at deploy time.


Namespaces

Each namespace exposes a different context:

Namespace Context When available Example
input Inputs declared in inputs.{required,optional,advanced} After the wizard ${input.WEB_HOSTNAME}
secrets Resolved secrets (via provider chain) After secrets: is processed ${secrets.DB_PASSWORD}
self Fields of the template itself (image, port, etc.) Always ${self.image}
instance Instance metadata (uid, slug, namespace) Always ${instance.uid}
context Deploy context (profile, dry_run, source) Always ${context.profile}
tenant Active tenant (multi-tenant deploys) When tenant scope exists ${tenant.id}
system Host attributes (memory, CPUs, hostname) After preflight ${system.memory_mb}
env Host environment variables Always ${env.HOME}
needs Values exported by assets satisfied via needs After auto-provision ${needs.main_db.host}
asset Shared asset declared by class After registry lookup ${asset.mysql.connection_string}

How `needs` and `asset` relate

  • needs references the asset as the consumer declared in the needs list. Useful when the template declares multiple options (e.g., needs: [{ class: mysql }, { class: postgresql }]) and you want to reference whichever was chosen without caring which.
  • asset references a specific asset by class, a direct lookup in the registry. Useful when the template assumes a specific class.

Functions

Functions can appear in any Reflang expression. Arguments can be literal strings, numbers, or other ${...} expressions.

Generators

Function Returns Description
random.hex(N) string N hex characters (0-9a-f)
random.alnum(N) string N alphanumeric characters (a-zA-Z0-9)
random.password(N) string N password-safe characters
random.uuid() string UUID v4

Example:

secrets:
  API_TOKEN: ${random.hex(32)}
  ADMIN_PASSWORD: ${random.password(24)}
  INSTANCE_ID: ${random.uuid()}

Logic and Control

Function Returns Description
if(cond, a, b) any Returns a if cond is truthy, else b
default(v, fallback) any Returns v if defined and non-empty, else fallback
exists(v) bool true if the expression resolves to a non-empty value

Example:

environment:
  LOG_LEVEL: ${if(input.DEBUG, "debug", "info")}
  DB_HOST: ${default(input.DB_HOST, "localhost")}
  HAS_SMTP: ${exists(input.SMTP_HOST)}

Strings

Function Returns Description
concat(...) string Concatenates the arguments
lower(s) string Lowercase
upper(s) string Uppercase
replace(s, from, to) string Replaces from with to in s

Example:

environment:
  DB_NAME: ${concat("app_", lower(input.TENANT_SLUG))}
  SAFE_HOST: ${replace(input.HOST, ".", "_")}

Nested Substitution

Reflang resolves nested references from inside out. Useful when the field name depends on another value:

environment:
  # SLUG_UPPER = "PROD", so this reads input.PROD_DOMAIN
  TARGET_DOMAIN: ${input.${SLUG_UPPER}_DOMAIN}

The engine resolves ${SLUG_UPPER} first (producing, for example, PROD), then reads input.PROD_DOMAIN.


Provider Chain on Secrets

Secrets have a provider chain tried in order. The first provider returning a value wins.

secrets:
  DB_PASSWORD:
    provider: input        # tries input.DB_PASSWORD
    fallback:
      provider: env        # if empty, tries env.DB_PASSWORD
      fallback:
        generate:          # last fallback: generate
          type: alnum
          length: 32

Shorthand (single provider + generate as automatic fallback):

secrets:
  DB_PASSWORD:
    provider: input
    generate:
      type: hex
      length: 32

Provider types:

Provider Source
input Template input (implicit key = secret name)
env Host environment variable
generate Generates a new value using a function (hex, alnum, password, uuid)
asset Reads from the asset declared in needs

When the provider is input or env without :KEY, the engine uses the secret name itself as the key.


Conditional Environment

Environment variables can be emitted conditionally:

environment:
  - name: SMTP_HOST
    value: ${input.SMTP_HOST}
    when: ${exists(input.SMTP_HOST)}
  - name: SMTP_PORT
    value: ${default(input.SMTP_PORT, "587")}
    when: ${exists(input.SMTP_HOST)}

Variables whose when expression evaluates to false are omitted from the container — they don't become empty strings.


Practical Examples

Application Template with Database

species: my-app
archetype: application

image: my-org/app:1.0

inputs:
  required:
    - WEB_HOSTNAME
    - ADMIN_EMAIL
  advanced:
    - DEBUG  # default: false

secrets:
  DB_PASSWORD:
    provider: input
    generate:
      type: alnum
      length: 32
  SESSION_KEY:
    generate:
      type: hex
      length: 64

needs:
  - class: mysql
    options:
      - { species: mysql, variant: mysql-8.4, default: true }
      - { species: mariadb, variant: latest }

environment:
  WEB_HOSTNAME: ${input.WEB_HOSTNAME}
  ADMIN_EMAIL: ${input.ADMIN_EMAIL}
  DB_HOST: ${needs.main_db.host}
  DB_USER: ${needs.main_db.user}
  DB_PASSWORD: ${secrets.DB_PASSWORD}
  DB_NAME: ${concat("app_", lower(instance.uid))}
  SESSION_KEY: ${secrets.SESSION_KEY}
  LOG_LEVEL: ${if(input.DEBUG, "debug", "info")}

routes:
  - hostname: ${input.WEB_HOSTNAME}
    port: 8080

Asset with per_instance Dispatcher

species: my-db
archetype: asset

image: my-org/db:latest

provides:
  asset_class: my-db
  functions:
    create-database:
      params:
        - name: db_name
          required: true
        - name: db_user
          required: true
          default: ${concat("user_", lower(input.db_name))}
      exec:
        command: ["create-db", "${input.db_name}", "${input.db_user}"]

storage:
  - kind: volume
    name: data
    path: /var/lib/db

Config File via Storage

storage:
  - kind: config_file
    path: /etc/app/config.yaml
    content: |
      app:
        port: ${self.port}
        log_level: ${default(input.LOG_LEVEL, "info")}
      database:
        host: ${needs.main_db.host}
        password: ${secrets.DB_PASSWORD}

The engine renders the content (Reflang substitution applied), saves it to /opt/deployally/<uid>/config/config.yaml, and single-file bind-mounts it into the container.


Quick Reference

Namespaces

Namespace Available in
input wizard inputs
secrets resolved secrets
self fields of the template itself
instance instance metadata
context deploy context
tenant active tenant
system host resources
env host env vars
needs assets via needs
asset asset by class

Functions

random.hex(N)
random.alnum(N)
random.password(N)
random.uuid()
if(cond, a, b)
default(v, fallback)
exists(v)
concat(...)
lower(s)
upper(s)
replace(s, from, to)

Provider Chain (Secrets)

input → env → generate → asset

First provider with a non-empty value wins.


  • Templates — overview of the template system and the six archetypes
  • Taxonomy — kingdom / family / species
  • Manifests — local storage of resolved configurations
  • Ecology — relationships between services and auto-provision

Operation

By Borlot.com.br on 05/06/2026