> ## Documentation Index
> Fetch the complete documentation index at: https://ubicloud-00f14c0d-dependabot-github-actions-actions-checkou-0.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: Self-Hosting Windmill on Ubicloud for Production

[Windmill](https://www.windmill.dev/) is an open-source platform that transforms scripts into UIs, APIs, and workflows. Supporting languages like Python and TypeScript, it simplifies internal tool creation, automation, and scaling for teams. This guide will help you set up Windmill on Ubicloud, with Ubicloud managed Kubernetes and PostgreSQL services.

## Preparing the Ubicloud Environment

To prepare the Ubicloud environment for running Windmill, follow the steps outlined in the Ubicloud documentation:

* [Setup your Ubicloud account](https://www.ubicloud.com/docs/quick-start/managed-services) and enter your billing details.
* [Create a Managed PostgreSQL server](https://www.ubicloud.com/docs/managed-postgresql/overview#create-managed-postgresql): Start with a hobby tier option and scale your database as needed.
* [Set up a Managed Kubernetes cluster](https://www.ubicloud.com/docs/managed-kubernetes/quickstart): Configure one control plane node and three worker nodes as a good initial setup.

## Preparing Your Environment

### Step 0: Ensure that your shell environment has [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) and [helm](https://helm.sh/) installed.

Use the commands to verify that `kubectl` and `helm` are installed:

```bash theme={null}
kubectl version
helm version
```

### Step 1: Obtain the kubeconfig File

* [Download the kubeconfig](https://www.ubicloud.com/docs/managed-kubernetes/quickstart#download-the-kubeconfig): Access the Kubernetes cluster in the Ubicloud console and download the kubeconfig file.
* Copy the file to the default kubeconfig location (`$HOME/.kube/config`) or set the `KUBECONFIG` environment variable to [configure your Kubernetes client](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) (e.g., kubectl).

### Step 2: Create a Namespace for Windmill

Create a namespace for the Windmill deployment:

```bash theme={null}
kubectl create namespace windmill
```

### Step 3: Store the Database Connection String in a Secret

[Retrieve the connection string for your PostgreSQL database](https://www.ubicloud.com/docs/managed-postgresql/overview#connect-to-managed-postgresql) from the Ubicloud console and create a secret:

```bash theme={null}
kubectl create secret generic -n windmill pg-db-credentials \
--from-literal=url='<postgres connection string copied from Ubicloud console>'
```

## Deploying Windmill

Add Windmill Helm repo:

```bash wrap theme={null}
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts/
```

Download the `values.yaml` file for the Windmill chart:

```bash wrap theme={null}
curl -O https://raw.githubusercontent.com/windmill-labs/windmill-helm-charts/refs/heads/main/charts/windmill/values.yaml
```

Disable the built-in PostgreSQL database in `values.yaml`:

```yaml theme={null}
postgresql:
  enabled: false
```

Set the secret name for the database connection string:

```yaml theme={null}
windmill:
...
  databaseUrlSecretName: pg-db-credentials
```

Install the Windmill chart with the modified values.yaml file:

```bash theme={null}
helm -n windmill install my-windmill windmill/windmill -f values.yaml
```

Wait for all pods to start running:

```bash theme={null}
kubectl -n windmill get pods
```

## Exposing Windmill to the Internet

By default, Windmill chart restricts the windmill-app service to internal cluster access. To access the Windmill UI via a browser, expose the service externally using a load balancer:

```bash theme={null}
kubectl -n windmill patch svc windmill-app -p '{"spec": {"type": "LoadBalancer"}}'
```

Wait for the service to get an external host assigned:

```bash theme={null}
kubectl -n windmill get service windmill-app
```

## Connecting to Windmill

The application will be accessible via your cluster's load balancer URL at port 8000 a few minutes after the `EXTERNAL-IP` is assigned. Run the following command to retrieve the address for your Windmill deployment.

```bash wrap theme={null}
echo "http://$(kubectl -n windmill get service windmill-app --output jsonpath='{.status.loadBalancer.ingress[0].hostname}'):8000"
```

Congratulations—Windmill is now running on Ubicloud!

## Configuring TLS for Secure Access

To expose Windmill securely via HTTPS, we'll employ Nginx Gateway Fabric, cert-manager, and Let's Encrypt using the Kubernetes Gateway API.

### Installing NGINX Gateway Fabric & cert-manager

Use the following commands to install the Gateway API CRDs, NGINX Gateway Fabric, and cert-manager:

```bash wrap theme={null}
kubectl kustomize https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v2.5.1 | kubectl apply -f -

helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
  --create-namespace -n nginx-gateway

helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true \
  --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
  --set config.kind="ControllerConfiguration" \
  --set config.enableGatewayAPI=true
```

Next, create a ClusterIssuer to obtain certificates from Let's Encrypt:

```bash theme={null}
export CLUSTER_ISSUER_EMAIL=email@yourcompany.com

kubectl apply -f <(cat <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
 name: letsencrypt
spec:
 acme:
   server: https://acme-v02.api.letsencrypt.org/directory
   email: $CLUSTER_ISSUER_EMAIL
   privateKeySecretRef:
     name: letsencrypt
   solvers:
   - http01:
       gatewayHTTPRoute: {}
EOF
)
```

### Configuring HTTPS with Gateway API

To expose Windmill securely, you first need the external domain for your cluster. You can find this "Service URL" (e.g., `mycluster-services-0b1c1.k8s.ubicloud.com`) in the **Overview** page of your Kubernetes cluster in the Ubicloud console. Alternatively, you can use the address retrieved in the previous step:

```bash wrap theme={null}
kubectl -n windmill get service windmill-app --output jsonpath='{.status.loadBalancer.ingress[0].hostname}'
```

<Note>If you prefer using a custom domain like `windmill.yourdomain.com`, create a `CNAME` record with your DNS provider pointing it to the Service URL mentioned above.</Note>

#### Updating Windmill Chart Values

Update the domain values and disable the built-in ingress in `values.yaml`, as we will manage the Gateway API resources manually:

```yaml theme={null}
windmill:
  baseDomain: xxxxx-services-xxxxx.k8s.ubicloud.com # Replace with your Service URL or custom domain
  baseProtocol: https

ingress:
  enabled: false
```

#### Reverting Service Type

Since external traffic will now be handled by the Gateway, revert the `windmill-app` service back to `ClusterIP`:

```bash theme={null}
kubectl -n windmill patch svc windmill-app -p '{"spec": {"type": "ClusterIP"}}'
```

Apply the updated values to the Windmill installation:

```bash theme={null}
helm -n windmill upgrade my-windmill windmill/windmill -f values.yaml
```

#### Creating Gateway and HTTPRoute

Finally, create the Gateway and HTTPRoute resources to expose Windmill via HTTPS:

```bash theme={null}
export WINDMILL_DOMAIN=xxxxx-services-xxxxx.k8s.ubicloud.com # Replace with your domain

kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: windmill-gateway
  namespace: nginx-gateway
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
  - name: https
    hostname: "$WINDMILL_DOMAIN"
    port: 443
    protocol: HTTPS
    allowedRoutes:
      namespaces:
        from: All
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: windmill-tls-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: windmill
  namespace: windmill
spec:
  parentRefs:
  - name: windmill-gateway
    namespace: nginx-gateway
    sectionName: https
  hostnames:
  - "$WINDMILL_DOMAIN"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: windmill-app
      port: 8000
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: windmill-redirect
  namespace: windmill
spec:
  parentRefs:
  - name: windmill-gateway
    namespace: nginx-gateway
    sectionName: http
  hostnames:
  - "$WINDMILL_DOMAIN"
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        statusCode: 301
EOF
```

### Verifying the Certificate Status

It may take a few minutes for the certificate to be issued and the gateway to become fully operational. Check the readiness of the TLS certificate for the Windmill application:

```bash theme={null}
kubectl -n nginx-gateway describe cert windmill-tls-cert
```

Once the certificate is ready, you can access your Windmill application securely at [https://xxxxx-services-xxxxx.k8s.ubicloud.com](https://xxxxx-services-xxxxx.k8s.ubicloud.com) or [https://windmill.yourdomain.com](https://windmill.yourdomain.com).
