# antil.yaml

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](/docs/reference/local-configuration/).

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

## top level

| 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

`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.

## environment values

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.

### service output templates

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.

## stacks

| 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

`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

`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]}` |

## rules

| 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.

## complete example

This is the runnable [shop configuration](/docs/quickstart/):

```yaml
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}"
```

## installation environment

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.
