Non-root nginx runtime for React/Vite SPAs with runtime env config. Multi-arch (amd64/arm64).
178
Production runtime for React / Vite single-page applications: a non-root nginx (based on nginxinc/nginx-unprivileged) on Alpine Linux, tuned for SPAs.
The image contains no application code and no secrets. Build your app with Node in one stage and copy the output on top of this image.
linux/amd64, linux/arm64nginx (uid 101)/usr/share/nginx/htmlTags follow <exact nginx version>-r<revision>:
| Tag | Example | Moves? | Use it for |
|---|---|---|---|
<nginx>-r<n> | 1.28.2-r1 | Never (immutable) | Production — reproducible builds |
<nginx> | 1.28.2 | To the latest revision of that nginx version | Inspection |
<line> | 1.28 | To the latest build of the nginx line | Environments that want patches automatically |
latest | — | To the latest build of the newest nginx line | Quick tests only |
The revision increases when the image is rebuilt without an nginx change and resets to -r1 with every new nginx release.
| Feature | Details |
|---|---|
| SPA routing | Unknown paths fall back to index.html (React Router and friends) |
| Caching | /assets/* (Vite hashed output): immutable, 1 year. index.html and routes: no-cache. /env-config.js: no-store |
| Health | /healthz returns 200 and backs the image HEALTHCHECK |
| Security | Runs without root, server_tokens off, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, dotfiles denied |
| Compression | gzip for text, JS, CSS, JSON and SVG |
| Runtime config | /env-config.js generated at startup from PUBLIC_* environment variables |
# syntax=docker/dockerfile:1
ARG BASE_IMAGE=madlabcg/react-nginx:1.28.2-r1
FROM --platform=$BUILDPLATFORM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM ${BASE_IMAGE}
COPY --from=build --chown=nginx:nginx /app/dist /usr/share/nginx/html
docker run -d -p 8080:8080 -e PUBLIC_API_URL=https://api.example.com my-react-app
VITE_* variables are baked into the bundle at build time, so an image built for staging cannot be promoted to production. This image solves that: on startup it writes every PUBLIC_* environment variable to /env-config.js:
window.__ENV__ = {
"PUBLIC_API_URL": "https://api.example.com",
};
Load it before your bundle and read it with a build-time fallback:
<!-- index.html -->
<script src="/env-config.js"></script>
const runtime = (window as any).__ENV__ ?? {};
export const API_URL: string = runtime.PUBLIC_API_URL ?? import.meta.env.VITE_API_URL;
Never put secrets in PUBLIC_* variables — they are served to every browser.
| Variable | Default | Description |
|---|---|---|
PUBLIC_* | — | Exposed in window.__ENV__ |
ENV_CONFIG_PREFIX | PUBLIC_ | Prefix of the variables to expose |
ENV_CONFIG_PATH | /usr/share/nginx/html/env-config.js | Output file |
All features of the official nginx image entrypoint (templates in /etc/nginx/templates/, NGINX_ENTRYPOINT_QUIET_LOGS, …) keep working.
| Need | How |
|---|---|
Proxy /api to a backend | Start from the bundled config (docker run --rm madlabcg/react-nginx:1.28 cat /etc/nginx/conf.d/default.conf), add a location /api/ { proxy_pass ...; } and COPY nginx.conf /etc/nginx/conf.d/default.conf |
| App served under a sub-path | Set base in vite.config and adjust try_files in the nginx config |
| pnpm / yarn | In the build stage: RUN corepack enable, then pnpm install --frozen-lockfile / yarn install --frozen-lockfile |
| Create React App | Copy /app/build instead of /app/dist |
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
Content type
Image
Digest
sha256:24f57eb0a…
Size
22 MB
Last updated
20 days ago
docker pull madlabcg/react-nginx