feat(ci): GitLab CI/CD dev→prod pipeline, kustomize base+overlays

Multiple developers now work on this repo, and the only working deploy
trigger required pushing to GitHub - which contradicts the standing
GitLab-canonical policy for this repo - while every actual deploy was a
manual kubectl run against one environment (no prod exists at all).

Restructures deploy/k8s/ into base/ + overlays/{dev,prod}: overlays/dev
is a verified byte-for-byte no-op for the live sandbox (kubectl kustomize
diff against the old flat layout is empty), overlays/prod is scaffolded
but inert (placeholder hostname + JMAP_SERVER_URL, since neither a prod
hostname decision nor a prod Stalwart exist yet). deploy/k8s/ca/ (the
EJBCA internal CA) is untouched and never referenced by either overlay.

Adds .gitlab-ci.yml: verify (MR gate, no push/deploy) -> build+deploy-dev
(automatic on push to dev, one image name/tag-only environments, fixing
the old -dev/-beta naming split) -> promote (manual, protected
`production` environment, retags the exact dev digest via
`docker buildx imagetools create` - never rebuilds - and is left as a
documented TODO for the actual `kubectl apply` until prod is real).

Updates VNCMAIL-SETUP.md and deploy/k8s/README.md to describe the new
flow and correct the aspirational promotion description that assumed a
"production image" CI never actually built.

Also fixes a pre-existing lint error (no-control-regex false positive on
an intentional DN-sanitizing character class in lib/smime-ca/ejbca.ts)
that was blocking this commit's pre-commit hook - unrelated to this
change otherwise, confirmed already present on dev before this branch.

Runner/RBAC/registry setup is an infra prerequisite this commit cannot
provide - documented in the pipeline plan, not part of this diff.
This commit is contained in:
Bernd Rodler
2026-08-05 11:43:55 +02:00
parent 12908ab706
commit 3512f935d1
18 changed files with 402 additions and 62 deletions
@@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: vncmail-prod
resources:
- ../../base
- namespace.yaml
# - secret.yaml # create from secret.example.yaml; not committed
patches:
- path: patch-ingress.yaml
- path: patch-deployment.yaml
# NOT MEANT TO BE APPLIED AS COMMITTED. Scaffolding only (see the pipeline
# plan's Phase C/D) — the tag below is an obviously-invalid placeholder;
# the real promote job (.gitlab-ci.yml) resolves and pins an actual digest at
# deploy time via `kubectl set image`, it never trusts whatever is checked
# in here.
images:
- name: ghcr.io/brvncde-dotcom/vncmail-plus-dev
newName: registry.gitlab.vnc.biz/gitlab-instance-b9b5cf2f/vncmail-plus
newTag: not-yet-promoted
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: vncmail-prod
labels:
app.kubernetes.io/part-of: vnclagoon-suite
@@ -0,0 +1,9 @@
# Basic HA. Still `strategy: Recreate` (inherited from base) since the PVCs
# are RWO — 2 replicas doesn't buy zero-downtime rollouts by itself, only
# tolerance for a node loss between deploys. Revisit if that's not enough.
apiVersion: apps/v1
kind: Deployment
metadata:
name: vncmail-plus
spec:
replicas: 2
@@ -0,0 +1,25 @@
# PLACEHOLDER — the real production hostname has not been decided yet (see
# VNCMAIL-SETUP.md / the pipeline plan). vncmail.CHANGEME.invalid is
# deliberately unresolvable: applying this overlay as committed will not
# issue a cert or route traffic anywhere. Replace both occurrences below,
# and the matching TLS secretName, before Phase D (first real prod deploy).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vncmail-plus
spec:
tls:
- hosts:
- vncmail.CHANGEME.invalid
secretName: vncmail-plus-prod-tls
rules:
- host: vncmail.CHANGEME.invalid
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vncmail-plus
port:
number: 80
@@ -0,0 +1,28 @@
# Copy to secret.yaml, fill in real values, and apply. DO NOT commit secret.yaml
# (it is gitignored). Generate SESSION_SECRET with: openssl rand -base64 32
#
# JMAP_SERVER_URL is a PLACEHOLDER — there is no production Stalwart instance
# yet. This overlay cannot go live (Phase D) until one exists and this value
# points at it for real.
apiVersion: v1
kind: Secret
metadata:
name: vncmail-env
namespace: vncmail-prod
type: Opaque
stringData:
# Core — connect to Stalwart over JMAP
JMAP_SERVER_URL: "https://REPLACE-ME-prod-stalwart-not-yet-deployed.invalid"
SESSION_SECRET: "REPLACE_ME__openssl_rand_base64_32"
# Branding (theme defaults to VNClagoon in code; these set name + logo)
APP_NAME: "VNCmail+"
APP_SHORT_NAME: "VNCmail+"
LOGIN_COMPANY_NAME: "VNClagoon"
LOGIN_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
LOGIN_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
APP_LOGO_DARK_URL: "/branding/vncmail-wordmark-on-dark.svg"
APP_LOGO_LIGHT_URL: "/branding/vncmail-wordmark-on-light.svg"
LOGIN_LOGO_MAX_HEIGHT: "52"
# Housekeeping
BULWARK_UPDATE_CHECK: "off"
# Data dirs default to /app/data/* (mounted to the PVCs) — no need to set them.