Skip to Content
Configuration reference

Configuration reference

Every setting you can change on a Marshal install, with its default and when you would change it. Marshal installs as one Helm chart. You set it up in one of two ways:

  • Terraform installer (EKS, GKE, single-node k3s). You write terraform.tfvars. The Terraform variables below are the settings each install target declares.
  • ArgoCD (GitOps path). You write chart values in the Application’s valuesObject. Use the Chart values section and the chart value named next to each Terraform variable.

Any chart value also works on the Terraform path: put it in values_overrides. This page covers the Marshal settings. Cluster, node pool, network and database settings that belong to one cloud are on that cloud’s install page.

What you must set

Five settings have no default, on every target:

SettingWhat it is
installation_jwtYour license.
deployment_idYour deployment ID (dep_...). It is also the registry username.
registry_pull_tokenYour registry pull token (mrpt_...).
chart_versionThe Marshal version to install.
ingress_hostThe hostname people use to open Marshal, for example marshal.acme.com.

The first three come with your onboarding. See Onboarding credentials. Each cloud adds its own: cluster_name and region on EKS, project_id and location on GKE.

Terraform variables

The Targets column shows which install targets declare the variable: EKS (aws-eks), GKE (gcp-gke) and k3s (single-node-k3s). A variable a target does not declare is not a tfvar there. Terraform warns about it and ignores the value. Set the chart value through values_overrides instead.

Release and chart

VariableDefaultTargetsWhat it does, and when to change it
release_namemarshalEKS, GKE, k3sThe Helm release name. Change it to run a second install (for example staging) on the same cluster, together with namespace.
namespacemarshalEKS, GKE, k3sNamespace for the Marshal services. Spaces run in their own namespace, <release_name>-spaces.
chartoci://registry.marshal.codes/marshal-charts/marshalEKS, GKE, k3sWhere the chart comes from. Keep the default.
chart_versionnone, requiredEKS, GKE, k3sThe product version. Each upgrade changes this one value. See Day-2 operations.

Credentials

VariableDefaultTargetsWhat it does, and when to change it
installation_jwtnone, requiredEKS, GKE, k3sYour license. The installer stores it in the marshal-license Secret. Change it when you renew your license.
deployment_idnone, requiredEKS, GKE, k3sYour deployment ID. Used as the registry username.
registry_pull_tokennone, requiredEKS, GKE, k3sRead-only registry password. One token pulls the chart, the product images and the tool images that spaces use.

Images and registry

All images resolve under one registry prefix. Point it at your image cache for production. See Registry options.

VariableDefaultTargetsWhat it does, and when to change it
image_cache_registryemptyEKS, k3sYour own pull-through cache, for example harbor.acme.internal/marshal. When set, every image pull goes through it. Set it when you run one cache for all your installs. Chart value: global.registry.
registryregistry.marshal.codes/marshalEKS, GKE, k3sThe image prefix when no cache is set. On GKE, set it to an Artifact Registry mirror you run yourself. Chart value: global.registry.
tools_registryemptyEKS, GKEPrefix for the tool images that spaces pull. Empty means the registry prefix with /marshal swapped for /marshal-tools. Set it when your own mirror does not follow that naming. Chart value: toolsRegistryBase.
registry_auth_modesecretEKS, GKE, k3sHow nodes authenticate to the registry. secret: a pull Secret built from your token, stamped on every pod. workloadIdentity: the node identity (GKE Workload Identity or EKS IRSA) pulls from a cloud cache. none: an open cache in your network. Chart value: registryAuth.mode.

Each cloud target can also build the cache for you. On GKE, create_registry_cache (default true) creates an Artifact Registry mirror and sets registry and registry_auth_mode for you. On EKS, create_harbor_cache (default false) runs Harbor in the cluster as the cache. See GKE and EKS.

Datastores

Postgres holds everything durable: spaces, organizations, credentials, manifests and the audit log. Redis holds only short-lived state that Marshal can rebuild. So the recommended setup is your own managed Postgres and the in-cluster Redis. See Datastore options.

VariableDefaultTargetsWhat it does, and when to change it
database_modeexternalEKS, GKEexternal: Marshal uses the Postgres at database_url. bundled: an in-cluster Postgres for evaluation only.
database_urlemptyEKS, GKE, k3sPostgres 16 connection string, for example postgres://marshal:PW@HOST:5432/marshal?sslmode=require. Required with database_mode = "external". Stored in a Secret, never in Helm values. On k3s it is used when provision_datastores = false. On EKS, create_rds = true fills it for you.
redis_modebundledEKS, GKEbundled: Redis runs in the cluster. external: Marshal uses the Redis at redis_url. Change it only if you already run managed Redis and want to use it.
redis_urlemptyEKS, GKE, k3sRedis 7 connection string, for example rediss://default:PW@HOST:6380. Required with redis_mode = "external". On k3s it is used when provision_datastores = false.

On k3s, provision_datastores (default true) runs Postgres and Redis on the same VM.

Ingress & TLS

VariableDefaultTargetsWhat it does, and when to change it
ingress_hostnone, requiredEKS, GKE, k3sThe main hostname. The web app, the API and the gateway share it. Plans need it before DNS exists, so pick the name first and add the DNS record when the load balancer has an address. Chart value: ingress.host.
extra_ingress_hosts[]EKS, GKE, k3sMore hostnames that reach the same install, for example a Tailscale MagicDNS name. Each one needs its own DNS record. Chart value: ingress.extraHosts.
ingress_class_nameEKS alb, GKE gce, k3s traefikEKS, GKE, k3sThe IngressClass. Change it when you run your own controller, such as ingress-nginx. On GKE it applies when internal_ingress = false. Chart value: ingress.className.
ingress_annotations{}EKS, GKE, k3sExtra annotations on the Ingress, for example a cert-manager issuer. Chart value: ingress.annotations.
internal_ingresstrueEKS, GKEtrue gives an internal load balancer, reachable only inside your VPC. Set false only to expose Marshal on the internet.
ingress_modeingressGKEgateway serves Marshal through a Gateway API Gateway instead of the Ingress, on the same internal IP. Use it for a Google-managed certificate. See Google-managed certificate. Chart value: ingress.mode.
create_managed_certificatefalseGKEWith ingress_mode = "gateway", creates a Google-managed certificate for ingress_host and *.<preview_domain>. Google issues and renews it.
gateway_certificate_names[]GKEWith ingress_mode = "gateway", Certificate Manager certificates you already have, by name. Chart value: ingress.gateway.certManagerCerts.
gateway_class_namegke-l7-rilbGKEThe GatewayClass for ingress_mode = "gateway". The default is the regional internal load balancer. Chart value: ingress.gateway.className.
tls_modeself_signedEKS, GKE, k3sWhere the certificate comes from. self_signed: Terraform creates a placeholder certificate that covers every host, so HTTPS works at once with a browser warning. inline: you paste a real certificate in tls_cert_pem and tls_key_pem. secret: you manage a TLS Secret yourself (for example with cert-manager) and name it in tls_secret_name. none: HTTP only. Move from self_signed to inline or secret before people use the install.
tls_cert_pem, tls_key_pememptyEKS, GKE, k3sPEM certificate and key, for tls_mode = "inline".
tls_secret_namemarshal-tlsEKS, GKE, k3sYour TLS Secret, for tls_mode = "secret".
ingress_tls[]EKS, GKE, k3sAdvanced. Raw TLS blocks for the Ingress. When set, it replaces what tls_mode would do. Use it only for host and Secret combinations tls_mode cannot express. Chart value: ingress.tls.
preview_domainemptyEKS, GKE, k3sBase domain for port previews from spaces. Spaces get *.<preview_domain>. Empty turns previews off. Chart value: ingress.preview.domain.
preview_schemehttpsEKS, GKE, k3sScheme of preview URLs. Use http only for a test without TLS. Chart value: ingress.preview.scheme.

With preview_domain set, the certificate must cover *.<preview_domain> as well as ingress_host. The self_signed mode covers it for you. For a certificate you bring, issue a wildcard certificate. See Troubleshooting.

Integrations

VariableDefaultTargetsWhat it does, and when to change it
webhook_relay_enabledfalseEKS, GKE, k3strue turns webhooks on for every organization whose admin has not set the Webhooks switch in Settings > Platform yet. Webhooks open Settings > Webhooks and Personal settings > Webhooks. External systems then deliver events through the relay, and your cluster needs no inbound access. Needs installation_jwt. Turn it on to start deputy missions from events. Chart value: webhookRelay.enabled.
webhook_relay_urlempty, which means https://hooks.marshal.codesEKS, GKE, k3sThe relay endpoint. Keep the default unless Marshal gives you another one. An admin can set an organization’s own Webhook Relay address in Settings > Platform. Chart value: webhookRelay.url.
auth_broker_urlemptyEKS, GKE, k3sSetting it turns the Slack and OAuth sign-in switch in Settings > Platform on for every organization whose admin has not set it yet, and makes this URL the Auth Broker address. The Auth Broker lets an org connect a Slack workspace without registering its own Slack app. With it empty, an admin can still turn the switch on, and Marshal uses https://auth.marshal.codes. Needs installation_jwt. Chart value: authBroker.url.

An organization’s admin turns these features on or off in Settings > Platform at any time, with no restart, and a value saved there always wins over these variables. Both need outbound HTTPS to the host they point at. See Networking.

Object storage

VariableDefaultTargetsWhat it does, and when to change it
create_spaces_persistence_bucketfalseEKS, GKECreates a bucket for space storage and grants Marshal access with no keys (IRSA or Workload Identity). Then an admin selects it in Settings > Space Storage.
create_audit_persistence_bucketfalseEKS, GKECreates a bucket for audit export, with the same keyless access. Then an admin selects it in Settings > Auditing.

See Object storage for the second step.

values_overrides

VariableDefaultTargetsWhat it does, and when to change it
values_overrides[]EKS, GKE, k3sRaw chart values, as a list of YAML strings. They are applied last, so they win over anything the installer sets. Use it for every chart value that has no variable on your target.
values_overrides = [ yamlencode({ api = { extraEnv = [{ name = "MARSHAL_REGISTRATION_INVITE_ONLY", value = "true" }] } license = { enforce = true } }) ]

Chart values

These are chart values with no Terraform variable on any target. Set them in values_overrides (Terraform) or valuesObject (ArgoCD).

Sign-in and license

Chart valueDefaultWhat it does, and when to change it
auth.basic.enabledtrueEmail and password sign-in to the web app. Turn it off only when you put your own SSO or identity proxy in front of Marshal.
license.enforcefalsefalse: Marshal logs the license state and blocks nothing. true: new spaces are paused once the install is no longer licensed and the grace period has ended.
license.jwksURLhttps://keys.marshal.codes/.well-known/jwks.jsonWhere Marshal reads the public keys that verify your license. Change it only when Marshal tells you to.
license.refreshURLhttps://tokens.marshal.codes/v1/licenseWhere Marshal exchanges your license for a short-lived one. Set "" to turn the exchange off for a fully offline license. An install with no refresh address sends no usage report.
license.telemetryURL""Where the hourly usage report is sent. Empty uses your refresh address with the path /v1/telemetry.
license.telemetryInterval"" (every hour)How often the install sends the usage report.

Marketplace and toolchains

Chart valueDefaultWhat it does, and when to change it
marketplace.upstreamhttps://marketplace.marshal.codesThe package catalog your install reads.
marketplace.tokenExchangeURLhttps://tokens.marshal.codesWhere your install exchanges its license for a marketplace token.
marketplace.customerRepos[]Your own package sources, merged on top of the catalog. Each entry: { url, authSecret: { name, key } }. Package names must carry your namespace (acme/foo) and cannot replace official packages.
toolchainMirror.baseUrlhttps://assets.marshalstatic.com/toolchainsFallback download location for toolchains. Spaces download from the toolchain’s own host first and use this mirror only when that fails. Each download is checked against a known hash. Point it at an internal host with the same layout, or set "" to turn it off.

Price list

The Compute tab of the Costs page estimates your monthly cost from public list prices for Google Cloud and AWS. Your install downloads the latest price list once a day and keeps a copy built into every release. It needs no cloud credentials.

Chart valueDefaultWhat it does, and when to change it
pricing.baseUrlhttps://assets.marshalstatic.com/pricing/v1Where your install downloads the price list. Set "" for an air-gapped install: Marshal uses the price list built into the release, and admins can still enter their own prices on the Compute tab of Costs. Or point it at an internal host that holds the same file layout.

Admins see the date of the price list in use on the Compute tab. See Costs.

Secrets management

Two app secrets live in one Kubernetes Secret: JWT_SECRET signs sign-in and API tokens, and KMS_LOCAL_KEY encrypts every stored credential. You choose how the chart gets them.

secrets.providerWhat it meansUse it for
generated (default)The chart creates both values on the first install and keeps them on every upgrade.Terraform installs. Terraform runs helm upgrade, which reads the existing Secret back.
existingYou create the Secret once and name it in secrets.existingSecret. The chart only reads it.ArgoCD and any GitOps tool. They never run helm upgrade, so generated would create new values on every sync.
externalSecretsThe External Secrets Operator fills the Secret from your cloud secret manager. Configure secrets.externalSecrets.GitOps installs that already run External Secrets Operator.
Chart valueDefaultWhat it does
secrets.existingSecretemptyWith existing: the name of your Secret, with keys JWT_SECRET and KMS_LOCAL_KEY.
secrets.externalSecretssee the chartWith externalSecrets: the secretStoreRef and the remote keys for both values.
kms.backendlocalThe key that encrypts credentials comes from KMS_LOCAL_KEY. Keep the default.

Back up the app secret. Never switch a running install back to generated, and never delete the Secret. If KMS_LOCAL_KEY is lost, stored credentials cannot be decrypted. A new JWT_SECRET signs everyone out and ends every API token. See Troubleshooting.

Namespaces, registry details and capacity

Chart valueDefaultWhat it does, and when to change it
spacesNamespaceempty, which means <release>-spacesThe namespace spaces run in. Set it to use a namespace you already govern, for example with your own NetworkPolicies.
manageSpacesNamespacetrueSet false when spacesNamespace names a namespace you created yourself, so Helm neither creates nor deletes it.
ingress.enabledtrueSet false only when your own gateway or load balancer routes to the Marshal Services.
registryAuth.secret.serverTerraform derives it from the image registryWith registryAuth.mode: secret: the registry host the pull credential is for. Set it only when the credential belongs to a different host than the images. On ArgoCD, set it to your registry host.
resources.*, autoscaling.*see SizingRequests, limits and replica counts of the Marshal services.

The MCP settings mcp.extraHosts, mcp.cimdEnabled and tailscaleIngress.* are explained on Connect an AI agent (MCP).

API database pool and readiness

The API keeps serving under heavy load. Its readiness probe checks the database on a connection of its own, outside the pool, so a busy pool that keeps completing queries stays in rotation. A pod leaves rotation only when its pool is fully in use and has completed nothing for staleAfter. At startup the API waits up to 60 seconds for Postgres and Redis to answer, so a fresh install comes up without a restart.

Chart valueDefaultWhat it does, and when to change it
api.db.maxConnsunset: the pool_max_conns in database_url, else 10 or the pod’s CPU count, whichever is largerDatabase connections per API pod. Keep API replicas × maxConns under your database’s connection limit.
api.readiness.path/readyzSet /healthz to keep pods ready while the database is unreachable.
api.readiness.initialDelaySeconds5Seconds before the first probe.
api.readiness.periodSeconds5Seconds between probes.
api.readiness.failureThreshold6Failed probes in a row before a pod leaves rotation. With the defaults that is about 30 seconds, longer than a Cloud SQL failover.
api.readiness.budget1sTime limit on the probe’s database check.
api.readiness.staleAfter30sHow long a fully used pool may complete nothing before the pod reports unready. 0 turns this check off, so readiness means only that the database is reachable.

Two request limits are set through api.extraEnv. Each takes a duration such as 45s, and 0 turns the limit off. Streaming routes, such as a run’s live events, and a few long-running actions, such as purging a deputy, are exempt. The configuration routes are limited like any other.

VariableDefaultWhat it does
API_REQUEST_TIMEOUT60sThe longest an ordinary API request may take.
API_AUTH_REQUEST_TIMEOUT10sThe limit for sign-in routes, so the sign-in page answers quickly.

A request that reaches its limit answers 503 with {"error":"service unavailable"} and Retry-After: 2. Routes behind a per-organization switch (runs and deputies, the Webhook Relay, the Auth Broker, Slack) answer with the same 503, error and header when the API cannot read the organization’s settings within 5 seconds, and add a message that says to try again. A slow database never shows a feature as switched off. A switch that is off still answers 503 with feature_disabled. API clients should treat service unavailable as retryable: earlier releases answered an unreadable switch with feature_disabled and reason unavailable.

Upgrades without failed requests

When an API or web console pod stops, during an upgrade, a rollout or a node drain, it keeps serving for a short drain window first. The API also reports itself not ready in that window, so Kubernetes, your ingress controller and any load balancer that checks pods directly move traffic to the other pods before this one closes. In-flight requests then finish and the pod exits on its own. With api.readiness.path set to /healthz, the API still drains but does not report itself not ready.

Chart valueDefaultWhat it does, and when to change it
api.shutdown.drainSeconds10Seconds an API pod keeps serving after it is told to stop. Raise it when a cloud load balancer in front of the cluster takes longer to stop sending traffic to a pod. 0 stops the pod at once.
api.terminationGracePeriodSeconds60Time Kubernetes gives an API pod to stop. It must be at least drainSeconds plus 35, the time the API needs to finish requests and background jobs. The chart refuses a smaller value.
web.shutdown.drainSeconds10Seconds a web console pod keeps serving after it is told to stop. 0 stops the pod at once.
web.terminationGracePeriodSeconds30Time Kubernetes gives a web console pod to stop. It must be at least drainSeconds plus 5. The chart refuses a smaller value.

A longer drain makes each upgrade a little slower: every stopping pod waits its drain window once.

Node tuning

Spaces watch many files, and the kernel’s inotify limits are shared by every container on a node. The kernel default of 128 instances runs out after a handful of spaces, and the next space fails to start with failed to create fsnotify watcher: too many open files.

These limits are node settings, so no pod can raise them for itself. The chart runs a small DaemonSet that raises them on every node, including nodes an autoscaler adds later. It only ever raises a value, so a node you tuned higher yourself keeps its value.

Chart valueDefaultWhat it does, and when to change it
nodeTuner.enabledtrueSet false if your policy forbids privileged pods. Then raise the same limits in your node image or node configuration.
nodeTuner.sysctlsfs.inotify.max_user_instances: "8192", fs.inotify.max_user_watches: "1048576"Any node-level sysctl works, not only these two.
nodeTuner.imagethe gateway imageReuses an image your cluster already pulls, so nothing new goes through your image cache.

The write happens in a short-lived privileged init container. The container that stays running has no privileges, runs as uid 65534 with a read-only root filesystem, and requests 10m CPU.

Optional features

Some features are off until you turn them on. This table lists each one, where it is switched on, and the default.

The first three are switches each organization’s owners and admins control themselves, under Settings > Platform. A change applies right away, with no restart. Webhooks and the Slack connection need the installation JWT, because they connect to Marshal’s hosted services.

FeatureHow to turn it onDefault
Runs and deputies, with their schedules and triggers (one switch)Deputies and runs in Settings > Platform.On
WebhooksWebhooks in Settings > Platform. Terraform webhook_relay_enabled = true or chart webhookRelay.enabled: true turns it on for every organization that has not set the switch.Off
Slack connectionSlack and OAuth sign-in in Settings > Platform. Terraform auth_broker_url or chart authBroker.url turns it on for every organization that has not set the switch. Turn on Webhooks as well, so people can answer with the buttons in Slack.Off
Space storage (stop, resume, fork and archive)An admin picks a backend in Settings > Space Storage.Disabled
Audit exportAn admin picks a backend in Settings > Auditing.Database only. Events are stored in the database, with no archive files in your own bucket.
MCP access for AI agentsEvery install serves /mcp. An org admin turns it on in Organization settings > General, card MCP access.Off in each org
Live resize of a running spaceThe cluster needs the Kubernetes feature gates InPlacePodLevelResourcesVerticalScaling and NodeDeclaredFeatures, on the API server and on every kubelet. Turn both on together. Size changes applied at the next restart work on any cluster.Depends on your cluster
Password sign-inauth.basic.enabledOn
Open registration (anyone can sign up)auth.openRegistration: trueOff. People join by invitation.
Invite-only registration (blocks sign-up even when open registration is on)Set MARSHAL_REGISTRATION_INVITE_ONLY to "true" on the API through api.extraEnv.Off
More than one organizationauth.disableOrgCreation: falseOff. The first organization is not affected.

Example of api.extraEnv in chart values:

api: extraEnv: - name: MARSHAL_REGISTRATION_INVITE_ONLY value: "true"

Notes:

  • Runs and deputies. One switch controls both, for one organization. With it off, the Deputies and Runs entries in the web app show as not enabled, the run and deputy API answers 503 with feature_disabled, and marshal run fails. Other organizations on the same install keep their own setting. An install that set MARSHAL_DEPUTIES_ENABLED on the API keeps that value as the starting point for every organization that has not set the switch.
  • First-run setup token. The chart stores it in the app secret under the key SETUP_TOKEN. Read it with kubectl get secret <release>-app-secrets -n <namespace> -o jsonpath='{.data.SETUP_TOKEN}' | base64 -d. With secrets.provider: existing, use your own Secret’s name, and add a SETUP_TOKEN key to it, or first-run setup stays off.