Skip to main content

DEPLOY WITH HEADLESS OAUTH

This guide shows you how to deploy swamp serve with OAuth authentication in an environment where no browser is available — Kubernetes pods, CI runners, or systemd services.

Prerequisites

  • A collective with owner or admin role
  • swamp auth login completed (to create the token)

1. Create a collective API token

Create a token with both oauth:manage and serve:* scopes:

swamp auth token create --collective myorg \
  --scopes oauth:manage,serve:* \
  --name headless-serve

Copy the token immediately — it is shown only once.

oauth:manage allows the server to register itself as an OAuth client. serve:* allows instance registration and heartbeats.

2. Set the token in your deployment environment

Store the token as SWAMP_API_KEY in your deployment's secret management:

Kubernetes Secret:

kubectl create secret generic swamp-serve \
  --from-literal=SWAMP_API_KEY=swamp_org_abc123...

Reference it in your pod spec:

env:
  - name: SWAMP_API_KEY
    valueFrom:
      secretKeyRef:
        name: swamp-serve
        key: SWAMP_API_KEY

systemd:

# /etc/swamp-serve.env
SWAMP_API_KEY=swamp_org_abc123...
# In the [Service] section of the unit file
EnvironmentFile=/etc/swamp-serve.env

Shell (CI or scripts):

export SWAMP_API_KEY=swamp_org_abc123...

3. Start the server

swamp serve --auth-mode oauth \
  --admins swampadmin \
  --allowed-collectives myorg

With SWAMP_API_KEY set, the server registers its OAuth client and resolves admin usernames without any browser interaction. No verification URL is shown, no code needs to be entered.

Setting a stable client name

In container and Kubernetes environments, the default client name (swamp-serve-{repoName}-{hostname}) includes an ephemeral hostname that changes on every pod restart. Use --oauth-client-name or SWAMP_OAUTH_CLIENT_NAME to set a stable, deployment-meaningful name:

swamp serve --auth-mode oauth \
  --oauth-client-name prod-api-server \
  --admins swampadmin \
  --allowed-collectives myorg

Or via environment variable in a pod spec:

env:
  - name: SWAMP_OAUTH_CLIENT_NAME
    value: prod-api-server

4. Verify the bootstrap succeeded

Check the server logs for these messages in order:

SWAMP_API_KEY verified with oauth:manage scope — registering OAuth client (headless)
Registered OAuth client <clientId> via SWAMP_API_KEY (headless)
Using SWAMP_API_KEY to resolve admin/allowed-user usernames: swampadmin
Resolved admin swampadmin to user:<sub>

Confirm the server is ready:

curl -s https://your-server:9090/ready | jq .
{ "ready": true, "instanceId": "a1b2c3d4" }

Subsequent starts

After the first boot, OAuth client credentials and admin mappings are cached in the vault. Subsequent starts use the cached values — SWAMP_API_KEY is still read (for instance registration) but the OAuth registration step is skipped:

No stored OAuth client credentials found — first-time setup required

This message appears only on the first boot. On subsequent starts, the server loads credentials from the vault silently.

Key rotation

SWAMP_API_KEY is read fresh from the environment on each boot. To rotate the key:

  1. Create a new token with the same scopes
  2. Update the secret in your deployment environment
  3. Restart the server

The server picks up the new key on restart. No vault changes or OAuth re-registration is needed — the stored client credentials remain valid.

Troubleshooting

Missing oauth:manage scope:

SWAMP_API_KEY is missing the "oauth:manage" scope required for headless OAuth
client registration. Generate a new collective API token that includes
"oauth:manage".

Create a new token with both oauth:manage and serve:* scopes.

Admin username not found:

Failed to resolve admin 'swampadmin': <message>. Ensure the username exists on
https://swamp-club.com.

Verify the username in --admins exists on swamp-club. The server resolves usernames to principal IDs at startup.

Key validation failed:

SWAMP_API_KEY validation failed: <status> <statusText>

Check that the token has not been revoked or expired. Verify with swamp auth whoami.