opbox

Opbox runs as one self-contained docker-compose stack on the box - there is no managed cloud tier. Each box is one tenant. The first-party services run from GHCR images (ghcr.io/wlilley93/opbox-*), pinned by the OPBOX_*_TAG env vars (default main; see versions.yaml). Only the reverse proxy is internet-facing; the data plane stays internal.

The services

ServiceImageRoleExposure
postgrespgvector/pgvector:pg18The single system of record (relational state + the vector store).internal only
docrenderopbox-docrenderThe document engine (DOCX/PDF), HMAC-gated, on :8651.internal only
kernelopbox-kernelThe Rust verb kernel - HTTP/MCP front door on :8088. Release image built with dev-identity dropped (ADR-0040).via Caddy
frontendopbox-frontendThe Next.js front end (Next.js 16 / React 19) on :3000 - the site root (marketing, sign-in, the whole app). Reads the kernel-owned tables directly (RLS-scoped) and writes via kernel verbs.via Caddy
agent-chatopbox-agent-chatThe agent engine: the caged-Hermes gateway (OpenAI /v1 + AG-UI /chat/stream) on :8099.via Caddy
draineropbox-agent-chatThe same image in drain-loop agent.run mode - a continuous caged seat that claims and works agent tasks.internal
cron-scheduleralpinePolls http://frontend:3000/api/cron/workflows (with the x-cron-secret header) on an interval to fire scheduled workflows.internal
docsopbox-docsThis documentation site (static, Astro). Served at /docs (login-gated by default via the frontend’s /api/docs/gate, with a public allowlist).via Caddy at /docs
minio + minio-initminio/minioObject storage for file bytes. The kernel seals before the seam, so MinIO holds ciphertext only. (OPBOX_STORAGE_BACKEND=bytea keeps bytes in Postgres instead.)internal only
frontdooropbox-frontdoorThe header/SSO front door on :8092.internal
caddycaddy:2TLS / HTTP reverse proxy - the only internet-facing service.:80 / :443
hatchet + hatchet-workerhatchet-lite / opbox-hatchet-workerOptional durable automations runner (see Hatchet).internal

The data plane (postgres, docrender, minio) sits on an internal Docker network with no host port and no egress; the front-facing services share the edge network; caddy is the single front door. The Beelink override (docker-compose.beelink.yml) adds loopback host-port bindings, plain-HTTP Caddy (no ACME on a local box), and a redis + srh pair for the frontend’s Upstash-style rate-limit store.

The secrets

Required secrets (copy .env.example to .env and fill them; the ${VAR:?} form fails the bring-up fast if one is missing, so a release can never silently boot on a dev default):

SecretUsed for
POSTGRES_PASSWORDThe database password.
OPBOX_PII_KEYThe AES-256 PII / file encryption key (32 bytes hex/base64). Fail-closed without it.
DOC_RENDER_HMAC_SECRETThe shared kernel ↔ docrender HMAC (identical in both).
OPBOX_WEBHOOK_KEYDerives the per-tenant inbound-webhook HMAC (board / payment / e-sign callbacks).
NEXTAUTH_SECRET, CSRF_SECRET, ENCRYPTION_KEY, APP_TOKEN_SIGNING_SECRET, IDENTITY_HASH_SECRETThe frontend’s session, CSRF, at-rest, token-signing, and identity-hashing secrets.
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYThe MinIO object-store credentials.
CRON_SECRETThe shared secret the cron-scheduler presents to /api/cron/workflows.
OPBOX_AGENT_KEY / OPBOX_DRAINER_AGENT_KEYThe chat-agent and drainer bearers - minted by genesis, left blank beforehand.
OPBOX_SITE_ADDRESS / FRONTEND_PUBLIC_URLThe public hostname (or :80 for plain-HTTP / tailnet access) and the frontend’s external URL.
an LLM keyThe model/provider for agent-chat is configurable (GLM / OpenRouter / OpenAI / local Ollama / any OpenAI-compatible): e.g. GLM_API_KEY (the default, cloud GLM-4.6 via z.ai), OPENROUTER_API_KEY, or OPENAI_API_KEY. The local Ollama path needs no key.

The Beelink override also wants SRH_TOKEN (the Redis-HTTP shim token); the optional Hatchet runner wants HATCHET_CLIENT_TOKEN and OPBOX_HATCHET_SERVICE_KEY.

Bring it up

cp .env.example .env    # fill the secrets (generation commands are in the file)
docker compose -f docker-compose.yml -f docker-compose.beelink.yml --env-file .env up -d

The first bring-up pulls the GHCR images, runs the kernel migrations on boot, and starts every service. On a fresh box the founding ceremony (opbox initiate, which mints the first owner bearer and the bounded chat-agent) is the one deliberate host act that turns the running stack into a usable tenant - see Genesis. The genesis.sh wrapper runs the whole sequence (copy .env, pull, migrate, up, found the owner) end to end.

Verify the release kernel dropped its dev identity (a no-bearer call must return 401) before exposing the box, and confirm the stack is healthy with docker compose ps.