> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swarmd.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Databases

> One Postgres or one per service, in-cluster or your own managed instance — and how to choose.

# Databases

Seven Swarmd services keep relational state, plus Keycloak. How they share
Postgres is the single most consequential choice you make at install time.

<Warning>
  **Pick your layout before you install.** Each mode stores data in different
  databases and PVCs, so switching later is a `pg_dump` / `pg_restore` exercise,
  not a values change. The chart will happily render the new mode and quietly
  point at empty databases.
</Warning>

***

## Who stores what

| Service        | Database       | Schema         | Growth                                                         |
| -------------- | -------------- | -------------- | -------------------------------------------------------------- |
| `registry`     | `registry`     | `registry`     | **Fast** — agents, MCP servers, health and invocation history  |
| `audit`        | `audit`        | `audit`        | **Fast** — append-only cryptographic event per platform action |
| `relay`        | `agent_relay`  | `agent_relay`  | Moderate — task and session state, purged on schedule          |
| `tenant-auth`  | `tenant_auth`  | `tenant_auth`  | Slow — users, groups, invites                                  |
| `billing`      | `billing`      | `billing`      | Slow — accounts and payment events                             |
| `teams`        | `teams`        | `teams`        | Slow — memberships, integration config                         |
| `notification` | `notification` | `notification` | Slow                                                           |
| Keycloak       | `keycloak`     | —              | Slow — grows when you onboard tenants                          |

Keycloak always gets its **own database**, in every mode. Its \~100-table data
model has no business mingling with application schemas.

***

## The three layouts

<Tabs>
  <Tab title="database-per-service (default)">
    One Postgres pod. Seven databases inside it, plus Keycloak's. **One shared
    PVC.**

    ```yaml theme={null}
    postgres:
      mode: database-per-service     # this is the default
      storage:
        size: 50Gi
        storageClass: gp3
    ```

    **Pick this when** you want DB-level isolation between services without
    running eight Postgres instances. It's the shipped default and the
    best-trodden path.

    **The trade-off:** every service shares one PVC and one set of resource
    limits. When audit grows, it competes with everything else for the same disk.
  </Tab>

  <Tab title="schema-per-service">
    One Postgres pod, **one** database (`swarmd`), seven schemas inside it.
    Still one shared PVC.

    ```yaml theme={null}
    postgres:
      mode: schema-per-service
      bootstrap:
        sharedDatabase: swarmd        # rename if you like
        keycloakDatabase: keycloak
    ```

    **Pick this when** your DBA wants one backup and restore surface, uniform
    grants, and the ability to join across services when debugging.

    **The trade-off:** no DB-level isolation. A bad grant or a runaway query has
    a wider blast radius.
  </Tab>

  <Tab title="instance-per-service">
    **Eight** Postgres deployments — one per DB-backed service plus one for
    Keycloak — each with its own PVC and resource limits.

    ```yaml theme={null}
    postgres:
      mode: instance-per-service
      storage:
        size: 10Gi                    # default for anything not overridden below
        storageClass: gp3
      resources:
        requests: { cpu: 250m, memory: 512Mi }
        limits:   { cpu: "1",  memory: 1Gi }
      perService:
        audit:
          storage: { size: 100Gi }
          resources:
            requests: { cpu: 500m, memory: 1Gi }
            limits:   { cpu: "2",  memory: 4Gi }
        registry:
          storage: { size: 100Gi }
          resources:
            requests: { cpu: 500m, memory: 1Gi }
            limits:   { cpu: "2",  memory: 4Gi }
        relay:
          storage: { size: 20Gi }
        # tenantAuth, billing, teams, notification, keycloak inherit the 10Gi default
    ```

    **Pick this when** audit and registry need to grow — and be backed up, and be
    sized — independently of the services that barely write.

    **The trade-off:** eight Postgres pods. Budget \~12 GiB of cluster memory
    before anything else, and accept eight things to monitor.
  </Tab>
</Tabs>

<Note>
  `postgres.mode` is validated at render time. A typo fails the install with
  *"postgres.mode must be one of: database-per-service, schema-per-service,
  instance-per-service"* rather than producing a half-wired release.
</Note>

### Sizing guidance

In `instance-per-service` mode, anything you don't override inherits
`postgres.storage` / `postgres.resources`. Reasonable starting points:

| Service                                          | Storage   | Why                                                          |
| ------------------------------------------------ | --------- | ------------------------------------------------------------ |
| `audit`                                          | 50–100 Gi | Append-only event per action across the whole platform       |
| `registry`                                       | 50–100 Gi | Registrations plus long-window health and invocation history |
| `relay`                                          | 10–20 Gi  | Task and session state; older tasks are purged               |
| `tenantAuth`, `billing`, `teams`, `notification` | 5–10 Gi   | Modest, human-paced growth                                   |
| `keycloak`                                       | 5–10 Gi   | Grows only as you onboard tenants                            |

***

## Bring your own Postgres

For anything you care about, run Postgres properly — RDS, Cloud SQL,
CloudNativePG, your DBA's cluster — and point the chart at it.

```yaml theme={null}
postgres:
  deploy: false
  external:
    jdbcUrlTemplate: "jdbc:postgresql://pg.internal:5432/${db}"
    credentialsSecret: my-pg-secret
    usernameKey: username         # override if your Secret uses other keys
    passwordKey: password
```

```bash theme={null}
kubectl -n swarmd create secret generic my-pg-secret \
  --from-literal=username='swarmd' \
  --from-literal=password='...'
```

**`${db}` is a placeholder the chart substitutes per service** — `registry`,
`agent_relay`, `audit` and so on from the table above. So one template covers
every service.

### One database instead of seven

If your managed instance gives you a single database and you want the
services to share it with a schema each — the external equivalent of
`schema-per-service`:

```yaml theme={null}
postgres:
  deploy: false
  external:
    jdbcUrlTemplate: "jdbc:postgresql://pg.internal:5432/${db}"
    credentialsSecret: my-pg-secret
    singleDatabase: swarmd_prod        # every service lands here
```

With `singleDatabase` set, `${db}` resolves to that one name for every
service and each gets its own schema. Flyway creates the schema on first
migration.

<AccordionGroup>
  <Accordion title="What you have to create yourself">
    The chart does **not** create databases on an external server — it has no
    superuser and no business having one. Before installing, create:

    * Each database from the table above (or the single `singleDatabase`), and
    * A role with full rights on them.

    Flyway handles tables, indexes and schemas from there.
  </Accordion>

  <Accordion title="External Postgres forces external Keycloak">
    `keycloak.deploy=true` with `postgres.deploy=false` is **not supported**, and
    the chart fails the render rather than pretending. The bundled Keycloak needs
    a host and port, and the chart won't parse them out of an arbitrary JDBC URL.

    Bringing your own Postgres? Bring your own Keycloak too — see
    [external Keycloak](/self-hosting/configuration#bring-your-own-keycloak).
  </Accordion>

  <Accordion title="TLS to your database">
    Put it in the JDBC URL — the chart passes the template through verbatim:

    ```yaml theme={null}
    jdbcUrlTemplate: "jdbc:postgresql://pg.internal:5432/${db}?sslmode=verify-full&sslrootcert=/etc/ssl/certs/rds-ca.pem"
    ```

    Mounting a CA bundle into every service is not something the chart does for
    you today; if you need `verify-full` with a private CA, talk to us.
  </Accordion>
</AccordionGroup>

***

## ClickHouse

Audit and registry write their long-window history to ClickHouse when it's
enabled. It's **off by default**, and the platform works without it — the
long-window scan endpoints simply return empty results.

<Tabs>
  <Tab title="In-cluster">
    ```yaml theme={null}
    clickhouse:
      enabled: true
      deploy: true
    ```

    Renders ClickHouse with embedded Keeper. Fine for a single node; not a
    replicated production topology.
  </Tab>

  <Tab title="Your own">
    ```yaml theme={null}
    clickhouse:
      enabled: true
      deploy: false
      external:
        url: "jdbc:clickhouse://ch.internal:8123/swarmd"
        credentialsSecret: my-ch-secret
    ```

    Both `url` and `credentialsSecret` are required — omit either and the install
    fails with a named error.
  </Tab>
</Tabs>

<Tip>
  Turn ClickHouse on if you plan to use the audit dashboards or trace history
  in anger. Turning it on later is a `helm upgrade`, but history is only
  recorded from that point — there is no backfill.
</Tip>

***

## Backups

The chart does not back anything up. What to point your tooling at:

| Mode                   | Surface                                                                 |
| ---------------------- | ----------------------------------------------------------------------- |
| `database-per-service` | One Postgres, eight databases — dump per database or the whole instance |
| `schema-per-service`   | One database — a single `pg_dump` covers every service                  |
| `instance-per-service` | Eight instances — eight schedules, and eight PVC snapshots              |
| External               | Whatever your managed provider gives you                                |

Also back up the **`swarmd-generated-credentials` Secret**. It holds the
`encryption-key` that registry, relay, teams and the UI use for data at rest.
Restoring a database without it leaves you with rows you cannot decrypt.

***

## Next

<CardGroup cols={2}>
  <Card title="Ingress" icon="globe" href="/self-hosting/ingress">
    Real hostnames and TLS.
  </Card>

  <Card title="Presets" icon="layer-group" href="/self-hosting/presets">
    Tested values files for each layout.
  </Card>
</CardGroup>
