Deploy and operate

Deployment topologies

The shapes a Hubzoid deployment can take, which processes each one runs, where it keeps its data, and when to choose PostgreSQL.

A deployment topology is how many Hubs share one Hubzoid deployment, and where their data lives. Every shape is built from the same three processes, so choosing one is mostly a choice about sign-in and storage.

When to use it

Read this before your first deployment past a laptop, and again when you add a second Hub, move to PostgreSQL or run Hubzoid on a container platform. Pick the shape before you provision a server, because moving an existing deployment from SQLite to PostgreSQL is a manual step.

SituationChoose
One team, one agent, one serverOne Hub on SQLite, behind a reverse proxy
Several teams on one server who should sign in onceSeveral Hubs in one hubzoid run, on SQLite
Hubs that must keep separate accounts and URLsSeparate hubzoid run processes, one per Hub
A managed database, point-in-time recovery, or more than one machineEither shape with PostgreSQL
pip install fails on the host, or your platform runs containersThe Docker image, one container per Hub

The smallest real example

One Hub on one machine is a single command. It starts every process the Hub needs and keeps the Hub's data in its own folder:

Terminal
hubzoid run my-hub
Output
✓ Hubzoid is ready: http://127.0.0.1:3080  (local mode, sign-in off)

Everything this Hub writes lands in my-hub/.hubzoid/. Turn sign-in on and put a TLS proxy in front, and the same command serves a team. Single server walks through that.

How it works

  1. Browser
  2. TLS proxy
  3. Edge
  4. Bridge
  5. Model provider
A chat message on one Hub. The edge forwards it to the Hub's bridge on loopback, and the bridge calls the model you configured.

hubzoid run starts and supervises these processes. A process that exits is started again on its own.

ProcessListens onWhat it does
EdgeThe public port, PORT (default 3080), on --host (default 127.0.0.1)The single front door. Sends requests to the Hub's bridge and inbound messages to the inbound server. Refuses the bridge's internal API.
Bridge127.0.0.1, from port 8000 upOne per Hub. Runs the agent, its tools, workflows and schedules, the web app with Settings, and the hosted MCP server. Busy ports are skipped.
Inbound server127.0.0.1:HUBZOID_INBOUND_PORT (default 8100)Only when a WhatsApp, Telegram or generic webhook channel is configured.

The bridge never listens on a public interface. The edge answers 404 for the bridge's internal API (/v1, /uploads and /otel), which only Hubzoid's own processes use on the same machine. The one exception is the OpenAI-compatible chat API (/v1/chat/completions, /v1/models), which the edge forwards only with a personal API key. See the security model for every public path.

Supported shapes

ShapeHow you run itAccounts and sign-in
One Hubhubzoid run <hub>Its own accounts, in the Hub's operational store
Separate Hubs on one machineOne hubzoid run per Hub, each with its own PORTSeparate accounts, sign-in and URL for each Hub
Several Hubs togetherhubzoid run with a hubzoid.yaml, or hubzoid run <hub> <hub>One set of accounts, one sign-in and one Settings page for every Hub
ContainerThe Hubzoid image, which runs hubzoid run /hubAs for one Hub
A hosting providerhubzoid deploy init railway (or maritime) writes the filesAs for one Hub. Not yet verified on either provider's live service.
  1. TLS proxy
  2. Edge
  3. Bridge for each Hub
  4. Shared operational store
Several Hubs in one run. The edge reaches every Hub's bridge on loopback, and all bridges read and write one operational store.

Hubs that run together share one login surface by design. When Hubs need separate login realms, run them as separate Hubs instead. Several Hubs covers hubzoid.yaml and the URL layout.

Rules for every shape

  • One bridge per Hub. Two processes serving the same Hub at once is not supported. On a container platform, run each Hub as its own service with a desired count of 1 and no autoscaling.
  • SQLite stays on a local disk. Do not put SQLite files on a network filesystem such as NFS or EFS, because file locking over the network corrupts them. Use PostgreSQL when processes run on different machines.
  • One SQLite workflow database per Hub. When several Hubs run together, each keeps its own .hubzoid/dbos.db. hubzoid run refuses to start a deployment of several Hubs whose HUBZOID_DBOS_DB names a SQLite file.
  • Unique Hub folder names. A Hub's folder name is its access domain. Within one deployment, names must differ even ignoring case.
  • SQLite 3.42 or newer on Python 3.12. The workflow engine needs it for schedules and workflows. hubzoid doctor reports it as deps.sqlite. Python from python.org, uv or Homebrew, Debian 13 and Ubuntu 24.04 ship a new enough SQLite. PostgreSQL avoids the requirement.

There is no certified maximum number of Hubs or people. Size the machine for the workload you run, and rehearse a backup, a restore and an interrupted workflow on a copy of the deployment before rollout.

Where data lives

StoreWhat it holdsOne Hub (SQLite)Several Hubs (SQLite)
Operational storeAccounts, sessions, conversations, connector credentials, grants, access changes, tool decisions, usage, workflow state, webhook events and alerts<hub>/.hubzoid/hub.db<data-dir>/hubzoid-operational.db, shared by every Hub
Workflow engineRun history and checkpoints for schedules and workflows<hub>/.hubzoid/dbos.db<hub>/.hubzoid/dbos.db for each Hub
Hub databaseWhatsApp and Telegram conversation history<hub>/.hubzoid/hub.db<hub>/.hubzoid/hub.db for each Hub
FilesChat uploads and files the agent made<hub>/.hubzoid/chats/, <hub>/output/The same, in each Hub
Deployment keyEncrypts stored credentials and signs messages between Hubzoid processes<hub>/.hubzoid/secret.key, or HUBZOID_SECRET_KEY<data-dir>/secret.key, or HUBZOID_SECRET_KEY
How Hubzoid finds each store

In a standalone Hub the operational store and the Hub database share one SQLite file. Each keeps its own schema version table, so they never collide.

Hubzoid finds each store in a fixed order:

StoreResolution order
Operational storeThe registered deployment manifest, then HUBZOID_OPERATIONAL_DB, then DATABASE_URL, then the Hub's own SQLite file
Workflow engineThe Hub's entry in the deployment manifest, then HUBZOID_DBOS_DB, then DATABASE_URL when it is PostgreSQL, then <hub>/.hubzoid/dbos.db
Hub databaseDATABASE_URL, then <hub>/.hubzoid/hub.db

When several Hubs run together, the manifest (<data-dir>/deployment.json) is authoritative. A bridge or operator command whose HUBZOID_OPERATIONAL_DB or HUBZOID_DBOS_DB disagrees with it stops with an error instead of writing to a second store.

SQLite or PostgreSQL

SQLite is the default and needs no setup. It suits one machine with one bridge per Hub, and hubzoid backup copies it consistently while the Hub runs.

Choose PostgreSQL for a managed database server with its own backups and point-in-time recovery, or for processes on more than one machine. Install the driver extra and use the postgresql+psycopg:// URL form, because Hubzoid installs the psycopg 3 driver only:

Terminal
pip install "hubzoid[postgres]"

Hubzoid upgrades its own tables (prefixed hz_) at start. The workflow engine keeps its tables in the dbos schema. Hubzoid does not copy an existing SQLite deployment into PostgreSQL, so start a PostgreSQL deployment fresh or move the data with your own tooling.

There are two ways to point Hubzoid at PostgreSQL:

SetMoves to PostgreSQLStays in SQLiteUse it for
HUBZOID_OPERATIONAL_DB and HUBZOID_DBOS_DBOperational store, workflow engineEach Hub's .hubzoid/hub.dbSeveral Hubs, and any deployment where Hubs could share one database
DATABASE_URLOperational store, workflow engine, Hub databaseNothingA single Hub

For several Hubs, set both in the .env next to hubzoid.yaml. They can name the same database:

.env next to hubzoid.yaml
HUBZOID_OPERATIONAL_DB=postgresql+psycopg://hubzoid:<password>@db.internal:5432/hubzoid
HUBZOID_DBOS_DB=postgresql+psycopg://hubzoid:<password>@db.internal:5432/hubzoid

hubzoid backup dumps each distinct PostgreSQL database with pg_dump into the same archive, so install the PostgreSQL client tools at the server's major version or newer. See backup and restore.

How sharing one database behaves

The choice between the two forms follows from what each store is keyed on. In short, the operational store is shared by design, and the Hub database is not.

What each store is keyed on
  • The operational store is shared by design. Every row carries its Hub, so one store serves every Hub of a deployment. One account signs in to all of them, an organization grant made in one is visible to all, and one Settings page covers the deployment.
  • The workflow engine can share PostgreSQL. Each Hub runs under its own application name and queues, so several Hubs can use one PostgreSQL system database. On SQLite each Hub keeps its own file.
  • The Hub database is not keyed by Hub. It stores WhatsApp and Telegram history per sender (whatsapp-<phone>, telegram-<id>). Hubs that share one DATABASE_URL share this table, so a person who writes to two Hubs over the same channel would get one merged history. The HUBZOID_OPERATIONAL_DB and HUBZOID_DBOS_DB form keeps one Hub database per Hub.

Check any shape with hubzoid doctor <hub>. It reads the deployment without changing it and fails on misconfigured or unreachable databases (db.operational, db.hub).

Choose which cookies Hubzoid can use. You can change this at any time from Cookie settings in the footer. Read the Cookie Policy for details.