# Kubernetes Deployment — ConfigMaps, Secrets, and Hot Reload > Deploy REP in Kubernetes with ConfigMaps for public vars, Secrets for sensitive vars, sidecar patterns, and hot reload via file watch. Includes health probes and Service config. Source: https://rep-protocol.dev/deployment/kubernetes/ ## Embedded deployment The simplest Kubernetes deployment — gateway serves files directly: ```yaml 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://abc@sentry.io/123" ``` ## Sidecar pattern (with nginx) When you want nginx serving files with the gateway as a sidecar: ```yaml 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: ```yaml 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 ``` ## Hot reload with ConfigMaps 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 > **Do not also set these variables via envFrom** > > Reload merges the env file first, then lets the process environment overwrite it. A variable supplied through both `envFrom` and `--env-file` keeps its `envFrom` value permanently — `envFrom` is captured once at container start, so the reload succeeds but the injected value never changes. Use `envFrom` only for variables you are content to change with a pod restart, and never for a key that also appears in the env file. 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. ```bash # Update a config value kubectl patch configmap frontend-dynamic-config \ -p '{"data":{"REP_PUBLIC_FEATURE_FLAGS":"dark-mode,ai-assist"}}' ``` > **Tip** > > Use `--health-port 9090` to serve health checks on a separate port. This keeps the health endpoint accessible only within the cluster while the main port faces the load balancer. ## Service ```yaml apiVersion: v1 kind: Service metadata: name: frontend spec: selector: app: frontend ports: - port: 80 targetPort: 8080 type: ClusterIP ```