← Back

Hardening React Deployments: Security-First Frontend Delivery with Nginx and Docker

·5 min read

Frontend security is often treated as an afterthought. A few lint rules, maybe a library, and a lot of optimism.

This post walks through a production-grade React deployment where security is enforced at the infrastructure layer, not scattered across JavaScript files and developer discipline.

The goal is simple: serve static assets fast, reduce attack surface, and make security defaults unavoidable.


What Problem This Setup Solves

Typical frontend deployments suffer from a few recurring issues:

  • Node.js running in production for no reason
  • Large container images with unnecessary tooling
  • Client-side routing breaking on refresh
  • Security headers applied inconsistently or not at all

This setup addresses those directly by:

  • Building React once, serving only static assets
  • Removing Node.js entirely from the runtime image
  • Enforcing security headers at the Nginx layer
  • Making the deployment predictable and auditable

If JavaScript fails, Nginx still enforces policy. That’s the point.


Architecture Overview

The deployment uses a multi-stage Docker build with a strict separation of concerns.

Build Stage

  • Uses Node.js solely to compile the React application
  • Outputs static assets (/dist)
  • Never ships to production

Runtime Stage

  • Uses Alpine-based Nginx
  • Serves static files only
  • Terminates all HTTP requests
  • Applies security headers centrally

This separation ensures:

  • Smaller images
  • Faster startup
  • Fewer vulnerabilities
  • No runtime dependency sprawl

Nginx as a Security Boundary

Nginx is not just a file server here. It’s the security enforcement point.

server {
  listen 80;
  server_name _;

  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri /index.html;

    add_header Content-Security-Policy "
      default-src 'self';
      style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
      font-src 'self' https://fonts.gstatic.com;
    ";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
  }
}

Dockerfile


# ---- build stage ----
FROM node:18-slim AS build
WORKDIR /app

COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# ---- runtime stage ----
FROM nginx:alpine

RUN rm /etc/nginx/conf.d/default.conf

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;"]


© 2026 Anshu Sharma