Deploy as a service

Run the hub under Podman Quadlet or Docker, and start it on boot

By the end you will have the hub running as a long-lived service that comes back after a restart and drains cleanly on stop. This page starts from a built image; building one, the binary, and every setting are in Install and run the hub.

Before you start

Thing Why
Podman with Quadlet, or Docker runs the published image ghcr.io/abn/agent-hub
An admin token the control surface refuses every request without one
A data volume hub.db, the session brains, and the artifact blobs live there

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 a service's stop timeout must be longer than that drain.

Set the configuration once

Every option below reads 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

Generate the token with openssl rand -hex 32. 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. Network and access covers the proxy.

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

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

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 it as deploy/agent-hub.container, so copy it rather than retype 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.

You should now see

The unit enabled or the container running, and a service that comes back after a restart. Confirm the hub is ready from the node:

agent-hub health --url http://127.0.0.1:8080

It prints ready and exits 0 while the store answers.

See also