Configuration Templates
Use Tera templates in configuration fields to reference values from other daemons, settings, and runtime state.
Why Templates?
When daemons depend on each other, you often need to pass connection details between them. Without templates, you have to hardcode ports and URLs:
[daemons.redis]
run = "redis-server --port $PORT"
port = 6379
[daemons.api]
run = "server --port 3000 --redis-port 6379" # hardcoded!
env = { DATABASE_URL = "redis://localhost:6379/0" } # hardcoded!
depends = ["redis"]This breaks when redis uses port = { expect = [6379], bump = 10 } and the port gets auto-bumped. Templates solve this by resolving values at start time.
Basic Usage
The supported fields listed below can use {{ ... }} template expressions:
[daemons.redis]
run = "redis-server --port $PORT"
port = 6379
[daemons.api]
run = "server --redis-port {{ daemons.redis.port }}"
env = { DATABASE_URL = "redis://localhost:{{ daemons.redis.port }}/0" }
depends = ["redis"]Template rendering follows the dependency order: daemons in later levels can reference values from daemons that completed successfully in earlier levels. If a daemon has depends = ["redis"], it starts after redis and can use {{ daemons.redis.port }}.
Template Fields
Templates work in these configuration fields:
| Field | Example |
|---|---|
run | run = "server --port {{ daemons.redis.port }}" |
env values | env = { DB_URL = "postgres://localhost:{{ daemons.db.port }}" } |
hooks.* commands | on_ready = "echo {{ id }} is ready" |
ready_cmd | ready_cmd = "redis-cli -p {{ daemons.redis.port }} ping" |
ready_http | ready_http = "http://localhost:{{ daemons.backend.port }}/health" |
ready_port | ready_port = "{{ daemons.redis.port }}" (must render to a port number) |
ready_output | ready_output = "listening on port {{ daemons.api.port }}" |
Health check fields (health_cmd, health_http, and health_port) also accept templates. These examples reference dependencies; use $PORT in commands for the current daemon's own assigned port.
Template Variables
Self Variables
The current daemon's own metadata is always available:
| Variable | Description | Example |
|---|---|---|
{{ name }} | Daemon short name | "api" |
{{ namespace }} | Daemon namespace | "myproj" |
{{ id }} | Qualified ID | "myproj/api" |
{{ slug }} | Proxy slug alias (or null) | "myapi" |
{{ dir }} | Resolved working directory | "/home/user/myproj" |
Daemon References
Reference same-namespace daemons by their short name:
| Variable | Description | Example |
|---|---|---|
{{ daemons.redis.port }} | First resolved port | 6379 |
{{ daemons.redis.ports }} | All resolved ports | [6379, 6380] |
{{ daemons.redis.ports[0] }} | Port by index | 6379 |
{{ daemons.redis.id }} | Qualified ID | "myproj/redis" |
{{ daemons.redis.name }} | Short name | "redis" |
{{ daemons.redis.namespace }} | Namespace | "myproj" |
{{ daemons.redis.slug }} | Slug alias | "myredis" |
{{ daemons.redis.dir }} | Working directory | "/home/user/myproj" |
TIP
port is shorthand for ports[0]. Use ports[N] when a daemon has multiple ports configured.
Environment values
Top-level [env] values apply to all daemons; per-daemon values override them. After the environment is rendered, commands and probes can reference it:
[env]
APP_ENV = "development"
[daemons.worker]
run = "./worker --environment {{ env.APP_ENV }}"Environment values are rendered independently, so do not use one environment template to depend on another environment template's result.
Cross-Namespace References
When referencing daemons in a different namespace, use the namespace.name key format in templates and namespace/name in depends. For example, if the registered infra namespace defines a redis daemon with a configured port:
[daemons.api]
run = "node server.js"
env = { REDIS_URL = 'redis://localhost:{{ daemons["infra.redis"].port }}' }
depends = ["infra/redis"]Here, server.js must read REDIS_URL from its environment. See the namespace registry to make the infra project available from other directories.
Settings
Global proxy settings are available:
| Variable | Description |
|---|---|
{{ settings.proxy.enable }} | Whether the proxy is enabled |
{{ settings.proxy.tld }} | Proxy TLD (default: "localhost") |
{{ settings.proxy.port }} | Proxy port (default: 443) |
{{ settings.proxy.https }} | Whether HTTPS is enabled |
Proxy URL
{{ proxy_url }} provides the full proxy URL for the current daemon when it has a registered slug:
[daemons.api]
run = "echo {{ proxy_url }}"
# Renders to: "echo https://myapi.localhost"Resolution Order
Templates are rendered level-by-level following the dependency graph:
Level 0: redis (no dependencies, starts first)
Level 1: api (depends on redis, templates can reference redis)
Level 2: worker (depends on api, templates can reference redis and api)- Daemons within the same level start concurrently and cannot reference each other's ports
- A daemon can reference any daemon that completed successfully in a previous level
portandportsfor the current daemon are not available at template rendering time (ports are resolved after the command is constructed). Use the$PORT/$PORT0,$PORT1, ... environment variables to access the current daemon's own resolved ports. Use{{ daemons.xxx.port }}to reference dependencies' ports instead
Error Handling
- Undefined template variables produce clear errors at start time (strict mode)
- A template error in
runorenvprevents the daemon from starting, and its dependents are also skipped - A template error in a hook command logs a warning and skips the hook execution
Examples
Database Connection String
[daemons.postgres]
run = "postgres -D /usr/local/var/postgres"
port = 5432
[daemons.api]
run = "node server.js"
env = { DATABASE_URL = "postgres://localhost:{{ daemons.postgres.port }}/myapp" }
depends = ["postgres"]Multiple Services
[daemons.redis]
run = "redis-server --port $PORT"
port = 6379
[daemons.postgres]
run = "postgres"
port = 5432
[daemons.api]
run = "server start"
env = { REDIS_URL = "redis://localhost:{{ daemons.redis.port }}", DATABASE_URL = "postgres://localhost:{{ daemons.postgres.port }}/app" }
depends = ["redis", "postgres"]Multiple Ports
[daemons.grpc]
run = "grpc-server"
port = [50051, 50052]
[daemons.gateway]
run = "gateway --grpc-port {{ daemons.grpc.ports[0] }} --metrics-port {{ daemons.grpc.ports[1] }}"
depends = ["grpc"]Auto-Bumped Port
When bump is configured, the resolved port may differ from the expected port. Templates always use the resolved value:
[daemons.redis]
run = "redis-server --port $PORT"
port = { expect = [6379], bump = 10 }
# If 6379 is occupied, redis starts on 6380 or another available port within the attempt limit
[daemons.api]
run = "server --redis-port {{ daemons.redis.port }}"
# Always uses the actual port, regardless of bumping
depends = ["redis"]Hook with Port Reference
[daemons.api]
run = "node server.js"
port = 3000
ready_http = "http://localhost:3000/health"
[daemons.monitor]
run = "echo monitoring"
depends = ["api"]
[daemons.monitor.hooks]
on_ready = "curl -X POST https://monitor.example.com/register -d '{\"url\": \"http://localhost:{{ daemons.api.port }}\"}'"