From 476c76c420f0c19a8e6e7125762060f290aacef2 Mon Sep 17 00:00:00 2001 From: Bernd Rodler Date: Mon, 3 Aug 2026 17:13:51 +0200 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20sharpen=20k8s=20admin=20runbook?= =?UTF-8?q?=20=E2=80=94=20inventory,=20pre-flight,=20ordered=20apply?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-contained guide: exactly what to deploy (7 objects + image), 3 cluster values to match against bulwark, copy-paste apply order, verify, update/rollback, troubleshooting table. Plain kubectl apply (no GitOps). Co-Authored-By: Claude Opus 4.8 --- deploy/k8s/README.md | 153 +++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 56 deletions(-) diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index 73d4b839..697cc771 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -1,88 +1,129 @@ -# VNCmail+ on Kubernetes (microk8s) +# VNCmail+ — Admin Deployment Guide (microk8s) -Deploys the VNCmail+ fork as a **new subdomain** (`vncmail.sandbox.vnc.de`), -alongside the existing `bulwark.sandbox.vnc.de`. This is Bulwark's native model: -a long-lived container + persistent volumes. (Vercel was dropped — its serverless -filesystem is read-only, which crashes Bulwark's disk-backed features.) +Deploy VNCmail+ (VNC's Bulwark fork) as a container at **`vncmail.sandbox.vnc.de`**, +**alongside** the existing `bulwark.sandbox.vnc.de`. Plain `kubectl apply` — no +GitOps needed. -## Image +> Why a container (not Vercel): Bulwark is stateful — it writes settings/admin/ +> telemetry to `/app/data`, which needs persistent volumes. -CI builds and pushes the image on every push to `dev`/`main` -(`.github/workflows/docker-publish.yml`): +--- -- `dev` → `ghcr.io/brvncde-dotcom/vncmail-plus-dev` -- `main` → `ghcr.io/brvncde-dotcom/vncmail-plus-beta` -- release tag → `ghcr.io/brvncde-dotcom/vncmail-plus` (clean name) +## 1. What you are deploying -The manifests use the `-dev` image. **For production, pin a digest** instead of -`:latest`: -``` -kubectl -n vncmail set image deploy/vncmail-plus \ - vncmail-plus=ghcr.io/brvncde-dotcom/vncmail-plus-dev@sha256: -``` +| # | Object | File | Purpose | +|---|--------|------|---------| +| 1 | Namespace `vncmail` | `namespace.yaml` | Isolates the app | +| 2 | 4× PersistentVolumeClaim | `pvc.yaml` | `/app/data/{settings,admin,admin-state,telemetry}` | +| 3 | Secret `vncmail-env` | `secret.yaml` *(you create it)* | App config (JMAP URL, session secret, branding) | +| 4 | Secret `ghcr-pull` | *(you create it — command below)* | Pull the private image from GHCR | +| 5 | Deployment `vncmail-plus` | `deployment.yaml` | The app pod | +| 6 | Service `vncmail-plus` | `service.yaml` | ClusterIP :80 → pod :3000 | +| 7 | Ingress `vncmail-plus` | `ingress.yaml` | TLS host `vncmail.sandbox.vnc.de` | -## Prerequisites — match your cluster +**Image:** `ghcr.io/brvncde-dotcom/vncmail-plus-dev:latest` +(built automatically by CI from the `dev` branch). For anything beyond the +sandbox, pin a digest — see §5. -These manifests use microk8s defaults; confirm they match how -`bulwark.sandbox.vnc.de` is deployed and edit if not: +--- -| Thing | File | Check with | -|-------|------|-----------| -| StorageClass (`microk8s-hostpath`) | `pvc.yaml` | `kubectl get sc` | -| IngressClass (`public`) | `ingress.yaml` | `kubectl get ingressclass` | -| cert-manager issuer (`letsencrypt-prod`) | `ingress.yaml` | `kubectl get clusterissuer` + bulwark's ingress | +## 2. Pre-flight — confirm 3 cluster values (2 min) -Quickest: copy bulwark's own settings — -`kubectl get ingress -A | grep bulwark` then `kubectl get ingress -n -o yaml`. - -## Deploy +The manifests use microk8s defaults. **Copy the exact values the existing +Bulwark uses** so VNCmail+ matches your cluster: ```bash -# 1. Namespace +# Find bulwark's ingress and read off its class + cert-manager annotations: +kubectl get ingress -A | grep -i bulwark +kubectl get ingress -n -o yaml + +# List available storage classes and ingress classes: +kubectl get sc +kubectl get ingressclass +kubectl get clusterissuer # cert-manager issuers (if used) +``` + +Then edit if they differ from the defaults below: + +| Value | Default in manifests | File to edit | +|-------|----------------------|--------------| +| StorageClass | `microk8s-hostpath` | `pvc.yaml` (all 4) | +| IngressClass | `public` | `ingress.yaml` | +| cert-manager issuer | `letsencrypt-prod` | `ingress.yaml` | + +--- + +## 3. Deploy (copy-paste, in order) + +```bash +cd deploy/k8s + +# a) Namespace kubectl apply -f namespace.yaml -# 2. GHCR pull secret (package is private by default). -# Use a GitHub PAT with read:packages. +# b) Image-pull secret — the GHCR package is private. +# Use a GitHub PAT (classic) with the read:packages scope. kubectl create secret docker-registry ghcr-pull \ --namespace vncmail \ --docker-server=ghcr.io \ --docker-username=brvncde-dotcom \ - --docker-password= \ + --docker-password='' \ --docker-email=br@vnc.biz -# (Or make the package public in GHCR and remove imagePullSecrets from deployment.yaml.) -# 3. App config secret +# c) App config secret — copy the template, set a real SESSION_SECRET, apply. cp secret.example.yaml secret.yaml -# edit secret.yaml → set SESSION_SECRET (openssl rand -base64 32) +# edit secret.yaml: SESSION_SECRET: "$(openssl rand -base64 32)" kubectl apply -f secret.yaml -# 4. Everything else -kubectl apply -k . # kustomization: pvc, deployment, service, ingress - -# 5. Watch it come up -kubectl -n vncmail rollout status deploy/vncmail-plus -kubectl -n vncmail get pods,ingress +# d) Everything else (PVCs, Deployment, Service, Ingress) +kubectl apply -k . ``` -## DNS +> Alternative to (b): make the GHCR package public +> (GitHub → Packages → vncmail-plus-dev → Package settings → Change visibility), +> then delete the `imagePullSecrets:` block from `deployment.yaml`. -Point `vncmail.sandbox.vnc.de` at the same ingress load-balancer IP as -`bulwark.sandbox.vnc.de` (A/AAAA or CNAME). cert-manager issues the TLS cert -once DNS resolves. +--- -## Verify +## 4. Verify ```bash -curl -sI https://vncmail.sandbox.vnc.de/api/health # expect 200 -``` -Then open `https://vncmail.sandbox.vnc.de` and log in with a full -`@sandbox.vnc.de` address (e.g. `bernd.rodler@sandbox.vnc.de`) — Stalwart -authenticates the **full email**, not a bare username. +kubectl -n vncmail rollout status deploy/vncmail-plus # -> successfully rolled out +kubectl -n vncmail get pods,pvc,ingress -## Update after a UI change +# DNS: point vncmail.sandbox.vnc.de at the same ingress IP as bulwark.sandbox.vnc.de. +# cert-manager issues TLS once DNS resolves. Then: +curl -sI https://vncmail.sandbox.vnc.de/api/health # -> HTTP/2 200 +``` + +Open `https://vncmail.sandbox.vnc.de` and log in with a **full** email address +(e.g. `bernd.rodler@sandbox.vnc.de`) — Stalwart authenticates the full email, not +a bare username. + +--- + +## 5. Update to a new build ```bash -git push origin dev # CI rebuilds ghcr.io/...-dev -kubectl -n vncmail rollout restart deploy/vncmail-plus # pull new image +# CI rebuilds ghcr.io/brvncde-dotcom/vncmail-plus-dev on every push to `dev`. +kubectl -n vncmail rollout restart deploy/vncmail-plus # pulls :latest (imagePullPolicy: Always) + +# Production: pin a digest instead of :latest so rollouts are deterministic. +kubectl -n vncmail set image deploy/vncmail-plus \ + vncmail-plus=ghcr.io/brvncde-dotcom/vncmail-plus-dev@sha256: ``` -Promote to production the usual dev-first way (see ../../VNCMAIL-SETUP.md). + +Rollback: `kubectl -n vncmail rollout undo deploy/vncmail-plus` + +--- + +## 6. Troubleshooting + +| Symptom | Cause / fix | +|---------|-------------| +| Pod `ImagePullBackOff` | `ghcr-pull` secret missing/expired, or package still private. Recreate the secret (§3b) or make the package public. | +| Pod `CrashLoopBackOff`, logs show `EACCES`/permission on `/app/data` | Volume not writable by uid 1001. `securityContext.fsGroup: 1001` is set in `deployment.yaml` — keep it; some storage drivers also need it on the PVC. | +| PVC stuck `Pending` | Wrong `storageClassName` in `pvc.yaml`. Set it to one from `kubectl get sc`. | +| Ingress has no address / no cert | Wrong `ingressClassName` or cert issuer. Match bulwark's (§2). Check `kubectl -n vncmail describe ingress vncmail-plus`. | +| Login shows "Ein Fehler ist aufgetreten" | Use the **full** email (`user@sandbox.vnc.de`), not a bare username. | +| Can't reach Stalwart | Check `JMAP_SERVER_URL` in the secret = `https://stalwart.sandbox.vnc.de`. |