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_startedDependency Conditions
| Condition | Meaning |
|---|---|
process_started | Process has started (default) |
process_healthy | Readiness probe passed |
process_completed | Process exited (any exit code) |
process_completed_successfully | Process exited with code 0 |
process_log_ready | Specific 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 skippedexit_on_failure— terminates entire composition when the process failsexit_on_end: true— stops composition when process ends regardless of exit codeexit_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: falseHealth 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_healthydependency 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 fileInternal 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 fileAuto-inserted per process: PC_PROC_NAME, PC_REPLICA_NUM.
.env files
process-compose -e .env -e .env.local # multiple files
process-compose --disable-dotenv # disableGo 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 flagOr 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: debugprocess-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 # aliasIn 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: 3Scale at runtime:
process-compose process scale worker 5Miscellaneous Config
is_strict: true # fail on unknown YAML fields (catches typos)
ordered_shutdown: true # shut down in reverse dependency orderordered_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 4CLI 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 downTUI Keyboard Shortcuts
| Key | Action |
|---|---|
↑/↓ | Select process |
Enter | View logs |
F4 | Toggle log screen size |
F5 | Toggle follow logs |
F6 | Toggle log text wrapping |
F7 | Start process (completed/disabled) |
F8 | Toggle process screen size |
F9 | Stop process |
Ctrl+R | Restart process |
Ctrl+E | Edit process config (not persisted) |
Ctrl+Q | Dependency graph view |
Ctrl+T | Theme selector |
n | Namespace operations modal |
F10 | Quit |
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/tokenUnix 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 envREST 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 addressRemote 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=1Integration with devenv / Nix
# devenv.nix
processes = {
api.exec = "go run ./cmd/api";
db.exec = "postgres -D $PGDATA";
};