Working from the command line
The terminal UI is one way to drive Kranz. The other is the command line, which matters when you want a project running in the background, when you are in a second terminal, or when a script has to make a decision from what Kranz reports.
Everything below works on the project in the current directory. You never have to name it.
In this guide, a runtime means one active Kranz session for a project. It owns the service state and logs; TUI, CLI, and MCP commands connect to it as clients. See Core concepts if those names are new to you.

When a shell or coding agent runs several commands from elsewhere, set a one-shot coordinate instead of repeating a flag:
KRANZ_PROJECT=shop-dev kranz status
KRANZ_DIRECTORY=/work/shop kranz servicesThe equivalent -p and -C flags remain available and take precedence over the environment.
Start a project and leave it running
$ kranz up --start -d
Started shop-dev (7fa21c8d), PID 18421.up --start -d creates a background runtime, starts every enabled service, and gives you the prompt back. The processes belong to that runtime, not to your shell, so closing the terminal leaves them alone. Use up -d without --start when you want an empty runtime and will start services explicitly.
Without -d, up keeps a foreground client attached. Selectors start and stream only the named services; --start starts and streams every enabled service; bare up starts none. The runtime supervisor is still a separate process, and Ctrl+C explicitly asks it to shut down before the foreground client terminates. This is the shape you want inside a container or under another supervisor.
See what is running
ps lists every runtime you have, across projects:
$ kranz ps
ID PID NAME PROJECT SERVICES CLIENTS STATE UPTIME
7fa21c8d 18400 shop-dev Shop 4/4 1 running 18m
91bc430a 18022 billing Billing 3/3 2 running 6mclients answers the other half: who is attached to those runtimes, whether that is a TUI, a CLI command, or a coding agent over MCP.
status describes the services of one runtime:
$ kranz status
NAME STATE HEALTH UPTIME PID PORTS
migrate stopped - - - -
api running ready 18m 26078 3000
worker running - 18m 26085 -HEALTH is - when no readiness or liveness probe is configured. Kranz does not turn the internal assumption that a missing probe permits startup into a false claim that a probe passed.
Use exact filters to narrow live tables, and --watch to follow changes. A bounded watch is convenient in automation:
kranz ps --filter client=mcp
kranz clients --filter client=tui,mcp --watch --count 3
kranz status api --filter state=running,unhealthy --watch --interval 2sAct on services
kranz stop worker
kranz start worker
kranz restart apiSelectors are service names or tags, the same ones the TUI uses. stop expands to dependents exactly as the TUI does, so stopping something nothing else can survive without stops those too.
down ends the whole runtime:
kranz downIt takes no service selectors. If you name one, Kranz says which command you wanted:
$ kranz down worker
Kranz: down stops the whole runtime and does not take service selectors.
Stop one service with `kranz stop worker`, or stop everything with `kranz down`.Read the logs
kranz logs --tail 20
kranz logs api --follow
kranz logs --since 5mLogs survive the service. A worker that crashed two minutes ago still answers kranz logs worker, which is when you actually need it.
Actions keep their own history under the same name kranz actions run uses, so an action that has already finished can be read again without running it twice:
kranz logs api/migrate
kranz logs analytics/stats --run -1 # the latest execution
kranz logs analytics/stats --run -2 # the one before it
kranz logs analytics/stats --run 7 # run number 7
kranz logs analytics/stats --runs 3 # the last threeA positive --run is the run number Kranz assigned; a negative one counts back from the newest run still buffered, which is what keeps -1 meaning "the latest" as older runs age out of the buffer.
A bare service name means "recent lines", because a service streams without end. A bare action name means its whole latest run: an action produces a finite, self-contained report, and capping that at the last lines would cut off the part explaining what the run did. --tail and --all still override both.
The timestamp and label columns exist to tell interleaved streams apart, which is exactly what reading one stream back does not need:
kranz logs analytics/stats --plain # the output as the command printed it
kranz logs analytics/stats --no-timestamps # keep the labels, drop the clock
kranz logs api --with-actions --no-labels--source narrows to where a line came from — stdout, stderr, or kranz for the lifecycle notes Kranz writes into the buffer itself. It narrows before --tail, so --source stderr --tail 20 means twenty error lines rather than whichever errors survive in the last twenty lines of everything:
kranz logs api --source stderr --tail 20
kranz logs analytics/stats --source stdout --plain > report.txtWhat a selector means
One rule everywhere. A name a service answers to means that service; a name no service answers to is tried as a tag. kranz plan api, kranz status api and kranz stop api therefore always cover the same services.
Actions extend the rule rather than change it: OWNER/ACTION addresses one action, using the same name kranz actions run uses. Because of that, a service and an action group may not share a name — the actions under the second one would be unreachable — and kranz config check rejects a project that tries.
A service name means the service's own command; a group name has no command of its own and so no stream. --with-actions folds an owner's actions into one timeline, labelled so every line says where it came from:
$ kranz logs api --with-actions
2026-08-21T09:12:04.118+03:00 [api stdout] listening on :3000
2026-08-21T09:12:31.902+03:00 [api/migrate#2 stdout] applied 3 migrationsLifecycle hooks are not addressed separately: their output is part of the life of the service they act on, and kranz logs api already carries it.
Buffers live in the runtime and are gone after kranz down. To reclaim one sooner:
kranz logs clear api
kranz logs clear analytics/stats
kranz logs clear --force # every stream in the projectOpen the interface on a running project
kranz attachThe TUI connects to the runtime that is already there. Choose detach in its quit confirmation to leave the runtime running, or confirm shutdown to stop it. down provides the same explicit runtime shutdown outside the TUI.
Work on another project without leaving this one
-p names a runtime explicitly and always wins over the current directory:
kranz -p billing status
kranz -p billing logs api --tail 20You can use the runtime's NAME, its ID, or a unique ID prefix.
Look before you start
None of these need a runtime, so they answer on a project that has never been started:
kranz config check
kranz doctor
kranz plan
kranz services
kranz portsplan is the useful one when a start is not doing what you expected: it prints the waves the runtime will actually use.
$ kranz plan
Wave 1:
shared-infra
Wave 2:
migrate (after shared-infra)
Wave 3:
catalog-api (after migrate)
billing-api (after migrate)Scripting
Non-interactive commands that return a result take --output json and answer with a versioned envelope, so a script never parses prose. The interactive TUI, help text, and generated completion scripts remain text:
if ! kranz doctor --output json > report.json; then
echo "preflight failed"
exit 1
fi
kranz status --output json |
jq -r '.data.services[] | select(.state != "running") | .name'Mutation results carry what changed. For example, restart api --output json returns the full service expansion, and reload --output json returns the added, removed, updated, and pending sets. Pending changes identify the stable service ID and explain why a running snapshot awaits an explicit restart. up -d --output json returns the runtime's full ID, name, PID, and mode.
Exit codes distinguish the failures worth branching on: 2 is your command being wrong, 3 is the configuration being wrong, 4 is something not existing, 6 is a runtime that is not answering. The full table is in the CLI reference.
A whole session
kranz init --from Procfile # convert what the project already has
kranz config check # confirm it loads
kranz up -d # start it in the background
kranz ps # confirm the runtime exists
kranz logs --tail 20 # see what happened
kranz restart api # act on one service
kranz attach # open the interface on it
kranz down # stop everything