Skip to content

Kubernetes Deployment — ConfigMaps, Secrets, and Hot Reload

The simplest Kubernetes deployment — gateway serves files directly:

apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: frontend-public-config
- secretRef:
name: frontend-sensitive-config
livenessProbe:
httpGet:
path: /rep/health
port: 8080
initialDelaySeconds: 5
readinessProbe:
httpGet:
path: /rep/health
port: 8080
initialDelaySeconds: 2
---
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-public-config
data:
REP_PUBLIC_API_URL: "https://api.example.com"
REP_PUBLIC_FEATURE_FLAGS: "dark-mode"
REP_PUBLIC_ENV_NAME: "production"
REP_GATEWAY_STRICT: "true"
---
apiVersion: v1
kind: Secret
metadata:
name: frontend-sensitive-config
type: Opaque
stringData:
REP_SENSITIVE_ANALYTICS_KEY: "UA-XXXXX-prod"
REP_SENSITIVE_SENTRY_DSN: "https://[email protected]/123"

When you want nginx serving files with the gateway as a sidecar:

apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
template:
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: static-files
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
- name: rep-gateway
image: ghcr.io/ruachtech/rep/gateway:latest
args:
- "--upstream=localhost:80"
- "--port=8080"
- "--strict"
- "--env-file=/config/.env"
- "--hot-reload"
- "--hot-reload-mode=file_watch"
- "--watch-path=/config"
volumeMounts:
- name: config-volume
mountPath: /config
readOnly: true
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /rep/health
port: 8080
initContainers:
- name: copy-static
image: myapp:latest
command: ['cp', '-r', '/app/dist/.', '/static/']
volumeMounts:
- name: static-files
mountPath: /static
volumes:
- name: static-files
emptyDir: {}
- name: config-volume
configMap:
name: frontend-dynamic-config

The mounted ConfigMap must hold a single key containing a whole .env file, because --env-file reads one env-format file:

apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-dynamic-config
data:
.env: |
REP_PUBLIC_API_URL=https://api.example.com
REP_PUBLIC_FEATURE_FLAGS=dark-mode
REP_PUBLIC_ENV_NAME=production

Use file watch mode to detect ConfigMap changes without pod restarts:

  1. Mount the ConfigMap as a volume, with a single key holding a whole .env file
  2. Point the gateway at it with --env-file=/config/.env — this is what reload re-reads
  3. Enable --hot-reload --hot-reload-mode=file_watch --watch-path=/config
  4. Update the ConfigMap: kubectl apply -f configmap.yaml
  5. Kubernetes swaps the volume’s ..data symlink, changing the mount directory’s mtime
  6. The gateway detects the change, re-reads the env file, and broadcasts via SSE
  7. Connected browsers receive the update

Variables passed through envFrom alone are not hot-reloadable. Changing frontend-public-config or frontend-sensitive-config above requires a pod restart (kubectl rollout restart). To rotate a REP_SENSITIVE_* value without one, move it into the mounted env file — sourced from a Secret instead of a ConfigMap — since --env-file accepts a single path.

Terminal window
# Update a config value
kubectl patch configmap frontend-dynamic-config \
-p '{"data":{"REP_PUBLIC_FEATURE_FLAGS":"dark-mode,ai-assist"}}'
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 8080
type: ClusterIP