Skip to main content

Infrastructure as code

Windmill supports managing instance and workspace configuration as code. This means you can version-control, review, and reproduce your Windmill setup across environments instead of configuring everything through the UI.

There are two distinct scopes of configuration:

ScopeWhat it coversHow to manage as code
Instance configurationGlobal settings, worker groups, SMTP, OAuth, license key, etc.YAML config file (sync-config) or Kubernetes operator (ConfigMap)
Workspace contentScripts, flows, apps, resources, variables, schedules, triggersWindmill CLI (wmill sync push/pull)

This page focuses on instance configuration. For workspace content, see the CLI sync section at the end.

Overview

Instance configuration covers everything you find under Instance settings in the Windmill UI: base URL, license key, retention policies, SMTP, OAuth providers, OpenTelemetry, worker group configs, and more.

Windmill provides two mechanisms to manage these settings declaratively:

  • sync-config CLI command — reads a YAML file, resolves environment variable references, and syncs the result to the database. Works with any deployment (Docker Compose, VMs, Kubernetes).
  • Kubernetes operator — watches a ConfigMap and continuously reconciles the database to match. Re-syncs every 5 minutes to detect and correct drift. Enterprise Edition only.

Both mechanisms use the same YAML schema and the same underlying diff engine. The only difference is how secrets are referenced and how the sync is triggered.

Configuration file format

The configuration file has two top-level sections:

global_settings:
# Instance-level settings (base URL, license, SMTP, OAuth, etc.)

worker_configs:
# Worker group definitions (tags, init scripts, autoscaling, etc.)

Global settings

All fields are optional. Only include the settings you want to manage.

global_settings:
base_url: "https://windmill.example.com"
retention_period_secs: 2592000 # 30 days
job_default_timeout: 900 # 15 minutes
request_size_limit_mb: 50
expose_metrics: true
dev_instance: false

# License key (Enterprise)
license_key: "your-license-key"

# SMTP for sending emails
smtp_settings:
smtp_host: "smtp.example.com"
smtp_port: 587
smtp_from: "windmill@example.com"
smtp_tls_implicit: false
smtp_username: "windmill"
smtp_password: "smtp-password"

# OAuth / SSO providers
oauths:
google:
id: "google-client-id"
secret: "google-client-secret"
login_config:
auth_url: "https://accounts.google.com/o/oauth2/v2/auth"
token_url: "https://oauth2.googleapis.com/token"
userinfo_url: "https://openidconnect.googleapis.com/v1/userinfo"
scopes: ["openid", "profile", "email"]

# OpenTelemetry
otel:
otel_exporter_otlp_endpoint: "http://otel-collector:4317"
tracing_enabled: true
metrics_enabled: true
logs_enabled: false

# Custom worker tags
custom_tags:
- gpu
- high-mem

# Critical error alert channels
critical_error_channels:
- email: "ops@example.com"

# Python/npm registries
pip_index_url: "https://pypi.example.com/simple"
npm_config_registry: "https://npm.example.com"

Worker group configs

Define one entry per worker group. The key is the group name.

worker_configs:
default:
worker_tags:
- deno
- python3
- bun
- go
- bash
- powershell
init_bash: "echo 'Worker ready'"
env_vars_static:
MY_VAR: "value"
cache_clear: 7

native:
worker_tags:
- nativets

gpu:
worker_tags:
- gpu-task
dedicated_worker: "f/gpu_scripts/inference"
autoscaling:
enabled: true
min_workers: 0
max_workers: 4

If you deploy Windmill on Kubernetes, using the operator is the recommended way to manage instance configuration. It watches a ConfigMap and continuously reconciles the database to match the declared state, re-syncing every 5 minutes to detect and correct configuration drift. Enterprise Edition only.

caution

Once the operator is enabled, it is the source of truth. Any settings changed directly in the UI will be reverted by the operator on the next reconciliation cycle (every 5 minutes). To make changes, update the instanceSpec in your Helm values or ConfigMap and re-deploy.

Quick start

Enable the operator and provide your instance spec in a single helm install:

# values.yaml
windmill:
operator:
enabled: true
instanceSpec:
global_settings:
base_url: "https://windmill.example.com"
license_key:
secretKeyRef:
name: windmill-secrets
key: license-key
retention_period_secs: 2592000
worker_configs:
default:
worker_tags: ["deno", "python3", "bun", "bash"]
native:
worker_tags: ["nativets"]
helm install windmill windmill/windmill -n windmill --create-namespace -f values.yaml

This deploys the operator, creates a ConfigMap with your instance spec, and sets up the necessary RBAC (Role for reading ConfigMaps, Secrets, and creating Events in the release namespace).

Applying the instance config manually

Instead of providing instanceSpec in your Helm values, you can manage the ConfigMap yourself:

apiVersion: v1
kind: ConfigMap
metadata:
name: windmill-instance
namespace: windmill
data:
spec: |
global_settings:
base_url: "https://windmill.example.com"
license_key:
secretKeyRef:
name: windmill-secrets
key: license-key
retention_period_secs: 2592000
smtp_settings:
smtp_host: "smtp.example.com"
smtp_port: 587
smtp_from: "windmill@example.com"
smtp_password:
secretKeyRef:
name: windmill-secrets
key: smtp-password
oauths:
google:
id: "google-client-id"
secret:
secretKeyRef:
name: windmill-secrets
key: google-oauth-secret
login_config:
auth_url: "https://accounts.google.com/o/oauth2/v2/auth"
token_url: "https://oauth2.googleapis.com/token"
userinfo_url: "https://openidconnect.googleapis.com/v1/userinfo"
scopes: ["openid", "profile", "email"]
worker_configs:
default:
worker_tags: ["deno", "python3", "bun", "go", "bash", "powershell"]
native:
worker_tags: ["nativets"]
kubectl apply -f windmill-instance.yaml

Monitoring

# Operator logs
kubectl logs -n windmill deployment/windmill-operator

Docker Compose setup (sync-config)

For Docker Compose deployments, managing instance configuration from the UI is perfectly fine. The sync-config approach is opt-in for teams that want to version-control their instance settings.

The sync-config subcommand reads a YAML config file, resolves envRef references, and syncs the result to the database.

How it works

  1. Windmill reads and parses the YAML file
  2. envRef fields are resolved from environment variables
  3. The current database state is read
  4. A diff is computed (changed settings are upserted, absent settings are deleted)
  5. Changes are applied

Setup

Create a config file (windmill-config.yaml):

global_settings:
base_url: "https://windmill.example.com"
license_key:
envRef: "WM_LICENSE_KEY"
retention_period_secs: 2592000

worker_configs:
default:
worker_tags: ["deno", "python3", "bun", "go", "bash", "powershell"]
native:
worker_tags: ["nativets"]

Add a one-shot sync container to your docker-compose.yml:

services:
windmill_config_sync:
image: ${WM_IMAGE}
restart: "no"
command: ["windmill", "sync-config", "/config/windmill-config.yaml"]
environment:
- DATABASE_URL=${DATABASE_URL}
- WM_LICENSE_KEY=${WM_LICENSE_KEY}
- SMTP_PASSWORD=${SMTP_PASSWORD}
volumes:
- ./windmill-config.yaml:/config/windmill-config.yaml:ro
depends_on:
db:
condition: service_healthy

windmill_server:
image: ${WM_IMAGE}
# ...
depends_on:
windmill_config_sync:
condition: service_completed_successfully

Set your secrets in a .env file (not committed to version control):

DATABASE_URL=postgres://postgres:changeme@db/windmill
WM_IMAGE=ghcr.io/windmill-labs/windmill-ee:main
WM_LICENSE_KEY=your-license-key-here
SMTP_PASSWORD=your-smtp-password

Re-syncing after changes

The sync container runs once at startup and exits. To re-apply after editing the YAML:

docker compose run --rm windmill_config_sync

Or run the binary directly in CI/CD:

windmill sync-config ./windmill-config.yaml

Replace semantics

sync-config uses replace mode: any setting present in the database but absent from your YAML file is deleted (except protected internal settings). This ensures the database state matches the file exactly.

If you only want to manage a subset of settings, include all settings you want to keep in the YAML file.

Handling secrets

Sensitive values (license key, SMTP password, OAuth secrets) should not be stored as plaintext in version-controlled files. Windmill supports two secret reference mechanisms depending on your deployment.

Environment variable references (envRef)

Use envRef to read a value from the process environment at sync time. Works everywhere (Docker Compose, Kubernetes, VMs).

global_settings:
license_key:
envRef: "WM_LICENSE_KEY"
smtp_settings:
smtp_password:
envRef: "SMTP_PASSWORD"
oauths:
github:
id: "github-client-id"
secret:
envRef: "GITHUB_OAUTH_SECRET"

The referenced environment variables must be set on the process that runs sync-config (or on the operator pod if using envRef with the Kubernetes operator).

Kubernetes secret references (secretKeyRef)

Use secretKeyRef to read a value directly from a Kubernetes Secret. Only available with the Kubernetes operator.

global_settings:
license_key:
secretKeyRef:
name: windmill-secrets
key: license-key
smtp_settings:
smtp_password:
secretKeyRef:
name: windmill-secrets
key: smtp-password

The operator resolves these references at reconciliation time by calling the Kubernetes Secrets API.

Choosing between envRef and secretKeyRef

envRefsecretKeyRef
Docker ComposeYesNo
KubernetesYesYes
Vault sidecarsYesNo (use envRef)
Reads fromProcess environmentK8s Secrets API
Requires RBAC for SecretsNoYes

Use envRef for portability across deployment targets. Use secretKeyRef for direct Kubernetes-native secret binding without intermediate environment variables.

Supported fields for secret references: license_key, hub_api_secret, scim_token, smtp_settings.smtp_password, oauths.<provider>.secret, and custom_instance_pg_databases.user_pwd.

Exporting current configuration

You can export the current instance configuration as YAML from the UI, the API, or the CLI to bootstrap your config file. The exported YAML can be copy-pasted directly as your instanceSpec (Kubernetes operator) or config file (sync-config), making it the easiest way to migrate from a UI-managed instance to a declarative setup.

From the API

The simplest way to get a complete snapshot of your current configuration:

GET /api/settings/instance_config/yaml

The response is the entire instance configuration as YAML, ready to be used as the instanceSpec value in your Helm values file or as a sync-config YAML file.

From the UI

In Instance settings, toggle to the YAML view to see the full configuration. Copy it as the starting point for your config file.

From the CLI

wmill instance get-config -o windmill-config.yaml

This dumps the current global settings and worker configs as YAML via the API.

Settings reference

Global settings fields

FieldTypeDescription
base_urlstringInstance base URL
license_keystring / refEnterprise license key
retention_period_secsintJob retention period in seconds
request_size_limit_mbintMax request body size
job_default_timeoutintDefault timeout for jobs (seconds)
dev_instanceboolMark as development instance
expose_metricsboolEnable Prometheus metrics
smtp_settingsobjectSMTP config (smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_tls_implicit)
oauthsobjectOAuth providers, keyed by provider name
otelobjectOpenTelemetry config (otel_exporter_otlp_endpoint, tracing_enabled, metrics_enabled, logs_enabled)
pip_index_urlstringCustom pip index URL
pip_extra_index_urlstringExtra pip index URL
npm_config_registrystringCustom npm registry
custom_tagslistCustom worker tags
critical_error_channelslistAlert channels (email, Slack, Teams)
indexer_settingsobjectFull-text search indexer config
hub_api_secretstring / refHub API secret
scim_tokenstring / refSCIM token (Enterprise)

For the complete list of fields, see the Helm chart README.

Worker group config fields

Keys are worker group names (e.g. default, native, gpu).

FieldTypeDescription
worker_tagslistTags this worker group handles
dedicated_workerstringDedicated worker script path
dedicated_workerslistMultiple dedicated worker paths
init_bashstringBash script run on worker init
cache_clearintCache clear interval
env_vars_staticobjectStatic environment variables
env_vars_allowlistlistAllowed environment variable names
pip_local_dependencieslistLocal pip dependencies
additional_python_pathslistExtra Python paths
priority_tagsobjectTag priority mapping
autoscalingobjectAutoscaling config (enabled, min_workers, max_workers, integration)

Workspace content with the CLI

Instance configuration (global settings, worker groups) is distinct from workspace content (scripts, flows, apps, resources, variables). To manage workspace content as code, use the Windmill CLI with wmill sync push and wmill sync pull.

Quick example

# Install the CLI
npm install -g windmill-cli

# Add a workspace
wmill workspace add my-workspace https://windmill.example.com/ --token <your-token>

# Pull workspace content to local files
wmill sync pull

# Edit scripts/flows locally, then push changes back
wmill sync push --yes

The CLI supports syncing scripts, flows, apps, resources, variables, and optionally schedules (--include-schedules), triggers (--include-triggers), users (--include-users), groups (--include-groups), and workspace settings (--include-settings).

You can also validate your YAML files before pushing:

wmill sync push --lint --yes