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
needsreferences 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.assetreferences 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: 32Shorthand (single provider + generate as automatic fallback):
secrets:
DB_PASSWORD:
provider: input
generate:
type: hex
length: 32Provider 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: 8080Asset 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/dbConfig 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 → assetFirst provider with a non-empty value wins.
Navigation
Related Concepts
- 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
- CLI Commands — how to deploy using Reflang templates
- API Integration — programmatic use (restricted)