Hubzoid
Deploy and operate

Docker

Build or pull the Hubzoid image, run a hub with Docker Compose on SQLite or PostgreSQL, and keep its state on a mounted hub folder.

The Hubzoid image runs hubzoid run /hub against a hub folder you mount into the container. It is a drop-in replacement for a native install: the same processes, the same .env, the same public port.

When to use this

Use the image when pip install hubzoid is impractical on the host (PyAV build problems, the wrong Python version, missing system libraries), or when your platform runs containers. For the processes and data stores the container runs, see deployment topologies.

The image

The Dockerfile (opens in a new tab) at the repository root builds the image from the checked-out source with the reviewed dependency set in requirements.lock, so the image version is the version you checked out.

PropertyValue
Basepython:3.12-slim-trixie (Debian 13, SQLite 3.46, new enough for the workflow engine)
System packagesffmpeg, plus the PyAV build dependencies
Python dependenciesrequirements.lock, including the PostgreSQL driver and the CPU build of PyTorch
Userhubzoid, unprivileged
Working directory/hub
Entrypoint and default commandhubzoid, then run /hub
DefaultsPORT=3080, BRIDGE_PORT=8000, HUBZOID_HOST=0.0.0.0
Published port3080, the edge

Only port 3080 is meant to be published. The edge on that port serves the chat app, download links, the Console at /portal/ and the hosted MCP server. The bridge listens on 127.0.0.1 inside the container and is never reachable from outside it.

The Docker path was not re-verified end to end for the 1.0.1 release, so run hubzoid doctor inside a new container before you rely on it.

Build and run

Check out the release you want and build it:

git checkout v1.0.1
docker build -t hubzoid:1.0.1 .

Run one hub:

docker run -d --restart unless-stopped \
  --name devops-agent \
  -p 3080:3080 \
  -v "$PWD/devops-agent:/hub" \
  --env-file "$PWD/devops-agent/.env" \
  hubzoid:1.0.1

Arguments after the image name replace the default command, so hubzoid:1.0.1 run /hub --slack also starts the Slack adapter. Put a reverse proxy with TLS in front of port 3080, exactly as on a single server.

Published images

The release pipeline in .github/workflows/ci.yml (opens in a new tab) runs when a vX.Y.Z tag is pushed. It runs the full test suite, builds the Console and the wheel, builds the image for amd64 and arm64, and checks that the image starts and keeps the bridge unreachable. Only then does it push the image to the GitHub Container Registry, followed by PyPI and the GitHub release:

docker pull ghcr.io/hubzoid/hubzoid:1.0.1

Each release is tagged with its version and latest. Pin the version tag in production. Building from the tag yourself gives the same result.

Docker Compose

The docker/ (opens in a new tab) folder holds three Compose files. The base file runs one hub on SQLite. The other two add to it.

FileAdds
docker/docker-compose.ymlThe hubzoid service: image ${HUBZOID_IMAGE:-hubzoid:local} built from the checkout, port ${HUBZOID_PUBLISH:-3080}, the hub folder ${HUB_PATH} mounted at /hub, restart unless-stopped
docker/docker-compose.postgres.ymlA postgres:17 service named db and DATABASE_URL for the hub
docker/browser-compose.ymlA shared, resource-limited browser for the hub's agents

The hub's databases, chat data and runtime state live inside the mounted folder.

HUB_PATH=$PWD/my-hub docker compose -f docker/docker-compose.yml up -d

Persistence

Everything a hub writes stays in the folder mounted at /hub, so it survives container replacement and hubzoid backup covers it.

Path in the hub folderContents
.hubzoid/Hubzoid's SQLite databases, the workflow engine's dbos.db, the download-link signing key and access snapshots
.openwebui-data/The chat app's webui.db, uploads and log
.inbound/Inbound channel state, including stored webhook events
logs/Hub runtime logs
output/Files the agent made for people

With the PostgreSQL file, the database itself lives in the named volume hubzoid-pg. Back it up with pg_dump as described in backup and restore.

On Linux the container runs as an unprivileged user, so the mounted folder must be writable by it. For example, chown -R 999:999 my-hub, or run the container with --user "$(id -u):$(id -g)".

Configuration

The hub reads its .env from the mounted folder at start, the same way a native install does. Values in that file win over variables in the container environment, including ones set by --env-file or a Compose environment: block. To drive a setting from the container environment instead, leave it out of the hub's .env.

The settings a public deployment needs are the same as on a server: MODEL and its provider key, BRIDGE_API_KEYS, and the sign-in block with WEBUI_AUTH=true, WEBUI_SECRET_KEY and WEBUI_URL. See single server and authentication.

Operator commands

Run operator commands inside the container against /hub:

docker compose -f docker/docker-compose.yml exec hubzoid hubzoid doctor /hub
docker compose -f docker/docker-compose.yml exec hubzoid hubzoid backup /hub --out /hub/backup.tar.gz
docker compose -f docker/docker-compose.yml exec hubzoid hubzoid schedule status /hub

For liveness probes, the public port answers Open WebUI's /health endpoint.

Upgrading the container

Back up, stop the container, pull or build the new image, and start again. The new version upgrades Hubzoid's tables at its first start. Read upgrading before moving between minor versions.

Container platforms

The image is the entry point for ECS, Kubernetes and other orchestrators. Two rules keep a hub's data safe there:

  • One task or pod per hub. Give each hub its own service with a desired count of 1 and no horizontal autoscaling. Several processes writing one SQLite database corrupt it.
  • No SQLite on a network filesystem. Keep webui.db and the .hubzoid databases on block storage such as an EBS volume. With a network filesystem such as EFS, mount it only for uploads, keep the databases on the task's own storage, and snapshot them on a schedule. Or move Hubzoid's stores to PostgreSQL.

The container listens on PORT (default 3080) and accepts .env values as container environment variables.

What differs in a container

FeatureIn the image
MODEL=claude-localDoes not work, because the image has no claude CLI. Use a model provider API key such as OpenRouter, OpenAI or Anthropic.
MODEL=codex-localDoes not work in the base image, which neither installs nor logs in to the Codex CLI. To use it in containers, build your own image with Codex CLI 0.147.0 and a file-backed login for the container user. A provider API key is simpler.
Shared browser in direct modeThe image has no Node.js, so Hubzoid cannot start the browser itself. Use docker/browser-compose.yml, which runs the browser as sidecars.
Mounted hub folderMust be writable by the container's unprivileged user.

Next steps

Read this page as Markdown

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.