Skip to main content
Star zrok on GitHub Star
Version: 2.0 (Current)

Self-host a zrok2 instance in Kubernetes

This guide deploys a self-hosted zrok2 instance in Kubernetes using the zrok2 Helm chart. The chart creates a controller, frontend, and bootstrap job that connect to an existing Ziti overlay network.

Prerequisites

  • A Kubernetes cluster (1.23+)
  • kubectl configured for your cluster
  • helm 3.x installed
  • An OpenZiti controller already deployed (e.g., via the ziti-controller Helm chart)
  • Traefik ingress controller (recommended; ingress-nginx is sunset/EOL)
  • A DNS zone with a wildcard * A record pointing to your ingress controller (e.g., *.share.example.com)

Add the Helm Repository

helm repo add openziti https://openziti.github.io/helm-charts/
helm repo update

Configure values.yaml

Create a values file for your deployment:

my-values.yaml
# DNS zone with wildcard record
dnsZone: "share.example.com"

# Ziti controller connection.
# When using Traefik ingress with TLS, set advertisedPort to 443 so
# zrok2 connects to the Ziti management API via the ingress endpoint.
ziti:
advertisedHost: ziti-controller.ziti.share.example.com
advertisedPort: "443"
username: admin
password: "" # set via --set or existingSecret
# Name of the ConfigMap containing Ziti's CA trust bundle.
# Must match the name used by your ziti-controller deployment.
caSecretName: ziti-controller-ctrl-plane-cas

# Controller ingress
controller:
ingress:
enabled: true
scheme: https
className: traefik
hosts:
- zrok2.share.example.com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns
tls:
- secretName: zrok2-api-tls
hosts:
- zrok2.share.example.com

# Frontend ingress (wildcard)
frontend:
ingress:
enabled: true
scheme: https
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns
tls:
- secretName: zrok2-wildcard-tls
hosts:
- "*.share.example.com"

Key Configuration Options

ValueDefaultDescription
dnsZonezrok.example.comDNS zone for public share URLs
ziti.advertisedHostziti-controller.ziti.svc.cluster.localZiti management API host; use the ingress hostname when connecting via ingress
ziti.advertisedPort1280Ziti management API port; use 443 when connecting via HTTPS ingress
ziti.password(required)Ziti admin password
ziti.caSecretNameziti-controller-ctrl-plane-casName of the ConfigMap containing the Ziti CA bundle — must match the name produced by your Ziti controller deployment
controller.service.containerPort18080Controller API port
frontend.service.containerPort8080Frontend port
controller.persistence.enabledtrueEnable PVC for SQLite3
controller.persistence.size2GiPVC size

Database Configuration

By default, the chart uses SQLite3 with a PersistentVolumeClaim. For production, configure an external PostgreSQL database:

postgresql:
host: "postgresql.database.svc.cluster.local"
port: 5432
database: zrok2
username: zrok2
existingSecret: zrok2-db-credentials # Secret with key "password"

When postgresql.host is set, the chart uses PostgreSQL and the SQLite3 PVC is not created.

Metrics Pipeline (Optional)

To enable usage metrics, configure external RabbitMQ and InfluxDB:

rabbitmq:
url: "amqp://guest:guest@rabbitmq.messaging.svc.cluster.local:5672"

influxdb:
url: "http://influxdb.monitoring.svc.cluster.local:8086"
org: zrok2
bucket: zrok2
existingSecret: zrok2-influx-token # Secret with key "token"

The chart deploys a metrics bridge only when rabbitmq.url is set.

Install the Chart

helm install zrok2 openziti/zrok2 \
--namespace zrok2 --create-namespace \
--values my-values.yaml \
--set ziti.password="your-ziti-admin-password"

The release creates:

  1. A controller deployment with an init container that runs zrok2 admin bootstrap
  2. A frontend deployment with an init container that creates the default "public" frontend in Ziti and zrok2
  3. A default "ziggy" user account whose enable token is saved in a Kubernetes Secret

Create Your First Account

After the bootstrap completes, retrieve the default account's enable token:

kubectl -n zrok2 get secret zrok2-ziggy-account-token \
-o jsonpath='{.data.token}' | base64 -d

Or create a new account:

kubectl -n zrok2 exec deploy/zrok2 -c zrok2 -- \
zrok2 admin create account you@example.com yourpassword

Enable a Client Environment

On your workstation, point the zrok2 CLI at your instance and enable:

export ZROK2_API_ENDPOINT=https://zrok2.share.example.com
zrok2 enable <token>

Ingress and TLS

The chart creates Ingress resources for the controller and frontend when enabled. Traefik is the recommended ingress controller (ingress-nginx is sunset/EOL). For TLS, use cert-manager with a ClusterIssuer that supports DNS-01 challenges (required for wildcard certificates).

Controller ingress

Serves the zrok2 API at https://zrok2.share.example.com.

Frontend ingress

Serves public shares at https://<token>.share.example.com. The chart automatically creates a wildcard ingress rule for *.{dnsZone}.

Example with Traefik and cert-manager

controller:
ingress:
enabled: true
className: traefik
hosts:
- zrok2.share.example.com
tls:
- secretName: zrok2-api-tls
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns

frontend:
ingress:
enabled: true
className: traefik
tls:
- secretName: zrok2-wildcard-tls
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns

Scaling Frontends

The frontend deployment can be scaled independently:

kubectl -n zrok2 scale deploy/zrok2-frontend --replicas=3

Or enable the HorizontalPodAutoscaler:

autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70

Uninstalling

helm uninstall zrok2 -n zrok2

The chart includes a pre-delete hook that cleans up Ziti identities and secrets created during bootstrap.

To also remove the PVC and namespace:

kubectl -n zrok2 delete pvc --all
kubectl delete namespace zrok2

Troubleshooting

Controller pod stuck in Init

The bootstrap init container waits for the Ziti controller CA ConfigMap to exist in the zrok2 namespace. If you're using the ziti-controller chart with trust-manager, the CA bundle is propagated automatically to all namespaces. Verify:

kubectl -n zrok2 get configmap ziti-controller-ctrl-plane-cas

If missing, check that trust-manager is running and the Bundle resource has namespaceSelector: {} (all namespaces).

If you see a ConfigMap with a different name (e.g., ziti-controller1-ctrl-plane-cas), update ziti.caSecretName in your values to match.

Frontend pod stuck in ContainerCreating

The frontend needs the zrok2-frontend-identity Secret, which is created by the controller's init container. Wait for the controller pod to complete its init phase:

kubectl -n zrok2 logs deploy/zrok2 -c zrok2-bootstrap

Bootstrap fails with database errors

If using PostgreSQL, verify the database is reachable:

kubectl -n zrok2 exec deploy/zrok2 -c zrok2 -- \
zrok2 admin bootstrap /etc/zrok2/ctrl.yaml --check

For SQLite3, ensure the PVC is bound and writable.

Chart Source

The chart source and full values reference are at: github.com/openziti/helm-charts/tree/main/charts/zrok2