---
sidebar_position: 1
title: Self-host overview
---

# Operating a self-hosted box

Run the entire Zibby agent platform on **one host**, inside your own VPC, with `docker compose`. This section is the **operator runbook** — for the person who holds the box's owner/admin token and is responsible for install, upgrade, backup, and storage. It is not the agent-authoring guide (that's the rest of these docs, and `zibby init` in your repo).

A self-hosted box is the **same codebase as cloud**, assembled differently: the control plane is `backend/server.js` (the Express server that wraps every Lambda handler, including the MCP server), the run executor is the host Docker engine instead of Fargate, and the datastore / object store are ScyllaDB Alternator + SeaweedFS instead of DynamoDB + S3.

## Install

One command, on a Linux/macOS host with **Docker Engine + Compose v2**:

```bash
curl -fsSL https://dl.zibby.app/selfhosted/latest/install.sh | bash
```

The installer downloads a signed release (a `docker save` image tarball + compose file + the marketplace-template bundle), **verifies it** (an Ed25519 signature over `SHA256SUMS`, then a per-file checksum — it fails closed on any mismatch and never runs unverified bytes), `docker load`s the images, and `docker compose up -d`s the stack. It is idempotent — re-running it is how you upgrade in place, and cached downloads are checksum-verified.

:::warning Compose v2 is required
`apt install docker.io` alone gives you the engine but **not** Compose v2. Install both — the installer aborts with a link if `docker compose version` fails. On a headless box also make sure `openssl` is present (the installer needs it to verify the release signature).
:::

### What comes up

The stack is a handful of containers on the `zibby-selfhosted` docker network:

| Container | Role |
|---|---|
| `zibby-control-plane` | the long-lived API + `/mcp` + `/hooks/*` server + the dashboard; drives the host Docker engine as the run executor over the mounted `docker.sock` |
| `zibby-scylla` | **ScyllaDB Alternator** (`:8000`) — speaks the DynamoDB API, so the backend is unchanged. All metadata: integrations + encrypted tokens, deployed agents, projects, members, executions, stores. |
| `zibby-minio` | the **object store** (`:9000`) for run sources + artifacts + logs. As of 0.1.126 this is **SeaweedFS** (the service keeps the historical name `minio` so every `S3_ENDPOINT` consumer is unchanged). Legacy boxes still run MinIO until they auto-migrate — see [Storage](./storage). |
| `zibby-bootstrap` | one-shot: provisions tables + buckets, syncs all marketplace templates, seeds the default project + token, then exits. |
| run containers | one ephemeral `zibby-agent` container per run, destroyed on exit — the same per-run isolation Fargate gives cloud. |

The control plane mounts `/var/run/docker.sock` — that socket **is** the run executor (no docker-in-docker).

### After install

The installer prints the access info and the seeded credential:

- **Dashboard** — `http://<host>:3001/` (and `/ui`). The self-host dashboard is a single vanilla HTML page served by the control plane; the cloud React app is not shipped on-prem.
- **API + CLI** — `http://<host>:3001`. Set `ZIBBY_API_URL` + `ZIBBY_API_KEY=<apiToken>` (the seeded `zby_…` project token printed at the end, or recover it with `docker logs zibby-bootstrap`).
- **MCP** — `http://<host>:3001/mcp`, `Authorization: Bearer <PAT>`. MCP needs a **user PAT** (`zby_pat_…`), minted with `zibby self-host token` — not the project token.
- **Health** — `http://<host>:3001/health`.

AI model keys are **per-project**, set in the dashboard (Settings → AI); the deploy modal collects a missing one. The box comes up keyless — there is deliberately no box-global model credential.

## Install knobs

Set these as environment variables or flags on the install command (they persist into `.env`, which is the runtime source of truth and is **reused untouched on upgrade**):

| Knob | Default | Effect |
|---|---|---|
| `--dir <path>` / `ZIBBY_DIR` | `./zibby-selfhosted` | install + data directory |
| `--url <u>` / `ZIBBY_DIST_URL` | `https://dl.zibby.app/selfhosted/latest` | release base URL |
| `--reconfigure` | — | re-run the external-URL / domain prompt |
| `CONTROL_PLANE_PORT` (`.env`) | `3001` | host port the control plane binds |
| `CONTROL_PLANE_BIND` (`.env`) | `0.0.0.0` | set to `127.0.0.1` to restrict the dashboard/API to loopback and front it with the bundled Caddy TLS proxy |
| `ZIBBY_EXTERNAL_URL` (`.env`/prompt) | unset | a domain → automatic Let's Encrypt HTTPS; an IP → self-signed HTTPS; `http://…` → plain HTTP via Caddy. Blank → plain HTTP on `CONTROL_PLANE_PORT`. |
| `MAX_CONCURRENT_RUNS` (`.env`) | `5` | run containers executing in parallel; the rest queue |
| `ZIBBY_SKIP_OBJECT_STORE_MIGRATION` | `0` | `1` opts a legacy MinIO box out of the auto-migration to SeaweedFS |

:::warning Plain-HTTP default binds in cleartext
With no external URL the dashboard/API bind to `0.0.0.0:3001` unencrypted — the operator token and login passcode cross the network in the clear. Either set `ZIBBY_EXTERNAL_URL` (→ auto-TLS via Caddy), firewall the port to trusted IPs, or set `CONTROL_PLANE_BIND=127.0.0.1`.
:::

## The runbook

- **[Upgrade & rollback](./upgrade)** — check the running version, the owner-only remote upgrade, and how auto-rollback works.
- **[Backup & restore](./backup-restore)** — snapshot the whole install to one tarball; restore onto the same or a fresh box.
- **[Storage](./storage)** — the object store, and the automatic MinIO → SeaweedFS migration.
- **[Troubleshooting](./troubleshooting)** — reading logs and the failure signatures worth recognizing.
