reference

Run your own hub

The hub is one container. It keeps its data in Postgres (or, for a small single-box install, in an embedded database on one volume). It needs a hostname with TLS in front, a reverse proxy that passes WebSockets, and outbound HTTPS to the AI provider.

Start it

docker run -d --name be-hub --restart unless-stopped \
  -p 8001:8001 -v be-hub-data:/data \
  -e PUBLIC_URL=https://hub.[yourdomain] \
  -e ADMIN_PASSWORD='…' -e AGENT_TOKEN='…' -e ANTHROPIC_API_KEY='…' \
  be-hub:latest   # ask us for the image and registry, or build it from the repository

Put TLS in front with Caddy, Traefik or nginx, and make sure the proxy passes WebSocket upgrades on /agent. Machines only need outbound 443 to that hostname.

Database

Set DATABASE_URL to a Postgres connection string and the hub keeps everything there: companies, users, machines, history, alerts, patches, scripts. Postgres 14 or newer; one database, one user that owns it; the hub creates and upgrades its own schema at start. Without the variable the hub uses an embedded database file on the data volume, which is fine for a single box you back up yourself.

Moving an existing hub to Postgres needs one change: add DATABASE_URL and restart. On the first start against an empty database the hub copies every table from the embedded file, advances the sequences, and renames the file .imported so it is never read again. Companies, enrollment tokens, users, machines and history all survive; agents reconnect as if nothing happened. The log says what was copied. To go back, remove the variable and rename the file.

Keep the database close to the hub (same host or same private network): the hub runs each query synchronously, so it is latency that matters, not throughput. Back up Postgres the way you back up any Postgres.

Environment

variablepurposedefault
DATABASE_URLa Postgres connection string (postgres://user:password@host:5432/db). Set it and the hub uses Postgres; leave it unset and the hub keeps an embedded database on the volumeembedded database
PUBLIC_URLthe address baked into install pages, scripts and agent configurationrequired
ADMIN_PASSWORDthe admin user's password, applied on every startgenerated once and printed if unset
ADMIN_API_KEYthe admin user's API key, applied on every startgenerated once into the volume if unset
AGENT_TOKENthe enrollment token of the default company on first bootrequired on first boot
ANTHROPIC_API_KEYthe AI provider key BE AI usesBE AI off if unset
CLAUDE_MODEL, CLAUDE_AUTORUN, CLAUDE_DAILY_RUNS, CLAUDE_DEDUPE_MINmodel, alert-driven runs on or off, cap per company per day, dedupe minutessonnet, on, 50, 30
CLAUDE_SCHEDULE_TIME, CLAUDE_SCHEDULE_TZdefault nightly pass time and time zone for companies that set none02:00, Australia/Melbourne
CLAUDE_PRICE_IN_PER_M, CLAUDE_PRICE_OUT_PER_M, CLAUDE_PRICE_CURRENCYcost estimates in the console3, 15, USD
OFFLINE_GRACE_MIN, ALERT_NOTIFY_DELAY_SECdowntime grace period; delay before the first notification5, 60
EMAIL_API_KEY, EMAIL_FROM, EMAIL_API_URLemail alertsoff
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROMSMS and phone-call alertsoff
SAMPLE_RETENTION_DAYS, EVENT_RETENTION_DAYS, HOURLY_RETENTION_DAYSretention14, 365, 730
PORTlistening port inside the container8001

What is on the volume

hub.db (the database, WAL mode), audit.jsonl (append-only audit trail), releases/ (signed agent releases and notes). Back up the volume; a nightly copy of those files is enough. Restore is a copy back.

Upgrades

Pull the new image and restart. Schema changes apply themselves at start. Agents reconnect within two minutes; runs in flight at the moment of restart are closed out and marked, and conversations resume. Pick a quiet time.

Moving from hosted to self-hosted

Ask for a copy of your volume. Start your hub with it, set PUBLIC_URL to the new hostname, and run each company's install line with the apply option on its machines so they point at the new address. Identities and history carry over.

Sizing

A small VM with 2 vCPU and 2 GB of memory serves hundreds of machines. Heartbeats are under 1.5 KB every five minutes; history is rolled up hourly.

Moving the hub to a new address

Every agent holds the hub's address in its own configuration, so a new hostname is a fleet change, not a DNS change. The order matters:

  • Make the new name serve the hub alongside the old one: add the domain to the hub's reverse proxy so it gets a certificate, and set PUBLIC_URL to the new address so install links, install scripts and updates use it from then on. Keep the old name resolving.
  • Roll an agent that has set_server through the old name from Updates; agents older than that cannot be re-pointed remotely and would need the install line with apply on each machine.
  • Press re-point on the hub address panel under Updates. Each online machine is told the new address, writes its own configuration, answers, and reconnects through the new name a few seconds later; the panel groups machines by the name they connect through, so you can see who has moved. Machines offline at the time are re-pointed when you press it again while they are on.
  • Retire the old name only when the panel shows nobody connecting through it.

An agent accepts only a wss://host/agent address; it refuses to move to plain ws:// or to any other path, so a mistake in the address is rejected rather than stranding the machine. Every re-point is in the audit trail with the person who did it.

Try it on one machine first.

The install line takes a minute. Uninstall the agent and you are out.