Configuration
Dploy has two configuration surfaces:
- The API is configured with environment variables (set via the Helm chart’s ConfigMap and Secret).
- The operator reads a cluster-scoped
OperatorConfigcustom resource for cluster-wide defaults.
API environment variables
Section titled “API environment variables”Required
Section titled “Required”| Variable | Description |
|---|---|
JWKS_URL | JWKS endpoint used to validate JWT signatures |
JWT_ISSUER | Expected JWT iss claim |
Authentication
Section titled “Authentication”| Variable | Default | Description |
|---|---|---|
JWT_AUDIENCE | dploy | Expected JWT aud claim |
JWT_USERNAME_CLAIM | name | Claim used as the username |
FORWARDED_CLAIMS | sub,preferred_username,email,name,groups | The only claims copied out of the token into a DployInstanceClaim, where templates read them as .Params. Everything else stops at the API server |
OIDC_ISSUER | $JWT_ISSUER | OIDC issuer (internal, for token exchange) |
OIDC_PUBLIC_ISSUER | $OIDC_ISSUER | OIDC issuer used for browser redirects |
OIDC_CLIENT_ID | dploy | OIDC client ID |
OIDC_CLIENT_SECRET | dploy-secret | OIDC client secret |
OIDC_REDIRECT_URL | http://localhost:8080/auth/callback | OIDC callback URL |
Kubernetes & defaults
Section titled “Kubernetes & defaults”| Variable | Default | Description |
|---|---|---|
DPLOY_NAMESPACE | dploy-system | Namespace where the dploy CRs live |
MAX_ENVIRONMENTS_PER_USER | 5 | Displayed quota. The cap the operator actually enforces comes from OperatorConfig.defaults.maxInstancesPerUser (or the template’s override) |
DEFAULT_TTL | 86400 | Fallback initial TTL in seconds, for display. The operator resolves the real one |
EXTEND_TTL | 7200 | Fallback extension granted per /extend |
Server
Section titled “Server”| Variable | Default | Description |
|---|---|---|
SERVER_HOST | 0.0.0.0 | Bind address |
SERVER_PORT | 8080 | Port |
DEBUG | false | Verbose logging |
OperatorConfig
Section titled “OperatorConfig”The operator reads a single cluster-scoped OperatorConfig named default. Any other name is ignored. It carries cluster-wide defaults that individual DployTemplates can override per-template.
apiVersion: dploy.dev/v1alpha1kind: OperatorConfigmetadata: name: defaultspec: # GitOps engine used when a template does not override it. defaultEngine: flux # only "flux" is implemented today
# Defaults for the Flux engine. flux: namespace: flux-system # where HelmRelease + Sources are reconciled serviceAccountName: "" # optional: SA the helm-controller impersonates interval: 5m # GitRepository / HelmChart poll interval
# Used to build the default per-instance hostname: <name>-<uid>.<baseDomain>. baseDomain: env.dploy.dev
# Cluster-wide default Go (text/template + sprig) for an instance's public URL. # Overridable per template via DployTemplate.spec.connectionURLTemplate. # Empty falls back to "https://<Host>". connectionURLTemplate: "http://{{ .Host }}"
# Cluster-wide default presentation: "web" (clickable URL, redirect on ready) # or "instructions" (copyable command, no redirect — see connectionMessageTemplate). defaultConnectionType: web
# Cluster-wide default instructions template. Rendered with the same context as # connectionURLTemplate plus .URL / .ConnectionURL (the resolved target). # Only used when connectionType is "instructions". connectionMessageTemplate: "" # e.g. "ssh root@{{ .ConnectionURL }} -p 22000"
# Applied to instances when their template omits the value. defaults: ttlSeconds: 86400 # initial TTL (24 h) extendSeconds: 7200 # TTL bonus per /extend call (2 h) maxExtends: 3 # -1 = unlimited; unset falls back to 3 maxInstancesPerUser: 5 # per-owner quota
# Free-form map exposed to value templates as `.Config.Values`. Anything you # want common across templates (image registries, organisation toggles, …). values: registry: ghcr.io/your-org ingressClass: nginxField reference
Section titled “Field reference”| Field | Description | Default |
|---|---|---|
defaultEngine | GitOps engine: flux only today (argocd reserved) | flux |
flux.namespace | Namespace where the helm-controller reconciles HelmReleases | flux-system |
flux.serviceAccountName | SA the helm-controller impersonates (optional) | — |
flux.interval | Poll interval for GitRepository / HelmChart sources | 5m |
baseDomain | Base domain for the fallback <name>-<uid>.<baseDomain> host | — |
connectionURLTemplate | Cluster default URL template (overridable per template) | https://<Host> |
defaultConnectionType | web (clickable) or instructions (copyable command) | web |
connectionMessageTemplate | Cluster default instructions template (when instructions) | — |
defaults.ttlSeconds | Default TTL for a new instance | 86400 |
defaults.extendSeconds | Seconds added per /extend call | 7200 |
defaults.maxExtends | Maximum extensions allowed (-1 = unlimited) | 3 |
defaults.maxInstancesPerUser | Per-owner quota (sanitized owner key) | 5 |
values | Free-form map exposed as .Config.Values | {} |
Templating context
Section titled “Templating context”connectionURLTemplate, connectionMessageTemplate (in OperatorConfig or per template) and valuesTemplate (per DployTemplate) are all rendered with Go text/template + sprig against this data:
| Variable | Type | Notes |
|---|---|---|
.Owner | string | Sanitized owner key — empty for unclaimed pool members and forbidden in pool valuesTemplate (CEL-enforced anonymity) |
.UUID | string | 8 hex chars, generated once by the operator, immutable in status.uuid |
.BaseDomain | string | Cluster baseDomain |
.Host | string | Precomputed <name>-<uid>.<baseDomain> — routing-neutral hostname |
.URL / .ConnectionURL | string | Set after connectionURLTemplate renders; only available in valuesTemplate and connectionMessageTemplate |
.Namespace | string | The instance’s workload namespace |
.Template | *DployTemplate | Full template spec, useful for {{ .Template.Name }} / {{ .Template.Spec.X }} |
.Params | map[string]string | The whole request context: the params the template declares, merged with the claims listed in FORWARDED_CLAIMS. The raw token never reaches the cluster (nil in pool — anonymity) |
.Config.Values | map[string]any | OperatorConfig.spec.values |
Overriding connectionURLTemplate per template
Section titled “Overriding connectionURLTemplate per template”A DployTemplate.spec.connectionURLTemplate wins over the cluster default. Examples:
# Owner-based URL (leaks identity but useful for "your own session" UIs).connectionURLTemplate: "https://{{ .Owner }}-{{ .UUID }}.{{ .BaseDomain }}"
# Path-based instead of subdomain (single ingress hostname, multiple instances).connectionURLTemplate: "https://shared.example.com/{{ .UUID }}/"
# Custom port + scheme.connectionURLTemplate: "https://{{ .Host }}:8443"connectionType: instructions
Section titled “connectionType: instructions”When a template (or the operator default) sets connectionType: instructions, the API doesn’t redirect on ready — it surfaces status.connectionMessage so the UI can show a copyable command:
# OperatorConfig (or DployTemplate.spec):defaultConnectionType: instructionsconnectionMessageTemplate: "ssh root@{{ .ConnectionURL }} -p 22000"# … combined with a connectionURLTemplate that returns the bare host:connectionURLTemplate: "{{ .Host }}"Result: status.connectionMessage = "ssh root@vscode-abc123.example.com -p 22000", displayed by the UI without redirecting.
Username sanitization
Section titled “Username sanitization”Usernames extracted from the JWT are sanitized for Kubernetes compatibility:
- lowercased
.and@replaced with-- any remaining non-
[a-z0-9-]characters removed - leading/trailing
-trimmed
John.Doe@example.com → john-doe-example-comResource naming
Section titled “Resource naming”| Resource | Pattern | Example (on-demand) | Example (pool) |
|---|---|---|---|
| Workload namespace | <owner>-<name>-<uid> | john-doe-vscode-a1b2c3d4 | pool-webshell-c7218ff8 |
Default Host | <name>-<uid>.<baseDomain> | vscode-a1b2c3d4.env.dploy.dev | webshell-c7218ff8.env.dploy.dev |
DployInstance (on-demand) | <owner>-<template> | john-doe-vscode | — (pool members get random suffixes: webshell-pool-XXXXX) |
| Flux source (per template) | <template>-src | vscode-src | webshell-src |
The UUID is 8 hex characters, generated once by the operator at the first reconcile and stored immutably in status.uuid. The workload namespace uses pool as its owner segment for pool members, and keeps it after they are claimed — the namespace is created when the instance is provisioned, long before an owner exists, and a namespace cannot be renamed. Look at dploy.dev/owner on the instance, not at the namespace name, to see who holds a pooled environment; the Host template segment always reflects the DployTemplate name regardless of mode, so webshell-<uid> and kasm-<uid> are visibly distinct even before they’re claimed.