Skip to content
bucker

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/Dockerfile builds 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.