# How to Set Up Self-Hosted OpenWork on Kubernetes (GKE, EKS, AKS): A Complete Helm Guide

> Learn how to set up self-hosted OpenWork on Kubernetes (GKE, EKS, AKS) with our complete Helm guide. Deploy the web app, PostgreSQL, Redis, and inference services easily.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-01

---

**Deploy OpenWork on any major cloud Kubernetes platform using the official Helm chart in `packaging/helm/openwork-ee`, which bundles the web application, PostgreSQL, Redis, and optional inference services.**

OpenWork's enterprise-ready deployment is packaged as a Helm chart located at `packaging/helm/openwork-ee` in the `different-ai/openwork` repository. This chart enables self-hosted OpenWork on Google Kubernetes Engine (GKE), Amazon Elastic Kubernetes Service (EKS), and Azure Kubernetes Service (AKS) without vendor-specific modifications. The deployment architecture uses standard Kubernetes primitives—Deployments, Services, Ingress resources, and ConfigMaps—making it portable across CNCF-compatible clusters.

This guide walks through complete setup, from cluster preparation to production hardening, with working configurations for each major cloud provider.

---

## Prerequisites for Kubernetes Deployment

Before installing OpenWork, ensure your environment meets these baseline requirements across all three platforms.

| Component | Purpose | Verification Command |
|-----------|---------|----------------------|
| **kubectl** v1.25+ | Cluster communication | `kubectl version --client` |
| **Helm 3.12+** | Chart installation | `helm version` |
| **Cluster access** | Authentication to target cluster | Provider-specific (see below) |
| **Domain name** (optional) | TLS and external access | DNS A record pointing to ingress IP |
| **Storage class** | PostgreSQL and Redis persistence | `kubectl get storageclass` |

### Provider-Specific Configuration

**GKE**

```bash
gcloud container clusters get-credentials CLUSTER_NAME --region=REGION --project=PROJECT_ID

```

Default storage class: `standard` (GCE Persistent Disk)

**EKS**

```bash
aws eks update-kubeconfig --region REGION --name CLUSTER_NAME

```

Default storage class: `gp2` or `gp3` (EBS CSI driver)

**AKS**

```bash
az aks get-credentials --resource-group RG_NAME --name CLUSTER_NAME

```

Default storage class: `managed-csi` or `managed-premium`

---

## Understanding the OpenWork Helm Chart Structure

The chart in `packaging/helm/openwork-ee` defines a multi-service architecture. Familiarity with these files enables effective customization.

### Core Chart Files

| File Path | Purpose |
|-----------|---------|
| [`Chart.yaml`](https://github.com/different-ai/openwork/blob/main/Chart.yaml) | Chart metadata, dependencies (PostgreSQL, Redis subcharts) |
| [`values.yaml`](https://github.com/different-ai/openwork/blob/main/values.yaml) | Default configuration values |
| [`templates/deployment.yaml`](https://github.com/different-ai/openwork/blob/main/templates/deployment.yaml) | Main application Deployment with web and inference containers |
| [`templates/ingress.yaml`](https://github.com/different-ai/openwork/blob/main/templates/ingress.yaml) | Ingress resource for external HTTP/HTTPS access |
| [`templates/service.yaml`](https://github.com/different-ai/openwork/blob/main/templates/service.yaml) | ClusterIP Services for internal communication |
| [`templates/configmap.yaml`](https://github.com/different-ai/openwork/blob/main/templates/configmap.yaml) | Non-sensitive environment configuration |

### Services Deployed

| Service | Container Image | Default Port | Description |
|---------|-----------------|--------------|-------------|
| **web-app** | `ghcr.io/different-ai/openwork-ee` | `3000` | Next.js UI and API server |
| **inference** (optional) | `ghcr.io/different-ai/openwork-inference` | `8000` | Model proxy for local LLMs |
| **postgres** | `postgres:15` | `5432` | Relational data store |
| **redis** | `redis:7` | `6379` | Session cache and job queue |

---

## Step 1: Clone and Inspect the Helm Chart

Retrieve the latest chart directly from the repository:

```bash
git clone https://github.com/different-ai/openwork.git
cd openwork/packaging/helm/openwork-ee

```

Review the default configuration:

```bash
cat values.yaml

```

Key defaults to note:
- `replicaCount: 1` (single instance, not HA)
- `ingress.enabled: false` (no external access)
- `persistence.size: 10Gi` (PVC for PostgreSQL)

---

## Step 2: Create Cloud-Specific values.yaml

### GKE Configuration

Create [`gke-values.yaml`](https://github.com/different-ai/openwork/blob/main/gke-values.yaml):

```yaml
replicaCount: 2

image:
  repository: ghcr.io/different-ai/openwork-ee
  tag: "latest"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP

ingress:
  enabled: true
  className: "gce"  # GKE Ingress controller

  annotations:
    kubernetes.io/ingress.global-static-ip-name: "openwork-ip"
    networking.gke.io/managed-certificates: "openwork-cert"
    kubernetes.io/ingress.allow-http: "false"
  hosts:
    - host: openwork.example.com
      paths:
        - path: /*
          pathType: ImplementationSpecific

postgres:
  enabled: true
  auth:
    postgresPassword: "CHANGE_ME_IN_SECRET"
    database: "openwork"
  persistence:
    enabled: true
    storageClass: "premium-rwo"  # SSD-backed GKE storage

    size: 20Gi

redis:
  enabled: true
  auth:
    password: "CHANGE_ME_IN_SECRET"

env:
  OPENWORK_JWT_SECRET: ""  # Set via external secret

  OPENWORK_APP_URL: "https://openwork.example.com"

```

**GKE-Specific Notes:**
- The `gce` ingress class creates a Google Load Balancer with automatic health checks
- `managed-certificates` reference a pre-created `ManagedCertificate` resource
- Use `premium-rwo` for production PostgreSQL workloads

### EKS Configuration

Create [`eks-values.yaml`](https://github.com/different-ai/openwork/blob/main/eks-values.yaml):

```yaml
replicaCount: 2

image:
  repository: ghcr.io/different-ai/openwork-ee
  tag: "latest"

service:
  type: ClusterIP

ingress:
  enabled: true
  className: "alb"  # AWS Load Balancer Controller

  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:REGION:ACCOUNT:certificate/CERT_ID"
  hosts:
    - host: openwork.example.com
      paths:
        - path: /
          pathType: Prefix

postgres:
  enabled: true
  persistence:
    storageClass: "gp3"  # EBS GP3 volumes

    size: 20Gi

redis:
  enabled: true

env:
  OPENWORK_JWT_SECRET: ""
  OPENWORK_APP_URL: "https://openwork.example.com"

```

**EKS-Specific Notes:**
- Requires AWS Load Balancer Controller installed in cluster
- `target-type: ip` enables direct pod routing without NodePort
- Certificate ARN references ACM certificate in same region

### AKS Configuration

Create [`aks-values.yaml`](https://github.com/different-ai/openwork/blob/main/aks-values.yaml):

```yaml
replicaCount: 2

image:
  repository: ghcr.io/different-ai/openwork-ee
  tag: "latest"

service:
  type: ClusterIP

ingress:
  enabled: true
  className: "azure-application-gateway"  # AGIC

  annotations:
    appgw.ingress.kubernetes.io/use-private-ip: "false"
    kubernetes.io/ingress.class: "azure/application-gateway"
  hosts:
    - host: openwork.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: openwork-tls
      hosts:
        - openwork.example.com

postgres:
  enabled: true
  persistence:
    storageClass: "managed-csi-premium"
    size: 20Gi

redis:
  enabled: true

env:
  OPENWORK_JWT_SECRET: ""
  OPENWORK_APP_URL: "https://openwork.example.com"

```

**AKS-Specific Notes:**
- Application Gateway Ingress Controller (AGIC) or NGINX ingress supported
- TLS secrets must be manually created or use cert-manager with Let's Encrypt

---

## Step 3: Secure Secrets Management

Never store credentials in [`values.yaml`](https://github.com/different-ai/openwork/blob/main/values.yaml). Instead, create Kubernetes Secrets:

```bash

# Generate cryptographically secure values

JWT_SECRET=$(openssl rand -base64 32)
POSTGRES_PASSWORD=$(openssl rand -base64 24)
REDIS_PASSWORD=$(openssl rand -base64 24)

# Create namespace

kubectl create namespace openwork

# Create secret

kubectl -n openwork create secret generic openwork-secrets \
  --from-literal=OPENWORK_JWT_SECRET="$JWT_SECRET" \
  --from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
  --from-literal=REDIS_PASSWORD="$REDIS_PASSWORD"

```

Reference this secret in your values file:

```yaml
envExistingSecret: "openwork-secrets"

postgres:
  auth:
    existingSecret: "openwork-secrets"
    secretKeys:
      postgresPassword: "POSTGRES_PASSWORD"

redis:
  auth:
    existingSecret: "openwork-secrets"
    secretKeys:
      password: "REDIS_PASSWORD"

```

---

## Step 4: Install the Helm Chart

### GKE Installation

```bash
helm dependency update  # Fetch PostgreSQL and Redis subcharts

helm install openwork . \
  -f gke-values.yaml \
  --namespace openwork \
  --set envExistingSecret=openwork-secrets

```

### EKS Installation

```bash
helm dependency update

helm install openwork . \
  -f eks-values.yaml \
  --namespace openwork \
  --set envExistingSecret=openwork-secrets

```

### AKS Installation

```bash
helm dependency update

helm install openwork . \
  -f aks-values.yaml \
  --namespace openwork \
  --set envExistingSecret=openwork-secrets

```

---

## Step 5: Verify and Access

Check deployment status:

```bash
kubectl -n openwork get pods
kubectl -n openwork logs -l app.kubernetes.io/name=openwork-ee --tail=100

# Expected output: "ready started server on 0.0.0.0:3000, url: http://localhost:3000"

```

Retrieve external access information:

**GKE**

```bash
kubectl -n openwork get ingress openwork-ee

# Look for IP under "ADDRESS" (takes 5-10 minutes to provision)

```

**EKS**

```bash
kubectl -n openwork get ingress openwork-ee

# ALB DNS name appears under "ADDRESS"

```

**AKS**

```bash
kubectl -n openwork get ingress openwork-ee

# Application Gateway IP or custom domain

```

---

## Step 6: External Database Configuration (Production)

For production workloads, replace the bundled PostgreSQL with a managed service:

```yaml
postgres:
  enabled: false

env:
  DATABASE_URL: "postgresql://openwork:${PASSWORD}@HOST.REGION.rds.amazonaws.com:5432/openwork"
  # GKE: Cloud SQL via proxy or private IP

  # AKS: Azure Database for PostgreSQL flexible server

```

Update with `helm upgrade`:

```bash
helm upgrade openwork . -f eks-values.yaml --namespace openwork

```

---

## Step 7: Enable Optional Inference Service

The inference container enables local LLM integration. Expose it via LoadBalancer for external model access:

```yaml
inference:
  enabled: true
  service:
    type: LoadBalancer
    port: 8000
    annotations:
      # GKE: Reserve static IP

      networking.gke.io/load-balancer-type: "External"

```

After deployment, retrieve the endpoint:

```bash
kubectl -n openwork get svc openwork-ee-inference

```

---

## Upgrading and Scaling

**Horizontal scaling:**

```bash
helm upgrade openwork . \
  -f gke-values.yaml \
  --namespace openwork \
  --set replicaCount=4

```

**Version upgrade:**

```bash
helm upgrade openwork . \
  -f gke-values.yaml \
  --namespace openwork \
  --set image.tag="v0.15.0"

```

**Uninstall:**

```bash
helm uninstall openwork --namespace openwork
kubectl delete namespace openwork

```

---

## Summary

- **Self-hosted OpenWork on Kubernetes** deploys via the official Helm chart at `packaging/helm/openwork-ee`
- **GKE, EKS, and AKS** require only ingress class and annotation changes—core manifests remain identical
- **Secrets management** uses Kubernetes Secrets referenced through `envExistingSecret` and `existingSecret` fields
- **Production hardening** replaces bundled PostgreSQL with managed databases and enables multiple replicas
- **Inference service** deploys as optional sidecar container exposed via LoadBalancer or ClusterIP

---

## Frequently Asked Questions

### What Kubernetes versions does OpenWork support?

OpenWork's Helm chart targets Kubernetes 1.25+ and uses the `networking.k8s.io/v1` Ingress API. The chart is tested against GKE 1.28+, EKS 1.27+, and AKS 1.28+. Older versions may require API version overrides in [`templates/ingress.yaml`](https://github.com/different-ai/openwork/blob/main/templates/ingress.yaml).

### Can I use an external Redis instance instead of the bundled one?

Yes. Set `redis.enabled: false` and provide the connection string via environment variables: `REDIS_HOST`, `REDIS_PORT`, and `REDIS_PASSWORD` in your custom values or secret.

### How do I configure TLS certificates for the ingress?

Three approaches are supported: **GKE ManagedCertificates** (referenced by annotation), **AWS ACM** (certificate ARN in ALB annotation), and **Kubernetes TLS secrets** (standard spec.tls blocks). cert-manager with Let's Encrypt works on all platforms when the NGINX ingress class is used instead of cloud-native controllers.

### Where are the container images hosted?

Official images publish to `ghcr.io/different-ai/openwork-ee` and `ghcr.io/different-ai/openwork-inference`. The [`scripts/docker-build.sh`](https://github.com/different-ai/openwork/blob/main/scripts/docker-build.sh) file in the repository root contains the build logic used to create these images.