Wardengate

Install

Kubernetes & Helm

The Helm chart is the canonical install path for production. It packages the control plane Deployment, the gateway workloads, the associated Services and Ingresses, RBAC, secret references, and a PodDisruptionBudget tuned for a three-replica setup.

Add the Helm repo

helm repo add wardengate https://charts.wardengate.io
helm repo update
helm search repo wardengate

Pin the chart version in CI with --version 2.4.x; floating to the latest minor is fine in lab.

Minimal values.yaml

Start small. Every other knob in the chart has a reasonable default. The example below assumes an external Postgres and Redis already exist — the preferred production shape.

# values.yaml
image:
  repository: wardengate/server
  tag: "2.4.1"
  pullPolicy: IfNotPresent

replicaCount: 3

publicUrl: https://wg.example.com

secretKey:
  existingSecret: wg-secret-key
  key: secret

externalPostgres:
  enabled: true
  host: pg.shared.svc.cluster.local
  port: 5432
  database: wardengate
  existingSecret: wg-postgres
  usernameKey: username
  passwordKey: password
  sslMode: require

externalRedis:
  enabled: true
  url: "redis://redis.shared.svc.cluster.local:6379/3"

persistence:
  data:
    enabled: true
    size: 20Gi
    storageClass: standard-rwo
  recordings:
    enabled: true
    size: 100Gi
    storageClass: standard-rwo

ingress:
  enabled: true
  className: nginx
  host: wg.example.com
  tls:
    secretName: wg-tls

proxies:
  ssh:
    enabled: true
    service:
      type: LoadBalancer
      port: 2222
  rdp:
    enabled: true
    service:
      type: LoadBalancer
      port: 3389
  database:
    enabled: true
    service:
      type: LoadBalancer
      port: 4400

identity:
  oidc:
    enabled: true
    issuer: https://corp.okta.example.com
    clientId: wardengate
    existingSecret: wg-oidc
    clientSecretKey: clientSecret

resources:
  requests: { cpu: 500m, memory: 1Gi }
  limits:   { cpu: "2",  memory: 4Gi }

Create the prerequisite Secrets

kubectl create ns wardengate

kubectl -n wardengate create secret generic wg-secret-key \
  --from-literal=secret="$(openssl rand -base64 32)"

kubectl -n wardengate create secret generic wg-postgres \
  --from-literal=username=wardengate \
  --from-literal=password="$(openssl rand -base64 24)"

kubectl -n wardengate create secret generic wg-oidc \
  --from-literal=clientSecret=...

Install the chart

helm upgrade --install wardengate wardengate/wardengate \
  --namespace wardengate \
  --version 2.4.1 \
  --values values.yaml

The first install provisions a Job that runs schema migrations, seeds the internal CA, and prints a bootstrap URL in the Job logs. Subsequent helm upgrade runs idempotently.

Verify the install

kubectl -n wardengate get pods,svc,ingress
kubectl -n wardengate logs deploy/wardengate
kubectl -n wardengate logs job/wardengate-migrate

# one-shot CLI against the in-cluster service
kubectl -n wardengate run wgctl --rm -it --restart=Never \
  --image=wardengate/wgctl:2.4 \
  -- system health --server https://wardengate:8443 --insecure

A healthy cluster shows three Running pods, ready Services for each enabled proxy, and an Ingress with an address populated.

StatefulSet or Deployment

The chart renders the control plane as a Deployment by default. The pods are effectively stateless — all durable state lives in Postgres and object storage — so rolling updates and arbitrary pod rescheduling are safe.

Switch to kind: StatefulSet only when you specifically want stable pod identity — for example, to pin each replica to a local PVC for fast recording scratch space on a bare-metal cluster. Set kind: StatefulSet at the top level of values.yaml and the chart will switch workload kinds and emit a headless Service.

Ingress examples

ingress-nginx

ingress:
  enabled: true
  className: nginx
  host: wg.example.com
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
  tls:
    secretName: wg-tls

The long proxy timeouts matter — the web console uses WebSockets for live session streaming, and a 60-second default will cut RDP viewers in the middle of a session.

Traefik

ingress:
  enabled: true
  className: traefik
  host: wg.example.com
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/service.serversscheme: https
  tls:
    secretName: wg-tls

For protocols that are not HTTP (SSH, RDP, database proxy), do not try to route through Ingress. Use a separate LoadBalancer Service per protocol, as shown in the values example above.

Persistence

Two PersistentVolumeClaims are created per pod when persistence is enabled: one for durable data and one for the recording scratch buffer. The data PVC holds internal CA material and host keys — back it up. The recording buffer is transient; sessions drain to object storage continuously.

Size the recording PVC for peak concurrency, not total volume. A host that brokers 200 active sessions with full RDP capture needs roughly 20–40 GB of buffer before the upload pipeline drains.

Scaling

helm upgrade wardengate wardengate/wardengate \
  --namespace wardengate \
  --reuse-values \
  --set replicaCount=5

The chart exposes a ready-made HPA template if you want horizontal autoscaling on CPU or on the custom wg_active_sessions metric. Keep at least 3 replicas to survive a node failure without dropping quorum-sensitive background jobs.

Upgrading

Upgrades are rolling. Bump the image tag, run helm upgrade, and the Deployment rolls one pod at a time honouring the PDB. Schema migrations run via a pre-upgrade Helm hook Job.

helm repo update
helm upgrade wardengate wardengate/wardengate \
  --namespace wardengate \
  --version 2.5.0 \
  --reuse-values \
  --set image.tag=2.5.0

kubectl -n wardengate rollout status deploy/wardengate

If the migration Job fails, the upgrade is aborted and the old pods keep serving. See Upgrading for rollback guidance.

Uninstall

helm uninstall wardengate -n wardengate
# PVCs and Secrets are kept by design — remove if you want a clean wipe
kubectl -n wardengate delete pvc -l app.kubernetes.io/name=wardengate
kubectl delete ns wardengate

External Postgres, Redis, and the object store are not touched. Drop the database and bucket manually if you want to wipe state.

Related