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
# 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.git
node_modules/
dist/
.env*
npm-debug.log*
Dockerfile
.dockerignoreBefore you build
- Use a Vite project that builds to dist/, which is Vite's default output folder.
- Commit package-lock.json so npm ci installs exact versions.
- Add an Nginx config with a try_files fallback if you use client-side routing such as React Router.
How this Dockerfile works
FROM node:24-alpine AS buildNode only runs the Vite build. This stage and its node_modules are discarded afterwards.
COPY package.json package-lock.json ./ RUN npm ciInstalls locked dependencies in a cached layer, so editing components doesn't trigger a full reinstall.
RUN npm run buildRuns the build script, which type-checks with TypeScript in the default template and bundles the app with Vite into dist.
FROM nginxinc/nginx-unprivileged:1.27-alpineAn official Nginx image that runs entirely as a non-root user and therefore listens on port 8080.
COPY --from=build /app/dist /usr/share/nginx/htmlPlaces the built HTML, JavaScript and assets in Nginx's document root, which the default configuration already serves.
EXPOSE 8080Documents the port the unprivileged image listens on. Map it to 80 or any other host port when running the container.
Build and run
docker build -t react-app .
docker run --rm -p 8080:8080 react-appOpen 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.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Vite build served by unprivileged Nginx | nginxinc/nginx-unprivileged:1.27-alpine | 48.5 MB | Passing |
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.