Windmill on Azure
Windmill can be deployed on Azure using AKS (Azure Kubernetes Service) with an Azure Database for PostgreSQL Flexible Server. This guide walks through the full setup: networking, database, Helm deployment, and ingress. It then covers optional Entra ID passwordless authentication for Enterprise users.
Adapt replica counts and VM sizes to your workload. As a rule of thumb, allocate 1 worker per vCPU and 1–2 GB of RAM per worker.
Prerequisites
Create a resource group
az group create --name <rg> --location <region>
Create an AKS cluster
az aks create \
--name <aks-cluster> \
--resource-group <rg> \
--node-count 3 \
--node-vm-size Standard_D4s_v3 \
--network-plugin azure \
--generate-ssh-keys
Get credentials to interact with the cluster:
az aks get-credentials --name <aks-cluster> --resource-group <rg>
If you plan to use Entra ID database authentication, enable the OIDC issuer and Workload Identity add-on now to avoid a cluster restart later:
az aks update \
--name <aks-cluster> \
--resource-group <rg> \
--enable-oidc-issuer \
--enable-workload-identity
Create a PostgreSQL Flexible Server
Server creation
az postgres flexible-server create \
--name <pg-server> \
--resource-group <rg> \
--tier GeneralPurpose \
--sku-name Standard_D4s_v3 \
--storage-size 256 \
--admin-user windmill \
--admin-password '<strong-password>' \
--version 16
Sizing guidance
- Instance size: Windmill requires roughly 50 connections per server plus 5 per worker.
Standard_D4s_v3supports up to 859 connections by default, which gives room to grow. - Storage: Windmill stores logs and job results in the database. A good starting point is 256 GiB with autoscaling enabled. Adjust based on your job retention policy.
- High availability: For production, enable zone-redundant HA with
--high-availability ZoneRedundant. - Backups: Automated backups are enabled by default (7-day retention). Consider increasing retention for production.
Create the windmill database
az postgres flexible-server db create \
--server-name <pg-server> \
--resource-group <rg> \
--database-name windmill
Network access
If the AKS cluster and PostgreSQL server are in the same VNet, use private access (VNet integration). Otherwise, use private endpoints or allow access from the AKS outbound IPs via firewall rules.
To add a firewall rule for a specific IP:
az postgres flexible-server firewall-rule create \
--name <pg-server> \
--resource-group <rg> \
--rule-name allow-aks \
--start-ip-address <aks-outbound-ip> \
--end-ip-address <aks-outbound-ip>
Database role setup
Azure Flexible Server does not provide a true PostgreSQL superuser. The standard init-db-as-superuser.sql uses WITH BYPASSRLS which is not available on Azure. Connect to the windmill database as the admin user and run the following adapted setup instead:
-- Create the windmill roles without BYPASSRLS
CREATE ROLE windmill_user;
CREATE ROLE windmill_admin;
GRANT windmill_user TO windmill_admin;
-- Grant schema access
GRANT USAGE ON SCHEMA public TO windmill_admin;
GRANT USAGE ON SCHEMA public TO windmill_user;
-- Grant the roles to your login user (the user in DATABASE_URL)
-- For password auth: GRANT windmill_admin TO windmill;
-- For Entra ID: GRANT windmill_admin TO "windmill-identity";
GRANT windmill_admin TO <your-login-user>;
GRANT windmill_user TO <your-login-user>;
On recent Windmill versions (>= 1.414.0), Windmill automatically creates admin policies on all RLS-enabled tables as a fallback when BYPASSRLS is not available. No manual policy creation is needed.
See running Windmill without a Postgres superuser for more details.
Deploy Windmill with Helm
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts/
helm repo update
Create a values.yaml:
windmill:
baseDomain: "windmill.example.com"
baseProtocol: "https"
databaseUrl: postgresql://windmill:<strong-password>@<pg-server>.postgres.database.azure.com:5432/windmill?sslmode=require
postgresql:
enabled: false
Install:
helm install windmill windmill/windmill \
--namespace windmill \
--create-namespace \
-f values.yaml
For Enterprise Edition, set enterprise.enabled: true in your values.yaml. The chart automatically uses the windmill-ee image when enterprise is enabled. See Helm chart documentation for all available options.
Expose Windmill
You can expose Windmill using an Azure Load Balancer (L4) or the Application Gateway Ingress Controller (AGIC, L7). For HTTPS, we recommend terminating TLS at the ingress layer.
Using Application Gateway (AGIC)
AGIC is not installed by default. Enable the add-on on your AKS cluster:
az aks enable-addons \
--name <aks-cluster> \
--resource-group <rg> \
--addons ingress-appgw \
--appgw-name windmill-appgw \
--appgw-subnet-cidr "<available-/24-in-your-vnet>"
This creates an Application Gateway instance automatically. See the AGIC add-on tutorial for detailed setup options.
The Windmill Helm chart already includes an Ingress resource with the correct path routing (/ws/, /ws_mp/, /). Configure it for AGIC in your values.yaml:
ingress:
enabled: true
className: "azure-application-gateway"
annotations:
appgw.ingress.kubernetes.io/ssl-redirect: "true"
appgw.ingress.kubernetes.io/request-timeout: "3600"
tls:
- hosts:
- windmill.example.com
secretName: windmill-tls
The request-timeout of 3600 seconds prevents Application Gateway from closing WebSocket connections (LSP and multiplayer) after the default 30-second timeout.
Point your domain to the Application Gateway's public IP and configure a TLS certificate (e.g. via cert-manager, Azure Key Vault, or a manual TLS secret). Then set the Base URL in Windmill instance settings.
Open Windmill
Navigate to your domain or load balancer IP. You should see the Windmill login screen. The default credentials are admin@windmill.dev / changeme. Follow the setup wizard to configure your instance.
Entra ID database authentication (optional)
Windmill Enterprise supports Azure Workload Identity for passwordless authentication to PostgreSQL Flexible Server via Microsoft Entra ID. Instead of a static password, Windmill obtains short-lived Azure AD tokens that are automatically refreshed in the background.
Azure Entra ID database authentication is only available in Windmill Enterprise Edition.
Enable Workload Identity on AKS
If you did not enable it during cluster creation:
az aks update \
--name <aks-cluster> \
--resource-group <rg> \
--enable-oidc-issuer \
--enable-workload-identity
Create a managed identity with federated credential
# Create identity
az identity create \
--name windmill-identity \
--resource-group <rg>
# Get the identity client ID and AKS OIDC issuer URL
export IDENTITY_CLIENT_ID=$(az identity show --name windmill-identity --resource-group <rg> --query clientId -o tsv)
export AKS_OIDC_ISSUER=$(az aks show --name <aks-cluster> --resource-group <rg> --query "oidcIssuerProfile.issuerUrl" -o tsv)
# Create the federated credential
az identity federated-credential create \
--name windmill-federated \
--identity-name windmill-identity \
--resource-group <rg> \
--issuer "$AKS_OIDC_ISSUER" \
--subject system:serviceaccount:windmill:windmill \
--audiences api://AzureADTokenExchange
The --subject format is system:serviceaccount:<namespace>:<service-account-name>. The service account name is generated by the Helm chart's fullname template (matching the release name). Replace both values if you use a different namespace or release name.
Enable Entra auth on PostgreSQL
az postgres flexible-server update \
--name <pg-server> \
--resource-group <rg> \
--microsoft-entra-auth Enabled
Set the Entra admin
Assign the managed identity as the Entra administrator of the Flexible Server.
az postgres flexible-server microsoft-entra-admin create \
--server-name <pg-server> \
--resource-group <rg> \
--display-name windmill-identity \
--object-id $(az identity show --name windmill-identity --resource-group <rg> --query principalId -o tsv) \
--type ServicePrincipal
Create the Entra-authenticated database role
Connect using an Azure AD token and create the principal:
export PGPASSWORD=$(az account get-access-token --resource-type oss-rdbms --query accessToken -o tsv)
psql "host=<pg-server>.postgres.database.azure.com dbname=postgres user=windmill-identity sslmode=require"
-- pgaadauth_create_principal must be run from the postgres database
SELECT * FROM pgaadauth_create_principal('windmill-identity', false, false);
Then grant database access and set up the Windmill roles as described in the database role setup section above, using "windmill-identity" as the login user:
\c windmill
GRANT ALL ON DATABASE windmill TO "windmill-identity";
pgaadauth_create_principal only exists in the postgres database. Always connect to postgres before calling it.
Configure Windmill for Entra ID
Set the DATABASE_URL environment variable with entraid as the password sentinel:
postgresql://windmill-identity:entraid@<pg-server>.postgres.database.azure.com:5432/windmill?sslmode=require
When Windmill detects entraid as the password, it switches to Azure AD token-based authentication.
When Workload Identity is configured on AKS, these environment variables are automatically injected into pods by the mutating webhook — you do not need to set them manually:
| Variable | Description |
|---|---|
AZURE_TENANT_ID | Your Azure AD tenant ID |
AZURE_CLIENT_ID | Client ID of the managed identity |
AZURE_FEDERATED_TOKEN_FILE | Path to the projected service account token |
AZURE_AUTHORITY_HOST | Azure AD authority endpoint |
For sovereign clouds (Azure Government, Azure China), the webhook sets AZURE_AUTHORITY_HOST to the appropriate endpoint automatically.
Helm values for Entra ID
windmill:
baseDomain: "windmill.example.com"
baseProtocol: "https"
databaseUrl: postgresql://windmill-identity:entraid@<pg-server>.postgres.database.azure.com:5432/windmill?sslmode=require
app:
labels:
azure.workload.identity/use: "true"
indexer:
labels:
azure.workload.identity/use: "true"
workerGroups:
- name: default
replicas: 2
labels:
azure.workload.identity/use: "true"
- name: native
replicas: 1
labels:
azure.workload.identity/use: "true"
serviceAccount:
annotations:
azure.workload.identity/client-id: "<IDENTITY_CLIENT_ID>"
enterprise:
enabled: true
postgresql:
enabled: false
The azure.workload.identity/use: "true" label must be present on every pod that connects to the database (app, indexer, and all worker groups).
How it works
- AKS projects a short-lived service account token into each pod via the Workload Identity webhook.
- At startup, Windmill detects the
entraidsentinel password and reads the projected token fromAZURE_FEDERATED_TOKEN_FILE. - Windmill exchanges the federated token for an Azure AD access token scoped to
https://ossrdbms-aad.database.windows.net/.default. - The access token is used as the PostgreSQL password when opening connections.
- A background task refreshes the token approximately every 24 hours, before the previous token expires. Active connections are re-authenticated transparently.