Four services and three stores
The split exists because the two kinds of data behave differently. Events are immutable and arrive fast; person profiles mutate constantly as merges, traits and interest scores land.
Browser ──▶ apps/ingest ──▶ Redis Stream ──▶ apps/worker ──┬──▶ ClickHouse (events)
(Bun + Hono) └──▶ Postgres (profiles)
p99 <10ms ▲
packages/queries ────────┘
│
apps/web (dashboard) · apps/mcp (assistants)Three names, on purpose
- a.yourdomain
- The collector. Public, high volume, immutable one-year cache on the tracker script.
- dashboard.yourdomain
- The dashboard. Session cookies, strict Content-Security-Policy with a per-request nonce.
- mcp.yourdomain
- The MCP server, for assistants connecting remotely with a bearer API key.
Serving the collector from your own subdomain is also the practical answer to ad blockers: the lists target vendor hostnames, and this one is yours.


What running it actually involves
infra/backup.sh does incremental for ClickHouse, and a verified gzip dump for Postgres. Two stores, two strategies, because one is large and append-only and the other is small and constantly rewritten.Production, in the same shape as development
# production stack
docker compose -f infra/docker-compose.production.yml up -d
# migrations are additive and safe to re-run
pnpm --filter @falorb/db migrate
pnpm --filter @falorb/db ch:migrate# is the pipeline actually intact?
node scripts/loadtest.mjs
# do all the scheduled jobs run?
pnpm --filter @falorb/worker verify:jobs
# collector readiness: redis, geo, tracker
curl https://a.yourdomain/healthRunning it in production
How much server do I need?
Falorb is designed for small-to-medium traffic on modest hardware. The reference deployment runs a portfolio of eight sites on a single small server alongside other services, which is why the ClickHouse configuration ships with capped memory, capped pools and bounded system logs rather than the defaults that assume a dedicated machine.
Why three hostnames?
The collector, the dashboard and the MCP server run on separate subdomains. The collector is publicly reachable and high-volume, so an ad-blocker rule against it cannot take the dashboard down with it, and the two can be cached and scaled independently.
What about backups?
The repository ships infra/backup.sh: incremental for ClickHouse, and a gzip dump for Postgres that is verified rather than assumed. Your event history is large and append-only; your control plane is small and mutates constantly, so they want different strategies.
Can I deploy it on Coolify, Dokku or plain Docker?
Yes. Dockerfiles and a production compose file are in the repository and verified locally, with a deployment guide in infra/DEPLOY.md. The Caddy configuration is provided for anyone terminating TLS themselves; behind a platform proxy that terminates TLS at the edge, the app sets its own security headers so they follow it into any deployment.
How do I upgrade?
Pull, install, run both migration runners, restart. Migrations are additive and the ClickHouse runner splits statements and substitutes placeholders itself. The historical backfill script assigns totals rather than incrementing them, so re-running it after an upgrade is safe.
The full deployment guide is infra/DEPLOY.md in the repository.
One server, one afternoon
Bring up the stack, point three subdomains at it, and paste one script tag. After that the only recurring cost is the machine.