Skip to main content

SERVE FLAGS

Flags

Flag Default Description
--config <path> Path to a YAML config file. Default: .swamp/serve.yaml (loaded silently if present). See Config file
--port <port> 9090 Port to listen on
--host <host> 127.0.0.1 Host to bind to
--cert-file <path> Path to PEM-encoded TLS certificate
--key-file <path> Path to PEM-encoded TLS private key
--auth-mode <mode> none Authentication mode: none (deprecated), token, or oauth. token and oauth require swamp auth login or SWAMP_API_KEY with serve:* scope; none has no Swamp auth requirement
--admins <principals> Comma-separated principal IDs for admin access
--grant-reload <mode> manual Policy snapshot reload: manual or auto
--grants-file <path> Path to an external grants YAML file loaded at startup. Grants from this file are merged with any grants in the grants/ directory
--remote-only (off) Disable local (loopback) execution — all workflow steps must declare placement (target, labels, or platform) and be dispatched to remote workers. Steps without placement error immediately. See Remote-only mode
--no-schedule Disable scheduled workflow execution
--webhook <spec> Register a webhook endpoint (repeatable)
--allowed-collectives Comma-separated collective slugs for OAuth admission
--allowed-users Comma-separated user identifiers for OAuth admission
--oauth-provider <url> OAuth authorization server URL
--oauth-client-id <id> OAuth client ID (required for oauth mode)
--oauth-client-name <name> Override the OAuth client_name used during dynamic client registration. Default: swamp-serve-{repoName}-{hostname} (truncated to 128 chars). Useful in container/k8s environments where hostname is ephemeral
--groups-field <field> Userinfo field name for group memberships (default: collectives)
--trust-proxy Trust X-Forwarded-For for client IP in token auth rate limiting
--ws-idle-timeout <duration> WebSocket idle timeout — how long the server waits for a pong before closing the connection. Accepts seconds (30), explicit units (2m, 5m), or 0 to disable
--queue-timeout <duration> 10m How long a placed step queues for a matching worker before timing out. Accepts seconds (60), explicit units (2m, 10m), or 0 to disable
--verify-on-enroll (off) Run a fleet probe on each enrolling worker before it becomes schedulable. Workers that fail the probe are marked unverified and excluded from dispatch until they re-enroll successfully
--hot-reload (off) Enable SIGHUP-based hot-reload for pulled extension bundles and the trusted collectives list. Writes a PID file to .swamp/serve.pid; use swamp serve reload to trigger a reload
--group-refresh-interval <d> 4h How often the server refreshes IdP group memberships from the OAuth provider's userinfo endpoint. Accepts duration units (30m, 4h, 1d), or 0 to disable
--restricted-model-types <types> Comma-separated model types that require admin on access:* to create or run. Non-admin users are denied with Access denied: <principal> does not have 'admin' on access:*. Types are normalized on parse — Command::Shell, COMMAND.SHELL, and command/shell all match
--restricted-commands <cmds> Comma-separated server commands that require admin authority (e.g. datastore.namespace.list,extension.install). Non-admin users are denied with Access denied: <principal> does not have 'admin' on access:<command>. Requires --auth-mode token or oauth
--trusted-hosts <hosts> Comma-separated hostnames to trust for Host header validation when binding off-loopback (e.g. host.docker.internal,host.minikube.internal)
--detach-runs (off) Deprecated. Accepted for backwards compatibility but has no effect. HA mode is now auto-detected based on the datastore configuration — when the datastore supports a control plane (e.g. S3), runs are automatically detached and reconciliation is enabled
--heartbeat-interval <duration> 30s How often to write an instance heartbeat to the control-plane store. Accepts duration units (15s, 30s, 1m). Only effective with a control-plane-capable datastore
--stale-ttl <duration> 90s How long a heartbeat can go without update before the instance is considered dead. Must be at least 2× --heartbeat-interval. Accepts duration units (60s, 90s, 3m). Only effective with a control-plane-capable datastore
--reconciliation-interval <duration> 60s How often to scan for dead peer instances and reconcile their orphaned runs. Accepts duration units (30s, 60s, 2m). Only effective with a control-plane-capable datastore
--hydration-timeout <duration> 60s Maximum time to wait for initial datastore cache hydration at startup. Accepts seconds (60), explicit units (60s, 5m). Increase for large repos where the initial pull takes longer
--max-concurrent-runs <n> 100 Maximum number of concurrent detached runs across all principals
--max-runs-per-principal <n> Maximum number of concurrent detached runs per authenticated principal. Unset by default (no per-principal limit)
--max-run-duration <duration> Maximum wall-clock time a detached run may execute before being aborted. Accepts seconds (3600), explicit units (1h, 30m). Unset by default (no limit)
--grants-dir <path> Path to a directory of grants YAML files loaded at startup
--dashboard (off) Enable the web dashboard at /dashboard. The dashboard is opt-in and adds no overhead when disabled. See Dashboard
--enable-internal-api (off) Enable the /internal/runs endpoint for full run history access

Off-loopback binding (--host set to anything other than 127.0.0.1 or ::1) requires both TLS (--cert-file + --key-file) and authentication (--auth-mode other than none, which is itself deprecated). The server refuses to start without them. By default, Host header validation rejects requests whose Host does not match the bound address — a DNS rebinding defense. Use --trusted-hosts to whitelist additional hostnames (such as host.docker.internal) so Docker and Kubernetes workers can connect via platform-specific hostnames without disabling the protection.

Resource limits

At startup, swamp serve automatically raises the process's open-file soft limit to 8192 using setrlimit. Each connected client, worker WebSocket, and datastore handle consumes a file descriptor, so the stock macOS soft limit of 256 is too low for most server workloads.

Condition Behavior
Hard limit ≥ 8192 (stock macOS: unlimited) Soft limit raised to 8192 automatically; an info-level log confirms the raise
Hard limit < 8192 Raise skipped; a warning is logged with instructions to raise the limit before starting

No user action is needed on stock macOS, where the soft limit is 256 and the hard limit is unlimited — the server fixes it automatically. When the hard limit is also restricted (some container runtimes and hardened Linux hosts), raise it before starting the server:

# Shell session (applies to the current shell and its children)
ulimit -Sn 8192

# systemd unit (permanent)
# Add to the [Service] section of the unit file:
LimitNOFILE=8192

Health endpoint

GET /ready returns a JSON object indicating whether the server is ready to accept requests. The response includes an instanceId field — a unique identifier for this server instance, stable for the lifetime of the process:

{ "ready": true, "instanceId": "a1b2c3d4" }

The instanceId is used internally for heartbeat registration and run ownership. In a multi-instance deployment, each instance reports a distinct instanceId, which can help identify which instance is handling a request or owns a particular run.

Config file

swamp serve can read configuration from a YAML file instead of (or in addition to) CLI flags. By default, the server looks for .swamp/serve.yaml in the repository directory. Use --config <path> to specify a different location.

Resolution priority

When the same setting is provided through multiple sources, the highest-priority source wins:

  1. CLI flag (explicit)
  2. Environment variable
  3. Config file
  4. Built-in default

See Configuration Layers for background.

Loading behavior

Scenario Behavior
Default path (.swamp/serve.yaml) exists Loaded automatically
Default path does not exist Silently skipped — the server starts with defaults
Explicit --config <path> exists Loaded
Explicit --config <path> does not exist Hard error: Serve config file not found: <path>
Invalid YAML Hard error at startup
Unknown keys in the file Warning logged per key (Unknown key "<key>") — server continues

Supported keys

The config file uses kebab-case keys matching the CLI flag names. Most flags are available in the config file, including authentication and TLS settings via the auth: and tls: nested blocks.

Top-level keys

YAML key Type CLI equivalent
port integer --port
host string --host
auth object See auth: block
tls object See tls: block
webhooks object[] --webhook (repeated)
triggers object See triggers: block
remote-only boolean --remote-only
schedule boolean --no-schedule (inverted: schedule: false = --no-schedule)
grant-reload string --grant-reload
grants-file string --grants-file
grants-dir string --grants-dir
trust-proxy boolean --trust-proxy
hot-reload boolean --hot-reload
detach-runs boolean --detach-runs (deprecated)
verify-on-enroll boolean --verify-on-enroll
ws-idle-timeout string --ws-idle-timeout
queue-timeout string --queue-timeout
trusted-hosts string[] --trusted-hosts
heartbeat-interval string --heartbeat-interval
stale-ttl string --stale-ttl
reconciliation-interval string --reconciliation-interval
max-concurrent-runs integer --max-concurrent-runs
max-runs-per-principal integer --max-runs-per-principal
max-run-duration string --max-run-duration
hydration-timeout string --hydration-timeout
dashboard boolean --dashboard
enable-internal-api boolean --enable-internal-api

Duration strings accept the same formats as the corresponding CLI flag: plain seconds (30), explicit units (2m, 5m), or 0 to disable.

List-typed keys (trusted-hosts) use YAML sequences instead of comma-separated values:

trusted-hosts:
  - host.docker.internal
  - host.minikube.internal

auth: block

Authentication settings are nested under auth:. In the config file, list-valued keys use YAML sequences; on the CLI the same flags accept comma-separated strings.

Sub-key Type CLI equivalent
mode string --auth-mode
admins string[] --admins
allowed-collectives string[] --allowed-collectives
allowed-users string[] --allowed-users
oauth-provider string --oauth-provider
oauth-client-id string --oauth-client-id
oauth-client-name string --oauth-client-name
groups-field string --groups-field
restricted-model-types string[] --restricted-model-types
restricted-commands string[] --restricted-commands
group-refresh-interval string --group-refresh-interval

No environment variable exists for auth.mode, auth.admins, auth.allowed-collectives, auth.allowed-users, auth.oauth-provider, or auth.oauth-client-id. These must be set via the config file or CLI flags.

auth:
  mode: token
  admins:
    - "user:abc-123"
    - "user:def-456"
  restricted-model-types:
    - command/shell

tls: block

TLS certificate paths are nested under tls:.

Sub-key Type CLI equivalent
cert-file string --cert-file
key-file string --key-file
tls:
  cert-file: /etc/certs/server.crt
  key-file: /etc/certs/server.key

triggers: block

Trigger overrides define or override scheduled execution for workflows. Each key is a workflow name; the value is an object with schedule (a cron expression) and/or inputs (a mapping of input values).

Trigger overrides can also be managed on a running server via swamp workflow trigger set and swamp workflow trigger remove with --server (see Hot Reload).

triggers:
  nightly-backup:
    schedule: "0 2 * * *"
  hourly-sync:
    schedule: "0 * * * *"
    inputs:
      target: production
      dry-run: false
Field Required Type Description
schedule No string Cron expression (must specify at least schedule or inputs)
inputs No object Input values passed to the workflow on each triggered run

Flags not available in the config file

The following flags are CLI-only and are not recognized in the YAML config file:

  • --config — identifies the config file itself
  • --repo-dir — sets the repository directory that the config file resolves relative to
  • --no-schedule — use schedule: false in the config file instead

The global flags --no-telemetry and --log-level are also CLI-only.

Webhook format in config files

On the CLI, webhooks use a colon-delimited string:

--webhook '<route>:<workflow>:<secret>[:<scheme>[:<header>[:<prefix>]]]'

In a config file, each webhook is a YAML object with named fields:

webhooks:
  - route: /hooks/github
    workflow: deploy-on-push
    secret: "@env=WEBHOOK_SECRET"
    scheme: github
  - route: /hooks/custom
    workflow: process-event
    secret: "@file=/run/secrets/webhook"
    scheme: generic
    header: X-Custom-Signature
    prefix: "sha256="
Field Required Description
route Yes URL path for the endpoint
workflow Yes Workflow to trigger on valid requests
secret Yes Signing secret or indirection (@env=, @file=, @vault=)
scheme No Provider scheme (default: github)
header No Custom header name (generic scheme only)
prefix No Expected value prefix (generic scheme only)

See Webhooks for provider schemes, signature verification, and secret indirection.

Example config file

# .swamp/serve.yaml
port: 8080
host: "0.0.0.0"

auth:
  mode: token
  admins:
    - "user:abc-123"

tls:
  cert-file: /etc/certs/server.crt
  key-file: /etc/certs/server.key

grant-reload: auto
trust-proxy: true
hot-reload: true
heartbeat-interval: "30s"
stale-ttl: "90s"
reconciliation-interval: "60s"

trusted-hosts:
  - host.docker.internal

webhooks:
  - route: /hooks/github
    workflow: deploy-on-push
    secret: "@env=WEBHOOK_SECRET"
    scheme: github

triggers:
  nightly-report:
    schedule: "0 6 * * *"
# All settings are in the config file — start with no extra flags
swamp serve

Environment variables

Variable Description
SWAMP_API_KEY Collective API token for server authentication. With serve:* scope, authenticates the server for instance registration and heartbeats. With oauth:manage scope (in addition to serve:*), enables headless OAuth client registration and admin username resolution — no browser interaction needed on first boot. Read fresh from the environment on each start; key rotation requires only a restart
SWAMP_REMOTE_ONLY Default for --remote-only (true or 1 to enable)
SWAMP_GRANTS_FILE Default for --grants-file
SWAMP_GRANTS_DIR Default for --grants-dir
SWAMP_SERVE_URL Default server URL for --server on client commands. Takes precedence over SWAMP_SERVER_URL
SWAMP_SERVER_URL Routing alias for SWAMP_SERVE_URL. Used as fallback when SWAMP_SERVE_URL is not set. Precedence: --server flag > SWAMP_SERVE_URL > SWAMP_SERVER_URL
SWAMP_SERVE_CERT_FILE Default for --cert-file / tls.cert-file
SWAMP_SERVE_KEY_FILE Default for --key-file / tls.key-file
SWAMP_SERVER_TOKEN Server token (alternative to stored credential)
SWAMP_OAUTH_CLIENT_NAME Default for --oauth-client-name / auth.oauth-client-name. Default: swamp-serve-{repoName}-{hostname}
SWAMP_WS_IDLE_TIMEOUT Default for --ws-idle-timeout (default: 30s)
SWAMP_QUEUE_TIMEOUT Default for --queue-timeout (default: 10m)
SWAMP_VERIFY_ON_ENROLL Default for --verify-on-enroll (true or 1 to enable)
SWAMP_SERVE_EXTRA_HEADERS Extra HTTP headers sent by clients through proxies/tunnels (newline-separated Name: value)
SWAMP_GROUP_REFRESH_INTERVAL Default for --group-refresh-interval (default: 4h)
SWAMP_TRUSTED_HOSTS Default for --trusted-hosts
SWAMP_HEARTBEAT_INTERVAL Default for --heartbeat-interval (default: 30s)
SWAMP_STALE_TTL Default for --stale-ttl (default: 90s)
SWAMP_RECONCILIATION_INTERVAL Default for --reconciliation-interval (default: 60s)
SWAMP_HYDRATION_TIMEOUT Default for --hydration-timeout (default: 60s)
SWAMP_MAX_CONCURRENT_RUNS Default for --max-concurrent-runs (default: 100)
SWAMP_MAX_RUNS_PER_PRINCIPAL Default for --max-runs-per-principal (no default — no per-principal limit)
SWAMP_MAX_RUN_DURATION Default for --max-run-duration (no default — no limit)
SWAMP_DASHBOARD Default for --dashboard (true or 1 to enable)
SWAMP_ENABLE_INTERNAL_API Default for --enable-internal-api (true or 1 to enable)

Authentication modes

none (default — deprecated)

Warning

Deprecated. --auth-mode none will be removed in a future release. See Set Up Token Authentication.

No authentication. Connections on loopback are accepted without credentials. Off-loopback binding is refused.

token

Token-based authentication. Users authenticate with a server token minted via swamp access token mint. Unauthenticated connections receive HTTP 401.

WebSocket token transports

The server accepts tokens on the WebSocket upgrade request via three transports:

Transport Header / parameter Format
Authorization header Authorization: Bearer <name>.<secret> Standard HTTP Bearer scheme
WebSocket subprotocol Sec-WebSocket-Protocol: bearer.<name>.<secret> Single subprotocol value, dot-delimited
Query parameter ?token=<name>.<secret> Appended to the WebSocket URL

When multiple transports are present on the same request, the server uses the first match in this priority order: Authorization header, Sec-WebSocket-Protocol, query parameter.

The Authorization header does not expose the token in URLs. Query-string tokens appear in proxy access logs, browser history, and Referer headers. See Token transports on WebSocket for background on when each transport applies.

See Connect with a token for connection examples.

oauth

OAuth-based authentication. Users authenticate via an OAuth device flow. The server validates tokens against the OAuth provider specified by --oauth-provider. See Set Up OAuth Authentication for the setup guide.

Credential storage

swamp auth server-login stores tokens in ~/.config/swamp/servers.json. Each entry maps a server URL to a token. When a command uses --server, the stored credential is used automatically. The --server flag can be omitted if SWAMP_SERVE_URL is set — the command resolves the URL the same way other remote commands do.

swamp auth server-login --server wss://swamp.example.com \
  --token 'paul-token.<secret>'

# Or, if SWAMP_SERVE_URL is already exported:
swamp auth server-login --token 'paul-token.<secret>'

Webhook endpoints

See Webhooks for the full reference — provider schemes, secret sources, signature verification, and CEL context.

The --webhook flag registers an HTTP endpoint that triggers a workflow on incoming requests:

--webhook '<route>:<workflow>:<secret>[:<scheme>[:<header>[:<prefix>]]]'

Supported schemes: github (default), linear, stripe, slack, jira, generic. The generic scheme requires a custom header name and accepts an optional value prefix.

Secret indirection

The <secret> field supports indirection prefixes so signing secrets don't appear as literal values in process argv:

Prefix Source Example
@env=VAR_NAME Environment variable VAR_NAME @env=WEBHOOK_SECRET
@file=/path File contents (trailing newline trimmed) @file=/run/secrets/webhook
@vault=<name>:<key> Named vault key @vault=prod-secrets:webhook-key
(plain string) Literal value (backward compatible) mysecret
swamp serve --webhook '/hooks/github:my-workflow:@env=WEBHOOK_SECRET'
swamp serve --webhook '/hooks/linear:wf:@file=/run/secrets/webhook:linear'
swamp serve --webhook '/hooks/github:deploy:@vault=prod-secrets:webhook-key'

The server reads the resolved secret at startup. If the environment variable is unset or the file cannot be read, the server refuses to start.