This walkthrough takes a freshly built gula binary from "I cloned the repo"
to "three cooperating processes are running with dependency ordering, a
readiness probe, a memory limit, and graceful shutdown" — without leaving the
terminal.
If you have not built Gula yet, see Installation first.
This page assumes a target/release/gula binary on a Linux host.
A tiny three-process stack:
config_writer— a one-shot setup job that writes a config file.api— a long-running fake API that waits for the config, then becomes ready when its health endpoint responds.worker— a long-running consumer that only starts afterapiis ready, with a memory limit that triggers a restart on overrun.
By the end you will have used:
- DAG ordering with both
depends_on_completionanddepends_on_ready. - A
readiness_probe. - A
memory_threshold_action. - Graceful shutdown via Ctrl-C.
mkdir -p /tmp/gula-tutorial && cd /tmp/gula-tutorial
mkdir -p logs scriptsThese stand in for real services. Each is just enough to exercise the supervisor.
cat > scripts/config_writer.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "writing config..."
sleep 1
echo 'api_port=8080' > /tmp/gula-tutorial/app.conf
echo "done"
EOF
cat > scripts/api.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Pretend we boot for a moment, then expose a "health" file.
sleep 2
: > /tmp/gula-tutorial/api.ready
trap 'rm -f /tmp/gula-tutorial/api.ready; exit 0' TERM INT
echo "api up"
while true; do sleep 1; done
EOF
cat > scripts/worker.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "worker started"
trap 'echo "worker stopping"; exit 0' TERM INT
i=0
while true; do
echo "tick $i"
i=$((i + 1))
sleep 1
done
EOF
chmod +x scripts/*.shSave this as gula.yaml:
# yaml-language-server: $schema=https://raw.githubusercontent.com/unstablecursor/gula/main/schemas/gula.schema.json
logs_dir: "logs"
processes:
- name: "config_writer"
command: "/tmp/gula-tutorial/scripts/config_writer.sh"
timeout_seconds: 30
- name: "api"
command: "/tmp/gula-tutorial/scripts/api.sh"
timeout_seconds: 86400
depends_on_completion: ["config_writer"]
readiness_probe:
command: "test -f /tmp/gula-tutorial/api.ready"
interval_ms: 250
timeout_seconds: 30
- name: "worker"
command: "/tmp/gula-tutorial/scripts/worker.sh"
timeout_seconds: 86400
depends_on_ready: ["api"]
memory_limit_bytes: 134217728 # 128 MB
memory_threshold_action: restart
restart_policy:
max_restarts: 3
backoff_base_seconds: 1.0And save the system config as gula_config.yaml:
cleanup_timeout_seconds: 5
metrics_flush_interval_ticks: 100gula validate --config gula.yaml --sys-config gula_config.yamlYou should see Configuration is valid. If not, the error message names the
field and the reason — fix it and re-run.
gula run --config gula.yaml --sys-config gula_config.yamlWhat you should observe:
config_writerruns first and exits 0 within ~1 s.apistarts, sleeps 2 s, then creates/tmp/gula-tutorial/api.ready. The readiness probe (test -f ...) flips to success.workeronly starts afterapireports ready. It then printstick Nevery second.
In a second shell:
ls /tmp/gula-tutorial/logs
tail -f /tmp/gula-tutorial/logs/worker.log
column -ts, /tmp/gula-tutorial/logs/worker.csv | headEach managed process gets its own <name>.log and <name>.csv. The
supervisor's own usage is in gula_metrics.csv.
In the first shell, press Ctrl-C once.
Gula records the signal, forwards that same shutdown signal to all process groups, interrupts
restart back-offs, opens a 5 s grace window (from cleanup_timeout_seconds),
and only escalates to SIGKILL for processes that did not exit. You should
see your worker stopping trap message before the supervisor returns.
Exit code 0 means "clean signal shutdown or all processes succeeded".
- Configuration reference — every field, default, and bound.
- Process state machine — what
STARTING,READY,BACKOFF, etc. actually mean inside the supervisor. - Exit code matrix — what each failure mode maps to.
- Security model — who Gula should run as and which Linux capabilities it needs.
- Troubleshooting — first stop when something unexpected happens.