# How to Deploy different-ai/openwork in a Production Environment: Complete Self-Hosting Guide

> Deploy different-ai/openwork in production on Kubernetes with the official Helm chart. Supports private-network, air-gapped, and single-org topologies.

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

---

**Deploy different-ai/openwork in production using the official Helm chart on Kubernetes, with support for private-network, air-gapped, and single-org topologies.**

OpenWork is an open-source AI workflow platform that combines a desktop client with a self-hosted backend. A production deployment requires orchestrating multiple services—**Den Web** (UI), **Den Controller** (API), **MySQL-compatible database**, and optional **inference service**—behind a unified origin. According to the source code, the recommended path for production workloads is the **official Helm chart** documented in [`packaging/helm/openwork-ee/README.md`](https://github.com/different-ai/openwork/blob/main/packaging/helm/openwork-ee/README.md), with supplementary guides for specialized network configurations.

## Production Deployment Architectures

Before installing, select the topology that matches your security and network constraints. The repository defines three supported patterns in `packages/docs/start-here/self-host.mdx`:

| Architecture | Use Case | Key Documentation |
|:---|:---|:---|
| **Standard self-hosted** | Single Kubernetes cluster with external database access | `packages/docs/start-here/self-host.mdx` |
| **Private-network deployment** | Den runs inside a VPC/VPN; desktop connects through VPN-reachable origin | `packages/docs/start-here/private-network-deployment.mdx` |
| **Air-gapped deployment** | Zero outbound internet; all artifacts mirrored internally | `packages/docs/start-here/air-gapped-deployment.mdx` |

Most enterprise deployments follow the **private-network pattern**—allowing laptops internet access while keeping the control plane inside corporate infrastructure.

## Prerequisites for Production

A production-ready openwork deployment requires:

- **Kubernetes cluster** (1.24+) with ingress controller
- **MySQL 8.0+** or PlanetScale for persistent state
- **SMTP relay** for email-based authentication flows
- **TLS certificates** (via cert-manager or external)
- **Container registry access** to `ghcr.io/different-ai/charts/openwork-ee`

The Helm chart ([`packaging/helm/openwork-ee/README.md`](https://github.com/different-ai/openwork/blob/main/packaging/helm/openwork-ee/README.md)) packages the **Den Web** frontend, **Den Controller API**, optional **inference service**, and database migration jobs into coordinated deployments.

## Step-by-Step Helm Deployment

### 1. Create Namespace and Prepare Secrets

```bash
kubectl create namespace openwork

```

Generate cryptographically secure keys for authentication and database encryption:

```bash
export BETTER_AUTH_SECRET=$(openssl rand -base64 32)
export DB_ENCRYPTION_KEY=$(openssl rand -base64 32)

```

### 2. Configure values.prod.yaml

The [`values.prod.yaml`](https://github.com/different-ai/openwork/blob/main/values.prod.yaml) file drives all runtime configuration. Per the chart README, these fields are required for single-organization mode:

```yaml
image:
  tag: "v0.18.0"  # Pin to specific chart version

config:
  tenancy:
    mode: "single_org"
    singleOrgName: "YourOrg"
    singleOrgSlug: "yourorg"
    ownerEmails: "admin@yourorg.com"
    allowPublicSignup: "false"
  public:
    webOrigin: "https://openwork.yourorg.internal"
    apiOrigin: "https://api.openwork.yourorg.internal"
    mcpResourceUrl: "https://api.yourorg.internal/mcp"
    desktopDenBaseUrl: "https://openwork.yourorg.internal"
    corsOrigins: "https://openwork.yourorg.internal,https://api.yourorg.internal"
    betterAuthTrustedOrigins: "https://openwork.yourorg.internal"
    bootstrapAdminEmails: "admin@yourorg.com"

secret:
  values:
    databaseUrl: "mysql://openwork:${DB_PASSWORD}@mysql.internal:3306/openwork_den?sslmode=verify-full"
    betterAuthSecret: "${BETTER_AUTH_SECRET}"
    denDbEncryptionKey: "${DB_ENCRYPTION_KEY}"
    emailFrom: "OpenWork <no-reply@yourorg.internal>"
    smtpHost: "smtp.yourorg.internal"
    smtpPort: "587"
    smtpUser: "openwork@yourorg.internal"
    smtpPass: "${SMTP_PASSWORD}"

```

**Critical security note:** Never commit [`values.prod.yaml`](https://github.com/different-ai/openwork/blob/main/values.prod.yaml) to version control. Inject secrets via CI/CD environment variables or external secret operators.

### 3. Handle Custom Certificate Authorities

For corporate environments with internal PKI, mount your root CA into the pods. Create the secret:

```bash
kubectl create secret generic openwork-custom-ca \
  --namespace openwork \
  --from-file=ca.crt=./corp-root-ca.pem

```

Then reference it in your values file (`customCa.enabled: true`, `customCa.existingSecret: openwork-custom-ca`).

### 4. Install the Helm Chart

```bash
helm upgrade --install openwork-ee \
  oci://ghcr.io/different-ai/charts/openwork-ee \
  --namespace openwork \
  --create-namespace \
  --version v0.18.0 \
  -f values.prod.yaml

```

For private registries, authenticate first and configure `imagePullSecrets` as documented in [`packaging/helm/openwork-ee/README.md`](https://github.com/different-ai/openwork/blob/main/packaging/helm/openwork-ee/README.md).

### 5. Verify Deployment Health

```bash
kubectl get pods -n openwork
kubectl rollout status deployment/openwork-ee-den-api -n openwork
kubectl rollout status deployment/openwork-ee-den-web -n openwork

```

The chart exposes `/health` and `/ready` endpoints automatically. Test locally via port-forward:

```bash
kubectl port-forward svc/openwork-ee-den-web 3005:3005 -n openwork
curl -s http://127.0.0.1:3005/api/health

```

### 6. Configure Desktop Client Connectivity

OpenWork Desktop connects via **install-link flow**. Set the **Connect URL** to your configured `webOrigin` (`https://openwork.yourorg.internal`). The client auto-derives API endpoints at `/api/den/...`.

For split-origin deployments (separate web and API hosts), additionally configure `DEN_API_PUBLIC_URL` as described in `packages/docs/start-here/private-network-deployment.mdx`.

### 7. Run Database Migrations

The Helm chart includes a **pre-install/upgrade hook Job** that executes migrations automatically. To run manually (e.g., after failed upgrade):

```yaml
migrations:
  enabled: true
  hook: false

```

Apply the chart, then create the Job manually with `kubectl`.

## Production Hardening Checklist

| Layer | Implementation | Source Reference |
|:---|:---|:---|
| **Database** | External managed MySQL with SSL verification; encryption key in `denDbEncryptionKey` | [`packaging/helm/openwork-ee/README.md`](https://github.com/different-ai/openwork/blob/main/packaging/helm/openwork-ee/README.md) |
| **Authentication** | `betterAuthSecret` 32+ characters; `allowPublicSignup: "false"` for single-org | [`values.prod.yaml`](https://github.com/different-ai/openwork/blob/main/values.prod.yaml) example |
| **Network** | Private VPC with VPN ingress; or air-gapped with mirrored registries | `packages/docs/start-here/private-network-deployment.mdx` |
| **Observability** | OpenTelemetry or Sentry via `observability` block | Chart README |
| **Custom CA** | `openwork-custom-ca` secret mounted to all pods | `customCa` values structure |

## CI/CD Deployment Script

Automate your openwork production deployment with this bash template:

```bash
#!/usr/bin/env bash
set -euo pipefail

NS=openwork
CHART=oci://ghcr.io/different-ai/charts/openwork-ee
VERSION="${OPENWORK_VERSION:-v0.18.0}"

# Generate secrets if not provided

BETTER_AUTH_SECRET="${BETTER_AUTH_SECRET:-$(openssl rand -base64 32)}"
DB_ENCRYPTION_KEY="${DB_ENCRYPTION_KEY:-$(openssl rand -base64 32)}"

cat >values.prod.yaml <<EOF
image:
  tag: "${VERSION}"
config:
  tenancy:
    mode: "single_org"
    singleOrgName: "${ORG_NAME}"
    singleOrgSlug: "${ORG_SLUG}"
    ownerEmails: "${ADMIN_EMAIL}"
    allowPublicSignup: "false"
  public:
    webOrigin: "https://openwork.${DOMAIN}"
    apiOrigin: "https://api.openwork.${DOMAIN}"
    mcpResourceUrl: "https://api.openwork.${DOMAIN}/mcp"
    desktopDenBaseUrl: "https://openwork.${DOMAIN}"
    corsOrigins: "https://openwork.${DOMAIN},https://api.openwork.${DOMAIN}"
    betterAuthTrustedOrigins: "https://openwork.${DOMAIN}"
    bootstrapAdminEmails: "${ADMIN_EMAIL}"
secret:
  values:
    databaseUrl: "mysql://openwork:${DB_PASSWORD}@${DB_HOST}:3306/openwork_den?sslmode=verify-full"
    betterAuthSecret: "${BETTER_AUTH_SECRET}"
    denDbEncryptionKey: "${DB_ENCRYPTION_KEY}"
    emailFrom: "OpenWork <no-reply@${DOMAIN}>"
    smtpHost: "${SMTP_HOST}"
    smtpPort: "${SMTP_PORT:-587}"
    smtpUser: "${SMTP_USER}"
    smtpPass: "${SMTP_PASSWORD}"
EOF

kubectl create namespace "$NS" 2>/dev/null || true

helm upgrade --install openwork-ee "$CHART" \
  --namespace "$NS" \
  --version "$VERSION" \
  -f values.prod.yaml

kubectl rollout status deployment/openwork-ee-den-api -n "$NS" --timeout=300s
kubectl rollout status deployment/openwork-ee-den-web -n "$NS" --timeout=300s

echo "OpenWork v${VERSION} deployed. Verify at https://openwork.${DOMAIN}"

```

## Troubleshooting Production Issues

| Symptom | Diagnostic | Resolution |
|:---|:---|:---|
| Desktop cannot connect | Check `webOrigin`/`apiOrigin` DNS resolution and TLS | `packages/docs/start-here/network-diagnostics.mdx` |
| Database connection failures | Verify `sslmode` and custom CA mounting | Review `customCa` configuration |
| Migration Job stuck | Check `kubectl logs job/openwork-ee-migrations` | Re-run with `hook: false` |
| Install-link validation fails | Regenerate keypair: `scripts/generate-connect-link-keypair.mjs` | Update `CONNECT_LINK_PRIVATE_KEY` secret |

The desktop client's **Settings → Debug** panel surfaces connection diagnostics for end-user troubleshooting.

## Summary

- **Deploy different-ai/openwork in production** using the official Helm chart (`oci://ghcr.io/different-ai/charts/openwork-ee`) with pinned versions and external database
- **Choose your topology**: standard self-hosted, private-network (VPC/VPN), or air-gapped (fully isolated)
- **Secure with generated secrets**: `betterAuthSecret` and `denDbEncryptionKey` must be 32+ random characters
- **Configure origins carefully**: `webOrigin`, `apiOrigin`, and `desktopDenBaseUrl` must be mutually reachable by desktop clients
- **Reference source documentation**: `packages/docs/start-here/self-host.mdx` for architecture, [`packaging/helm/openwork-ee/README.md`](https://github.com/different-ai/openwork/blob/main/packaging/helm/openwork-ee/README.md) for Helm specifics, `packages/docs/start-here/private-network-deployment.mdx` for enterprise networking

## Frequently Asked Questions

### What Kubernetes versions are supported for openwork production deployment?

The Helm chart targets Kubernetes 1.24+. Earlier versions may function but are not validated in CI. The chart uses standard Deployments, Services, and Job resources without CRDs, ensuring broad compatibility with managed offerings (EKS, GKE, AKS) and on-premise distributions.

### Can I deploy openwork without Kubernetes?

Yes, but not via official automation. The repository contains Dockerfiles (`packaging/docker/Dockerfile.den-web`) for manual container orchestration. However, the Helm chart in `packaging/helm/openwork-ee/` is the only production-hardened, maintained deployment path. Docker Compose and bare-metal deployments require custom integration work.

### How do I handle certificate rotation in a private-network deployment?

Mount custom CAs via `customCa.enabled: true` and `customCa.existingSecret`, then trigger rolling restarts:

```bash
kubectl rollout restart deployment/openwork-ee-den-api -n openwork
kubectl rollout restart deployment/openwork-ee-den-web -n openwork

```

The `network-diagnostics.mdx` guide validates TLS handshake behavior from both server and desktop perspectives.

### What database permissions does openwork require?

The `databaseUrl` user needs **FULL** privileges on the target database: CREATE, ALTER, DROP for migrations; plus standard DML for runtime operations. The migration Job executes schema changes automatically on chart upgrade. Use a dedicated database user rather than `root`, and enable SSL verification (`sslmode=verify-full`) in production.