Deploy the hub as a service

Run the container under systemd with Podman Quadlet or Docker, start it on boot, or bring the stack up with compose.

The published image is ghcr.io/abn/agent-hub. This page runs it as a long-lived service: under Podman Quadlet or Docker with an auto-start policy, or through the compose file. It does not repeat how to build or configure the hub; see the quickstart for the binary and the full settings table.

Two properties of the image shape every option below. It writes only to its data directory, so it runs with a read-only root filesystem and one mounted volume. And it drains for ten seconds on SIGTERM before it checkpoints the store and exits, so the service stop timeout must be longer than that drain.

Prerequisites

Thing Why
Podman with Quadlet, or Docker runs the published image
an admin token the control surface refuses every request without one
a data volume hub.db, the session files, and the artifact blobs live there

Set the configuration once

Both options below read the same environment file, ~/.config/agent-hub/hub.env at mode 0600:

HUB_ADMIN_TOKEN=<a long random token>
HUB_PUBLIC_URL=https://hub.example

Set HUB_PUBLIC_URL whenever the hub sits behind a reverse proxy that rewrites the host: the served bootstrap, the artifact frame policy and the share links all name that address. Generate the token with openssl rand -hex 32.

To be nudged when something waits while the app is closed, add a notify target you run, such as a self-hosted ntfy topic on the LAN:

HUB_NOTIFY_URL=https://ntfy.lan/agent-hub
HUB_NOTIFY_TOKEN=<an ntfy access token>

The hub POSTs one fixed sentence there and nothing about the item itself. Operations covers the coalescing and the failure behaviour.

Compose

The repository ships deploy/compose.yaml. It builds the Containerfile, mounts a named volume at /data, keeps the rest of the filesystem read-only, sets a stop grace period above the hub's drain, and refuses to render without HUB_ADMIN_TOKEN:

HUB_ADMIN_TOKEN="$(openssl rand -hex 32)" \
  docker compose -f deploy/compose.yaml up --detach

Podman reads the same file through podman compose. The service carries restart: unless-stopped, so the container returns after a host restart unless it was stopped by hand.

Podman Quadlet

Quadlet is the supported way to run a container under systemd, per user or system-wide, and it supersedes podman generate systemd. Save the unit as ~/.config/containers/systemd/agent-hub.container. The repository ships this file as deploy/agent-hub.container, so copy it rather than retyping it:

[Unit]
Description=Agent Hub
After=network-online.target
Wants=network-online.target

[Container]
Image=ghcr.io/abn/agent-hub:1.0.0
ContainerName=agent-hub
PublishPort=127.0.0.1:8080:8080
Volume=agent-hub-data:/data
EnvironmentFile=%h/.config/agent-hub/hub.env
Environment=HUB_DATA_DIR=/data
Environment=HUB_BIND=0.0.0.0:8080
ReadOnly=true
Tmpfs=/tmp
HealthCmd=/usr/local/bin/agent-hub health --url http://127.0.0.1:8080
HealthInterval=30s
HealthStartPeriod=10s
HealthRetries=3

[Service]
Restart=always
TimeoutStopSec=20

[Install]
WantedBy=default.target

Load and start it:

systemctl --user daemon-reload
systemctl --user enable --now agent-hub
systemctl --user status agent-hub

enable writes the [Install] target, so the service starts whenever the user's systemd starts. A user manager stops at logout unless lingering is on, so on a headless node turn it on once and the service survives with no login:

loginctl enable-linger "$USER"

For a system-wide service, put the unit in /etc/containers/systemd/, use WantedBy=multi-user.target, and enable it with sudo systemctl enable --now agent-hub. Rootless Podman is the better default when it works on the host.

The healthcheck is the image's own: the binary's health subcommand GETs /readyz and exits non-zero the moment the store stops answering. Quadlet forwards it to Podman, so podman healthcheck run agent-hub runs it by hand and systemctl --user status shows the result.

Docker

Docker has no Quadlet. Run the container with an auto-start policy so the daemon brings it back after a restart:

docker run --detach --name agent-hub \
  --restart unless-stopped \
  --publish 127.0.0.1:8080:8080 \
  --env-file ~/.config/agent-hub/hub.env \
  --volume agent-hub-data:/data \
  --stop-timeout 20 \
  ghcr.io/abn/agent-hub:1.0.0

unless-stopped restarts the container after a daemon or host restart, and --stop-timeout 20 matches the drain so the store checkpoints on a clean stop. Compose sets both, which is the shorter path when a compose file is acceptable. For a unit-managed lifecycle without Podman, run the compose file from a small systemd unit whose ExecStart is docker compose up.

Reaching the hub

The port above is published to loopback only. Put a TLS-terminating reverse proxy in front and set HUB_PUBLIC_URL to the address callers use. An upgrade is a pull of the new image tag and a restart of the unit; the store migrates forward on startup. Operations covers backup, verification, upgrade and rollback.

See also

  • Quickstart - build the binary, the settings table, compose
  • Operations - backup, restore, upgrade and roll back
  • Components - the deployment shape and the data volume