Kubernetes
ConfigMap
React
Environment Variables
Pod

Reading environmental variables set in configmap of kubernetes pod from react application?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

React applications built with Create React App (or Vite) are static files that run in the browser, not on the server. This means process.env is resolved at build time, not at runtime. You cannot directly read Kubernetes ConfigMap environment variables from a running React application the way a Node.js backend can. The solution is to either inject environment variables at build time, serve a runtime configuration file from the container, or use an API endpoint that returns configuration values.

The Problem

A ConfigMap injects environment variables into a pod:

yaml
1# configmap.yaml
2apiVersion: v1
3kind: ConfigMap
4metadata:
5  name: react-app-config
6data:
7  REACT_APP_API_URL: "https://api.production.example.com"
8  REACT_APP_FEATURE_FLAG: "true"
yaml
1# deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5  name: react-app
6spec:
7  template:
8    spec:
9      containers:
10        - name: react-app
11          image: my-react-app:latest
12          envFrom:
13            - configMapRef:
14                name: react-app-config

But inside the React code, process.env.REACT_APP_API_URL is replaced with its value during npm run build. It does not read from the container's environment at runtime.

Solution 1: Build-Time Injection

Set environment variables before building the Docker image:

dockerfile
1# Dockerfile
2FROM node:20-alpine AS builder
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci
6COPY . .
7
8# Build args become environment variables during build
9ARG REACT_APP_API_URL
10ARG REACT_APP_FEATURE_FLAG
11ENV REACT_APP_API_URL=$REACT_APP_API_URL
12ENV REACT_APP_FEATURE_FLAG=$REACT_APP_FEATURE_FLAG
13
14RUN npm run build
15
16FROM nginx:alpine
17COPY --from=builder /app/build /usr/share/nginx/html
bash
1# Build with specific values
2docker build \
3  --build-arg REACT_APP_API_URL=https://api.production.example.com \
4  --build-arg REACT_APP_FEATURE_FLAG=true \
5  -t my-react-app:latest .

Downside: You need a separate Docker image for each environment (dev, staging, production). This defeats the purpose of ConfigMaps, which are meant to decouple configuration from images.

Generate a JavaScript config file at container startup that reads environment variables:

bash
1#!/bin/sh
2# docker-entrypoint.sh
3
4# Generate runtime config from environment variables
5cat <<EOF > /usr/share/nginx/html/config.js
6window.__RUNTIME_CONFIG__ = {
7  API_URL: "${REACT_APP_API_URL}",
8  FEATURE_FLAG: "${REACT_APP_FEATURE_FLAG}",
9  ENVIRONMENT: "${REACT_APP_ENVIRONMENT}"
10};
11EOF
12
13# Start nginx
14exec nginx -g 'daemon off;'
dockerfile
1# Dockerfile
2FROM node:20-alpine AS builder
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci
6COPY . .
7RUN npm run build
8
9FROM nginx:alpine
10COPY --from=builder /app/build /usr/share/nginx/html
11COPY docker-entrypoint.sh /docker-entrypoint.sh
12RUN chmod +x /docker-entrypoint.sh
13ENTRYPOINT ["/docker-entrypoint.sh"]

Include the config file in public/index.html:

html
1<!-- public/index.html -->
2<head>
3  <script src="%PUBLIC_URL%/config.js"></script>
4</head>

Access in React:

javascript
1// src/config.js
2const config = {
3  apiUrl: window.__RUNTIME_CONFIG__?.API_URL || process.env.REACT_APP_API_URL,
4  featureFlag: window.__RUNTIME_CONFIG__?.FEATURE_FLAG === "true",
5  environment: window.__RUNTIME_CONFIG__?.ENVIRONMENT || "development",
6};
7
8export default config;
jsx
1// src/App.jsx
2import config from "./config";
3
4function App() {
5  return <div>API URL: {config.apiUrl}</div>;
6}

Now the same Docker image works in all environments. The ConfigMap controls the values injected at pod startup.

Solution 3: ConfigMap as a Volume-Mounted File

Mount the ConfigMap as a JSON file instead of environment variables:

yaml
1# configmap.yaml
2apiVersion: v1
3kind: ConfigMap
4metadata:
5  name: react-app-config
6data:
7  config.json: |
8    {
9      "apiUrl": "https://api.production.example.com",
10      "featureFlag": true,
11      "environment": "production"
12    }
yaml
1# deployment.yaml
2spec:
3  containers:
4    - name: react-app
5      image: my-react-app:latest
6      volumeMounts:
7        - name: config-volume
8          mountPath: /usr/share/nginx/html/config.json
9          subPath: config.json
10  volumes:
11    - name: config-volume
12      configMap:
13        name: react-app-config
javascript
1// src/config.js
2let config = null;
3
4export async function loadConfig() {
5  const response = await fetch("/config.json");
6  config = await response.json();
7  return config;
8}
9
10export function getConfig() {
11  if (!config) throw new Error("Config not loaded. Call loadConfig() first.");
12  return config;
13}
jsx
1// src/index.jsx
2import { loadConfig } from "./config";
3
4loadConfig().then(() => {
5  const root = ReactDOM.createRoot(document.getElementById("root"));
6  root.render(<App />);
7});

Solution 4: API Endpoint

Serve configuration through a backend API:

javascript
1// backend/server.js (Node.js)
2app.get("/api/config", (req, res) => {
3  res.json({
4    apiUrl: process.env.REACT_APP_API_URL,
5    featureFlag: process.env.REACT_APP_FEATURE_FLAG === "true",
6  });
7});
jsx
1// React component
2const [config, setConfig] = useState(null);
3
4useEffect(() => {
5  fetch("/api/config")
6    .then(res => res.json())
7    .then(setConfig);
8}, []);

This requires an additional backend service but provides the most flexibility, including the ability to change configuration without restarting pods.

Vite Projects

For Vite-based React apps, environment variables use the VITE_ prefix:

yaml
# configmap.yaml
data:
  VITE_API_URL: "https://api.example.com"

The runtime config approach works the same way. Only the build-time variable prefix changes from REACT_APP_ to VITE_.

Common Pitfalls

  • Assuming process.env works at runtime in React: React (CRA/Vite) replaces process.env.REACT_APP_* with literal strings at build time. They cannot read pod environment variables at runtime.
  • Forgetting the REACT_APP_ prefix: Create React App only exposes environment variables prefixed with REACT_APP_. Variables without this prefix are ignored during the build. Vite uses VITE_ instead.
  • Storing secrets in ConfigMaps: ConfigMaps are not encrypted. Use Kubernetes Secrets for API keys, tokens, and passwords. Even with Secrets, avoid exposing sensitive values to frontend code.
  • Caching the config.js file: Browsers may cache config.js aggressively. Add a cache-busting query string (config.js?v=timestamp) or set Cache-Control: no-cache headers in nginx for this file.
  • Not loading config before rendering: If using fetch-based config (Solution 3/4), the config may not be available when components first render. Load config before calling ReactDOM.createRoot().render() or show a loading state.

Summary

  • React apps cannot read Kubernetes ConfigMap environment variables at runtime because process.env is resolved at build time
  • The recommended approach is generating a config.js file at container startup using an entrypoint script
  • Alternatively, mount a ConfigMap as a JSON file served by the web server
  • Use the same Docker image across all environments by decoupling configuration from the build
  • Never expose secrets in frontend configuration — use backend APIs for sensitive values

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.