DEV Community

ZèD
ZèD

Posted on • Edited on

Deploying a React App with Docker using Nginx

Deploying a React App with Docker Using Nginx

For production React deployments, the goal is simple: build once, serve fast, keep image small. A multi-stage Docker build with Nginx is the standard approach for this.

This guide shows a clean setup that separates build tooling from runtime delivery.

Why It Matters

  • Keeps runtime image small and efficient.
  • Removes Node build toolchain from production container.
  • Serves static assets with high-performance Nginx.
  • Supports SPA client-side routing correctly.

Core Concepts

1. Multi-Stage Docker Strategy

Use one stage for building React assets and one stage for serving with Nginx.

2. Build Stage with Node

Use node:alpine, install dependencies, and generate optimized build output.

FROM node:alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
Enter fullscreen mode Exit fullscreen mode

3. Runtime Stage with Nginx

Copy build artifacts only into Nginx image.

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
Enter fullscreen mode Exit fullscreen mode

4. SPA Routing Support

Configure Nginx to fallback to index.html for unknown routes.

server {
  listen 80;
  server_name _;
  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Container Startup

Expose port and keep Nginx in foreground mode.

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

6. Deterministic Dependency Install

Use npm ci with package-lock.json for consistent builds.

Practical Example

Complete Dockerfile:

FROM node:alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
RUN echo 'server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }' > /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode

Build and run:

docker build -t react-app .
docker run -d -p 80:80 --name react-app-container react-app
Enter fullscreen mode Exit fullscreen mode

If route refresh does not return 404, your SPA + Nginx configuration is doing exactly what it should.

Common Mistakes

  • Using npm install --frozen-lockfile in npm projects instead of npm ci.
  • Serving SPA without try_files ... /index.html fallback.
  • Shipping Node runtime to production when only static files are needed.
  • Copying full source too early and losing layer-cache efficiency.
  • Ignoring .dockerignore and bloating build context.

Quick Recap

  • Build React assets in Node stage.
  • Serve static output in Nginx stage.
  • Keep runtime image minimal and secure.
  • Use SPA fallback for client-side routes.
  • Use npm ci for deterministic dependency installs.

Next Steps

  1. Add gzip/brotli and cache headers in Nginx config.
  2. Add health checks in deployment platform.
  3. Add CI pipeline for build, scan, and push.
  4. Add runtime env injection strategy if needed.

Top comments (0)