> ## 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.

# Self-hosting Swarmd

> Run the whole Swarmd platform in your own Kubernetes cluster from a single Helm chart.

# Run Swarmd in your own cluster

One `helm install` stands up the entire platform in your cluster: eight
backend services, the platform UI, Keycloak and Postgres — all auto-wired
from a single `values.yaml`.

Your data, your network, your cluster. Nothing leaves it except a periodic
licence check.

<CardGroup cols={2}>
  <Card title="Install it" icon="rocket" href="/self-hosting/quickstart">
    From a licence key to a running platform, step by step.
  </Card>

  <Card title="How licensing works" icon="key" href="/self-hosting/licence-and-images">
    Your key is also how the cluster authenticates image pulls.
  </Card>

  <Card title="Choose a database layout" icon="database" href="/self-hosting/databases">
    One Postgres or one per service — and how to bring your own.
  </Card>

  <Card title="Expose it" icon="globe" href="/self-hosting/ingress">
    Worked examples for Traefik and AWS Load Balancer Controller.
  </Card>
</CardGroup>

***

## What gets deployed

```mermaid theme={null}
flowchart TB
    subgraph edge["Ingress"]
        ui_h["app.your-domain"]
        api_h["api.your-domain"]
        auth_h["auth.your-domain"]
    end

    subgraph app["Swarmd services"]
        gw["gateway"]
        reg["registry"]
        rel["relay"]
        aud["audit"]
        ta["tenant-auth"]
        bill["billing"]
        teams["teams"]
        pay["a2a-payments (off)"]
    end

    subgraph data["State"]
        pg[("Postgres")]
        kc["Keycloak"]
        ch[("ClickHouse (opt)")]
    end

    subgraph pii["PII detection"]
        pir["Piiranha (opt)"]
        pres["Presidio (opt)"]
    end

    uipod["Platform UI"]

    ui_h --> uipod
    api_h --> gw
    auth_h --> kc
    gw --> reg & rel & aud & ta & bill & teams & pay
    reg & rel & aud & ta & bill & teams --> pg
    aud & reg -.-> ch
    rel -.-> pir & pres
    gw & reg & rel & ta & teams --> kc
```

| Component                                                    | Default         | Toggle                         |
| ------------------------------------------------------------ | --------------- | ------------------------------ |
| gateway, registry, relay, audit, tenant-auth, teams, billing | on              | `services.<name>.enabled`      |
| Platform UI                                                  | on              | `ui.platform.enabled`          |
| Postgres                                                     | on (in-cluster) | `postgres.deploy`              |
| Keycloak                                                     | on (in-cluster) | `keycloak.deploy`              |
| Piiranha — CPU-only PII detection                            | **off**         | `piiranha.enabled`             |
| ClickHouse — audit history, registry events                  | **off**         | `clickhouse.enabled`           |
| Presidio — richer PII detection                              | **off**         | `presidio.enabled`             |
| SMTP — verification, reset, invite emails                    | **off**         | `smtp.enabled`                 |
| a2a-payments — x402 settlement                               | **off**         | `services.a2aPayments.enabled` |

<Note>
  **Keycloak is not optional.** Every service authenticates against it, and
  agent credentials are Keycloak service accounts. You can point at your own
  Keycloak instead of the bundled one — see
  [external Keycloak](/self-hosting/configuration#bring-your-own-keycloak).
</Note>

### Every service runs split

Each of the seven DB-backed services deploys as **two** workloads plus a Job:

| Workload            | Runs                                                         |
| ------------------- | ------------------------------------------------------------ |
| `<service>` (core)  | The API. No schedulers, no migrations.                       |
| `<service>-worker`  | Schedulers and outbox drainers. Never receives traffic.      |
| `<service>-migrate` | A Job that runs Flyway once and exits, before either starts. |

This is the default because the combined topology fails quietly: scaling the
API to *N* replicas also runs *N* copies of every `@Scheduled` job, so outbox
drainers and monitor evaluators race each other. Splitting also stops a slow
scheduler starving the API's thread pool, and moves migrations out of pod
startup so a long Flyway run can't trip readiness on every replica at once.

Budget for it — a default install is **18 Deployments** (seven core, seven
worker, plus gateway, UI, Postgres and Keycloak), on top of the one-shot Jobs.
On a laptop, collapse a service back to a single container with
`services.<name>.worker.enabled=false`.

***

## The three-state pattern

Every optional component works the same way, which is worth learning once:

| State                          | Meaning                                                                         |
| ------------------------------ | ------------------------------------------------------------------------------- |
| `enabled: false`               | Feature off. Services that read its env var see `false` and degrade gracefully. |
| `enabled: true, deploy: true`  | The chart runs the component in your cluster.                                   |
| `enabled: true, deploy: false` | You run it; point the chart at yours via the `external.*` block.                |

The install **fails loudly** if you pick the third state and leave a required
`external` URL empty — you get a named error at render time, not a
`CrashLoopBackOff` twenty minutes later.

`postgres` and `keycloak` follow the same shape minus the `enabled` toggle:
they're required, so it's only ever "ours or yours".

***

## What you need

<AccordionGroup>
  <Accordion title="A Kubernetes cluster">
    Any conformant cluster — EKS, GKE, AKS, OpenShift, k3s, or minikube for a
    first look. You need:

    * A **default StorageClass**, or a name to set in `postgres.storage.storageClass`.
    * Enough headroom: roughly **12 GiB / 6 vCPU** for the default shape, since
      every DB-backed service runs a core *and* a worker pod. Add \~4 GiB if you
      choose one Postgres per service. Trimming workers off
      (`services.<name>.worker.enabled=false`) brings it back under 8 GiB for a
      laptop trial.
    * **Traefik**, or AWS Load Balancer Controller on EKS, if you want real
      hostnames rather than `kubectl port-forward`. See
      [Ingress](/self-hosting/ingress) — ingress-nginx is not supported.
  </Accordion>

  <Accordion title="Helm 3 and kubectl">
    Nothing exotic. The chart is a plain Helm 3 chart with no subchart
    dependencies to fetch.
  </Accordion>

  <Accordion title="A Swarmd licence key">
    Format `LIC-` followed by 32 hex characters. It does two jobs: it
    authenticates the pull of Swarmd's private container images, and
    billing-service verifies it periodically to establish your tier.

    **There is no unlicensed install path for this chart, and no self-serve way
    to get a key** — licences are issued per deployment, so self-hosting starts
    by [talking to us](https://swarmd.ai/contact). Without a key the cluster
    cannot pull the images at all.
  </Accordion>

  <Accordion title="Egress to the licence server (only)">
    The cluster needs outbound HTTPS to `api.dev.swarmd.ai` and to the ECR
    registry that serves the images. Nothing else leaves your network — no
    telemetry, no agent traffic, no prompts.

    Air-gapped? Mirror the images into your own registry and set
    `global.image.registry`. Talk to us about offline licence validation.
  </Accordion>
</AccordionGroup>

***

## Self-hosted or Cloud?

**It is the same platform either way** — the same services, the same tiers,
the same licence. The only thing that changes is who runs it.

|                            | Self-hosted                                                    | Swarmd Cloud                                            |
| -------------------------- | -------------------------------------------------------------- | ------------------------------------------------------- |
| The platform               | Identical                                                      | Identical                                               |
| Pricing                    | Tiered, set by your licence                                    | Tiered, set by your licence                             |
| Where data lives           | Your cluster, your network                                     | Swarmd's infrastructure                                 |
| Uptime, patching, upgrades | **Yours to run** — `helm upgrade` on your schedule             | **Ours** — we operate, patch and upgrade it             |
| Getting started            | [Talk to us](https://swarmd.ai/contact) — we issue the licence | [Sign up](https://app.dev.swarmd.ai) and start building |
| Best for                   | Data residency, regulated workloads, an existing k8s estate    | Getting going without taking on operations              |

Both are licensed, and both are tiered. Choosing self-hosted is an
operational decision — where the data sits and who carries the pager — not a
smaller product or a cheaper tier.

<Card title="Self-hosting starts with a conversation" icon="comments" href="https://swarmd.ai/contact">
  Licences are issued per deployment, so there's no self-serve path to a key.
  Tell us the shape of your estate and we'll size the tier and get you a
  licence.
</Card>

<Accordion title="Why the in-product billing screens are inactive here">
  Commercial terms for a self-hosted deployment are agreed with us directly
  rather than through a card form in the product, so billing-service runs its
  **licence backend**: your tier comes from the licence, and the self-serve
  endpoints (`checkout`, `verify`, `customer-portal`) return `503`.

  That is a difference in how you *pay*, not in what you get. Nothing about the
  platform's capabilities is gated on it.
</Accordion>

***

## Where to go next

<Steps>
  <Step title="Install">
    [Quickstart](/self-hosting/quickstart) — licence key to running platform.
  </Step>

  <Step title="Understand the licence loop">
    [Licence and images](/self-hosting/licence-and-images) — how one key
    becomes a rotating image-pull credential.
  </Step>

  <Step title="Shape it for your estate">
    [Databases](/self-hosting/databases) ·
    [Ingress](/self-hosting/ingress) ·
    [Configuration](/self-hosting/configuration)
  </Step>

  <Step title="Start from a known-good file">
    [Presets](/self-hosting/presets) — eight tested `values.yaml` shapes,
    from laptop to hardened production.
  </Step>
</Steps>
