DEV Community

Sohana Akbar
Sohana Akbar

Posted on

ConfigMaps for Environment Variables in a React App: Stop Rebuilding, Start Injecting

TL;DR: Create React App builds bake environment variables at build time. ConfigMaps let you inject runtime configs into your container. Here’s how to bridge them so the same Docker image works across dev, staging, and production.

The Problem
You’ve built a React app with Create React App (CRA), Vite, or Next.js. You use .env files:

js
// api.js
const API_URL = process.env.REACT_APP_API_URL;
You build your Docker image:

dockerfile
FROM node:18 AS builder
COPY . .
RUN npm run build # REACT_APP_API_URL gets baked here

FROM nginx:alpine
COPY --from=builder /build /usr/share/nginx/html
Then you deploy to Kubernetes.

But now you want different API URLs for staging vs production.

You could rebuild the image for each environment (bad – slow, wasteful). Or you could use a ConfigMap to inject values at runtime.

ConfigMap to the Rescue
A ConfigMap stores key-value pairs. Kubernetes can mount it as a file inside your pod.

But React runs in the browser, not in the container’s filesystem. So how does the browser read a file from a ConfigMap?

Simple: You serve a dynamic env-config.js file from your web server.

Step-by-Step Solution

  1. Create a ConfigMap with your environment variables yaml # configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: react-env-config data: env-config.js: | window.__env = { REACT_APP_API_URL: "https://api.production.com", REACT_APP_FEATURE_FLAG: "true" }; Apply it:

bash
kubectl apply -f configmap.yaml

  1. Modify your React app to read from window.__env Instead of reading process.env directly, use a runtime config:

js
// config.js
export function getEnvVar(name) {
// Runtime injection from window.env (provided by ConfigMap)
if (window.
env && window.env[name] !== undefined) {
return window.
env[name];
}
// Fallback to build-time env vars (for local dev)
return process.env[name];
}
Use it in your components:

js
// api.js
import { getEnvVar } from './config';

const API_URL = getEnvVar('REACT_APP_API_URL');

  1. Serve the ConfigMap file via your web server Update your nginx.conf (or serve via Express) to include the dynamic config:

nginx
server {
listen 80;
location /env-config.js {
alias /usr/share/nginx/html/env-config.js;
}
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}
}

  1. Mount the ConfigMap into your deployment yaml # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: react-app spec: replicas: 2 selector: matchLabels: app: react-app template: metadata: labels: app: react-app spec: containers:
    • name: app image: myregistry/react-app:latest ports:
      • containerPort: 80 volumeMounts:
      • name: env-config mountPath: /usr/share/nginx/html/env-config.js subPath: env-config.js volumes:
    • name: env-config configMap: name: react-env-config
  2. Include the script in your index.html Make sure your index.html loads the config before your main bundle:

html
<!DOCTYPE html>



<!-- your main bundle after -->






How It Works
The ConfigMap provides env-config.js with window.__env = {...}

The pod mounts this file into the nginx HTML directory

The browser requests index.html, which loads env-config.js first

Your app reads from window.__env instead of process.env

Result: The same Docker image now gets different configs per environment.

Different Environments, Same Image
Environment ConfigMap data
Dev REACT_APP_API_URL: "http://localhost:3000"
Staging REACT_APP_API_URL: "https://api.staging.com"
Production REACT_APP_API_URL: "https://api.production.com"
No rebuilds. No new images. Just update the ConfigMap and restart pods (or use a reloader like stakater/Reloader).

For Vite Users
Vite uses import.meta.env. The same pattern works, but you need to expose the window config similarly:

js
// config.js
export const API_URL = window.__env?.VITE_API_URL || import.meta.env.VITE_API_URL;
Security Considerations
Never put secrets in ConfigMaps (use Secrets instead, but even then, browser-accessible secrets are exposed to devtools).

For sensitive values, your backend should handle them. Frontend env vars are always readable by the user.

Common Pitfalls
Caching: Browsers may cache env-config.js. Add cache-busting headers or a unique query param.

Missing fallback: Always provide a fallback to process.env for local development.

Order of scripts: env-config.js must load before your app bundle.

Full Example Repository
I’ve created a working example: react-configmap-demo (replace with your actual repo).

The key files:

public/env-config.js (placeholder for local dev)

src/config.js (reads from window or process.env)

Kubernetes configmap.yaml and deployment.yaml

Conclusion
Stop rebuilding Docker images for config changes. Use ConfigMaps to inject runtime environment variables into your React app served via nginx. This gives you:

One image, many environments

No build per deployment

Instant config changes (just rollout restart)

Your CI/CD will thank you. Your SRE team will thank you. And your future self will thank you when you don’t have to rebuild the frontend at 2 AM just to change an API endpoint.

Have you tried other approaches like react-runtime-config or Helm templating? Let me know in the comments!

Top comments (0)