Prerequisites: work that must finish before a service starts
Some work has to happen before a service is worth starting: apply migrations, build an asset bundle, bring up shared infrastructure. This example shows how before_start expresses that without turning one-shot work into a fake service.

Run it
cd examples/prerequisites
kranzPress a, then s, and read the order in the logs:
check-toolsruns — a project-level action shared by the whole workspace.migrateruns once, printing three lines.catalog-apistarts and becomes ready on port18891.reporting-workerwaits for the same migration, does not run it a second time, and starts after the API is healthy.
Everything is safe: the "migration" only prints messages, and both services are localhost HTTP servers on high ports.
Read the important configuration
action_groups:
environment:
actions:
check-tools:
command: python3 --version
services:
catalog-api:
command: python3 -u ../scripts/http_service.py
actions:
migrate:
command: /bin/sh ../scripts/migrate.sh
before_start:
- group: environment
action: check-tools
run: always
- action: migrate
reporting-worker:
command: python3 -u ../scripts/worker.py
depends_on: [catalog-api]
before_start:
- service: catalog-api
action: migrateThree things are worth noticing.
A prerequisite is a reference, not an inline command. migrate stays an ordinary action: you can expand catalog-api with Enter and run it by hand whenever you want. There is one definition of the command and one place to fix it.
once is per session, not per start. The migration runs the first time something needs it and is then considered satisfied — restarting a service does not re-apply it. run: always is for checks that are cheap and whose answer can change, like check-tools above.
Two services sharing a prerequisite share one run. reporting-worker references the same action as catalog-api. Whichever one gets there first runs it; the other waits for that same run instead of starting a second copy.
Where prerequisites sit in the order
dependencies ready → before_start actions → service starts → readiness probeA prerequisite runs after everything the service depends on is ready, so a migration can rely on the database being up. It runs before the service itself, so a failed prerequisite means the service never starts.
See a blocked start
The gated-demo service exists for this. It is disabled, so batch starts skip it.
- Edit its
preflightaction command toexit 1. - Press
Ctrl+Lto reload. - Focus
gated-demoand presss.
The service stays stopped and its log explains why:
[Kranz] Running prerequisite: action "preflight"
[Kranz] Prerequisite failed: action "preflight" · exited with code 1Change the command back, reload, and start it again. Because that prerequisite uses run: always, it is retried on every attempt; a failed once prerequisite is retried too, since only success is remembered.
Try an action that asks a question
catalog-api also owns migrate-interactive, which is what a real migration usually looks like: it lists what it would apply and waits for an answer.
Expand catalog-api with Enter, focus migrate-interactive, and press s. Kranz asks first, in yellow, because it is about to leave the screen. Accept, and the command gets your terminal:
Pending migrations:
- 20260801_add_orders_table
- 20260812_backfill_customer_ids
Apply these migrations? [y/N]Answer y and it applies them (this example writes nothing) and exits 0; answer anything else and it exits 1. Either way Kranz comes back and records the outcome — succeeded or failed, with the exit code and duration — in the action's Details.
That is the difference between the two flags: confirm: true makes Kranz ask before launching a command, while interactive: true lets the command ask you anything it wants once it is running.
Compare with dependency conditions
The native example gates api on a migrate service using process_completed_successfully. Both approaches work, and they answer different questions:
| Use | When |
|---|---|
before_start | The work is an operation. You want it out of the service list, runnable on demand, and shared between services. |
A one-shot service with process_completed_successfully | The work is a node in the graph that other services should visibly wait on, with its own state in the list. |
Cleanup
Press a, then s, and confirm. Both services are local processes owned by Kranz, and the example writes nothing outside its own directory.
Full source: examples/prerequisites/kranz.yaml.