How to Deploy OpenWork Server to Kubernetes Using Helm Charts
Deploy OpenWork EE to Kubernetes by installing the openwork-ee Helm chart from the OCI registry at ghcr.io/different-ai/charts/openwork-ee, configuring your cloud-specific values file, and running helm upgrade --install.
The different-ai/openwork repository provides a production-ready Helm chart that packages all server-side components—den-api (core control-plane), den-web (web UI), optional inference proxy, database migration jobs, and observability—into a single deployable unit. This guide walks through the complete deployment process using the chart templates in packaging/helm/openwork-ee/templates and the configurable values hierarchy defined in packaging/helm/openwork-ee/values.yaml.
What the OpenWork Helm Chart Deploys
The openwork-ee chart renders the following Kubernetes resources:
| Component | Port | Purpose |
|---|---|---|
den-api |
8788 | Core API for authentication, MCP, and organization management |
den-web |
3005 | Web UI that the desktop application communicates with |
inference (optional) |
8791 | AI inference proxy for LLM features |
migration Job |
— | Pre-install hook that bootstraps MySQL schema and runs migrations |
| ConfigMap / Secret | — | Runtime configuration, database credentials, custom CA certificates |
| Ingress / Service | — | External exposure via LoadBalancer or Ingress controller |
| Observability | — | Optional OpenTelemetry or Sentry export configuration |
All templates live under packaging/helm/openwork-ee/templates. The chart follows this rendering sequence:
- Secret generation — creates or references an existing secret with keys defined in
secret.keys - Custom CA mounting — when
customCa.enabled=true, mounts certificates and setsNODE_EXTRA_CA_CERTSfor strict TLS (e.g., with MySQL) - Migration hook — runs as a pre-install/upgrade Job to prepare the database before services start
- Observability injection — configures
den-apiandden-webto export traces and logs based onobservability.backend
Preparing Your Helm Values File
Start from a cloud-provider example in packaging/helm/openwork-ee/examples/ rather than building from scratch.
AWS LoadBalancer Example
The file packaging/helm/openwork-ee/examples/values.aws-load-balancer.yaml demonstrates AWS-specific service annotations and SSL certificate integration:
# values.aws.yaml — adapted from the AWS example
image:
tag: "REPLACE_OPENWORK_VERSION"
config:
public:
webOrigin: "https://openwork.example.com"
apiOrigin: "https://api.openwork.example.com"
corsOrigins: "https://openwork.example.com,https://api.openwork.example.com"
betterAuthTrustedOrigins: "https://openwork.example.com"
webAppHosts: "openwork.example.com"
bootstrapAdminEmails: "admin@example.com"
secret:
values:
databaseUrl: "mysql://openwork:CHANGE_ME@my-rds-endpoint:3306/openwork_den?sslaccept=accept"
betterAuthSecret: "CHANGE_ME_32_CHARS_MINIMUM_BETTER_AUTH"
denDbEncryptionKey: "CHANGE_ME_32_CHARS_MINIMUM_DB_ENCRYPTION"
emailFrom: "OpenWork <no-reply@example.com>"
smtpHost: "smtp.example.com"
smtpPort: "587"
smtpUser: "openwork@example.com"
smtpPass: "REPLACE_SMTP_PASSWORD"
smtpSecure: "false"
denWeb:
service:
type: LoadBalancer
port: 443
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:...
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
denApi:
service:
type: LoadBalancer
port: 443
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:...
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
Critical placeholders to replace:
REPLACE_OPENWORK_VERSION— chart version from the OCI registryCHANGE_ME_*— all secret values (32+ characters recommended for encryption keys)my-rds-endpoint— your managed MySQL endpoint- ACM certificate ARN — for TLS termination at the LoadBalancer
Azure and GCP Equivalents
- Azure AKS: Use
values.azure-ingress.yamlwithingress-nginxor Application Gateway annotations - GCP GKE: Use
values.gcp-ingress.yamlwith Google-managed SSL certificates
Reference the dedicated guides in docs/azure-aks-helm.md and docs/gcp-gke-helm.md for provider-specific networking setup.
Installing the OpenWork Helm Chart
Method 1: OCI Registry (Recommended)
The chart is published as an OCI artifact at oci://ghcr.io/different-ai/charts/openwork-ee:
helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
--version REPLACE_OPENWORK_VERSION \
--namespace openwork-ee \
--create-namespace \
-f values.aws.yaml
Method 2: Local Chart Checkout
For air-gapped environments or custom template modifications:
helm upgrade --install openwork-ee ./packaging/helm/openwork-ee \
--namespace openwork-ee \
--create-namespace \
-f values.aws.yaml
Optional: Pre-Flight Rendering
Validate manifests before installation:
helm template openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
--version REPLACE_OPENWORK_VERSION \
-f values.aws.yaml > rendered.yaml
# Verify secrets are properly templated and database URL is correct
grep -E 'DATABASE_URL|BETTER_AUTH_SECRET|DEN_DB_ENCRYPTION_KEY' rendered.yaml
Authenticating to Private Container Registry
If your deployment pulls images from private GHCR repositories, create a pull secret:
helm registry login ghcr.io
kubectl create secret docker-registry ghcr-pull-secret \
--namespace openwork-ee \
--docker-server=ghcr.io \
--docker-username=<github-user> \
--docker-password=<github-token>
Reference this secret in your values file under image.imagePullSecrets as documented in packaging/helm/openwork-ee/values.yaml.
Verifying the OpenWork Deployment
Check Pod Status
kubectl get pods -n openwork-ee
Expect: den-api-*, den-web-*, and completed migration-* pods in Ready state.
Verify Service Endpoints
kubectl get svc -n openwork-ee
Note the EXTERNAL-IP values for LoadBalancer services, or configure DNS to point to your Ingress hosts.
Health Check Endpoints
# API readiness probe
curl -fsS https://api.openwork.example.com/ready
# Web UI health via API proxy
curl -fsS https://openwork.example.com/api/ready
Both endpoints return HTTP 200 when services are fully initialized.
Post-Deployment: Bootstrapping the First Organization
For single-organization mode, add tenancy configuration before the first user sign-in:
config:
tenancy:
mode: "single_org"
singleOrgName: "Acme"
singleOrgSlug: "acme"
ownerEmails: "admin@acme.com"
requireEmailVerification: "false"
public:
bootstrapAdminEmails: "admin@acme.com"
Apply with helm upgrade and navigate to https://openwork.example.com. The first sign-up with admin@acme.com automatically creates the singleton organization with full admin privileges.
Enabling Observability
OpenTelemetry Configuration
observability:
backend: otel
otel:
endpoint: "http://otel-collector.observability.svc.cluster.local:4318"
headers:
existingSecret: openwork-otel-headers
key: OTEL_EXPORTER_OTLP_HEADERS
Create the referenced secret:
kubectl create secret generic openwork-otel-headers \
--namespace openwork-ee \
--from-literal=OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer <token>'
den-api and den-web will now emit distributed traces to your collector. Alternative: set observability.backend: sentry and provide sentry.dsn for error tracking.
Advanced: Custom CA for Private TLS
When connecting to MySQL or internal services with private PKI, enable the custom CA feature:
customCa:
enabled: true
crt: |
-----BEGIN CERTIFICATE-----
MIIFXTCCA0WgAwIBAgIQVf...
-----END CERTIFICATE-----
The chart mounts this certificate into all Node.js processes and sets NODE_EXTRA_CA_CERTS, ensuring TLS verification succeeds without disabling security. See packages/docs/start-here/certificate-trust-and-proxies.mdx for detailed CA chain handling.
Summary
- Get the chart from
oci://ghcr.io/different-ai/charts/openwork-eeor clonedifferent-ai/openworkfor local use - Start from examples in
packaging/helm/openwork-ee/examples/matched to your cloud provider - Configure secrets in
secret.values— never commit the actual values file with real credentials - Deploy with
helm upgrade --installin a dedicated namespace - Verify health via
/readyendpoints before routing production traffic - Bootstrap tenancy via
config.tenancyfor immediate organization setup
Frequently Asked Questions
How do I upgrade OpenWork without downtime?
Run helm upgrade --install with the new version tag. The chart uses RollingUpdate strategies for den-api and den-web Deployments, and migration Jobs run as pre-upgrade hooks. Ensure your values.yaml remains compatible—review the chart's README.md for breaking changes between versions.
Can I use an external secret manager instead of Helm values?
Yes. Set secret.existingSecretName to reference a pre-created Kubernetes secret, then omit secret.values from your values file. The chart expects keys matching those defined in secret.keys within packaging/helm/openwork-ee/values.yaml.
What database does OpenWork require?
OpenWork EE requires MySQL 8.0+. The migration Job automatically creates tables and runs schema migrations on startup. Configure connectivity via secret.values.databaseUrl with proper SSL parameters for production—use customCa if your database uses private certificates.
How do I expose OpenWork without a cloud LoadBalancer?
Use values.yaml configurations that set service.type: ClusterIP and define ingress rules instead. The chart supports any Kubernetes Ingress controller—configure annotations for cert-manager, external-dns, or your preferred tooling. Examples for nginx-ingress and Traefik are referenced in the cloud-specific documentation files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →