GovernanceMode k3s
0 / 27
Phase 0 Decisions
01
Decide target host and registry
Where does k3s run (new VM or 84.13.157.154)? Where do images go (private registry on 84.8.124.104, Docker Hub, or local k3s import)?
Phase 1 Dockerfiles
02
Write the shared PHP base Dockerfile
One multi-stage Dockerfile for all 13 Laravel/RoadRunner services. PHP 8.3 + mongodb/redis/grpc extensions + composer install + rr binary. Build args for service name & gRPC port.
03
Write the Go gateway Dockerfile
Multi-stage: Go 1.22 builder → scratch/alpine runtime with the compiled api-gateway binary.
04
Write the Vue frontend Dockerfile
Multi-stage: Node 22 builder (npm install + build) → Nginx alpine serving front/dist. VITE_* via build args.
05
Write build-and-push script
Shell script: loop 13 PHP services + gateway + frontend, build tagged images, push to registry. VERSION variable at top.
Phase 2 Helm Chart
06
Scaffold Helm chart directory
Create helm/governancemode/ with Chart.yaml, values.yaml, templates/_helpers.tpl. All service names, ports, images in values.yaml.
07
Create Secret template
templates/secrets.yaml — JWT_SECRET, SERVICE_API_KEY, API_GATEWAY_SECRET, REVERB keys, MONGO credentials. Set once, used everywhere.
08
Create ConfigMap template
templates/configmap.yaml — MONGO_HOST=mongodb, REDIS_HOST=redis, API_GATEWAY_GRPC=gateway:50050, APP_URL patterns.
09
Create MongoDB StatefulSet + PVC
templates/mongodb.yaml — mongo:7 image, PVC for /data/db, Service on 27017. Init container for seed restore.
10
Create Redis Deployment
templates/redis.yaml — redis:7-alpine, Service on 6379. No persistence (cache/rate-limit only).
11
Create PHP service template (parameterized)
templates/php-service.yaml — loops over .Values.phpServices (13 entries). Each gets Deployment + ClusterIP Service + per-service env overrides.
12
Create queue worker Deployments
templates/queue-workers.yaml — loop phpServices, each gets "php artisan queue:work mongodb". Same image, different command.
13
Create Reverb Deployment
templates/reverb.yaml — NotificationAndLogsBackEnd image, command: reverb:start --port=7002. Service on 7002.
14
Create Go gateway Deployment
templates/gateway.yaml — HTTP :8000 + gRPC :50050. GATEWAY_SERVICES from phpServices list using k8s service names.
15
Create frontend Deployment
templates/frontend.yaml — Nginx image with built Vue SPA, Service on 80.
16
Create Ingress template
templates/ingress.yaml — /governance_mode/ → frontend, /governance-mode/gateway/api/ → gateway:8000, WS → reverb:7002. TLS optional.
17
Create shared storage PVC
templates/storage-pvc.yaml — single PVC for cross-service uploads mounted at /var/www/html/storage. local-path on single-node.
18
Create MongoDB seed Job
templates/mongo-seed-job.yaml — one-shot Job, mongorestore from backup zip. Helm hook: post-install. Seeds 11 DBs + grc.
19
Fill values.yaml with all 13 services
Complete phpServices list: name, helmName, gatewaySlug, grpcPort, dbName, hasQueue, image tag. Source of truth for the chart.
20
Write deploy.sh script
End-to-end: install k3s, wait for Ready, helm install. Rollback and uninstall as comments.
Phase 3 Build & Deploy
21
Build all Docker images
Run build-all.sh on the build host. Fix build failures. Verify each image runs locally.
22
Install k3s on target host
curl -sfL https://get.k3s.io | sh -. Verify: kubectl get nodes shows Ready. If reusing host, stop supervisor services first.
23
Load images into k3s
docker save + k3s ctr images import (offline), or configure registries.yaml for private registry, or push to Docker Hub.
24
helm install the full stack
helm install governancemode ./helm/governancemode. Watch pods with kubectl get pods -w. Debug CrashLoopBackOff.
25
Run MongoDB seed job
Trigger seed job. Verify all 11 DBs + grc.service_model_maps exist with documents.
Phase 4 Verify & Document
26
Smoke test everything
Gateway HTTP, login endpoint, SPA in browser, Reverb websocket, all pods Running, queue workers processing.
27
Write day-2 ops cheatsheet
kubectl restart, logs, scale, update image, backup mongo, rebuild frontend. Replaces supervisor-based ops.
Ingress
Deployment
StatefulSet
Service
PVC
Secret
ConfigMap
Job
k3s cluster — namespace: governancemode
Ingress (Traefik)
governancemode-ingress
Host: governancemode.datamode.advancedcontrols.tech
/governance_mode/ → frontend:80 /governance-mode/gateway/api/ → gateway:8000 /ws → reverb:7002
↓
Deployment
frontend
Image: governancemode/frontend:v1
Nginx → Vue SPA (front/dist)
:80 HTTP
Deployment
gateway
Image: governancemode/gateway:v1
Go binary — JWT, rate-limit, CORS, gRPC proxy
:8000 HTTP :50050 gRPC enrich
↓ gRPC to each service by slug ↓
user-service
:50051 · Shared-User-Service
Slug: SharedUserService
DB: Shared-User-Service
Queue: yes
Source: values.phpServices[0]
notification
:50053 · NotificationAndLogs
Slug: NotificationLogs
DB: NotificationAndLogsBackEnd
Queue: yes
Reverb sidecar: yes (:7002)
regulator
:50054 · Regulator-Service
Slug: RegulatorService
DB: Regulator-Service
Queue: yes
governance
:50055 · CyberSec Governance
Slug: GovernanceService
DB: Domain-Cybersecurity-Governance-Strategy-Performance
Queue: yes
operations
:50056 · CyberSec Operations
Slug: OperationsService
DB: Domain-Cybersecurity-Operations-Asset-Exposure-Management
Queue: yes
continuity
:50057 · Business Continuity
Slug: BusinessContinuityService
DB: Domain-Business-Continuity-Crisis-Resilience
Queue: yes
workflow
:50058 · Workflow Engine
Slug: WorkflowService
DB: Domain-Workflow-Engine
Queue: yes
dashboard
:50059 · Shared-Dashboard
Slug: DashboardService
DB: Shared-Dashboard
Queue: yes
messaging
:50060 · Messaging & Collab
Slug: MessagingService
DB: Shared-Enterprise-Messaging-Collaboration
Queue: yes
task-mgmt
:50061 · Task Management
Slug: TaskService
DB: Shared-Enterprise-Task-Management
Queue: yes
import-export
:50062 · Import/Export
Slug: ImportExportService
DB: Shared-Import-Export-Service
Queue: yes
reporting
:50063 · Shared-Reporting
Slug: ReportingService
DB: Shared-Reporting
Queue: yes
risk
:50070 · Enterprise Risk
Slug: EnterpriseRiskThirdPartyService
DB: Enterprise-Risk-Third-Party
Queue: yes
Deployment (per service)
Queue Workers
Same image as gRPC pod, command: php artisan queue:work mongodb
13 separate Deployments — replaces supervisor gw-*-queue
Deployment
Reverb (WebSocket)
notification image
reverb:start --port=7002
:7002 WS
↓
StatefulSet
MongoDB 7
mongo:7 · :27017
11 service DBs + grc (gateway maps)
PVC: mongodb-data (10Gi)
Deployment
Redis 7
redis:7-alpine · :6379
JWT denylist, rate-limit, cache
No persistence needed
Secret
gm-secrets
JWT_SECRET
SERVICE_API_KEY
API_GATEWAY_SECRET
REVERB_APP_KEY/SECRET
ConfigMap
gm-config
MONGO_HOST=mongodb
REDIS_HOST=redis
API_GATEWAY_GRPC=gateway:50050
APP_URL patterns
PersistentVolumeClaim
shared-storage
Cross-service uploads
Mounted: /var/www/html/storage
local-path (single node)
Job (helm post-install hook)
mongo-seed
mongorestore from backup zip → 11 service DBs + grc.service_model_maps (157 models)
How values.yaml feeds the entire chart
Every manifest pulls its values from one central file. No hardcoded ports, names, or secrets in templates.
values.yaml
phpServices[]
gateway.image
frontend.image
secrets.jwtSecret
secrets.serviceApiKey
mongodb.storage
ingress.host
→
Templates (range loop)
{{ range .Values.phpServices }}
  Deployment: {{ .helmName }}
  Service port: {{ .grpcPort }}
  DB_DATABASE: {{ .dbName }}
  Queue deploy if: {{ .hasQueue }}
{{ end }}
→
k8s Resources
13 × Deployment (gRPC)
13 × ClusterIP Service
13 × Queue Deployment
1 × Gateway Deployment
1 × Frontend Deployment
1 × Ingress
The phpServices list
Each entry generates a Deployment, a Service, and optionally a queue worker. The gateway reads this list to build GATEWAY_SERVICES.
phpServices: - name: Shared-User-Service # repo folder name helmName: user-service # k8s resource name (DNS-safe) gatewaySlug: SharedUserService # Go gateway route key grpcPort: 50051 # unique per service dbName: Shared-User-Service # MongoDB database name hasQueue: true # deploy queue worker? image: repository: registry/gm-user-service tag: v1 - name: NotificationAndLogsBackEnd # + 12 more entries... helmName: notification gatewaySlug: NotificationLogs grpcPort: 50053 dbName: NotificationAndLogsBackEnd hasQueue: true hasReverb: true # only this one
Secret wiring
Secrets are set once in values.yaml and consumed by every pod via envFrom.
values.yaml
secrets.jwtSecret
secrets.serviceApiKey
secrets.apiGatewaySecret
secrets.reverbAppKey
secrets.reverbAppSecret
→
templates/secrets.yaml
kind: Secret
data:
  JWT_SECRET: {{ .Values... | b64enc }}
  SERVICE_API_KEY: {{ ... | b64enc }}
  API_GATEWAY_SECRET: {{ ... }}
→
Every Pod
envFrom:
  - secretRef:
     name: gm-secrets
All 13 PHP + gateway
read the same Secret
Gateway GATEWAY_SERVICES env var
Auto-generated from phpServices using k8s service DNS names instead of localhost.
# template builds this from .Values.phpServices: GATEWAY_SERVICES: "{{- range $i, $svc := .Values.phpServices -}} {{- if $i }};{{ end -}} {{ $svc.gatewaySlug }}|{{ $svc.helmName }}:{{ $svc.grpcPort }} {{- end -}}" # renders to: SharedUserService|user-service:50051;NotificationLogs|notification:50053;RegulatorService|regulator:50054;... # vs. the old bare-metal version: SharedUserService|127.0.0.1:50051;NotificationLogs|127.0.0.1:50053;...
What changes vs. bare metal
No application code changes. Only infrastructure addressing.
STAYS THE SAME
All Laravel PHP code
Go gateway code
Vue frontend code
gRPC proto definitions
.rr.yaml structure (command changes)
MongoDB schema & collections
CHANGES (CONFIG ONLY)
127.0.0.1 → k8s service names
Supervisor → k8s Deployments
Nginx site → Ingress + frontend pod
.rr.yaml command: Linux PHP path
.env files → ConfigMap + Secret
Local filesystem → PVCs
NEW (INFRASTRUCTURE FILES)
3 Dockerfiles
Helm chart (values.yaml + templates)
build-all.sh
deploy.sh