Skip to content

Annotated kranz.yaml ​

One file using every part of the format, with a comment on each field. Nothing here is required except project and one service or action group — copy the parts you need and delete the rest.

For field types, defaults, and validation rules, see the configuration reference.

This page includes examples/reference/kranz.yaml from the repository, and a test loads and validates that file, so what you read here always parses.

yaml
# Project identity
project: Northstar          # Required. Shown in the header.
version: "1.0"              # Optional label for you; Kranz does not read it.
runtime:
  name: northstar-dev       # Stable name for -p and KRANZ_PROJECT addressing.

# Appearance for this project
# Personal preferences live in your user settings file instead. Ctrl+T opens
# the live picker and can write either place.
ui:
  theme: tokyo-night        # A named built-in theme.
  accent: "#7AA2F7"         # Overrides the theme accent.
  background: terminal      # terminal | theme | #RRGGBB
  color_mode: auto          # auto | dark | light

# Shared execution context
# Inherited by every service that does not override it.
defaults:
  dir: .                    # Relative to this file, not to your shell.
  shell: /bin/sh            # Default is /bin/bash.
  env:
    NODE_ENV: development
  env_files: [.env.shared]  # Applied in order, before service-level files.

# Project-level one-shot commands
# For work that belongs to no single service.
action_groups:
  infrastructure:
    description: Shared development infrastructure
    dir: infra              # Inherited by the actions below.
    env_files: [.env.infra]
    actions:
      up:
        command: docker compose up -d
        timeout: 2m         # Covers the whole command.
      inspect:
        command: docker compose ps
      reset:
        command: docker compose down --volumes
        confirm: true       # Destructive, so ask first.

services:
  # A plain long-running process
  api:
    description: REST API and background scheduler
    command: npm run dev    # Shorthand for lifecycle.start.command.
    dir: apps/api           # Relative to this file.
    shell: /bin/bash
    env_files: [.env, .env.local]
    env:
      PORT: "3001"          # Highest precedence; wins over every dotenv file.
    ports: [3001]           # Checked before start; conflicts are reported.
    tags: [backend, core]   # Expandable groups in the list.

    # What "ready" means for anything depending on this service.
    healthcheck:
      readiness:
        type: http
        url: http://127.0.0.1:3001/ready
        interval: 2s
        timeout: 1s
        failure_threshold: 5
      liveness:             # Independent of readiness.
        type: tcp
        port: 3001
        initial_delay: 10s
        interval: 15s

    # Recovery for this process only. Detached resources are never restarted.
    availability:
      restart: on_failure   # no | always | on_failure | exit_on_failure
      backoff: 2s
      max_restarts: 3

    # How the process group is stopped.
    shutdown:
      signal: 15            # SIGTERM.
      timeout: 10s          # Then the group is killed.

    # One-shot commands owned by this service.
    actions:
      migrate:
        command: npm run db:migrate
        description: Apply pending database migrations
        timeout: 2m
        confirm: true
      test:
        command: npm test
        timeout: 5m
      console:
        command: npm run console
        interactive: true   # Hands the terminal over until the command exits.

    # Work that must succeed before this service starts. Each entry points at
    # an action that also stays runnable on its own.
    before_start:
      - group: infrastructure
        action: up
        run: always         # Before every start; default is once per session.
      - action: migrate     # This service's own action, once per session.

  # A service that waits for another one
  web:
    command: npm run dev
    dir: apps/web
    tags: [frontend]
    detect_ports: true      # Discover the port Vite actually chose.
    depends_on: [api]
    dependency_conditions:
      api:
        condition: process_healthy   # Wait for readiness, not just for a PID.
    healthcheck:
      readiness:
        type: tcp           # No port: use the first detected listener.
        interval: 3s

  # A worker with no endpoint to probe
  worker:
    command: npm run worker
    dir: apps/worker
    is_dotenv_disabled: true             # Do not load the adjacent .env.
    tags: [backend]
    depends_on: [api]
    ready_log_line: "worker listening"   # Readiness from output instead.
    success_exit_codes: [0, 2]           # 2 is also a clean exit here.
    disabled: true                       # Visible, but not started by `a`.

  # An external resource Kranz does not own
  # The start command finishes while the thing it started keeps running.
  remote-stack:
    description: Docker stack on the build host
    supervision: detached   # process (default) | detached
    stop_on_exit: false     # Survives Kranz. This is the detached default.
    detect_ports: false     # No local process group to inspect.
    tags: [infra, remote]
    lifecycle:
      start:
        command: ssh build-host 'cd app && docker compose up -d'
        timeout: 2m
      stop:
        command: ssh build-host 'cd app && docker compose down'
        timeout: 2m
      status:
        # Does the resource exist? This is not a health check.
        type: command
        command: ssh build-host 'cd app && docker compose ps -q api | grep -q .'
        interval: 10s
        stopped_interval: 30s
        timeout: 15s
        # With no exit codes declared, 0 means running and anything else means
        # stopped. Declare stopped_exit_codes only when the probe can also say
        # "I could not tell" — see the note below this file.
      logs:
        command: ssh build-host 'cd app && docker compose logs -f --tail=100'
    ports: [5432, 6379]     # Documentation; discovery is unavailable here.
    actions:
      status:
        command: ssh build-host 'cd app && docker compose ps -a'
        timeout: 30s

The three-way status contract ​

The remote-stack probe above uses the default: exit 0 is running, anything else is stopped. Declare both code sets when your probe can distinguish "not running" from "I could not find out":

yaml
status:
  type: command
  # 0 = running, 3 = stopped, anything else = the host did not answer
  command: ssh build-host ./stack-status.sh
  running_exit_codes: [0]
  stopped_exit_codes: [3]
  failure_threshold: 2      # Two unclassified probes in a row → unknown

Without stopped_exit_codes, an unreachable host would be reported as stopped, which is a claim Kranz has no evidence for. With it, the service goes unknown and Kranz stops guessing.

Layering personal overrides ​

Keep the shared file in version control and your own tweaks out of it:

bash
kranz -f kranz.yaml --override kranz.local.yaml
yaml
# kranz.local.yaml — only the differences
services:
  api:
    env:
      LOG_LEVEL: debug
    lifecycle:
      start:
        confirm: true       # Overrides only this, keeping the command above.

Released under the MIT License.