process

Long-running user binary. The orchestrator supervises the PID, captures stdout / stderr into a ring buffer, runs the configured healthcheck, and tears the process down on stop / project close.

Use this for app code (Python web servers, Node dev servers, Go APIs) and for any binary we don't ship a blessed wrapper for.

Manifest knobs

[services.api]
type = "process"
command = "python app.py"          # or "node server.js", "go run ./..."
cwd = "."                          # relative to the project root
port = "auto"                      # injected as $PORT in the env
healthcheck = { kind = "tcp" }     # or "http", "command"; see below
depends_on = [
    { service = "postgres", condition = "healthy" },
]

The orchestrator does not parse the command string itself — it hands it to bash -c so shell quoting, pipes, and redirects work the way they look. $PORT is the orchestrator-allocated port, $HOME is preserved from the daemon's environment, and every dependency's $<NAME>_PORT / $<NAME>_HOST are exported.

Runtime detection

When the project root contains a recognized lockfile (package.json / pyproject.toml / Gemfile / go.mod / etc.), the orchestrator runs mise install for the matching tool before the first start and prepends the resolved bin/ to the service's PATH. So command = "python app.py" picks up the Python the project's lockfile pins, not the system Python.

The detection is purely additive — without a lockfile (or without mise installed), the service falls back to whatever's on the daemon's inherited PATH.

Healthcheck variants

  • tcp (default if port is set): TCP connect to 127.0.0.1:<port>.
  • http: GET <url> returning a 2xx (or one of expect_status if set).
  • command: run a shell command; healthy iff it exits 0.

Retry policy is 3 attempts at 5s / 15s / 45s by default. Override via healthcheck_retries = N for slow first-runs.

Inspection drawer

process services don't get a type-specific inspector — they show the Summary / Logs / Env / Ports tabs that every service has. The Logs tab is usually where the diagnostic ends up.

Common gotchas

  • cwd is relative to the project root. Not relative to wherever you invoke unibench start from.
  • $PORT is the orchestrator's port, not your service's. Frameworks that read process.env.PORT (Express, Next.js, etc.) pick it up automatically. Frameworks that hard-code a port (Flask's default 5000, Vite's default 5173) need an explicit -p $PORT or --port $PORT flag.
  • Lifecycle hooks run synchronously. pre_start blocks the service start until it exits cleanly — useful for one-shot DB migrations, expensive for anything slow.