Dependencies
Dependencies let you control process startup order. Each dependency is polled in a loop until it is satisfied or its timeout expires. A process is not started until all of its dependencies are met.
Dependency types
HTTP health check
Wait for an HTTP endpoint to return an expected status code.
depends:
- url: http://localhost:8080/health
code: 200
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | yes | — | URL to GET |
code | integer | yes | — | Expected HTTP status code |
poll_interval | float | no | 1.0 | Seconds between polls |
timeout_seconds | integer | no | 60 | Seconds before giving up |
The HTTP client uses a 5-second per-request timeout. Only the status code is checked — the response body is ignored.
TCP connect
Wait for a TCP port to accept connections.
depends:
- tcp: "127.0.0.1:5432"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
tcp | string | yes | — | Address in host:port form |
poll_interval | float | no | 1.0 | Seconds between polls |
timeout_seconds | integer | no | 60 | Seconds before giving up |
Each poll attempt uses a 1-second connect timeout.
File exists
Wait for a file to appear on disk.
depends:
- path: /tmp/ready.flag
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | Path to check |
Poll interval is 1 second. Timeout is 60 seconds. These are not configurable for this dependency type.
File contains key
Wait for a file to contain a specific key, with optional value extraction into the process environment.
depends:
- file_contains:
path: /tmp/config.yaml
format: yaml
key: "$.database.url"
env: DATABASE_URL
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | Path to the file |
format | string | yes | — | "json" or "yaml" |
key | string | yes | — | JSONPath expression (RFC 9535) |
env | string | no | — | If set, the resolved value is injected as this env var |
poll_interval | float | no | 1.0 | Seconds between polls |
timeout_seconds | integer | no | 60 | Seconds before giving up |
The key field accepts a JSONPath expression (RFC 9535). Use $ to refer to the document root,
. to traverse nested maps, and bracket notation for array filtering. The first matching value
is used. Scalar values (strings, numbers, booleans) are converted to strings. Null values are
treated as missing. Mappings and sequences are serialized as JSON strings.
Array filtering example — extract rpc from the entry where alias == "local":
depends:
- file_contains:
path: /tmp/sui_client.yaml
format: yaml
key: "$.envs[?(@.alias == 'local')].rpc"
env: SUI_RPC_URL
When env is specified, the value at key is extracted and injected into the dependent
process’s environment under that variable name. This happens after all dependencies are
satisfied, just before the process is spawned. See Templates for more on
passing data between processes.
Process exited
Wait for another process to exit successfully (once: true processes only, in practice).
depends:
- process_exited: migrate
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
process_exited | string | yes | — | Name of the process to wait for |
Poll interval is 100ms. Timeout is 60 seconds. These are not configurable for this dependency type.
This dependency is satisfied when the named process has exited (with any exit code). It is
typically used with once: true processes like migrations or setup scripts.
For for_each processes, a process_exited dependency on the template name is satisfied
only when all fan-out instances have exited.
How polling works
When a process has dependencies, procman spawns a dedicated waiter thread that evaluates dependencies in declaration order. Each dependency is fully satisfied before the next one is evaluated:
- Start with the first dependency.
- Poll the current dependency using its check function.
- If the check succeeds, log
dependency satisfied: <description>and advance to the next dependency. - If the check fails for the first time, log
dependency not ready: <description>(logged only once per dependency to avoid noise). - If the check fails, sleep for the dependency’s
poll_intervaland retry. - Once all dependencies are satisfied, proceed to spawn the process.
This sequential evaluation prevents stale-data races — for example, a file_contains
dependency listed after a process_exited dependency will not be checked until the process
has actually exited, ensuring it reads freshly generated data rather than leftovers from a
prior run.
Timeout behavior
Each dependency’s timeout clock starts when that dependency begins being evaluated (i.e., when the previous dependency is satisfied), not when the waiter thread starts. This means total wall-clock time for a process with multiple dependencies is the sum of individual wait times rather than the maximum.
If any single dependency exceeds its timeout:
- The waiter logs
dependency timed out: <description>. - The global shutdown flag is set.
- All processes are torn down (SIGTERM, then SIGKILL after a grace period).
A timed-out dependency is fatal — procman does not continue with partial dependencies.
Circular dependency detection
At parse time, procman builds a directed graph from process_exited dependencies and runs a
DFS cycle detection pass. If a cycle is found, parsing fails with an error showing the full
cycle path:
Error: circular dependency: a -> b -> c -> a
Self-dependencies (a -> a) are also detected. References to process names not defined in the
config file are rejected with:
Error: process 'a' depends on unknown process 'nonexistent'
Environment variable expansion in paths
The url, tcp, path, and file_contains.path fields support environment variable
expansion at parse time. See the Configuration
chapter for the full syntax.
depends:
- path: $HOME/.config/app/ready.flag
- tcp: "${DB_HOST}:5432"