Docker Compose
The fastest way to self-host Bee Flow on a single host. Production-ready for small/mid orgs.
Easy install (recommended)
Pulls prebuilt public images from ghcr.io/bee-flow — no registry login, no source build.
Prerequisites: a Linux/macOS host with Docker and the Docker Compose plugin.
-
Get the three install files (from a release bundle, or copied out of the repo):
selfhost.shdocker-compose.from-registry.yml.env.selfhost.example
-
Run the installer:
./selfhost.shIt checks Docker, generates fresh secrets into
.env, pulls the images, starts the core stack, waits for the server to report healthy, and prints your URL and the generatedadminpassword. -
Open http://localhost:5176, sign in as
adminwith the printed password, and change it.
Add optional sidecars by passing profiles:
PROFILES="core search guard" ./selfhost.sh # adds knowledge search + PII guard
This runs in DEPLOYMENT_MODE=self-hosted: no billing, and paid features unlock when you activate a
server-wide licence key (see Activate a licence).
What the core profile starts
| Service | Image | Address |
|---|---|---|
| Bee Flow server (API) | ghcr.io/bee-flow/server | localhost:3001 |
Web UI (agent-hub) | ghcr.io/bee-flow/agent-hub | localhost:5176 |
| PostgreSQL + pgvector | pgvector/pgvector:pg15 | internal |
| Object storage (RustFS) | rustfs/rustfs | internal |
Health check once it's up:
curl http://127.0.0.1:3001/api/health # {"status":"ok",...}
Three databases, not one
The stack uses three Postgres databases on the same instance:
| Database | What it stores | Env var |
|---|---|---|
beeflow_core | Users, agents, conversations, KBs, audit log, settings — the bulk of state | CORE_DATABASE_URL |
beeflow_tasks | Project / reminder / aiTasks rows for the productivity layer | DATABASE_URL |
monitoring_db | Long-tail metrics + ticket-assistant analytics | MONITORING_DATABASE_URL |
docker/init-db.sh (bind-mounted into the Postgres container, and materialised by selfhost.sh if
missing) creates the two auxiliary databases and enables the pgvector extension on first start.
Activate a licence (unlock paid features)
Self-hosted installs run on the free community tier by default — no licence key is required, and community is a fully usable floor (chat, knowledge bases, multi-user, the Nextcloud connector). You only need a licence to unlock premium features (automations, meeting notes, guardrails, and more).
To unlock enterprise features for the whole install, sign in as a super-admin → Admin → Licence →
paste your server-wide licence key (a JWT issued by Bee Flow, or an admin-issued blob). Pricing and
keys are at beeflow.nl; see Applying a licence key. To
verify production keys, set LICENSE_PUBLIC_KEY (or LICENSE_PUBLIC_KEY_FILE) in .env.
Connect your Nextcloud
You do not need to set any Nextcloud env vars on the server. Instead, point the Bee Flow connector (the Nextcloud ExApp) at your self-hosted server from the Nextcloud admin UI:
- Install the Bee Flow connector on your Nextcloud (App Store, or
occ app_api:app:register bee_flow …— see Getting started → Nextcloud). - In Nextcloud, go to Settings → Administration → AI → Bee Flow.
- Set Deployment mode to Self-hosted server and enter your server's base URL
(e.g.
https://bee-flow.your-domain.com, orhttp://<host>:3001on a LAN). - Within ~60 s the connector re-bootstraps against your server (it provisions an organisation and a tenant key automatically). Nextcloud users can now open Bee Flow and sign in.
The connector keeps working against Bee Flow Cloud by default — switching to Self-hosted server is opt-in per Nextcloud and is reversible (a bad URL rolls back to the previous target).
Manual / advanced installs
- Build from source: clone the repo and use
docker-compose.install.ymlwith--buildinstead of the registry compose file. - Credentialed wizard:
install-from-registry.shlaunches a graphical wizard that pulls from a private Harbor registry (requires a.credentialsfile). Preferselfhost.shfor the public path.
Reverse proxy + TLS
To expose the install on a public hostname, terminate TLS in a reverse proxy.
With the easy install, the agent-hub container already serves the SPA and
proxies /api, /auth, /agents, /ai to the server internally. So you only need to reverse-proxy
your domain to the agent-hub container (localhost:5176) — no per-path rules required:
bee-flow.example.com {
reverse_proxy 127.0.0.1:5176
}
The per-path examples below are for the manual/source topology where you serve a static frontend
separately and talk to the server on localhost:3001 directly.
Caddy (simplest)
beeflow.example.com {
handle /api/* { reverse_proxy 127.0.0.1:3001 }
handle /auth/* { reverse_proxy 127.0.0.1:3001 }
handle /webhook/* { reverse_proxy 127.0.0.1:3001 }
handle { root * /var/www/beeflow-frontend; file_server; try_files {path} /index.html }
}
Nginx
server {
listen 443 ssl http2;
server_name beeflow.example.com;
ssl_certificate /etc/letsencrypt/live/.../fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/.../privkey.pem;
root /var/www/beeflow-frontend;
index index.html;
location / { try_files $uri /index.html; }
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off; # SSE
proxy_read_timeout 600s;
}
location /auth/ { proxy_pass http://127.0.0.1:3001; }
location /webhook/{ proxy_pass http://127.0.0.1:3001; }
}
The proxy_buffering off + proxy_read_timeout lines are required for chat streaming to work.
Traefik (Docker Compose labels)
services:
beeflow-server:
labels:
- "traefik.enable=true"
- "traefik.http.routers.beeflow.rule=Host(`beeflow.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/auth`) || PathPrefix(`/webhook`))"
- "traefik.http.routers.beeflow.tls.certresolver=letsencrypt"
- "traefik.http.services.beeflow.loadbalancer.server.port=3001"
- "traefik.http.services.beeflow.loadbalancer.server.scheme=http"
Serving the frontend (manual topology only)
The easy install already serves the web UI from the agent-hub container — you do not
need to build it yourself. Build a standalone static bundle only if you want to host the frontend
elsewhere (CDN, separate static host):
# from the agent-hub/ directory of the source repo
npm install
VITE_API_URL=https://bee-flow.example.com npm run build # outputs dist/
Then serve dist/ from any static host and point it at your server URL.
Licence and Nextcloud
- Unlock paid features: see Activate a licence above.
- Connect a Nextcloud: see Connect your Nextcloud above — done from the Nextcloud admin UI, no server env vars required.
Backups
Everything durable lives in Postgres. Back up daily with pg_dump:
# back up each database (beeflow_core holds the bulk of state)
docker exec -t beeflow-postgres \
pg_dump -U beeflow -d beeflow_core -Fc > /backups/beeflow_core-$(date +%F).dump
Restore:
cat beeflow_core-YYYY-MM-DD.dump | docker exec -i beeflow-postgres \
pg_restore -U beeflow -d beeflow_core --clean --if-exists
The beeflow-data volume holds short-lived state only — losing it is recoverable from Postgres on restart.
Swap Postgres for managed
Bee Flow needs Postgres 16+ with pgvector if you want vector KBs. Tested managed providers:
| Provider | Notes |
|---|---|
| Neon | ✅ — pgvector built-in. |
| Supabase | ✅ — pgvector built-in. |
| AWS RDS | ✅ — install pgvector extension (RDS supports it). |
| Google Cloud SQL | ✅ — install pgvector extension. |
| Heroku Postgres | ⚠️ — works for non-vector KBs only (no pgvector). |
Point DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME at the managed instance and remove the postgres service from the compose file.
Restart and update
# Restart server only
docker compose restart beeflow-server
# Pull new image and restart
docker compose pull beeflow-server
docker compose up -d beeflow-server
# View logs
docker compose logs -f beeflow-server
Production hardening checklist
-
SESSION_SECRETandMASTER_ENCRYPTION_KEYset to fresh random values (selfhost.shdoes this) - First admin password (
INIT_ADMIN_PASSWORD) changed after first login -
DB_PASSWORD/RUSTFS_SECRET_KEYset; Postgres and object storage not exposed publicly - HTTPS in front of the stack (
COOKIE_SECURE=trueonce behind TLS) -
LICENSE_PUBLIC_KEYset if you activate production licence keys - Daily
pg_dumprunning and tested-restored at least once - At least one model provider key set (in-app, Admin → AI settings)
- Rate limit at the reverse proxy (e.g. Caddy
request_body max_size 10MB, Nginxlimit_req_zone) - Audit log shipped off-host (Loki / ELK / Splunk) — see Reference → Telemetry