Dockerizing React Apps
Docker packages an application with the files and runtime needed to run it consistently. For React apps, Docker is most useful when you want repeatable builds, containerized previews, or a static app served by a production web server such as Nginx.
Most client-side React apps do not need Node.js at runtime. They need Node.js to build, then a static server to serve the generated files.
The Common Production Pattern
A common Docker setup uses two stages:
- Build the React app with Node.
- Serve the static output with Nginx.
Example for a Vite app:
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]This keeps the final image smaller because it does not include Node, source files, or development dependencies.
SPA Routing with Nginx
React Router apps often need fallback routing. If a user refreshes /dashboard, Nginx must serve index.html, not return 404.
Example nginx.conf:
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
}The /assets/ rule works well when build files have fingerprinted names.
.dockerignore
Do not send unnecessary files into the Docker build context.
node_modules
dist
build
.git
.env
coverage
npm-debug.logThis makes builds faster and reduces the chance of copying secrets.
Build Arguments vs Runtime Environment
Frontend environment variables are often build-time values.
ARG VITE_API_BASE_URL
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run buildBuild command:
docker build --build-arg VITE_API_BASE_URL=https://api.example.com -t react-app .Important:
This value is embedded in the built JavaScript. Changing a container environment variable later will not change the already-built frontend bundle.
If you need runtime configuration, serve a separate config.json file or endpoint and fetch it when the app starts.
Running Locally
Build and run:
docker build -t react-app .
docker run --rm -p 8080:80 react-appThen open:
http://localhost:8080Test both / and a nested route like /settings.
When Docker Is Useful
Docker is useful when:
- the deployment target runs containers
- CI should build the same artifact every time
- frontend and backend are deployed together
- you need an Nginx fallback and caching configuration
- the app is part of a larger Docker Compose environment
Docker may be unnecessary when:
- deploying directly to Netlify or Vercel
- the app is a simple static site
- your hosting provider already handles builds and CDN serving
Common Mistakes
- Serving
distwith Node in production when a static server would be enough. - Forgetting SPA fallback routing.
- Copying
.envinto the image. - Using
npm installinstead ofnpm ciin repeatable builds. - Not pinning a reasonable Node major version.
- Assuming runtime container variables change build-time React values.
- Shipping a huge image containing source and dev dependencies.
Edge Cases
Client-side routing:
Without fallback routing, direct navigation to nested routes fails.
API proxying:
If Nginx proxies /api to a backend, configure timeouts and headers intentionally. Do not hide CORS issues by accident.
Health checks:
For container platforms, expose a simple path like / or /health.html that confirms the static server is running.
In a typical Dockerized Vite React app, why use a multi-stage Dockerfile?
Practice Challenge
Dockerize a React app.
Requirements:
- create a
Dockerfile - create a
.dockerignore - serve production files with Nginx
- support direct refresh on nested routes
- add long-term caching for fingerprinted assets
- document build arguments in the README
Manual tests:
- open the home page
- refresh a nested route
- inspect response headers for assets
- confirm
.envwas not copied into the image
Recap
Dockerizing a React app is usually a build-and-serve process. Use Node to produce static files, serve them from a small web server image, configure SPA fallback routing, and be clear about build-time configuration.