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
| Requirement | Detail |
|---|---|
| Python | 3.11 or 3.12. |
| Operating system | macOS or Linux. Windows works in PowerShell, with the limits in step 2. Docker runs on any host that has it. |
| A model | A 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. |
| Memory | 2 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.
python3.12 -m venv .venv
source .venv/bin/activateInstall the package
pip install hubzoidWith 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.
hubzoid versionCreate a Hub to check against
hubzoid init my-hubThis 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
hubzoid doctor my-hubThe 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:
✓ 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:
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:
DATABASE_URL=postgresql+psycopg://hubzoid:<password>@db.internal:5432/hubzoidHubzoid'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.
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-hubThe 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.
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:
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-inThen run it with the Hub folder mounted at /hub:
docker run -d --restart unless-stopped \
-p 3080:3080 \
-v "$PWD/my-hub:/hub" \
hubzoidNo Hub yet and no Python on the host? The image can write the starter for you:
mkdir -p ~/my-hub && chmod a+rwX ~/my-hub
docker run --rm -v ~/my-hub:/hub hubzoid init /hubThe 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-localandMODEL=codex-localdo not work inside it, because it has noclaudeor Codex CLI. SetMODELand a provider key in the Hub's.env. - State. It stays in the mounted Hub folder (
.hubzoid/), so it survives container restarts andhubzoid backupcovers 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:3080and 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 /hubon 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.
# 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| Variable | Default | Purpose |
|---|---|---|
HUB_PATH | none, required | Absolute path of the Hub folder mounted at /hub. |
HUBZOID_IMAGE | hubzoid:local | Image to run. Compose builds it from the checkout. |
HUBZOID_PUBLISH | 3080 | Host port published for the web app. 127.0.0.1:3080 keeps it on this machine. |
HUBZOID_AUTH | true | Sign-in. The Hub's .env wins over this value. |
HUBZOID_ALLOW_UNAUTHENTICATED_NETWORK | false | Set to true with HUBZOID_AUTH=false to try the image without sign-in. |
POSTGRES_PASSWORD | none | Required by the PostgreSQL override. |
Run operator commands inside the container:
docker compose -f docker/docker-compose.yml exec hubzoid hubzoid doctor /hubDocker 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:
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 see | What to do |
|---|---|
hubzoid: command not found | Activate the virtual environment you installed Hubzoid into, or use python -m hubzoid. |
pip cannot find a matching version of hubzoid | Check python --version inside the environment. Hubzoid needs Python 3.11 or 3.12. |
✗ on deps.sqlite | Your Python's SQLite is too old for scheduled work. See SQLite for scheduled work. |
✗ on runtime.build naming a variable | The model key is missing. Add that variable to my-hub/.env. |
hubzoid run refuses --host 0.0.0.0 | Local mode is protecting you. Turn sign-in on first. See authentication. |
Docker Compose stops with Set HUB_PATH to your hub folder, an absolute path | Set HUB_PATH to the absolute path of your Hub folder. |
| The container cannot write the Hub folder on Linux | Run chown -R 999:999 my-hub, or start it with --user "$(id -u):$(id -g)". |

