process-compose
6 min read

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

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

yaml
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

yaml
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

yaml
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.

yaml
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

yaml
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

yaml
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

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

Go template vars

yaml
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

yaml
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

yaml
disable_env_expansion: true # global flag

Or per-command: echo I am $$MY_VAR

Shell Configuration

yaml
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

yaml
processes:
debugger:
command: "..."
namespace: debug

sh
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

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

Scale at runtime:

sh
process-compose process scale worker 5

Miscellaneous Config

yaml
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

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

CLI Commands

sh
# 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.

sh
# 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

sh
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)

sh
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:

sh
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

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