Bulwark is stateful (local /app/data) — Vercel serverless (read-only fs) crashes it. Deploy as a container with 4 persistent volumes on microk8s, alongside bulwark.sandbox.vnc.de. Adds deploy/k8s/ (namespace, pvc, deployment, service, ingress, secret template, runbook) + rewrites setup doc off Vercel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.8 KiB
VNCmail+ — setup & deploy runbook
VNCmail+ is VNC's fork of Bulwark, a
Next.js (App Router) JMAP webmail client for Stalwart. Stalwart is the source
of truth; VNCmail+ is the UI. It deploys as a container on Kubernetes
(microk8s) at vncmail.sandbox.vnc.de — see deploy/k8s/.
License: AGPL-3.0. Serving a modified VNCmail+ to users over the network obligates VNC to offer those users the corresponding source. Keeping this fork public (with a "Source" link in the imprint/UI) satisfies that. Loop in legal before a public/customer-facing launch if a closed fork is ever desired.
Architecture — why a container, not Vercel
- Bulwark is a stateful, long-lived server: it persists settings-sync, admin
config/state, and telemetry to a local data directory (
/app/data/*). - Vercel serverless was tried and dropped — its filesystem is read-only
except
/tmp, so Bulwark'smkdir ./datacrashes (ENOENT /var/task/data). You cannot point its data dirs at a remote host either (they're POSIX paths, not URLs). Bulwark's native model is a container + persistent volumes. - So VNCmail+ runs as a Docker image (
ghcr.io/brvncde-dotcom/vncmail-plus-*) with 4 persistent volumes, exactly like the existingbulwark.sandbox.vnc.de. - JMAP calls go through server-side
/api/*routes (proxy.ts) → server-to- server to Stalwart, no browser CORS. Config is runtime-read.
Branches (dev-first)
| Branch | Role |
|---|---|
main |
Production — CI builds …/vncmail-plus-beta. Only updated by an explicit promote. |
dev |
Integration + QA — CI builds …/vncmail-plus-dev on push. Default working branch. |
vnc/* |
Feature branches for UI work (branch off dev, PR into dev). |
All VNC customization lives under vnc/ (see vnc/VNC-CHANGES.md).
Deploy (Kubernetes / microk8s)
Full runbook: deploy/k8s/README.md. In short:
- CI builds the image on push to
dev/main→ghcr.io/brvncde-dotcom/vncmail-plus-dev(.github/workflows/docker-publish.yml). kubectl applythe manifests indeploy/k8s/(namespace, 4 PVCs, deployment, service, ingress) + asecret.yaml(fromsecret.example.yaml) + aghcr-pullimage-pull secret.- Point
vncmail.sandbox.vnc.deDNS at the ingress; cert-manager issues TLS.
Runs alongside the existing bulwark.sandbox.vnc.de. Match your cluster's
StorageClass / IngressClass / cert issuer to bulwark's (see the runbook).
Deploy workflow (dev-first — ALWAYS)
Same flow as every other VNC/SRC repo:
- Work on
dev(orvnc/*→ PR intodev). Push todev→ CI builds the-devimage →kubectl -n vncmail rollout restart deploy/vncmail-plusto pull it. QA atvncmail.sandbox.vnc.de. - Promote to production only on explicit go-live — merge
dev→main:Then roll the production deployment to the new image (pin its digest — see deploy/k8s/README.md). Never push straight togit log dev..main # MUST be empty — main must have nothing dev lacks (else prod would revert) git checkout main && git merge --ff-only dev git push origin main # CI builds the production image git checkout devmain. Never let a dev→main merge silently revert prod.
Syncing upstream (Bulwark releases)
Bring upstream into dev (NOT main), integrate + QA on the dev image, then promote as above:
git fetch upstream
git checkout dev && git merge upstream/main # resolve conflicts via vnc/VNC-CHANGES.md; QA on preview
Auth
Basic auth via Stalwart is the default — users sign in with their
@sandbox.vnc.de address + password; VNCmail+ authenticates them over JMAP. No
extra config. (SSO via vncdirectory/OIDC is a later option — see
vnc/vercel.env.template.)