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

# Quickstart

> Install Swarmd into a Kubernetes cluster and sign in — about ten minutes, most of it waiting for pods.

# Quickstart

By the end you'll have the full platform running in your cluster and be
signed in to the UI as your first admin user.

<Info>
  Need the prerequisites? [Overview → What you need](/self-hosting/overview#what-you-need).
  The short version: a cluster with a default StorageClass, Helm 3, and a
  licence key.

  Don't have a key yet? Licences are issued per deployment —
  [talk to us](https://swarmd.ai/contact) and we'll size the tier and get you
  one. There is no self-serve path, and the cluster cannot pull Swarmd's images
  without it.
</Info>

***

## Step 1: Store your licence key

You *can* pass the key with `--set licence.key=...`, and for a throwaway test
that's fine. Don't do it for anything else: `--set` values are recorded in
Helm's release history and visible to anyone who can run `helm get values`.

Put it in a Secret instead:

```bash theme={null}
kubectl create namespace swarmd

kubectl -n swarmd create secret generic swarmd-licence \
  --from-literal=value='LIC-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 
```

<Warning>
  `licence.key` and `licence.existingSecretRef.name` are **mutually exclusive**.
  Setting both fails the install with a named error rather than silently
  preferring one.
</Warning>

***

## Step 2: Install the chart

We send you the chart as a file — `swarmd-<version>.tgz` — over whatever
channel we agreed, with your licence key delivered separately. Point Helm
straight at it:

```bash theme={null}
# Point this at the file we sent you — its filename carries the version,
# so `<version>` below is a placeholder, not something to type literally.
export SWARMD_CHART=~/Downloads/swarmd-<version>.tgz

helm install swarmd $SWARMD_CHART \
  --namespace swarmd \
  --set licence.existingSecretRef.name=swarmd-licence
```

That's the whole command. Everything else has a working default.

<Note>
  **Nothing to log in to.** The chart is a self-contained file, so Helm needs no
  registry access and you need no credentials on your workstation. Your
  *cluster* still pulls Swarmd's private images, but it sorts that out on its
  own: a pre-install Job exchanges the licence key in your Secret for
  image-pull credentials and a CronJob refreshes them every 8 hours. You never
  wire that up by hand. See
  [Licence and images](/self-hosting/licence-and-images).
</Note>

<Note>
  The version is the filename. There is no `--version` flag to get wrong and no
  "latest" to resolve — the file you were sent *is* the release, which is also
  what makes it easy to say exactly what a cluster is running.

  Keep the file. It is what `helm rollback` cannot give you back if you need to
  reinstall an older release from scratch.
</Note>

<Accordion title="Reading the values file and presets">
  The chart carries its own `values.yaml` and a set of tested presets. Unpack it
  alongside the tarball to read or copy them — you still install from the file
  itself:

  ```bash theme={null}
  tar xzf $SWARMD_CHART        # creates ./swarmd/

  less swarmd/values.yaml
  ls swarmd/presets/
  ```

  Then install with a preset:

  ```bash theme={null}
  helm install swarmd $SWARMD_CHART \
    --namespace swarmd \
    -f swarmd/presets/values-shared-pg.yaml \
    --set licence.existingSecretRef.name=swarmd-licence
  ```

  `helm show values $SWARMD_CHART` prints the defaults without unpacking
  anything.
</Accordion>

<Accordion title="What just happened, in order">
  1. **Validation runs first.** The chart checks every combination it knows to
     be broken — a missing external URL, an unknown `postgres.mode`, SMTP
     enabled without credentials — and refuses to render with a specific
     message. Nothing reaches your cluster until it passes.
  2. **A pre-install Job fetches image-pull credentials.** It calls the licence
     server with your key and writes the result as a `dockerconfigjson` Secret.
     This has to succeed before anything else — without it the cluster cannot
     pull Swarmd's images. See
     [Licence and images](/self-hosting/licence-and-images).
  3. **A credentials Secret is generated.** Postgres, Keycloak admin and
     ClickHouse passwords, plus an encryption key — 24 random alphanumeric
     characters each.
  4. **Postgres and Keycloak start**, and a bootstrap Job loads the Swarmd realm.
  5. **Migration Jobs run Flyway**, then the services and UI roll out.
</Accordion>

***

## Step 3: Watch it come up

```bash theme={null}
kubectl -n swarmd get pods -w
```

Expect every pod `Ready` in about **three minutes** on a fresh cluster —
longer on first pull, since the images come from ECR.

```
NAME                                  READY   STATUS      RESTARTS   AGE
swarmd-licence-ecr-bootstrap-x9k      0/1     Completed   0          3m
swarmd-postgres-df9c7b8d4-2kx9n       1/1     Running     0          3m
swarmd-keycloak-7c4f8b6d9-mn2rt       1/1     Running     0          3m
swarmd-registry-migrate-p4l2n         0/1     Completed   0          2m
swarmd-registry-5b7d8f9c6-tz8yu       1/1     Running     0          2m
swarmd-registry-worker-7f9b2c4d8-kk1  1/1     Running     0          2m
swarmd-audit-migrate-h7z9x            0/1     Completed   0          2m
swarmd-audit-6c4d8f9b7-mm3nn          1/1     Running     0          2m
swarmd-audit-worker-9d7f6c5b4-pp8qq   1/1     Running     0          2m
...
swarmd-gateway-6d8f9c7b5-qw3er        1/1     Running     0          2m
swarmd-platform-ui-8f9c7b6d5-ax2z     1/1     Running     0          2m
```

<Note>
  **Three workloads per DB-backed service is expected**, not a mistake: a
  `-migrate` Job that runs Flyway once and exits, the API pod, and a `-worker`
  pod running schedulers and outbox drainers. A default install is 18
  Deployments and wants roughly **12 GiB**. See
  [Scaling: core and worker](/self-hosting/configuration#scaling-core-and-worker)
  for why, and how to collapse it on a laptop.
</Note>

<Note>
  **`Init:0/1` for the first minute is the gate, not a hang.** Each service
  waits on a `wait-for-keycloak` init container until Keycloak's realm answers,
  so nothing races it during startup. Once past that, **expect zero restarts** —
  a service restarting is worth investigating rather than waiting out. See
  [Startup ordering](/self-hosting/configuration#startup-ordering).
</Note>

<AccordionGroup>
  <Accordion title="Pods stuck in ImagePullBackOff">
    The licence loop failed. Check the bootstrap Job first — it's the only thing
    that can create the pull secret:

    ```bash theme={null}
    kubectl -n swarmd logs job/swarmd-licence-ecr-bootstrap
    ```

    The usual causes are a mistyped key, a licence that has expired or been
    revoked, and no cluster egress to the licence server. Full breakdown in
    [Licence and images](/self-hosting/licence-and-images#when-it-goes-wrong).
  </Accordion>

  <Accordion title="Postgres pod Pending">
    Almost always storage. `kubectl -n swarmd describe pvc` will say so —
    typically no default StorageClass, or a `storageClass` name that doesn't
    exist on this cluster:

    ```bash theme={null}
    kubectl get storageclass

    helm upgrade swarmd $SWARMD_CHART -n swarmd \
      --set licence.existingSecretRef.name=swarmd-licence \
      --set postgres.storage.storageClass=gp3
    ```

    Repeat every `--set` from the install — Helm does not carry values forward
    between releases. Once you have more than a couple, move them into a values
    file and pass `-f`; see
    [Your own values file](/self-hosting/configuration#your-own-values-file).
  </Accordion>

  <Accordion title="The install failed before creating anything">
    That's the validation gate, and the message names the exact value to fix.
    It runs before any resource is created, so there's nothing to clean up —
    correct the value and re-run.
  </Accordion>
</AccordionGroup>

***

## Step 4: Get in

Ingress is off by default, so port-forward:

```bash theme={null}
kubectl -n swarmd port-forward svc/swarmd-platform-ui 3000:80
```

Open **[http://localhost:3000](http://localhost:3000)** and create your tenant and first admin user
through the sign-up flow.

<Warning>
  **Email is off by default, which makes this a one-account deployment.** The
  admin you just created can sign in normally — but invite and password-reset
  emails are logged and dropped, so **you cannot add a second user**, and a
  forgotten password can only be fixed by a Keycloak admin.

  Fine for a first look. Wire up [SMTP](/self-hosting/configuration#smtp) before
  anyone else needs access.
</Warning>

You'll also want the API gateway reachable — it's what agents and SDKs talk to:

```bash theme={null}
kubectl -n swarmd port-forward svc/swarmd-gateway 8080:80
```

```bash theme={null}
curl http://localhost:8080/registry/v1/agents -H "Authorization: Bearer $TOKEN"
```

Ready for real hostnames instead? → [Ingress](/self-hosting/ingress).

<Accordion title="Getting the Keycloak admin password">
  The chart generates it. To reach the Keycloak admin console directly:

  ```bash theme={null}
  kubectl -n swarmd get secret swarmd-generated-credentials \
    -o jsonpath='{.data.keycloak-admin-password}' | base64 -d
  ```

  The username is in the same Secret under `keycloak-admin-username`. You
  rarely need this — Swarmd manages realm objects for you — but it's there for
  debugging identity-provider setup.
</Accordion>

***

## Step 5: Point an SDK at it

Your install is a complete Swarmd platform, so the SDKs work against it
unchanged — you just override the two URLs:

```bash .env theme={null}
SWARMD_AGENT_ID=<from registering an agent>
SWARMD_CLIENT_SECRET=<...>
SWARMD_BASE_URL=https://api.your-domain.example
SWARMD_TOKEN_URL=https://auth.your-domain.example/realms/swarmd/protocol/openid-connect/token
```

<Note>
  Both URLs must point at the **same** install. A base URL from one environment
  with a token URL from another mints tokens the gateway rejects on audience —
  a `401` that looks like a bad secret but isn't.
</Note>

From there the [Python SDK](/sdks/python/overview) and the
[TypeScript channel client](/sdks/typescript/channel-client) apply exactly as
written.

***

## Step 6: Make it real

The default install is deliberately minimal — it boots anywhere, needs no
external accounts, and stores everything in one in-cluster Postgres. Before
production:

<CardGroup cols={2}>
  <Card title="Pick a database layout" icon="database" href="/self-hosting/databases">
    One Postgres, one per service, or your own managed instance.
  </Card>

  <Card title="Set up ingress and TLS" icon="lock" href="/self-hosting/ingress">
    Traefik or AWS Load Balancer Controller.
  </Card>

  <Card title="Turn on SMTP" icon="envelope" href="/self-hosting/configuration#smtp">
    Without it, no user can verify an address or accept an invite.
  </Card>

  <Card title="Plan your first upgrade" icon="arrow-up-right-dots" href="/self-hosting/upgrades">
    What survives a version bump, how to preview one, and the credential
    rotation to avoid.
  </Card>

  <Card title="Start from a preset" icon="layer-group" href="/self-hosting/presets">
    Eight tested shapes, laptop through hardened production.
  </Card>
</CardGroup>

***

## Uninstalling

```bash theme={null}
helm uninstall swarmd -n swarmd
```

<Warning>
  PVCs and the generated credentials Secret **survive** on purpose — the Secret
  carries `helm.sh/resource-policy: keep`, so a reinstall reuses the same
  passwords and your data is still there. To destroy the data too:

  ```bash theme={null}
  kubectl -n swarmd delete pvc --all
  kubectl -n swarmd delete secret swarmd-generated-credentials
  ```

  That is irreversible.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.