Operational

Reference: systemd Service + Timer for a Long-Running Agent

TL;DR: How I run a long-lived Node agent on a plain Hetzner box without Docker — a systemd service for the always-on process plus systemd timers for scheduled work (knowledge ingestion, daily planning, due-task runs). systemd handles restarts, logging, and scheduling, so there's no separate process manager or cron.

Not everything needs Docker. For a long-running Node agent I deploy it as a systemd service and drive its scheduled tasks with systemd timers — restarts, journald logging, and scheduling all come from the init system.

The always-on service

# /etc/systemd/system/agent.service
[Unit]
Description=Marketing agent (reason→tool loop)
After=network-online.target

[Service]
WorkingDirectory=/opt/agent
ExecStart=/usr/bin/node dist/index.js
Restart=on-failure
EnvironmentFile=/opt/agent/.env

[Install]
WantedBy=multi-user.target

Scheduled work as timers

Instead of cron, each recurring job is a oneshot service plus a timer. For example, re-ingesting the knowledge base every morning:

# /etc/systemd/system/agent-ingest.timer
[Unit]
Description=Daily knowledge-base ingest

[Timer]
OnCalendar=*-*-* 09:00:00
Persistent=true

[Install]
WantedBy=timers.target

In practice this agent runs one service plus several timers — knowledge ingest, daily planning, and a due-task runner — each a small oneshot unit fired on its own schedule.

Operating it

systemctl daemon-reload
systemctl enable --now agent.service agent-ingest.timer
systemctl list-timers                 # confirm next run times
journalctl -u agent.service -f        # tail logs (no separate log files)
systemctl status agent-ingest.timer

Why this over cron + a process manager: one mechanism handles supervision, restart-on-failure, environment loading, scheduling, and logging. Persistent=true even catches up a missed run if the box was down at the scheduled time.