Self-hosting notes
Bucker is Postgres-first on purpose. The design doc's verdict is blunt: do not clone Sentry's stack — 20+ containers (Kafka, ZooKeeper, ClickHouse, Snuba, Relay) and a 16–32 GB RAM floor, whose heaviness spawned an entire competitor segment. Heavy infrastructure gets added only when a named trigger fires.
For development the whole thing runs on three containers: Postgres (with pgvector), a second Postgres for tests, and MinIO. The packaged Community Edition is four — Postgres, api, worker, web — with no object store; see self-host-ce.md.
Two ways to self-host
Community Edition — four containers, packaged, with a bootstrap script:
selfhost/ and self-host-ce.md. That is
the supported path, and it is where the commercial boundary is documented.
From the monorepo — what the rest of this page describes. Useful for development and for running a modified build.
What runs
| Process | Command | Port |
|---|---|---|
| Control plane + ingest accept | pnpm --filter @bucker/api dev |
4000 |
| Workers (drain the ingest queue) | pnpm --filter @bucker/workers start |
— |
| Dashboard | pnpm --filter @bucker/frontend dev |
3000 |
| MCP server | pnpm --filter @bucker/mcp-server dev |
4100 |
Nothing between them but Postgres. The queue is a Postgres table drained with
SELECT … FOR UPDATE SKIP LOCKED, with visibility timeouts and a dead-letter state —
there is no broker to run. Grouping embeddings, remediation memory and codebase chunks
all live in pgvector, so there is no vector service either.
Bring it up
cp .env.example .env
pnpm install
pnpm db:up # Postgres 5435, test Postgres 5436, MinIO 9002
pnpm db:migrate
pnpm --filter @bucker/api keys:generate # paste JWT_* into .env
pnpm dev
Port note. The dev database listens on 5435, not 5432, and the test database on
5436. A native Postgres commonly binds 127.0.0.1:5432 while Docker binds the
wildcard address, so host clients silently reach the wrong server. High ports keep it
unambiguous. MinIO is on 9002/9003 for the same reason.
Configuration that actually matters
| Variable | Notes |
|---|---|
DATABASE_URL |
Dev database |
TEST_DATABASE_URL |
Used by the test suite; tests run against real Postgres, never a mock |
JWT_PRIVATE_KEY / JWT_PUBLIC_KEY / JWT_KEY_ID |
EdDSA (Ed25519) keypair. Generate with keys:generate; rotation is by key id through JWKS |
AUTH_SECRET |
Signs short-lived challenge/verification tokens. Minimum 32 characters |
INGEST_REGION / INGEST_HOST |
Stamped into every DSN — see dsn-and-regions.md |
INGEST_INLINE_MAX_BYTES |
Envelopes at or below this go inline in the queue row instead of object storage |
INGEST_MAX_ENVELOPE_BYTES |
Hard reject above this, with an accounted too_large outcome |
STORAGE_PROVIDER |
filesystem (default) | memory | s3. Production refuses anything but s3 unless STORAGE_ALLOW_LOCAL=true — the api and the workers do not share a disk |
INGEST_BLOB_DIR |
Where the filesystem store keeps objects. Empty = a directory under the system temp dir, which the OS may delete |
S3_* |
Any S3-compatible store (MinIO locally, Tigris/R2/AWS hosted). ONE bucket: S3_BUCKET_ENVELOPES. BlobStore is a flat keyspace and callers separate by key prefix |
EMAIL_PROVIDER |
console in dev: invitations and verification links are logged, not sent |
Storage growth
Events are the volume table and they are never aggregated at query time. Every
count and every sortable magnitude on the issues list is read from denormalized
columns the workers maintain on Issue — a well-indexed COUNT over hundreds of
millions of events takes 8–15 seconds, and a list endpoint that does that once per page
is the whole product feeling broken.
Retention is partition drop rather than row deletion (instant, bloat-free), and it is
wired: RetentionScheduler in workers/src/main.ts runs runRetentionPass, which
provisions a lookahead window of day partitions (RETENTION_LOOKAHEAD_DAYS), re-attaches
an orphaned DEFAULT partition on every pass, archives what is due and drops what is past
retention. It is on unless RETENTION_ENABLED=false.
(An earlier revision of this page said partition management was not wired up. It was
already true then; the sentence outlived it.)
Object storage is a real seam now, not only a described one: STORAGE_PROVIDER=s3
selects S3BlobStore against any S3-compatible endpoint. The filesystem default is
correct for one process on one machine and wrong for anything else — the api writes an
envelope blob and the worker reads it back by key, so two containers with two disks
lose every envelope over INGEST_INLINE_MAX_BYTES. Production refuses to start on a
local-disk store unless STORAGE_ALLOW_LOCAL=true says the deployment really is one
machine with a persistent volume.
What self-hosting deliberately does not include
Community Edition does ingest and triage. Two capabilities stay in Bucker Cloud and are refused in CE with an explanation rather than a crash:
- Sandbox fix verification — CE ships no sandbox provider.
- Cross-tenant fix memory — a single-tenant install has no cross-tenant corpus, and the capability is not built in any edition yet.
Everything else is in CE, including release health and crash-free rates, which are free-tier by design.
Beyond that split, these are not built anywhere:
- Auto-merge of a fix, at any verification tier — a human merges, always
- Play Console and App Store Connect rollout halts (the generic webhook provider works; those two are honest stubs that throw)
- Automated retention / partition management
- On-call rotations and escalation policies
- Published container images —
selfhost/Dockerfilebuilds them locally, nothing is on a registry, and it has not been built in CI
Verifying an install
pnpm verify # typecheck + lint + test across the workspace
Tests run against the real test Postgres — the storage layer is where things actually
break, so it is not mocked. pnpm db:up must be running first.