Running as a Service¶
This guide configures TriOnyx to run as an always-on service that starts on boot, restarts on failure, and survives logout. It uses a systemd user service combined with Docker Compose restart policies for two layers of resilience.
Prerequisites¶
- A working TriOnyx installation (see Getting Started)
- Linux with systemd (Ubuntu 20.04+, Debian 11+, Fedora 34+, etc.)
- Docker Engine with
docker composev2
How it works¶
The setup has two layers:
| Layer | Handles | Mechanism |
|---|---|---|
| Container | Individual service crashes (gateway OOM, connector disconnect) | Docker restart: unless-stopped |
| System | Full stack lifecycle (boot start, Docker daemon restart, compose crash) | systemd user service |
The gateway's built-in health check (GET /health) drives both layers — Docker uses it to detect unhealthy containers, and an optional systemd timer monitors overall availability.
graph TD
BOOT[System Boot] --> DOCKER[Docker Daemon]
BOOT --> LINGER[User Manager via Linger]
LINGER --> SYSTEMD[trionyx.service]
SYSTEMD --> COMPOSE[docker compose up]
COMPOSE --> GW[Gateway]
COMPOSE --> CONN[Connector]
COMPOSE --> PROXY[Docker Proxy]
GW -->|healthcheck| GW
TIMER[trionyx-health.timer] -->|curl /health| GW
1. Enable restart policies¶
The docker-compose.yml should have restart policies and a health check on the gateway. These are included by default:
services:
gateway:
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-sf", "http://127.0.0.1:4000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
# ...
connector:
restart: unless-stopped
depends_on:
gateway:
condition: service_healthy
# ...
docker-proxy:
restart: unless-stopped
# ...
The start_period gives the gateway 60 seconds to compile dependencies and boot the OTP application before health checks begin. The connector waits for the gateway to be healthy before starting.
2. Create the systemd user service¶
Create the unit file:
[Unit]
Description=TriOnyx Agent Gateway
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/<your-user>/Repositories/TriOnyx
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down --timeout 30
Restart=on-failure
RestartSec=10
TimeoutStopSec=45
Environment=PWD=/home/<your-user>/Repositories/TriOnyx
StandardOutput=journal
StandardError=journal
SyslogIdentifier=trionyx
[Install]
WantedBy=default.target
Update the paths
Replace /home/<your-user>/Repositories/TriOnyx with your actual project path in both WorkingDirectory and Environment=PWD=.... The PWD variable is required because docker-compose.yml uses ${PWD} for bind mount paths.
Key design decisions:
docker compose upwithout-d— runs in the foreground so systemd can track the process directlyRestart=on-failure— only restarts on crashes, not on cleansystemctl --user stopRestartSec=10— prevents tight restart loops if Docker isn't ready yetTimeoutStopSec=45— gives containers 30 seconds for graceful shutdown (agent cleanup, session logging)- No
--build— rebuild manually withdocker compose buildwhen Dockerfiles change
3. Create the health check timer (optional)¶
A systemd timer that checks /health every 5 minutes. Failed checks appear in the journal, making it easy to spot intermittent issues or hook up alerting later.
[Unit]
Description=TriOnyx health check
After=trionyx.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'response=$(curl -sf http://127.0.0.1:4000/health 2>&1) && echo "TriOnyx healthy: $response" || (echo "TriOnyx health check FAILED: $response" && exit 1)'
[Unit]
Description=TriOnyx health check timer
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
4. Enable linger¶
By default, systemd stops user services when the user logs out. Linger makes them persist and start at boot:
Verify with:
This is the only step that requires sudo.
5. Enable and start¶
# Reload unit files
systemctl --user daemon-reload
# Enable (auto-start on boot) and start now
systemctl --user enable --now trionyx.service
# Enable the health timer (optional)
systemctl --user enable --now trionyx-health.timer
Verify it's running:
Daily operations¶
# Service status
systemctl --user status trionyx
# Live logs (all services)
journalctl --user -u trionyx -f
# Logs since last boot
journalctl --user -u trionyx -b
# Restart (e.g., after config changes)
systemctl --user restart trionyx
# Stop
systemctl --user stop trionyx
# Health check history
journalctl --user -u trionyx-health --since "1 hour ago"
# List active timers
systemctl --user list-timers
Rebuilding images
After changing Dockerfiles or Go code, rebuild before restarting:
For runtime-only changes (Python files inruntime/), a restart is enough — no rebuild needed.
How failures are handled¶
| Failure | Recovery |
|---|---|
| Gateway container crashes | Docker restarts it (restart: unless-stopped) |
| Connector container crashes | Docker restarts it; waits for gateway health |
| Docker daemon restarts | systemd restarts docker compose up via Restart=on-failure |
| Machine reboots | systemd starts the service at boot via linger |
| Gateway hangs (no crash) | Docker healthcheck marks it unhealthy after 3 failed checks (90s), then restarts |
The gateway's Application.start/2 includes orphaned container cleanup, so agent containers from a previous crash are cleaned up automatically on restart.
Disabling the service¶
To stop running TriOnyx on boot:
Troubleshooting¶
Service fails immediately on start
Check the journal for errors:
Common causes:- Docker daemon not running (
systemctl status docker) - Missing
.envfile or secrets - Port 4000 already in use (
ss -tlnp | grep 4000)
Service starts but connector won't connect
The connector now waits for the gateway to be healthy (via depends_on: condition: service_healthy). If the gateway takes longer than 60 seconds to start (the start_period), the connector may fail.
Check gateway health manually:
Service doesn't start after reboot
Verify linger is enabled:
IfLinger=no, re-run sudo loginctl enable-linger $(whoami).
Also check that the service is enabled:
Logs are too verbose
Docker Compose outputs all container logs to stdout, which systemd captures. To filter:
User systemd can't depend on Docker
User-mode systemd units cannot formally depend on system units like docker.service. The Restart=on-failure with RestartSec=10 handles this — if Docker isn't ready yet, the service retries after 10 seconds. In practice, Docker starts well before user services on a standard setup.