DEV Community

ZèD
ZèD

Posted on

Runtime Environment Variables for React Apps with Nginx and Docker

React environment variables are normally injected at build time. Once the app is built, changing API URLs, feature flags, or monitoring config requires rebuilding the image. This becomes a problem when the same Docker image needs to run in staging, production, or other environments.

This setup allows runtime configuration for React apps served by Nginx in Docker. The image is built once and configured when the container starts.

How it works

Add public/runtime-env.template.js

window.RUNTIME_ENV = {
  API_URL: "${API_URL}",
  FEATURE_FLAG_ANALYTICS: "${FEATURE_FLAG_ANALYTICS}",
  FEATURE_FLAG_NEW_DASHBOARD: "${FEATURE_FLAG_NEW_DASHBOARD}",
  SENTRY_DSN: "${SENTRY_DSN}",
};
Enter fullscreen mode Exit fullscreen mode

In index.html, add in <head>:

<script src="/runtime-env.js"></script>
Enter fullscreen mode Exit fullscreen mode

In your code:

const config = {
  API_URL: 'https://fallback.com',
  FEATURE_FLAG_ANALYTICS: false,
  // defaults
  ...(window.RUNTIME_ENV || {}),
};

export default config;
Enter fullscreen mode Exit fullscreen mode

Dockerfile

# Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

### Runtime
FROM nginx:alpine
RUN apk add --no-cache gettext
COPY --from=builder /app/build /usr/share/nginx/html  # /dist for Vite
COPY public/runtime-env.template.js /usr/share/nginx/html/runtime-env.template.js
COPY entrypoint.sh /docker-entrypoint.d/40-runtime-env.sh
RUN chmod +x /docker-entrypoint.d/40-runtime-env.sh
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

entrypoint.sh

#!/bin/sh
set -eu

export API_URL=${API_URL:-}
export FEATURE_FLAG_ANALYTICS=${FEATURE_FLAG_ANALYTICS:-}
export FEATURE_FLAG_NEW_DASHBOARD=${FEATURE_FLAG_NEW_DASHBOARD:-}
export SENTRY_DSN=${SENTRY_DSN:-}

envsubst '${API_URL} ${FEATURE_FLAG_ANALYTICS} ${FEATURE_FLAG_NEW_DASHBOARD} ${SENTRY_DSN}' \
  < /usr/share/nginx/html/runtime-env.template.js \
  > /usr/share/nginx/html/runtime-env.js

rm /usr/share/nginx/html/runtime-env.template.js

exec "$@"
Enter fullscreen mode Exit fullscreen mode

Run

docker run -p 80:80 \
  -e API_URL=https://api.prod.com \
  -e FEATURE_FLAG_ANALYTICS=true \
  your-image:latest
Enter fullscreen mode Exit fullscreen mode

Done. Container generates the config file on startup from env vars. Safe because we only allow specific ones with envsubst.

Why this is useful

With this approach, the frontend behaves more like a backend service:

  • One Docker image can be reused across environments
  • API URLs, feature flags, and DSNs can change without rebuild
  • Works well with Docker, Kubernetes, and CI/CD pipelines
  • Configuration is explicit and controlled

It avoids the common issue where frontend config is tightly coupled to the build step.

How it works

A small JavaScript template is added to the public folder. At container startup, Nginx runs a script that replaces placeholders in this file with actual environment variable values using envsubst. The generated file is then loaded before the React app and exposes the config through window.RUNTIME_ENV.

Only variables explicitly listed in the template are exposed, reducing the risk of leaking unintended values.

Result

You get true runtime configuration for a static React app, without rebuilding the image. The same Docker image can safely run in different environments with different settings, using standard environment variables.

Top comments (0)