process-compose

docker-compose-like process scheduler and orchestrator for non-containerized apps. Runs multiple processes from a YAML config with dependency ordering, log aggregation, health checks, and a terminal UI. No containers needed — single Go binary.

process-compose.yaml

version: "0.5"
 
# Global environment variables
environment:
  - GLOBAL_VAR=value
 
# Global variables (Go template engine)
vars:
  VERSION: v1.2.3
 
# Global log location
log_location: ./pc.global.log
log_level: info  # trace, debug, info, warn, error, fatal, panic
 
processes:
  db:
    command: "postgres -D /var/lib/postgres"
    readiness_probe:
      exec:
        command: "pg_isready"
      initial_delay_seconds: 2
      period_seconds: 5
 
  api:
    command: "go run ./cmd/api"
    depends_on:
      db:
        condition: process_healthy
    environment:
      - DATABASE_URL=postgres://localhost/mydb
    availability:
      restart: on_failure
      max_restarts: 3
      backoff_seconds: 2
 
  worker:
    command: "go run ./cmd/worker"
    depends_on:
      api:
        condition: process_started

Dependency Conditions

ConditionMeaning
process_startedProcess has started (default)
process_healthyReadiness probe passed
process_completedProcess exited (any exit code)
process_completed_successfullyProcess exited with code 0
process_log_readySpecific log line matched via ready_log_line

process_log_ready example

processes:
  world:
    command: "echo Connected"
    depends_on:
      hello:
        condition: process_log_ready
  hello:
    command: |
      echo 'Preparing...'
      sleep 1
      echo 'I am ready to accept connections now'
    ready_log_line: "ready to accept connections"

Availability / Restart Policies

processes:
  api:
    command: "..."
    availability:
      restart: on_failure        # no | on_failure | exit_on_failure | always
      backoff_seconds: 2
      max_restarts: 5
      exit_on_end: false         # stop all when this process ends
      exit_on_skipped: false     # stop all if this process is skipped
  • exit_on_failure — terminates entire composition when the process fails
  • exit_on_end: true — stops composition when process ends regardless of exit code
  • exit_on_skipped: true — stops when a dependency prevents this process from running

Process Options

processes:
  example:
    command: "..."
    description: "Human-readable description"
    working_dir: "/path/to/dir"
    namespace: myns
    disabled: true           # appears in UI but won't auto-start
    is_daemon: true          # background/forked process
    is_foreground: true      # interactive, requires full terminal (F7 to start)
    is_tty: true             # allocate pseudo-terminal (for color output etc.)
    is_elevated: true        # run with sudo (Linux/macOS) or runas (Windows)
    is_template_disabled: true   # skip Go template rendering for command
    is_dotenv_disabled: true     # don't inject .env file variables
    replicas: 2              # run N concurrent instances
    launch_timeout_seconds: 2
    ready_log_line: "pattern"
 
    shutdown:
      command: "docker stop mycontainer"
      timeout_seconds: 10
      signal: 15             # SIGTERM
      parent_only: false

Health Checks

Both probes support the same timing parameters. Probes are mutually exclusive per process.

processes:
  api:
    readiness_probe:
      http_get:
        host: localhost
        port: 8080
        path: /health
        scheme: http          # http | https
        status_code: 200      # expected response code
      initial_delay_seconds: 2
      period_seconds: 10
      timeout_seconds: 1
      success_threshold: 1
      failure_threshold: 3
 
    liveness_probe:
      exec:
        command: "pg_isready"
      period_seconds: 15
  • Readiness probe — gates process_healthy dependency condition; unhealthy → restart per availability policy
  • Liveness probe — monitors daemons; unhealthy → restart per availability policy

Logging

log_location: ./pc.log        # global log file
log_level: info
 
log_configuration:
  fields_order: ["time", "level", "message"]
  disable_json: true
  timestamp_format: "2006-01-02 15:04:05"
  no_metadata: false          # omit process name / replica number
  add_timestamp: false
  no_color: false
  flush_each_line: false
  rotation:
    max_size_mb: 100
    max_age_days: 7
    max_backups: 3
    compress: true
 
processes:
  api:
    log_location: ./api.log   # per-process log file

Internal log default: /tmp/process-compose-$USER.log. Disable colors: PC_LOG_NO_COLOR=1.

Environment & Variables

Environment variables

environment:
  - GLOBAL_VAR=value
 
processes:
  api:
    environment:
      - DATABASE_URL=postgres://localhost/db
    env_file: ".env.backend"   # per-process .env file

Auto-inserted per process: PC_PROC_NAME, PC_REPLICA_NUM.

.env files

process-compose -e .env -e .env.local   # multiple files
process-compose --disable-dotenv        # disable

Go template vars

vars:
  VERSION: v1.2.3
 
processes:
  version:
    vars:
      LOG_DIR: "./logs"
    command: "echo {{.VERSION}}"
    log_location: "{{.LOG_DIR}}/app.log"

Rendered fields: command, working_dir, log_location, description, environment values, probe exec.command, probe http_get.*.

Disable rendering per process: is_template_disabled: true.

Escape template syntax: {{ "{{.State.Running}}" }} or $$VAR for env vars.

env_cmds — dynamic env from commands

env_cmds:
  DATE: "date"
  OS_NAME: "awk -F= '/PRETTY/ {print $2}' /etc/os-release"
 
processes:
  my-process:
    command: "echo Current date is: $${DATE}"

Commands must complete within 2 seconds and output a single line.

Disable auto expansion

disable_env_expansion: true   # global flag

Or per-command: echo I am $$MY_VAR

Shell Configuration

shell:
  shell_command: "bash"    # or python3, fish, etc.
  shell_argument: "-c"

Platform defaults: Linux/macOS → bash, Windows → cmd. Override with COMPOSE_SHELL env var.

Namespaces

processes:
  debugger:
    command: "..."
    namespace: debug
process-compose -n ns1 -n ns3          # start only these namespaces
process-compose namespace list
process-compose namespace start <name>
process-compose namespace stop <name>
process-compose namespace restart <name>
process-compose ns ls                   # alias

In TUI: press n to open Namespace Operations modal.

Process Scaling / Replicas

processes:
  worker:
    command: "worker --id {PC_REPLICA_NUM}"
    log_location: ./worker.{PC_REPLICA_NUM}.log
    replicas: 3

Scale at runtime:

process-compose process scale worker 5

Miscellaneous Config

is_strict: true           # fail on unknown YAML fields (catches typos)
ordered_shutdown: true    # shut down in reverse dependency order

ordered_shutdown also via: process-compose --ordered-shutdown or PC_ORDERED_SHUTDOWN=1.

Multiline commands

processes:
  setup:
    command: >
      echo 1
      && echo 2
  script:
    command: |
      echo 3
      echo 4

CLI Commands

# Start
process-compose                          # start all, with TUI
process-compose up                       # same
process-compose up api worker            # specific processes + their dependencies
process-compose up api --no-deps         # specific processes only
process-compose -f custom.yaml           # custom config file
process-compose -f base.yaml -f override.yaml   # merge configs
 
# Process control (against running instance)
process-compose process list
process-compose process start <name>
process-compose process stop <name>
process-compose process restart <name>
process-compose process scale <name> <n>
 
# Namespace control
process-compose namespace list
process-compose namespace start <name>
process-compose namespace stop <name>
process-compose namespace restart <name>
 
# Attach TUI to running instance
process-compose attach
 
# Dependency graph
process-compose graph                    # ASCII tree
process-compose graph --format mermaid
process-compose graph --format json
 
# Config update (live reload)
process-compose project update -f process-compose.yaml
 
# Shutdown
process-compose down

TUI Keyboard Shortcuts

KeyAction
↑/↓Select process
EnterView logs
F4Toggle log screen size
F5Toggle follow logs
F6Toggle log text wrapping
F7Start process (completed/disabled)
F8Toggle process screen size
F9Stop process
Ctrl+RRestart process
Ctrl+EEdit process config (not persisted)
Ctrl+QDependency graph view
Ctrl+TTheme selector
nNamespace operations modal
F10Quit

TUI config files in $XDG_CONFIG_HOME/process-compose/: shortcuts.yaml, theme.yaml, settings.yaml. Use --read-only to disable auto-save of settings.

Dependency graph colors: green = running, yellow = pending/launching, blue = completed, red = failed, white = other.

REST API & Remote Access

Default address: http://localhost:8080. Change with -p PORT or PC_PORT_NUM.

# Auth token (min 20 chars), sent as X-PC-Token-Key header
PC_API_TOKEN=myverylongsecrettoken process-compose
# or
process-compose --token-file /path/to/token

Unix Domain Sockets

process-compose -U                        # auto-generate path from PID
process-compose --unix-socket /path/to/s  # manual path
PC_SOCKET_PATH="/path/to/socket"          # via env

REST endpoints

  • GET /graph — JSON dependency graph
  • Standard CRUD for processes, namespaces, logs

TUI attach (remote)

process-compose attach                   # connect to localhost:8080
process-compose attach -p 9090 -a host   # custom address

Remote TUI shows ⚡ instead of 🔥.

.pc_env File

Controls local process-compose settings:

PC_DISABLE_TUI=1
PC_PORT_NUM=8080
PC_NO_SERVER=1
PC_ORDERED_SHUTDOWN=1
PC_LOG_NO_COLOR=1

Integration with devenv / Nix

# devenv.nix
processes = {
  api.exec = "go run ./cmd/api";
  db.exec = "postgres -D $PGDATA";
};