React Dockerfile: Vite Build Served by Unprivileged Nginx

This React Dockerfile builds your Vite project on node:24-alpine and serves the static dist folder from nginxinc/nginx-unprivileged:1.27-alpine. Nginx runs as a non-root user on port 8080, and no Node runtime ships in the final image. It suits single-page apps created with Vite's React templates.

CI status
Build passing
Image size (uncompressed)
48.5 MB
Build time
11 s
Runtime image
nginxinc/nginx-unprivileged:1.27-alpine
Port
8080
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginxinc/nginx-unprivileged:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080
.dockerignore
.git
node_modules/
dist/
.env*
npm-debug.log*
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM node:24-alpine AS build

    Node only runs the Vite build. This stage and its node_modules are discarded afterwards.

  2. COPY package.json package-lock.json ./ RUN npm ci

    Installs locked dependencies in a cached layer, so editing components doesn't trigger a full reinstall.

  3. RUN npm run build

    Runs the build script, which type-checks with TypeScript in the default template and bundles the app with Vite into dist.

  4. FROM nginxinc/nginx-unprivileged:1.27-alpine

    An official Nginx image that runs entirely as a non-root user and therefore listens on port 8080.

  5. COPY --from=build /app/dist /usr/share/nginx/html

    Places the built HTML, JavaScript and assets in Nginx's document root, which the default configuration already serves.

  6. EXPOSE 8080

    Documents the port the unprivileged image listens on. Map it to 80 or any other host port when running the container.

Build and run

terminal
docker build -t react-app .
docker run --rm -p 8080:8080 react-app

Open http://localhost:8080. Vite inlines VITE_ prefixed variables at build time, so pass them as build arguments or set them before npm run build, not at docker run.

Other React Dockerfile variants

Serving a static Vite build from Nginx is the simplest way to containerize a client-side React app. React frameworks with server rendering, such as Next.js, need a Node runtime instead, and their Dockerfiles are listed separately on this site.

VariantRuntime imageImage sizeCI
Vite build served by unprivileged Nginxnginxinc/nginx-unprivileged:1.27-alpine48.5 MBPassing

Troubleshooting

Deep links like /settings return 404 after a refresh
Nginx looks for a real file at that path. Add a server block with try_files $uri $uri/ /index.html so React Router can handle the URL.
The build fails on TypeScript errors that don't appear in the dev server
The template's build script type-checks the whole project, while the dev server only transpiles. Fix the reported errors locally with npm run build.
Environment variables are undefined in the browser
Only variables prefixed with VITE_ are exposed, and they're read at build time. Set them before the build step, not when the container starts.

FAQ

Why use Nginx instead of vite preview?
vite preview is meant for checking a build locally, not for production traffic. Nginx serves static files faster, with less memory and no Node runtime.
How do I add caching headers?
Copy a custom server block into the image's conf.d directory. Give hashed files in assets a long cache lifetime and serve index.html with no-cache.
Does this work for Vue or Svelte Vite projects?
Yes. Any Vite project that builds static files to dist works with this Dockerfile unchanged.