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. The mode governs clients connecting to Serve. The Serve process itself needs a swamp-club.com credential in every mode; token and oauth additionally need it to carry the serve:* scope
--club-api-key-file <path> Path to a file holding the collective API key. Takes precedence over SWAMP_API_KEY_FILE and SWAMP_API_KEY, and the same key is used for the whole process: OAuth client registration, admin and allowed-user name lookup, instance registration, the club heartbeat, extension trust and pulls, and telemetry. Read once at startup for registration, name lookup and the heartbeat, so a rotated file needs a restart. Also accepted by swamp serve check-config; swamp serve daemon enable stores only the absolute path. See API key authentication
--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. A relative path resolves against the repository directory, so the same configuration loads the same grants wherever swamp serve, or the daemon, is started from
--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 (env: SWAMP_OAUTH_CLIENT_ID). When set, serve never registers a client and needs the client's secret from --oauth-client-secret-file, SWAMP_OAUTH_CLIENT_SECRET_FILE, SWAMP_OAUTH_CLIENT_SECRET or a usable stored copy, or it refuses to start. When omitted, a client is registered on first start
--oauth-client-secret-file <path> Path to a file holding the secret of the client named by --oauth-client-id. Used for this run only and never stored. Overrides SWAMP_OAUTH_CLIENT_SECRET_FILE and SWAMP_OAUTH_CLIENT_SECRET. Refused without a client ID. Requires Swamp v20261103.225037.0-sha.5911dc9c or later
--keep-oauth-client-in-vault (off) Leave oauth-client-id and oauth-client-secret in the user vault when moving OAuth secrets into the control-plane store (env: SWAMP_KEEP_OAUTH_CLIENT_IN_VAULT). Off by default: anything that can read that vault can read the client secret. Requires Swamp v20261103.225037.0-sha.5911dc9c or later
--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 the collectives are read from (default: collectives)
--trust-proxy Trust a reverse proxy's forwarding headers: the client IP, used for rate limiting, is read from the leftmost X-Forwarded-For entry instead of the connection's address, and dashboard sign-in takes the public scheme and host from X-Forwarded-Proto and X-Forwarded-Host
--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
--dispatch-env-allow <names> Comma-separated environment variable names. Only these ship to workers with each dispatch. Unset ships serve's whole environment minus a fixed denylist. See Dispatch environment
--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 behavior types whose behaviors require admin on access:* to create, run, edit, delete, delete or rename data of, or reference from a changed workflow step. 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 — and a leading @ is ignored, so @acme/deploy and acme/deploy restrict the same type. An entry that names no type stops serve from starting. See Restricted behavior types
--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>. Names match exactly; serve warns at startup about an entry that is not a server command. Requires --auth-mode token or oauth
--approve-requires-explicit-grant (off) Require a grant that names approve to decide a manual approval gate; an allow grant of run alone no longer implies approve. A deny grant of run still denies approve. Every replica must use the same value. Requires --auth-mode token or oauth. See Run implies approve
--trusted-hosts <hosts> Comma-separated hostnames or host:port entries to trust for Host header and browser Origin validation when binding off-loopback or serving through a reverse proxy (e.g. host.docker.internal,host.minikube.internal). A public address on a port other than 443 is given as host:port
--advertise-address <url> This node's own http(s) origin, as the other serve nodes sharing its v3 datastore reach it (e.g. https://node-1.internal:9090). The leader record names it, and followers forward writes to the leader at this address; without it a node bound to a wildcard or loopback host cannot be forwarded to. Refused with a path, query, credentials or wildcard host. A plain http:// address to a host that is not loopback starts with a warning. See Run a Multi-Instance Deployment
--ca-cert <path> PEM file of CA certificates to trust, besides the system store, when this node forwards to its team's leader. A relative path resolves against the repository. Serve reads the file at startup and refuses to start when it cannot
--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
--datastore-poll-interval <duration> 30s How often to pull config, access data (grants and groups) and runtime data from a remote datastore, and how often serve checks a v2 filesystem datastore for grants and groups changed by another process. This is how stale a peer instance can be: lower values converge instances faster at the cost of more list and get calls against the bucket. Minimum 1s. Accepts seconds (30) or units (30s, 1m, 1h); milliseconds are rejected. See Datastore polling
--token-gc-interval <duration> 1h How often to garbage-collect server tokens. Accepts seconds (3600) or units (30s, 1h, 1d), or 0 to disable. See Token garbage collection
--token-gc-grace-period <duration> 1h How long after its expiry an expired token is kept before it is collected. Accepts seconds (3600) or units (30s, 1h, 1d), or 0 to collect a token as soon as it expires
--max-concurrent-runs <n> 100 Maximum number of concurrent detached runs across all principals
--max-concurrent-scheduled-runs <n> 1 Maximum number of scheduled workflow runs in flight at once. Different workflows run together up to this limit; a workflow never overlaps itself. Separate from --max-concurrent-runs, which limits API runs. Read at startup, not on reload. See Run Workflows on a Schedule
--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. A relative path resolves against the repository directory, so the same configuration loads the same grants wherever swamp serve, or the daemon, is started from
--dashboard (off) Enable the web dashboard at /dashboard. The dashboard is opt-in and adds no overhead when disabled. See Dashboard
--auto-resume (off) Resume a suspended run once an approval made through serve decides its last gate. Applies to workflows that declare no inputs and leave autoResume unset; a workflow's own autoResume takes precedence. A local swamp workflow approve never triggers it. See Auto-resume
--enable-internal-api (off) Enable the /internal/runs endpoint for full run history access

Duration flags that drive a timer (--heartbeat-interval, --reconciliation-interval, --group-refresh-interval, --hydration-timeout, --datastore-poll-interval, --token-gc-interval and --max-run-duration) reject values above 2147483647 ms, about 24.8 days:

Error: --token-gc-interval (30d) exceeds the maximum safe timer duration (~24.8 days)

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.

A --trusted-hosts entry is a hostname or host:port. Each entry applies to three checks:

Check Bare hostname host:port
Host header (DNS rebinding) Trusts the hostname Trusts the hostname; port ignored
Browser Origin, for WebSocket upgrades and no-auth HTTP APIs Trusts the host on any port Trusts only that exact origin
Dashboard sign-in Trusts https://<host> (port 443) Trusts https://<host>:<port>

Dashboard sign-in needs no entry when the browser origin matches the scheme and authority of the request serve receives (with --trust-proxy, the forwarded scheme and host). An entry is for a proxy that rewrites them.

When a proxy publishes the dashboard on a port other than 443, list the public address as host:port. For example, behind tailscale serve --https=9443 the browser origin is https://<public-host>:9443, and the one entry <public-host>:9443 passes both the WebSocket and dashboard sign-in checks; a bare <public-host> passes the WebSocket check but not sign-in.

Dispatch environment

Serve ships a snapshot of its environment with every step it dispatches to a worker. dispatch-env-allow limits the snapshot to the names it lists.

Setting Ships
Unset (default) Serve's whole environment, minus the denylist
A list of names Only the listed names, minus the denylist
dispatch-env-allow: [] Nothing
SWAMP_DISPATCH_ENV_ALLOW empty Nothing
  • Names match case-insensitively.

  • The denylist always applies: HOME, PATH, SWAMP_*, DENO_*, XDG_*, OTEL_* and the other process-identity and host-runtime variables never ship.

  • The flag needs a value. To ship nothing, use the config file or the empty environment variable.

  • In serve.yaml, a blank entry or an entry containing a comma stops serve from starting:

    Error: Invalid dispatch-env-allow in /srv/repo/.swamp/serve.yaml: expected array of environment variable names

At startup, serve logs the scope and warns about any listed name the denylist drops:

system │ dispatch-env-allow: only these environment variables ship to workers: "DEPLOY_ENV, AWS_REGION, HOME"
system │ dispatch-env-allow lists "HOME", which never ships to workers

A name left off the list keeps the worker's own value. See Remote Execution.

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.

Auto-resume

A run that passes a manual_approval gate stays suspended until it is resumed. With --auto-resume, serve resumes the run itself when an approval made through serve decides the run's last pending gate. An approval made with a local swamp workflow approve (no --server) never triggers it.

Whether a run is resumed automatically depends on the workflow's top-level autoResume field and whether it declares inputs:

Workflow autoResume Workflow declares inputs Resumed automatically
unset No When --auto-resume is on
unset Yes Never
true Either Yes, regardless of the flag
false Either Never

An automatic resume supplies no new inputs.

When serve resumes the run, swamp workflow approve --server prints Serve is resuming run <id> automatically and its JSON output carries autoResumed: true. A resume that is refused or fails is logged, recorded as a workflow.auto_resume_failed audit event, and leaves the run awaiting a manual resume. A started resume is recorded as workflow.auto_resume.

Setting Default
Flag: --auto-resume off
Config file: auto-resume false
Environment: SWAMP_AUTO_RESUME unset

Datastore polling

With a remote datastore, three pollers pull changes that peer instances pushed: config (ConfigPoller), access data such as grants and groups (AccessDataPoller), and runtime data (RuntimeDataPoller). --datastore-poll-interval sets all three, and how often serve checks the managed config extension lockfile for changes. On a v2 filesystem datastore it sets how often serve checks the shared grant and group data for changes another process made; see Multiple instances. It has no effect on a v3 datastore. Serve logs a warning when the flag is set on any filesystem datastore without managed config, even though the v2 check uses it.

Setting Default
Flag: --datastore-poll-interval 30s
Config file: datastore-poll-interval 30s
Environment: SWAMP_DATASTORE_POLL_INTERVAL unset

Token garbage collection

Serve deletes revoked and expired server tokens in every auth mode. The first sweep runs just after boot, then one runs every --token-gc-interval.

  • A revoked token is deleted at the next sweep.
  • An expired token is deleted once --token-gc-grace-period has passed since its expiry.
  • A collected token is removed entirely: its secret, its OAuth access token, its definition and its data. It no longer appears in swamp access token list.
  • Each serve replica sweeps on its own. With a remote datastore, deletions are pushed to the datastore.
  • A sweep collects at most 100 tokens, counting tokens it collected and tokens it failed to collect. A larger backlog drains over later sweeps — for example, the first sweep after an upgrade on a long-running deployment, which finds every revoked or expired token ever minted. Tokens that failed in the previous sweep are tried after the others.
  • Revoked and expired tokens are refused at authentication whether or not they have been collected. A backlog only keeps them in swamp access token list longer.
Setting Default
Flag: --token-gc-interval 1h
Flag: --token-gc-grace-period 1h
Config file: token-gc-interval 1h
Config file: token-gc-grace-period 1h
Environment: SWAMP_TOKEN_GC_INTERVAL unset
Environment: SWAMP_TOKEN_GC_GRACE_PERIOD unset

In serve.yaml, quote these values: token-gc-interval: "0", not token-gc-interval: 0. See Supported keys.

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; a relative path resolves against the repository directory
grants-dir string --grants-dir; a relative path resolves against the repository directory
trust-proxy boolean --trust-proxy
hot-reload boolean --hot-reload
detach-runs boolean --detach-runs (deprecated)
verify-on-enroll boolean --verify-on-enroll
dispatch-env-allow string[] --dispatch-env-allow; [] ships no variables
ws-idle-timeout string --ws-idle-timeout
queue-timeout string --queue-timeout
trusted-hosts string[] --trusted-hosts
advertise-address string --advertise-address
heartbeat-interval string --heartbeat-interval
stale-ttl string --stale-ttl
reconciliation-interval string --reconciliation-interval
max-concurrent-runs integer --max-concurrent-runs
max-concurrent-scheduled-runs integer --max-concurrent-scheduled-runs
max-runs-per-principal integer --max-runs-per-principal
max-run-duration string --max-run-duration
hydration-timeout string --hydration-timeout
datastore-poll-interval string --datastore-poll-interval
token-gc-interval string --token-gc-interval
token-gc-grace-period string --token-gc-grace-period
dashboard boolean --dashboard
auto-resume boolean --auto-resume
enable-internal-api boolean --enable-internal-api
token-secrets object None. See token-secrets: block

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

Quote a duration that is only digits. YAML reads an unquoted 0 or 3600 as a number, and serve refuses to start:

token-gc-interval: "0"
token-gc-grace-period: "3600"
Error: Invalid token-gc-interval in /path/to/repo/.swamp/serve.yaml: expected string, got number; quote the value, e.g. "0"

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
keep-oauth-client-in-vault boolean --keep-oauth-client-in-vault
groups-field string --groups-field
restricted-model-types string[] --restricted-model-types
restricted-commands string[] --restricted-commands
group-refresh-interval string --group-refresh-interval
approve-requires-explicit-grant boolean --approve-requires-explicit-grant

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

auth.oauth-client-id takes its value from --oauth-client-id, then SWAMP_OAUTH_CLIENT_ID, then the config file.

swamp serve check-config accepts --oauth-client-id and --oauth-client-secret-file, as passed to swamp serve. It reports a secret file it cannot read, a secret with no client ID, and both secret environment variables set together, as swamp serve would refuse to start. It does not report a client ID with no secret.

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

Serve and swamp serve check-config check restricted-model-types and restricted-commands entries, from flags and the config file:

Entry swamp serve swamp serve check-config
A restricted-model-types entry that names no type (@, ::) Refuses to start Failed check
A restricted-model-types entry without a leading @ that names no registered type Startup warning Warning, built-in types only
A restricted-commands entry that is not a server command Startup warning Warning

check-config does not load extension types, so it checks an entry without a leading @ against the built-in behavior types only, and warns with the reason unknown-type when the entry is not one of them. Serve checks the same entry against every registered type at startup. Write an extension type with a leading @: neither serve nor check-config checks an entry with a leading @, because an extension type can be installed after startup.

Restriction warnings:
  ! restricted-model-types entry "command/shel" is not a built-in model type — check the spelling; if it names an extension type, write it as @command/shel
  ! restricted-commands entry "extension.instal" is not a server command and restricts nothing — command names match exactly

An entry that names no type fails the check:

✗ swamp serve would refuse to start:
  Invalid restricted-model-types entry "::": it names no model type
...
Result: FAILED (swamp serve would refuse to start)
Unknown usernames

In oauth mode, serve resolves each username in admins and allowed-users on the OAuth provider and records the result in its resolved-admins cache. Startup looks up only names the cache has no record of.

  • A resolved name is pinned to its account. Startup never looks it up again, including when the list is edited. To point a name at a different account, remove it, restart serve, then add it back.
  • A username the provider reports as not found is skipped with an ERROR log, and stays skipped until it is removed from the list.
  • Each entry must be a plain username, with an optional user: prefix. An entry that contains a colon after that prefix, such as allowed:bob, group:eng, or idp-group:eng, is skipped with an ERROR and is never looked up. Groups are not supported in admins or allowed-users with --auth-mode oauth.
  • Looking up a name needs a credential that can look up users: a login token, or a collective API token with the oauth:manage scope (see Deploy with Headless OAuth). Startup needs one only when a name is added. A refused lookup (401 or 403) stops startup with an error that names the oauth:manage scope.
  • Any other lookup error stops startup.
  • Serve refuses to start if no admin resolves, or if every entry in allowed-users is unknown and no allowed-collectives are set.

swamp serve check-config reports unknown usernames before a deploy. An admin who cannot log in after a deploy is often listed under a username that did not resolve: search the serve log for the ERROR line that names it, correct the entry, and restart serve.

tls: block

TLS certificate paths are nested under tls:.

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

token-secrets: block

Server token secrets are stored in the _token-secrets control-plane vault, encrypted with an AES-256-GCM key. By default that key is stored in the datastore beside the secrets it encrypts, so read access to the datastore decrypts every token. The token-secrets: block keeps the key in a vault outside the datastore instead.

Sub-key Type Description
vault string Vault that holds the key. Any user vault except _token-secrets. Its storage must be outside the datastore
key string Name of the secret in that vault
token-secrets:
  vault: prod-secrets
  key: swamp-token-secrets-key
  • The key is 32 bytes, hex- or base64-encoded, generated by the operative with a CSPRNG, for example openssl rand -base64 32. Swamp never generates it. A key with the same value in every byte is rejected.
  • The block has no CLI flag or environment variable. Serve reads it from its config file once at startup; changing it requires a restart.
  • swamp access token mint, rotate and reveal, and swamp worker token create, read the block from .swamp/serve.yaml when run locally. Once serve has moved the control plane to an external key, they refuse to run without it.
  • swamp serve check-config reports whether the key is usable, without printing it.
  • There is no way back to a key stored in the datastore.

See Keep the token secrets key outside the datastore to enable it on an existing deployment.

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
  • --oauth-client-secret-file — use SWAMP_OAUTH_CLIENT_SECRET_FILE to set it without a flag

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"
datastore-poll-interval: "30s"

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_API_KEY_FILE Path to a file holding the collective API key, in place of SWAMP_API_KEY. Setting both is an error. --club-api-key-file takes precedence over it
SWAMP_SIGNIN_TOKEN Signin token issued with the collective token in SWAMP_API_KEY. Lets Serve start while swamp-club.com is unreachable; checked against swamp-club.com at most once an hour. Shown once when the token is created — see Swamp Account Requirement
SWAMP_REMOTE_ONLY Default for --remote-only (true or 1 to enable)
SWAMP_GRANTS_FILE Default for --grants-file. A relative path resolves against the repository directory
SWAMP_GRANTS_DIR Default for --grants-dir. A relative path resolves against the repository directory
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. Also scopes SWAMP_SERVER_TOKEN: the token is sent only to the server this URL names
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 in <name>.<secret> format (alternative to a stored credential). Requires SWAMP_SERVER_URL: the token is used only when the target server matches SWAMP_SERVER_URL. SWAMP_SERVE_URL does not scope it. With SWAMP_SERVER_URL unset, the token is not sent and the CLI logs SWAMP_SERVER_TOKEN is set but SWAMP_SERVER_URL is not — token not sent. Set SWAMP_SERVER_URL to the target server URL or use --token instead. A --token flag overrides it
SWAMP_OAUTH_CLIENT_ID Default for --oauth-client-id / auth.oauth-client-id. Precedence: flag, environment variable, config file
SWAMP_OAUTH_CLIENT_SECRET_FILE Path to a file holding the OAuth client secret. Default for --oauth-client-secret-file. Mutually exclusive with SWAMP_OAUTH_CLIENT_SECRET
SWAMP_OAUTH_CLIENT_SECRET OAuth client secret, used for that run and never stored. Mutually exclusive with SWAMP_OAUTH_CLIENT_SECRET_FILE. Hidden from env.* in expressions and from method subprocesses; extension code runs in-process and can read it
SWAMP_OAUTH_CLIENT_NAME Default for --oauth-client-name / auth.oauth-client-name. Default: swamp-serve-{repoName}-{hostname}
SWAMP_KEEP_OAUTH_CLIENT_IN_VAULT Default for --keep-oauth-client-in-vault / auth.keep-oauth-client-in-vault (default: off)
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_DISPATCH_ENV_ALLOW Default for --dispatch-env-allow / dispatch-env-allow (comma-separated). Set and empty, it ships no variables
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_SERVE_ADVERTISE_ADDRESS Default for --advertise-address / advertise-address
SWAMP_CA_CERT Default for --ca-cert / tls.ca-cert on swamp serve; on client commands, the CA certificate trusted for TLS connections to the server
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_DATASTORE_POLL_INTERVAL Default for --datastore-poll-interval (default: 30s)
SWAMP_TOKEN_GC_INTERVAL Default for --token-gc-interval (default: 1h)
SWAMP_TOKEN_GC_GRACE_PERIOD Default for --token-gc-grace-period (default: 1h)
SWAMP_MAX_CONCURRENT_RUNS Default for --max-concurrent-runs (default: 100)
SWAMP_MAX_CONCURRENT_SCHEDULED_RUNS Default for --max-concurrent-scheduled-runs (default: 1)
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)
SWAMP_AUTO_RESUME Default for --auto-resume (true or 1 to enable)
SWAMP_APPROVE_REQUIRES_EXPLICIT_GRANT Default for --approve-requires-explicit-grant / auth.approve-requires-explicit-grant (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.

Token authentication failures

A connection without a token is rejected with 401 Unauthorized: token required. A presented token that fails authentication is rejected with 401 Unauthorized: <reason>, and the server logs the reason with the token name:

Reason Meaning
expired The token has passed its expiry. Token garbage collection deletes it once the grace period has passed, after which it fails as no-definition
revoked The token has been revoked. Token garbage collection deletes it at the next sweep, after which it fails as no-definition
secret-mismatch The presented secret does not match the stored secret
invalid-principal The token's stored principal is not user:<id> or worker:<id> — it was minted for an unsupported principal kind. Fix with swamp access token revoke <name>, then mint it again with a valid --principal. swamp auth server-login does not fix it
no-definition No token with the presented name exists
invalid-format The credential is not in <name>.<secret> format, or exceeds 512 characters
vault-error The token's secret could not be read from its vault
unknown Any other failure

On a 401 or 403, the CLI reports Authentication failed — run: swamp auth server-login --server <url>. On a 5xx it reports Server error (HTTP <status>) while connecting to <url> — check the server log for the cause.

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.