diff --git a/docs/docs/self-hosting/deploy/kubernetes.mdx b/docs/docs/self-hosting/deploy/kubernetes.mdx deleted file mode 100644 index a3814e4f4..000000000 --- a/docs/docs/self-hosting/deploy/kubernetes.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Set up ZITADEL on Kubernetes -sidebar_label: Kubernetes ---- - -For getting started with an easily testable insecure setup with Postgres, follow the [Insecure Postgres Example](https://github.com/zitadel/zitadel-charts/tree/main/examples/1-postgres-insecure). - -For more information and configuration examples, go to the [ZITADEL charts repo](https://github.com/zitadel/zitadel-charts). diff --git a/docs/docs/self-hosting/deploy/kubernetes/configuration.md b/docs/docs/self-hosting/deploy/kubernetes/configuration.md new file mode 100644 index 000000000..5765dc782 --- /dev/null +++ b/docs/docs/self-hosting/deploy/kubernetes/configuration.md @@ -0,0 +1,315 @@ +--- +title: Configuration +sidebar_label: Configuration +sidebar_position: 3 +--- + +# Configuration + +This guide covers the major configuration options for the Zitadel Helm chart. For a complete list of options, see the [values.yaml](https://github.com/zitadel/zitadel-charts/blob/main/charts/zitadel/values.yaml) in the chart repository. + +## Global Settings + +Global settings affect multiple components of the deployment. + +### Replica Count + +Set the number of Zitadel replicas: + +```yaml +replicaCount: 2 +``` + +### Container Images + +#### Zitadel Image + +Configure the Zitadel container image: + +```yaml +image: + repository: ghcr.io/zitadel/zitadel + tag: "v4.9.1" + pullPolicy: IfNotPresent +``` + +#### Login Image + +Configure the Login container image: + +```yaml +login: + image: + repository: ghcr.io/zitadel/login + tag: "v4.9.1" + pullPolicy: IfNotPresent +``` + +### Pod Security Context + +Configure security settings for the pods. These settings apply globally to all pods in the deployment: + +```yaml +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +securityContext: + runAsNonRoot: true + runAsUser: 1000 + readOnlyRootFilesystem: true + privileged: false + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +``` + +## Zitadel Settings + +### ExternalDomain + +The domain where Zitadel is accessible. This is used for generating URLs, cookies, and OIDC endpoints. + +```yaml +zitadel: + configmapConfig: + ExternalDomain: "zitadel.example.com" + ExternalPort: 443 + ExternalSecure: true +``` + +| Setting | Description | +|---------|-------------| +| `ExternalDomain` | The public domain name (no protocol or port) | +| `ExternalPort` | The public port (443 for HTTPS, 80 for HTTP) | +| `ExternalSecure` | Whether the external connection uses HTTPS | + +### TLS + +TLS is terminated at the ingress controller. The Zitadel containers do not handle TLS termination: + +```yaml +zitadel: + configmapConfig: + TLS: + Enabled: false +``` + +### FirstInstance / Bootstrapping + +Configure the initial Zitadel instance created during setup. + +```yaml +zitadel: + configmapConfig: + FirstInstance: + Org: + Human: + UserName: "admin" + Password: "SecurePassword123!" + FirstName: "Zitadel" + LastName: "Admin" + Email: "admin@zitadel.example.com" + PasswordChangeRequired: false +``` + +#### Machine User for System API + +For programmatic access, configure a system user with RSA keys: + +```yaml +zitadel: + configmapConfig: + SystemAPIUsers: + systemuser: + KeyData: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... + -----END PUBLIC KEY----- +``` + +Generate the RSA private key: + +```bash +openssl genrsa -out system-user-private.pem 2048 +``` + +Extract the public key: + +```bash +openssl rsa -in system-user-private.pem -pubout -out system-user-public.pem +``` + +## Secrets + +### Referenced Secrets + +Reference Kubernetes Secrets for sensitive values: + +```yaml +zitadel: + masterkeySecretName: zitadel-masterkey + configSecretName: zitadel-config-secret +``` + +Create the masterkey secret: + +```bash +kubectl create secret generic zitadel-masterkey \ + --from-literal=masterkey="$(tr -dc A-Za-z0-9 +``` + +5. Monitor the upgrade. Watch the pods: + +```bash +kubectl get pods --watch +``` + +Check the Helm release status: + +```bash +helm status my-zitadel +``` + +## Scaling + +### Manual Scaling + +Adjust the replica count in your values: + +```yaml +replicaCount: 3 +``` + +Or scale directly: + +```bash +kubectl scale deployment my-zitadel --replicas=3 +``` + +### Horizontal Pod Autoscaler + +Enable HPA for automatic scaling based on resource utilization: + +```yaml +zitadel: + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 +``` + +This creates an HPA that: + +- Maintains at least 2 replicas +- Scales up to 10 replicas +- Targets 80% CPU and memory utilization + +View HPA status: + +```bash +kubectl get hpa +``` + +Get detailed HPA information: + +```bash +kubectl describe hpa my-zitadel +``` + +### Resource Requests and Limits + +Configure appropriate resource allocations: + +```yaml +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi +``` + +**Recommendations by deployment size:** + +| Size | CPU Request | CPU Limit | Memory Request | Memory Limit | +|------|-------------|-----------|----------------|--------------| +| Small (dev) | 100m | 500m | 256Mi | 512Mi | +| Medium | 250m | 1000m | 512Mi | 1Gi | +| Large | 500m | 2000m | 1Gi | 2Gi | + +### Pod Disruption Budget + +Ensure availability during voluntary disruptions by using `minAvailable`: + +```yaml +podDisruptionBudget: + enabled: true + minAvailable: 1 +``` + +Alternatively, use `maxUnavailable`: + +```yaml +podDisruptionBudget: + enabled: true + maxUnavailable: 1 +``` + +This ensures at least one pod remains available during node drains, upgrades, or other voluntary disruptions. + +### Database Scaling Considerations + +When scaling Zitadel horizontally, ensure your PostgreSQL database can handle the increased connection load: + +- Each Zitadel pod opens multiple connections +- Consider using PgBouncer for connection pooling +- Monitor database connection usage + +Example connection pooling setup with PgBouncer: + +```yaml +zitadel: + configmapConfig: + Database: + Postgres: + Host: "pgbouncer.database.svc.cluster.local" + Port: 6432 + MaxOpenConns: 20 + MaxIdleConns: 10 +``` + +## Next Steps + +- [Configuration](./configuration) — Review all configuration options +- [Uninstalling](./uninstalling) — Remove Zitadel from your cluster diff --git a/docs/docs/self-hosting/deploy/kubernetes/uninstalling.md b/docs/docs/self-hosting/deploy/kubernetes/uninstalling.md new file mode 100644 index 000000000..2954ae9b9 --- /dev/null +++ b/docs/docs/self-hosting/deploy/kubernetes/uninstalling.md @@ -0,0 +1,60 @@ +--- +title: Uninstalling +sidebar_label: Uninstalling +sidebar_position: 7 +--- + +# Uninstalling + +This guide covers how to remove Zitadel from your Kubernetes cluster. + +## Uninstall the Helm Release + +```bash +helm uninstall my-zitadel +``` + +## Clean Up Helm Hooks + +> ⚠️ **Important:** The Zitadel chart uses Helm hooks, which are [not garbage collected by helm uninstall](https://helm.sh/docs/topics/charts_hooks/#hook-resources-are-not-managed-with-corresponding-releases). + +Remove hook resources manually: + +```bash +for k8sresourcetype in job configmap secret rolebinding role serviceaccount; do + kubectl delete $k8sresourcetype \ + --selector app.kubernetes.io/name=zitadel,app.kubernetes.io/managed-by=Helm +done +``` + +## Complete Namespace Teardown + +To remove everything including the namespace and any Persistent Volume Claims: + +```bash +kubectl delete namespace zitadel +``` + +> ⚠️ **Warning:** This permanently deletes all data in the namespace, including any PVCs. Ensure you have backups if needed. + +## Verify Removal + +Confirm all resources have been removed. + +Check for remaining Zitadel resources: + +```bash +kubectl get all --selector app.kubernetes.io/name=zitadel +``` + +Check for remaining secrets: + +```bash +kubectl get secrets --selector app.kubernetes.io/name=zitadel +``` + +If you did not delete the namespace, check for remaining PVCs: + +```bash +kubectl get pvc --selector app.kubernetes.io/name=zitadel +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 4e52d6a67..accd76982 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -742,7 +742,32 @@ module.exports = { "self-hosting/deploy/macos", "self-hosting/deploy/devcontainer", "self-hosting/deploy/compose", - "self-hosting/deploy/kubernetes", + { + type: "category", + label: "Kubernetes", + link: { + type: "doc", + id: "self-hosting/deploy/kubernetes/index", + }, + items: [ + "self-hosting/deploy/kubernetes/installation", + { + type: "category", + label: "Configuration", + link: { + type: "doc", + id: "self-hosting/deploy/kubernetes/configuration", + }, + items: [ + "self-hosting/deploy/kubernetes/ingress", + "self-hosting/deploy/kubernetes/database", + "self-hosting/deploy/kubernetes/observability", + ], + }, + "self-hosting/deploy/kubernetes/operations", + "self-hosting/deploy/kubernetes/uninstalling", + ], + }, { type: "category", label: "Manage",