Getting started

Installation

Install Hubzoid with pip on macOS, Linux or Windows, check it with the doctor command, and add PostgreSQL, a source install or Docker when you need them.

You will install Hubzoid into a Python virtual environment, create a small Hub and check the whole setup with one command. It takes a few minutes.

One package holds everything hubzoid run needs: the Hubzoid web app and its Settings, the bridge, the agent runtimes (OpenAI Agents SDK, Claude Agent SDK and local Codex), LiteLLM model routing, the channel adapters, the workflow engine and the hosted MCP server. There is no separate UI or Node install.

What you need

RequirementDetail
Python3.11 or 3.12.
Operating systemmacOS or Linux. Windows works in PowerShell, with the limits in step 2. Docker runs on any host that has it.
A modelA signed-in Claude Code (opens in a new tab) CLI, a Codex CLI 0.163.0 or 0.147.0 signed in with file-backed credentials, or an API key for OpenRouter, Anthropic, OpenAI or another provider. The CLIs are installed separately. See agents and models.
Memory2 GB RAM for one Hub on a server, 4 GB for two or three Hubs on the same machine.

Create a virtual environment

Use Python 3.12 (or 3.11) explicitly. The default python3 on recent macOS is newer than Hubzoid supports.

Terminal
python3.12 -m venv .venv
source .venv/bin/activate

Install the package

Terminal
pip install hubzoid

With uv (opens in a new tab), uv venv --python 3.12 and uv pip install hubzoid do the same.

Check that it worked. hubzoid version prints the installed version number, and python -m hubzoid works anywhere the hubzoid command does.

Terminal
hubzoid version

Create a Hub to check against

Terminal
hubzoid init my-hub

This writes my-hub/, a small starter with every folder Hubzoid reads and a private .env that holds the model and a random bridge key. The quickstart walks through it.

Run the doctor

Terminal
hubzoid doctor my-hub

The doctor checks the Hub and its deployment without changing anything. On a new Hub that has not been started yet, with a signed-in claude CLI, you should see:

Output
✓ AGENTS.md present (hub.agents_md)
✓ .env present (hub.env)
· 2 configured key(s): 2 from the hub (config.layers)
✓ Agent builds: 'my-hub' via ClaudeRuntime (runtime.build)
· hubzoid <version> (deps.versions)
✓ SQLite <version> (deps.sqlite)
· Not created yet; created at first start (db.operational)
· Not created yet; created at first start (db.hub)
✓ 1 bridge key(s) set (auth.bridge_keys)
· Sign-in is off: local mode, whoever opens the web app is the hub's owner (admin@localhost). Fine on this machine. (auth.chat_signin)
· Chat UI: Hubzoid web app (ui.mode)
✓ Local mode: the web app stays on 127.0.0.1 (exposure.local_mode)
· No deployment key yet; one is created in <path>/my-hub/.hubzoid/secret.key when first needed. Back it up apart from the data. (deployment.key)
✓ The public port listens on 127.0.0.1; bridges stay on 127.0.0.1 (exposure.bind)
· claude-local: uses the local `claude` login (check it with `claude /status`) (model.credentials)

Each line has a mark (✓ passed, · information, ! warning, ✗ failed), a summary and a stable check id. The command exits with code 1 when any check fails, so it also works in scripts and CI. After the first start it adds checks such as backup.age and access.who. The doctor reference lists every check.

You are ready for the quickstart. The rest of this page covers PostgreSQL, a source install and Docker.

Sign-in before sharing

Without sign-in, whoever opens the web app is the Hub's owner. hubzoid run therefore keeps the public port on 127.0.0.1 and refuses --host 0.0.0.0 until sign-in is on. Authentication covers the settings.

Use PostgreSQL

Hubzoid stores its data in SQLite inside the Hub folder by default. To use PostgreSQL instead, install the postgres extra, which adds the psycopg 3 driver:

Terminal
pip install "hubzoid[postgres]"

Then point the Hub at the database with the postgresql+psycopg:// form of the URL. For several Hubs, set it once in the .env next to hubzoid.yaml:

.env
DATABASE_URL=postgresql+psycopg://hubzoid:<password>@db.internal:5432/hubzoid

Hubzoid's own tables (accounts, conversations, access, usage and workflow state) and the workflow engine's dbos schema share that database, and each upgrades its own tables at start. Deployment topologies explains when to choose PostgreSQL.

Install from source

Install from a clone when you want to read or change Hubzoid itself. A development branch can be ahead of the package on PyPI.

Terminal
git clone https://github.com/hubzoid/hubzoid.git
cd hubzoid
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
hubzoid init my-hub
hubzoid run my-hub

The dev extra adds pytest and the packaging tools contributors use. pip install -e . installs without them. The web app and Settings are served from the built bundle in hubzoid/portal_dist. After you change anything in portal/, rebuild it with cd portal && npm ci && npm run build.

Run in Docker

Build the image from a clone. The Dockerfile installs the checked-out source with the reviewed dependency set in requirements.lock, so the image version is the version you checked out. Each release is also published as ghcr.io/hubzoid/hubzoid:<version> for amd64 and arm64.

Terminal
git clone https://github.com/hubzoid/hubzoid.git
cd hubzoid
docker build -t hubzoid .

The image listens on all interfaces, so it starts only with sign-in on. Before the first start, add these lines to the Hub's .env, and remove the two administrator lines after it:

my-hub/.env
HUBZOID_AUTH=true
HUBZOID_ADMIN_EMAIL=you@example.com
HUBZOID_ADMIN_PASSWORD=<a long password>
# HUBZOID_PUBLIC_URL=https://hub.example.com   # behind a proxy, or for Google sign-in

Then run it with the Hub folder mounted at /hub:

Terminal
docker run -d --restart unless-stopped \
  -p 3080:3080 \
  -v "$PWD/my-hub:/hub" \
  hubzoid

No Hub yet and no Python on the host? The image can write the starter for you:

Terminal
mkdir -p ~/my-hub && chmod a+rwX ~/my-hub
docker run --rm -v ~/my-hub:/hub hubzoid init /hub

The starter's .env says MODEL=claude-local, which the image cannot use, so change it to a provider model and its key.

Things to know about the image:

  • Models. MODEL=claude-local and MODEL=codex-local do not work inside it, because it has no claude or Codex CLI. Set MODEL and a provider key in the Hub's .env.
  • State. It stays in the mounted Hub folder (.hubzoid/), so it survives container restarts and hubzoid backup covers it.
  • Ports. Publish only port 3080. It serves the web app, downloads, Settings and MCP. The bridge stays on 127.0.0.1 inside the container.
  • Trying it without sign-in. Publish the port on this machine only with -p 127.0.0.1:3080:3080 and add -e HUBZOID_ALLOW_UNAUTHENTICATED_NETWORK=true. Anyone who can reach that port uses the Hub as its owner.
  • Permissions on Linux. The container runs as an unprivileged user, so it must be able to write the mounted folder, for example chown -R 999:999 my-hub, or run with --user "$(id -u):$(id -g)".
  • Commands. The image runs hubzoid run /hub on Python 3.12 (Debian 13, SQLite 3.46). Put other arguments after the image name to run a different command.

Docker Compose

docker/docker-compose.yml runs one Hub with SQLite and sign-in on. It needs HUB_PATH, the absolute path of your Hub folder. docker/docker-compose.postgres.yml adds a PostgreSQL 17 container whose port is not published.

Terminal
# SQLite: state lives in the mounted Hub folder
HUB_PATH=$PWD/my-hub docker compose -f docker/docker-compose.yml up -d

# PostgreSQL: add the override file
HUB_PATH=$PWD/my-hub POSTGRES_PASSWORD=<letters and digits> \
  docker compose -f docker/docker-compose.yml -f docker/docker-compose.postgres.yml up -d
VariableDefaultPurpose
HUB_PATHnone, requiredAbsolute path of the Hub folder mounted at /hub.
HUBZOID_IMAGEhubzoid:localImage to run. Compose builds it from the checkout.
HUBZOID_PUBLISH3080Host port published for the web app. 127.0.0.1:3080 keeps it on this machine.
HUBZOID_AUTHtrueSign-in. The Hub's .env wins over this value.
HUBZOID_ALLOW_UNAUTHENTICATED_NETWORKfalseSet to true with HUBZOID_AUTH=false to try the image without sign-in.
POSTGRES_PASSWORDnoneRequired by the PostgreSQL override.

Run operator commands inside the container:

Terminal
docker compose -f docker/docker-compose.yml exec hubzoid hubzoid doctor /hub

Docker deployment covers production settings.

SQLite for scheduled work

On Python 3.12 the workflow engine needs SQLite 3.42 or newer. Python builds that link an older system SQLite (Debian 12, Ubuntu 22.04) cannot run scheduled tasks or workflows, and the bridge logs that the workflow engine did not start. Chat still works, and Python 3.11 is not affected.

Check the SQLite your Python uses:

Terminal
python -c "import sqlite3; print(sqlite3.sqlite_version)"

If it is older than 3.42, use a Python build with a newer SQLite (python.org, uv, Homebrew, Debian 13, Ubuntu 24.04) or PostgreSQL. On Ubuntu and Debian, install Python 3.12 with sudo apt update && sudo apt install -y python3.12 python3.12-venv git curl.

If something goes wrong

What you seeWhat to do
hubzoid: command not foundActivate the virtual environment you installed Hubzoid into, or use python -m hubzoid.
pip cannot find a matching version of hubzoidCheck python --version inside the environment. Hubzoid needs Python 3.11 or 3.12.
✗ on deps.sqliteYour Python's SQLite is too old for scheduled work. See SQLite for scheduled work.
✗ on runtime.build naming a variableThe model key is missing. Add that variable to my-hub/.env.
hubzoid run refuses --host 0.0.0.0Local mode is protecting you. Turn sign-in on first. See authentication.
Docker Compose stops with Set HUB_PATH to your hub folder, an absolute pathSet HUB_PATH to the absolute path of your Hub folder.
The container cannot write the Hub folder on LinuxRun chown -R 999:999 my-hub, or start it with --user "$(id -u):$(id -g)".

Next steps

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.