Skip to content

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:

toml
[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:

toml
[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:

FieldExample
runrun = "server --port {{ daemons.redis.port }}"
env valuesenv = { DB_URL = "postgres://localhost:{{ daemons.db.port }}" }
hooks.* commandson_ready = "echo {{ id }} is ready"
ready_cmdready_cmd = "redis-cli -p {{ daemons.redis.port }} ping"
ready_httpready_http = "http://localhost:{{ daemons.backend.port }}/health"
ready_portready_port = "{{ daemons.redis.port }}" (must render to a port number)
ready_outputready_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:

VariableDescriptionExample
{{ 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:

VariableDescriptionExample
{{ daemons.redis.port }}First resolved port6379
{{ daemons.redis.ports }}All resolved ports[6379, 6380]
{{ daemons.redis.ports[0] }}Port by index6379
{{ 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:

toml
[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:

toml
[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:

VariableDescription
{{ 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:

toml
[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
  • port and ports for 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 run or env prevents 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

toml
[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

toml
[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

toml
[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:

toml
[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

toml
[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 }}\"}'"
MIT LicenseCopyright © 2026jdx.dev