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 1Dockerfiles
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.
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
↓
Data Layer
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
Configuration & Secrets
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)
Bootstrap
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 namehelmName:user-service# k8s resource name (DNS-safe)gatewaySlug:SharedUserService# Go gateway route keygrpcPort:50051# unique per servicedbName:Shared-User-Service# MongoDB database namehasQueue:true# deploy queue worker?image:repository:registry/gm-user-servicetag:v1
- name:NotificationAndLogsBackEnd# + 12 more entries...helmName:notificationgatewaySlug:NotificationLogsgrpcPort:50053dbName:NotificationAndLogsBackEndhasQueue:truehasReverb: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.