# Overview

> 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](../running-a-server/installing.md) covers setting them up.

## The parts

| Part | Required | What it does |
| --- | --- | --- |
| `kribble-web` | 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. |
| `kribble-builder` | 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 `kribble-web` starts it rebuilds the index from the database if it is empty or was built by a Kribble version that indexed notes differently, so the index holds nothing the database doesn't. While Meilisearch is unreachable, notes still save but full-text search fails. |
| File storage | yes | Uploads (images, attachments, avatars) and built sites. Either a local directory (`[storage].backend = "disk"`) or S3-compatible object storage (`"s3"`: Cloudflare R2, AWS S3, MinIO, Backblaze B2 and others). |
| Redis 7+ | no | Shared caches between several `kribble-web` processes. Only needed when you run more than one. |
| Chromium or Chrome | no | PDF export. `kribble-web` starts a headless browser the first time someone exports a PDF. Without it, PDF export returns an error and everything else works. |

## What kribble-web serves

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) |
| `/api/v1/…` | The REST API used by the web app, the CLI, the desktop app and CI scripts |
| `/mcp` | The MCP server for AI agents (streamable HTTP; takes an access token) |
| `/p/<owner>/<slug>/` | Sites published to the built-in target. `<owner>` is the organisation name, or `~username` for a personal vault |
| `/files/…` | Uploaded files, for signed-in users with access to the space |
| `/files-public/…` | Assets of public published sites |
| `/install.sh`, `/install.ps1`, `/downloads/…` | The `kribble` CLI, the editor extensions and the desktop apps (see [The CLI and desktop downloads](../running-a-server/the-cli-and-desktop-downloads.md)) |
| `/healthz`, `/readyz` | Health checks. `/readyz` returns 503 while the server shuts down |

There are no WebSockets. Browser tabs stay current by polling `/api/v1/workspace/version`, so a load balancer needs no sticky sessions.

## What kribble-builder does

- **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](../running-a-server/publish-targets.md). `[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.

:::callout{type="warning"}
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.
:::

## How they connect

```mermaid
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](../running-a-server/installing.md) 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.

## Running more than one kribble-web

`kribble-web` keeps no state of its own. To run several:

1. Use the `s3` storage backend, or a shared directory that every replica mounts.
2. 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.
3. Put any HTTP load balancer in front. No sticky sessions are needed.

Keep a single `kribble-builder`.

## Sizing

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 `kribble-web` replica and the builder each open their own pool. Make sure PostgreSQL's `max_connections` covers 25 × the number of processes, plus your own tools. |
| Concurrent PDF renders | the number of CPU cores, at most 4 | One headless Chromium per `kribble-web`, restarted every 30 minutes or 100 exports to limit its memory use. |
| Upload size | 500 MB per attachment, 100 MB per image | With the `s3` backend an upload is held in memory while it is sent to storage. Plan RAM for your largest concurrent uploads. |
| Note size | 5 MB per note | Measured as the stored document, not the markdown |
| Markdown import and `kribble push` | 100 MB in total, 10 MB per file | Per request |
| Site builds | `[builder].workers` at a time (default 2) | Each build writes the site to a temporary directory first. |
| Redis | a few MB | Used only as a cache. PostgreSQL holds the real data. |

## Ports

| Port | Service | Expose it publicly? |
| --- | --- | --- |
| 8080 | `kribble-web` (`[server].port`) | No. Only the reverse proxy should reach it |
| 8081 | `kribble-builder` health checks (`[builder].port`) | No |
| 5432 | PostgreSQL | No |
| 7700 | Meilisearch | No |
| 6379 | Redis | No |
| 443 | Your reverse proxy | Yes |

## Next

- [Installing](../running-a-server/installing.md): set it up on a server or with Docker Compose.
- [Configuration](../running-a-server/configuration.md): every setting in `config.toml`.
- [Backups and upgrades](../running-a-server/backups-and-upgrades.md): what to back up and how to update.
