What a Kribble server is made of, how the parts connect, and what each one needs
A Kribble server is two Go programs, kribble-web and kribble-builder, built from the same source and reading the same config.toml. They need PostgreSQL, Meilisearch and file storage. Redis and Chromium are optional. This page describes each part. Installing covers setting them up.
Part | Required | What it does |
|---|---|---|
| yes | The HTTP server: the web app, the REST API, the MCP endpoint, published sites, file downloads and the CLI downloads. Stateless; you can run several behind a load balancer. |
| yes | Builds published sites from a queue and runs the hourly clean-up jobs. Run exactly one. |
PostgreSQL 16+ | yes | The database. Holds everything except uploaded files, built sites and the search index. |
Meilisearch v1.12+ | yes | Full-text search over notes. Kribble updates the index after each change is saved, and when |
File storage | yes | Uploads (images, attachments, avatars) and built sites. Either a local directory ( |
Redis 7+ | no | Shared caches between several |
Chromium or Chrome | no | PDF export. |
Everything is on one host name, by default on port 8080:
Path | What |
|---|---|
| The landing page, then the web app (the client is embedded in the binary) |
| The REST API used by the web app, the CLI, the desktop app and CI scripts |
| The MCP server for AI agents (streamable HTTP; takes an access token) |
| Sites published to the built-in target. |
| Uploaded files, for signed-in users with access to the space |
| Assets of public published sites |
| The |
| Health checks. |
There are no WebSockets. Browser tabs stay current by polling /api/v1/workspace/version, so a load balancer needs no sticky sessions.
Site builds. When someone publishes or rebuilds a site, kribble-web writes the site's row to the database with status queued. The builder checks the database for queued rows every 10 seconds, claims them one at a time, generates the static site and copies it to its destination: file storage for the built-in target, or a directory, an SSH server or Cloudflare Pages for an operator-configured target. [builder].workers (default 2) builds run in parallel.
Clean-up jobs, every hour: expired sessions, pending registrations and password-reset tokens are deleted, expired invites are marked expired, uploads that were never saved into a note are deleted after 1 hour, and files no longer used by any note or kept version are deleted after 24 hours.
A health server on [builder].port (default 8081) with /healthz and /readyz. It serves nothing else.
Both binaries connect to PostgreSQL, Meilisearch and storage. The database is the only connection between them: if the builder is stopped, publishing still works in the web app, but sites stay queued until the builder runs again.
Never run more than one kribble-builder against the same database. The clean-up jobs are not coordinated between processes. To build faster, raise [builder].workers and give the host more CPU.
flowchart LR users[Browsers, CLI, desktop app, agents] -->|HTTPS| proxy[Reverse proxy with TLS] proxy -->|HTTP :8080| web[kribble-web] web --> pg[(PostgreSQL)] web --> meili[(Meilisearch)] web --> storage[(Disk or S3 storage)] web -.-> redis[(Redis, optional)] web -.-> chrome[Chromium, for PDF] builder[kribble-builder] --> pg builder --> meili builder --> storage builder -.-> redis builder -->|local, SSH, Cloudflare Pages| targets[External publish targets]
Clients reach only kribble-web, through a reverse proxy that terminates TLS. The proxy must use HTTPS, because outside dev mode the session cookies are marked Secure. See Installing for the proxy settings.
PostgreSQL, Meilisearch and Redis should listen only on localhost or a private network.
With the disk backend, kribble-web and kribble-builder must see the same directory, so they run on the same host, or share a volume in Docker. With s3, they can run on different hosts.
With s3, files up to [storage.s3].proxy_threshold_bytes (15 MiB by default) are streamed through kribble-web. Larger files are sent as a redirect to a presigned URL, so browsers must be able to reach the S3 endpoint directly.
kribble-web keeps no state of its own. To run several:
Use the s3 storage backend, or a shared directory that every replica mounts.
Install Redis and set [redis].url in every replica's config, and in the builder's. Without Redis, each process has its own in-memory caches (workspace versions, unread notifications, site and space access), and users may see stale data for up to a minute.
Put any HTTP load balancer in front. No sticky sessions are needed.
Keep a single kribble-builder.
The repository has no benchmarks. These per-process limits help you size a host:
Limit | Value | Where it comes from |
|---|---|---|
Database connections | up to 25 per process (10 idle) | Every |
Concurrent PDF renders | the number of CPU cores, at most 4 | One headless Chromium per |
Upload size | 500 MB per attachment, 100 MB per image | With the |
Note size | 5 MB per note | Measured as the stored document, not the markdown |
Markdown import and | 100 MB in total, 10 MB per file | Per request |
Site builds |
| Each build writes the site to a temporary directory first. |
Redis | a few MB | Used only as a cache. PostgreSQL holds the real data. |
Port | Service | Expose it publicly? |
|---|---|---|
8080 |
| No. Only the reverse proxy should reach it |
8081 |
| No |
5432 | PostgreSQL | No |
7700 | Meilisearch | No |
6379 | Redis | No |
443 | Your reverse proxy | Yes |
Installing: set it up on a server or with Docker Compose.
Configuration: every setting in config.toml.
Backups and upgrades: what to back up and how to update.