Restart Policies
Runix supports three restart policies that control automatic process restart after exit.
Policies
| Policy | On Exit Code 0 | On Exit Code ≠ 0 | Use Case |
|---|---|---|---|
always | Restart | Restart | Long-running services that should never be down |
on-failure | No restart | Restart | Workers that should restart on crash but not on clean exit |
never | No restart | No restart | One-shot tasks, batch jobs |
Configuration
processes:
api:
entrypoint: ./cmd/api
restart_policy: always
max_restarts: 10
worker:
entrypoint: worker.py
restart_policy: on-failure
max_restarts: 5
migrate:
entrypoint: migrate.sh
restart_policy: never
Exponential Backoff
When a restart is triggered, Runix waits using exponential backoff to avoid rapid restart loops.
Backoff Formula
delay = min(backoff_base * 2^attempt, backoff_max)
Configuration
| Field | Default | Description |
|---|---|---|
backoff_base | 1s | Initial delay after first crash |
backoff_max | 60s | Maximum delay cap |
Example Progression
With defaults (base: 1s, max: 60s):
| Restart # | Delay |
|---|---|
| 1 | 1s |
| 2 | 2s |
| 3 | 4s |
| 4 | 8s |
| 5 | 16s |
| 6 | 32s |
| 7 | 60s (capped) |
| 8 | 60s (capped) |
Implementation
The backoff calculator is in internal/supervisor/backoff.go:
type Backoff struct {
Base time.Duration
Max time.Duration
attempt int
}
func (b *Backoff) Next() time.Duration {
delay := b.Base * time.Duration(1<<uint(b.attempt))
b.attempt++
if delay > b.Max {
return b.Max
}
return delay
}
func (b *Backoff) Reset() {
b.attempt = 0
}
The backoff is reset to 0 when the process stays running for a sustained period (indicating stable operation).
Max Restarts
The max_restarts field caps the total number of automatic restarts:
| Value | Behavior |
|---|---|
0 | Unlimited restarts |
N > 0 | Stop restarting after N attempts |
When max_restarts is reached:
- The process stays in
crashedstate - No more automatic restarts
- Manual
runix restart apistill works - The restart counter resets on manual restart
Interaction with Health Checks
When health checks are configured:
- Process starts → health checker begins polling
- Health check fails N consecutive times (default 3) → process marked unhealthy
onUnhealthycallback triggers a restart- Restart uses the same backoff and max_restarts limits
What's Next
- Process Lifecycle — Full start/stop/restart sequences
- Health Checks — HTTP/TCP/command health monitoring
- Process State Machine — State transitions