Configuration
Everything that isn’t the database layout or ingress: how settings reach the services, where secrets come from, and how to substitute your own infrastructure.Your own values file
Whatever we send you — the plain chart, or a values file sized for your cluster — is a starting point, not something to edit in place. Keep your overrides in a file you own, in your own git repo, and layer it on top.-f wins, and --set beats every -f. You only need to write the
keys you are changing; everything else falls through to the file underneath
and then to the chart’s defaults.
That separation is what makes the next upgrade cheap. When we send a new
baseline, you replace one file and your overrides are untouched — instead of
diffing your edits back out of a file that has moved on.
Sizing one service
services.<name>.resources merges onto services.defaults.resources, so you
can raise one number without restating the block:
How the layering actually merges
Maps merge key by key
Maps merge key by key
Setting one key leaves its siblings alone:Result:
LOG_LEVEL: debug, TZ: UTC. Nesting goes as deep as you like.Lists replace wholesale — they do not append
Lists replace wholesale — they do not append
This is the one that surprises people:Result:
[{ name: c }]. a and b are gone — not appended to.Any list is affected: pullSecrets, tolerations, ingress.hosts. If you
mean to add to a list, restate the whole thing including the entries you
want to keep.Empty is not the same as absent
Empty is not the same as absent
Omitting a key inherits it. Setting it to
{}, [] or null
overrides it with emptiness — which is how you deliberately clear
something the baseline set, and also how people accidentally wipe a block
they meant to leave alone.Checking what is actually applied
Application settings
Anything that would otherwise be a bespoke env var — encryption keys, timeouts, log levels, model config — goes underappSettings, and lands as an
env var on every service.
Each entry takes one of three shapes:
env: entries — scalars as value:, maps as
valueFrom.secretKeyRef or valueFrom.configMapKeyRef. A map that is neither
shape fails the install rather than rendering something odd.
Per-service overrides
services.<name>.settings takes the same three shapes and wins over
appSettings for the same key:
The defaults ship
LOG_LEVEL=info globally and wire the keys the services
need from the chart-generated Secret, each only to the service that reads it:
SWARMD_ENCRYPTION_KEY to registry, relay and teams, and
AUDIT_HASHCHAIN_SIGNING_KEYS to audit (under services.audit.settings).
That is enough for a first install to work with no input. Override them with
your own key management when you go to production — see
Bring your own encryption keys.Secrets
The chart generates a Secret namedswarmd-generated-credentials at install:
Passwords are 24 random alphanumeric characters. On
helm upgrade the chart
reads the existing Secret via lookup and reuses the values — upgrades do
not rotate credentials.
The Secret carries helm.sh/resource-policy: keep, so helm uninstall leaves
it behind.
Supplying your own
encryption-keys and service-client-secret-* keys are
optional in your own Secret: without them the services fall back to built-in
defaults, which work but are not secret, so add them before production.
Bring your own encryption keys
The services readSWARMD_ENCRYPTION_KEY as a comma-separated list of
v<N>:<base64 of 32 bytes> entries. The highest version encrypts new values;
every listed version can decrypt. Generate a key with openssl rand -base64 32
and set it as a setting, which replaces the generated list:
Agents and MCP servers on private addresses
The relay refuses to call private, in-cluster, loopback or link-local addresses (SSRF protection). An agent or MCP server running in your cluster (http://invoice-agent.agents.svc.cluster.local:8080) or on your private
network is refused with Blocked request to private/internal address until
its host is listed:
- Entries are exact host names, or
*.patterns that match any host in that domain (*.agents.svc.cluster.localmatchesinvoice.agents.svc.cluster.local). - A host admitted by a pattern still may not resolve to loopback, link-local
(cloud metadata) or
0.0.0.0. - Public hosts need no entry.
- Keep patterns to the namespaces your agents live in.
*.svc.cluster.localwould let anyone who can register an agent reach every service in the cluster through the relay, Keycloak included.
SMTP
tenant-auth sends email for verification, password reset and team invites. Withsmtp.enabled: false — the default — every one of those is logged and
dropped. Users can register, but nothing arrives.
Nobody is locked out, though
smtp.enabled also sets the realm’s verifyEmail flag, so with SMTP off
Keycloak never raises the “verify your email” required action and sign-up
followed by login works normally. Verification is genuinely off, not skipped by
a special case: tenant-auth does refuse a login for an unverified user, but only
when Keycloak reports the account as not fully set up, which cannot happen while
verifyEmail is false.
Those users are still recorded as unverified, so they show as PENDING in
user listings. That is cosmetic — it gates nothing.
smtp.enabled: true and any of host,
from or credentialsSecret missing.
swarmd.tenant-auth.auto-verify-email-patterns marks matching addresses
verified at creation — a comma-separated list of globs, e.g.
*@yourcompany.com. Useful once verification is enforced but you want your own
domain to skip it. Empty by default, so nobody is auto-verified.Bring your own infrastructure
Every optional component follows the same three-state pattern:enabled: false · enabled: true, deploy: true · enabled: true, deploy: false plus an external block. The chart fails the render if you
choose the third and leave a required URL empty.
Bring your own Keycloak
adminSecret holds master-realm admin credentials. Registry, relay,
tenant-auth and teams call the Admin API to manage agent service accounts, so
this is not optional.
PII detection
Both analyzers are off by default. With neither enabled, prompts pass through the LLM path unfiltered.a2a-payments
Off by default, and the only service that genuinely cannot start without configuration: itsX402_WALLET_PRIVATE_KEY is @NotBlank-validated with no
degraded mode.
Turning services off
Settings that bite later
Some settings don’t block startup but fail the first time a feature is used. The chart can’t enforce them, because the service boots fine without them.
Provide them under
services.<name>.settings when you enable the feature.
Scaling: core and worker
Every DB-backed service ships as one image that can run three ways, and the chart splits every one of them by default:
The worker’s web server stays up so health probes work, but the Service never
routes traffic to it. Worker sizing falls back to the service’s own
replicaCount / resources when left empty.
Why this is the default. In the combined topology, scaling the API to N
replicas also runs N copies of every
@Scheduled job — outbox drainers and
monitor evaluators racing each other, which is a data problem rather than a
performance one. Splitting also stops a slow scheduler starving the API’s
thread pool, and takes Flyway out of pod startup so a long migration can’t
trip readiness on every replica at once.Collapsing a service back
Startup ordering
Every service talks to Keycloak while it boots — the Admin API for service-account management, the JWK set for token validation — and exits non-zero if it cannot reach it. On a cold install that is a race the services lose, so each one is held at await-for-keycloak init container until
Keycloak’s realm answers its OIDC discovery endpoint.
You will see pods sit in Init:0/1 for roughly a minute on a first install.
That is the gate working; expect zero restarts.
waitForKeycloak: false per service (or on defaults) if you sequence
startup another way. Migration Jobs are never gated — they run Flyway and
never touch Keycloak.
The gate checks the realm’s discovery document, not the TCP port.
Keycloak accepts connections well before the realm-bootstrap Job has imported
the realm, and a service starting in that window fails exactly as it would
have without the gate.
JVM sizing
The chart sizes the JVM against the container memory limit:MaxRAMPercentage too or the JVM will size a heap the container cannot hold.
No garbage collector is pinned deliberately — JVM ergonomics picks one suited
to the container, which matters more here than in a large deployment because
this chart is routinely run with small limits.
Observability
Verifying a change before you apply it
$SWARMD_CHART is the chart file we sent you — see
Quickstart → Step 2.
template, so a bad combination fails here
— before it touches your cluster.
Unpacked the tarball (tar xzf)? Both commands work against the ./swarmd
directory the same way.
Checks before anything is installed
helm install and helm upgrade validate your values before they apply
anything. A failed check stops the command with what is wrong, why it matters
and what to change:
Checks that need to see the cluster (Secrets, an existing ClickHouse) only run
when Helm has a live connection; under
helm template they are skipped.
Valid choices that switch features off do not stop the install, but the
install notes say what you lose: ClickHouse off (no monitors or alerts),
notification-service off (no notices or alerts delivered), SMTP off (one
account), and an empty relay allow-list (in-cluster agents refused).
After installing or upgrading, helm test swarmd -n swarmd --logs checks the
running install the same way (see Upgrades › Verifying afterwards).
For air-gapped clusters, mirror curlimages/curl and set tests.image.repository.
What the platform says at runtime
The services answer the same way when something is not set up:Next
Presets
Tested values files that combine all of this.
Databases
Layouts, external Postgres, ClickHouse and backups.
Upgrades
Moving between chart versions without taking the install down.
