Configuration Reference
Complete reference for pitchfork.toml configuration files.
Configuration Hierarchy
Pitchfork loads configuration files in order, with later files overriding earlier ones:
- System-level:
/etc/pitchfork/config.toml(namespace:global) - User-level:
~/.config/pitchfork/config.toml(namespace:global) - Project-level:
.config/pitchfork.toml,.config/pitchfork.local.toml,pitchfork.toml,pitchfork.local.tomlfrom filesystem root to current directory
Within each directory, files are processed in this order:
.config/pitchfork.toml(lowest precedence in directory).config/pitchfork.local.toml(overrides.config/pitchfork.toml)pitchfork.toml(overrides everything in.config/)pitchfork.local.toml(highest precedence in directory, not committed to version control)
This mirrors mise behavior, allowing you to store project config in a centralized .config/ directory if preferred.
JSON Schema
A JSON Schema is available for editor autocompletion and validation:
URL: https://pitchfork.jdx.dev/schema.json
Editor Setup
VS Code with Even Better TOML:
#:schema https://pitchfork.jdx.dev/schema.json
[daemons.api]
run = "npm run server"JetBrains IDEs: Add the schema URL in Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings.
File Format
All configuration uses TOML format:
namespace = "my-project" # optional, per-file namespace override
[daemons.<daemon-name>]
run = "command to execute"
# ... other optionsDaemon Naming Rules
Daemon names must follow these rules:
| Rule | Valid | Invalid |
|---|---|---|
| No double dashes | my-app | my--app |
| No slashes | api | api/v2 |
| No spaces | my_app | my app |
| No parent references | myapp | .. or foo..bar |
| ASCII only | myapp123 | myäpp |
The -- sequence is reserved for internal use (namespace encoding). See Namespaces for details.
Namespace Derivation Rules
- Global config files (
/etc/pitchfork/config.toml,~/.config/pitchfork/config.toml) use namespaceglobal - Project config files (
.config/pitchfork.toml,.config/pitchfork.local.toml,pitchfork.toml,pitchfork.local.toml) use:- Top-level
namespace = "..."if set in the config file - Otherwise, the parent directory name as namespace
- Top-level
- For
.config/pitchfork.tomland.config/pitchfork.local.toml, the namespace is derived from the project directory (the.configdirectory's parent), not from.configitself - If the derived directory name is invalid (
--, spaces, non-ASCII, etc.), parsing fails and you should set top-levelnamespace
Top-level namespace (optional)
Overrides the namespace used for all daemons in that specific config file.
namespace = "frontend"
[daemons.api]
run = "npm run dev"Notes:
pitchfork.local.tomlshares namespace with siblingpitchfork.toml- If both declare
namespace, the values must match - Global config files must use
global
Daemon Options
run (required)
The command to execute.
[daemons.api]
run = "npm run server"dir
Working directory for the daemon. Relative paths are resolved from the pitchfork.toml file location. If not set, defaults to the directory containing the pitchfork.toml file.
# Relative path (resolved from pitchfork.toml location)
[daemons.frontend]
run = "npm run dev"
dir = "frontend"
# Absolute path
[daemons.api]
run = "npm run server"
dir = "/opt/myapp/api"env
Environment variables to set for the daemon process. Variables are passed as key-value string pairs.
[daemons.api]
run = "npm run server"
env = { NODE_ENV = "development", PORT = "3000" }
# Multi-line format for many variables
[daemons.worker]
run = "python worker.py"
[daemons.worker.env]
DATABASE_URL = "postgres://localhost/mydb"
REDIS_URL = "redis://localhost:6379"
LOG_LEVEL = "debug"retry
Number of retry attempts on failure, or true for infinite retries. Default: 0
- A number (e.g.,
3) means retry that many times truemeans retry indefinitelyfalseor0means no retries
[daemons.api]
run = "npm run server"
retry = 3 # Retry up to 3 times
[daemons.critical]
run = "npm run worker"
retry = true # Retry foreverauto
Auto-start and auto-stop behavior with shell hook. Options: "start", "stop"
[daemons.api]
run = "npm run server"
auto = ["start", "stop"] # Both auto-start and auto-stopready_delay
Seconds to wait before considering the daemon ready. Default: 3
[daemons.api]
run = "npm run server"
ready_delay = 5ready_output
Regex pattern to match in output for readiness.
[daemons.postgres]
run = "postgres -D /var/lib/pgsql/data"
ready_output = "ready to accept connections"ready_http
HTTP endpoint URL to poll for readiness (2xx = ready).
[daemons.api]
run = "npm run server"
ready_http = "http://localhost:3000/health"ready_port
TCP port to check for readiness. Daemon is ready when port is listening.
[daemons.api]
run = "npm run server"
ready_port = 3000ready_cmd
Shell command to poll for readiness. Daemon is ready when command exits with code 0.
[daemons.postgres]
run = "postgres -D /var/lib/pgsql/data"
ready_cmd = "pg_isready -h localhost"
[daemons.redis]
run = "redis-server"
ready_cmd = "redis-cli ping"depends
List of daemon IDs that must be started before this daemon. Dependencies can be:
- short IDs in the same namespace (e.g.
postgres) - fully qualified cross-namespace IDs (e.g.
global/postgres)
When you start a daemon, its dependencies are automatically started first in the correct order.
[daemons.api]
run = "npm run server"
depends = ["postgres", "redis"]Behavior:
- Auto-start: Running
pitchfork start apiwill automatically startpostgresandredisfirst - Transitive dependencies: If
postgresdepends onstorage, that will be started too - Parallel starting: Dependencies at the same level start in parallel for faster startup
- Skip running: Already-running dependencies are skipped (not restarted)
- Circular detection: Circular dependencies are detected and reported as errors
- Strict validation: Invalid dependency IDs fail config parsing (they are not skipped)
- Force flag: Using
-fonly restarts the explicitly requested daemon, not its dependencies
Example with chained dependencies:
[daemons.database]
run = "postgres -D /var/lib/pgsql/data"
ready_port = 5432
[daemons.cache]
run = "redis-server"
ready_port = 6379
[daemons.api]
run = "npm run server"
depends = ["database", "cache"]
[daemons.worker]
run = "npm run worker"
depends = ["database"]Running pitchfork start api worker starts daemons in this order:
databaseandcache(in parallel, no dependencies)apiandworker(in parallel, after their dependencies are ready)
watch
Glob patterns for files to watch. When a matched file changes, the daemon is automatically restarted.
[daemons.api]
run = "npm run dev"
watch = ["src/**/*.ts", "package.json"]Pattern syntax:
*.js- All.jsfiles in the daemon's directorysrc/**/*.ts- All.tsfiles insrc/and subdirectoriespackage.json- Specific file
Behavior:
- Patterns are resolved relative to the
pitchfork.tomlfile - Only running daemons are restarted (stopped daemons ignore changes)
- Changes are debounced for 1 second to avoid rapid restarts
See File Watching guide for more details.
boot_start
Start this daemon automatically on system boot. Default: false
[daemons.postgres]
run = "postgres -D /var/lib/pgsql/data"
boot_start = truehooks
Lifecycle hooks that run shell commands in response to daemon events. Hooks are fire-and-forget — they run in the background and never block the daemon.
[daemons.api]
run = "npm run server"
retry = 3
[daemons.api.hooks]
on_ready = "curl -X POST https://alerts.example.com/ready"
on_fail = "./scripts/cleanup.sh"
on_retry = "echo 'retrying...'"Fields:
on_ready- Runs when the daemon becomes ready (passes readiness check)on_fail- Runs when the daemon fails and all retries are exhaustedon_retry- Runs before each retry attempt
Hook commands receive environment variables: PITCHFORK_DAEMON_ID (fully-qualified namespace/name), PITCHFORK_DAEMON_NAMESPACE, PITCHFORK_RETRY_COUNT, and (for on_fail) PITCHFORK_EXIT_CODE. See Lifecycle Hooks guide for details.
cron
Cron scheduling configuration.
[daemons.backup]
run = "./backup.sh"
cron = { schedule = "0 0 2 * * *", retrigger = "finish" }Fields:
schedule- Cron expression (6 fields: second, minute, hour, day, month, weekday)retrigger- Behavior when schedule fires:"finish"(default),"always","success","fail"
mise
Enable mise integration for this daemon. When true, the daemon's command is wrapped with mise x -- to activate mise-managed tools and environment variables.
[daemons.api]
run = "node server.js"
mise = trueThis is especially useful for daemons running via pitchfork boot (login daemon mode) where interactive shell hooks haven't set up tool paths. When not set, falls back to the global general.mise setting. See mise Integration guide for details.
Complete Example
# Database - starts on boot, no auto-stop
[daemons.postgres]
run = "postgres -D /var/lib/pgsql/data"
ready_output = "ready to accept connections"
boot_start = true
retry = 3
# Cache - starts with API
[daemons.redis]
run = "redis-server"
ready_output = "Ready to accept connections"
# API server - depends on database and cache, hot reloads on changes
[daemons.api]
run = "npm run server"
dir = "api"
depends = ["postgres", "redis"]
watch = ["src/**/*.ts", "package.json"]
ready_http = "http://localhost:3000/health"
auto = ["start", "stop"]
retry = 5
env = { NODE_ENV = "development", PORT = "3000" }
[daemons.api.hooks]
on_ready = "curl -X POST https://alerts.example.com/ready"
on_fail = "./scripts/alert-failure.sh"
# Frontend dev server in a subdirectory
[daemons.frontend]
run = "npm run dev"
dir = "frontend"
env = { PORT = "5173" }
# Scheduled backup
[daemons.backup]
run = "./scripts/backup.sh"
cron = { schedule = "0 0 2 * * *", retrigger = "finish" }