skip to content
early access · antil is not publicly available yet and commands may change · join the waitlist

the antil field manual reference

antil.yaml

view markdown

The effective config is committed antil.yaml overlaid by .antil/local.yaml. Unknown fields, duplicate keys, multiple YAML documents, invalid references and dependency cycles are rejected. Local config has the same schema and defined merge rules.

Names for Projects, Services, Resources, OperationDefinitions and Rules are lowercase slugs of 1–40 characters, using letters, digits and internal hyphens.

key type default / requirement example
version integer required; only 1 version: 1
project string required; lowercase slug project: shop
target string HEAD; resolved and pinned at Session admission target: main
services map of Service required; at least one see below
stacks map of StackDefinition if absent or empty: default selects every Service default: {services: [web]}
resources map of Resource empty db: {kind: postgres}
operations map of OperationDefinition empty see below
rules map of Rule empty see below

services.<name> accepts exactly these keys:

key type default / requirement example
command string list (argv) required, nonempty executable [node, api/server.mjs]
dir string Workspace root .
env map of literal or typed env value empty MODE: development
port map required {env: PORT}
port.env string required, nonempty PORT
ready map exactly one probe required {http: {path: /}}
ready.http map absent if TCP chosen {path: /health}
ready.http.path string required for HTTP, begins /; 2xx passes /
ready.tcp empty map absent if HTTP chosen; connection passes {}
depends_on string list of Service names empty [api]
timeout Go duration string 60s; zero also defaults to 60s; negatives invalid 30s
requires Resource name → permission list empty {db: [read, write]}

command executes argv without an implicit shell. Dependencies include transitive dependencies; cycles are invalid. Each Service gets one port. dir is joined to the Workspace root.

These forms work in both Service and OperationDefinition env:

form / key type default / requirement example
literal value scalar public config, not a secret MODE: development
secret string file reference nonempty, resolved at dispatch; relative to Workspace root unless absolute TOKEN: {secret: .antil/token}
resource string Resource name required with output; declared Resource must exist DATABASE_URL: {resource: db, output: url}
output string required with resource; url, database, host, port DB_NAME: {resource: db, output: database}

secret_env and resource_env are internal JSON representations, not accepted YAML keys. Secret files are regular files of at most 1 MiB, nonempty after trailing CR/LF removal, with no NUL bytes. Resource url is a secret Output; status returns its reference.

Templates expand in literal env values and argv strings. $$ escapes a literal dollar sign.

template value
${port} this Service’s allocated port
${services.api.port} api’s allocated port
${services.api.host} 127.0.0.1
${services.api.url} api’s named proxy URL

A Service can reference itself or a transitive dependency. Named OperationDefinitions use explicit named Service references and require referenced Services to be ready; ${port} has no own Service to resolve to there.

key type default / requirement example
stacks.<name>.services string list required, nonempty; every name must exist [web]

Starting a selected Service also starts its transitive dependencies. If you declare a nonempty stacks map, name the desired StackDefinition at start or include a default entry for unnamed starts.

resources.<name> accepts exactly these keys:

key type default / requirement example
kind string required; only postgres postgres
image string postgres:17-alpine postgres:17-alpine
sharing string enum private; alternative seed seed
on_conflict string enum reject; alternatives fork:empty, fork:seed fork:empty
claim map defaults below {access: shared, permissions: [read]}
claim.access string enum exclusive; alternative shared shared
claim.permissions string list [read, write]; nonempty, no duplicates; choices read, write, ddl [read, write, ddl]
schema string list of relative file paths empty; contributes identity, does not execute SQL [seed.sql]
seed map absent; required for seed sharing or fork:seed see below
seed.file relative file path required when seed is set; SQL initialization seed.sql
seed.validate string SQL query required when seed is set; boolean validation SELECT count(*) = 2 FROM products

Schema and seed inputs must be readable regular Workspace-relative files of at most 8 MiB each. They are exact file paths, not globs. Source hashes are checked again before use. Sharing also depends on relevant config and actual runtime image identity. Missing evidence prevents unsafe reuse.

wait and fork:snapshot are not accepted conflict policies. Permissions coordinate antil-managed work; they do not configure database ACLs.

operations.<name> accepts the following keys. Named operations have no Service port or readiness probe.

key type default / requirement example
command string list (argv) required, nonempty executable [node, --test, tests/shop.test.mjs]
dir string Workspace root .
env map empty; same literal / secret / Resource forms as Services {MODE: test}
timeout Go duration string 60s; zero defaults to 60s; negatives invalid 30s
requires Resource name → permission list empty; declared nonempty lists use known permissions {db: [ddl]}
key type default / requirement example
rules.<name>.paths string list required, nonempty; repository-relative patterns ["migrations/**"]
rules.<name>.requires Resource name → permission list required, nonempty; known Resources and permissions {db: [ddl]}

Supported glob operators: * and ? do not cross /; ** does; **/ also matches zero directories. Absolute paths, parent segments and backslashes are invalid config. Bracket syntax is unsupported and evaluates as unknown, conservatively activating the Rule. Git observations that are incomplete or ambiguous also activate Rules.

This is the runnable shop configuration:

version: 1
project: shop
services:
api:
command: [node, api/server.mjs]
port: {env: PORT}
env:
DATABASE_URL: {resource: db, output: url}
REDIS_URL: {secret: .antil/redis-url}
ready: {http: {path: /health}}
web:
command: [bun, node_modules/vite/bin/vite.js, --host, 127.0.0.1, --port, "${port}", --strictPort]
port: {env: PORT}
depends_on: [api]
env:
API_URL: "http://127.0.0.1:${services.api.port}"
ready: {http: {path: /}}
resources:
db:
kind: postgres
seed:
file: seed.sql
validate: "SELECT count(*) = 2 FROM products"
operations:
test:
command: [node, --test, tests/shop.test.mjs]
env:
SHOP_API_URL: "http://127.0.0.1:${services.api.port}"

These are environment variables read by the daemon at startup, not YAML keys. A running daemon keeps its settings until restarted.

variable type / bounds default example
ANTIL_HOME directory path ~/.local/state/antil; CLI --home takes precedence /tmp/antil-docs/home
ANTIL_PORT_RANGE inclusive FROM-TO, within 1024–65535 4000-4999 44100-44199
ANTIL_PROXY_PORT integer 1–65535 1355 1365
ANTIL_STALE_AFTER positive Go duration 30m 1h

Separate Installations do not coordinate ports. Use disjoint service ranges and different proxy ports. ANTIL_RUNNER_TOKEN is an internal runner credential, not user configuration.