React.js language-specific guide
This guide explains how to containerize React.js applications using Docker.
The React.js language-specific guide shows you how to containerize a React.js application using Docker, following best practices for creating efficient, production-ready containers.
React.js is a widely used library for building interactive user interfaces. However, managing dependencies, environments, and deployments efficiently can be complex. Docker simplifies this process by providing a consistent and containerized environment.
Acknowledgment
Docker extends its sincere gratitude to Kristiyan Velkov for authoring this guide. As a Docker Captain and experienced Front-end engineer, his expertise in Docker, DevOps, and modern web development has made this resource invaluable for the community, helping developers navigate and optimize their Docker workflows.
What will you learn?
In this guide, you will learn how to:
- Containerize and run a React.js application using Docker.
- Set up a local development environment for React.js inside a container.
- Run tests for your React.js application within a Docker container.
To begin, you’ll start by containerizing an existing React.js application.
Prerequisites
Before you begin, make sure you're familiar with the following:
- Basic understanding of JavaScript or TypeScript.
- Basic knowledge of Node.js and npm for managing dependencies and running scripts.
- Familiarity with React.js fundamentals.
- Understanding of Docker concepts such as images, containers, and Dockerfiles. If you're new to Docker, start with the Docker basics guide.
Once you've completed the React.js getting started modules, you’ll be ready to containerize your own React.js application using the examples and instructions provided in this guide.
Containerize a React.js Application
Prerequisites
Before you begin, make sure the following tools are installed and available on your system:
- You have installed the latest version of Docker Desktop.
- You have a git client. The examples in this section use a command-line based git client, but you can use any client.
New to Docker?
Start with the Docker basics guide to get familiar with key concepts like images, containers, and Dockerfiles.
Overview
This guide walks you through the complete process of containerizing a React.js application with Docker. You’ll learn how to create a production-ready Docker image using best practices that improve performance, security, scalability, and deployment efficiency.
By the end of this guide, you will:
- Containerize a React.js application using Docker.
- Create and optimize a Dockerfile for production builds.
- Use multi-stage builds to minimize image size.
- Serve the application efficiently with a custom NGINX configuration.
- Follow best practices for building secure and maintainable Docker images.
Get the sample application
Clone the sample application to use with this guide. Open a terminal, change directory to a directory that you want to work in, and run the following command to clone the git repository:
$ git clone https://github.com/kristiyan-velkov/docker-reactjs-sample
Build the Docker image
React.js is a front-end library that compiles into static assets, so the Dockerfile is tailored to optimize how React applications are built and served in a production environment.
TipGordon, Docker's AI assistant, can generate Docker assets for your project. Ask Gordon to create a Dockerfile, Compose file, and
.dockerignoretailored to your application.
Step 1: Create the Dockerfile
Before creating a Dockerfile, you need to choose a base image. You can either use the Node.js Official Image or a Docker Hardened Image (DHI) from the Hardened Image catalog.
Choosing DHI offers the advantage of a production-ready image that is lightweight and secure. For more information, see Docker Hardened Images.
ImportantThis guide uses a stable Node.js LTS image tag that is considered secure when the guide is written. Because new releases and security patches are published regularly, the tag shown here may no longer be the safest option when you follow the guide. Always review the latest available image tags and select a secure, up-to-date version before building or deploying your application.
Official Node.js Docker Images: https://hub.docker.com/_/node
Docker Hardened Images (DHIs) are available for Node.js in the Docker Hardened Images catalog. Docker Hardened Images are freely available to everyone with no subscription required. You can pull and use them like any other Docker image after signing in to the DHI registry. For more information, see the DHI quickstart guide.
Sign in to the DHI registry:
$ docker login dhi.ioPull the Node.js DHI (check the catalog for available versions):
$ docker pull dhi.io/node:24-alpine3.22-devPull the Nginx DHI (check the catalog for available versions):
$ docker pull dhi.io/nginx:1.28.0-alpine3.21-dev
In the following Dockerfile, the FROM instructions use dhi.io/node:24-alpine3.22-dev and dhi.io/nginx:1.28.0-alpine3.21-dev as the base images.
# =========================================
# Stage 1: Build the React.js Application
# =========================================
# Use a lightweight Node.js image for building (customizable via ARG)
FROM dhi.io/node:24-alpine3.22-dev AS builder
# Set the working directory inside the container
WORKDIR /app
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json package-lock.json* ./
# Install project dependencies using npm ci (ensures a clean, reproducible install)
RUN --mount=type=cache,target=/root/.npm npm ci
# Copy the rest of the application source code into the container
COPY . .
# Build the React.js application (outputs to /app/dist)
RUN npm run build
# =========================================
# Stage 2: Prepare Nginx to Serve Static Files
# =========================================
FROM dhi.io/nginx:1.28.0-alpine3.21-dev AS runner
# Copy custom Nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy the static build output from the build stage to Nginx's default HTML serving directory
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html
# Use a non-root user for security best practices
USER nginx
# Expose port 8080 to allow HTTP traffic
# Note: The default NGINX container now listens on port 8080 instead of 80
EXPOSE 8080
# Start Nginx directly with custom config
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]Create a file named Dockerfile with the following contents:
# =========================================
# Stage 1: Build the React.js Application
# =========================================
ARG NODE_VERSION=24.12.0-alpine
ARG NGINX_VERSION=alpine3.22
# Use a lightweight Node.js image for building (customizable via ARG)
FROM node:${NODE_VERSION} AS builder
# Set the working directory inside the container
WORKDIR /app
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json package-lock.json* ./
# Install project dependencies using npm ci (ensures a clean, reproducible install)
RUN --mount=type=cache,target=/root/.npm npm ci
# Copy the rest of the application source code into the container
COPY . .
# Build the React.js application (outputs to /app/dist)
RUN npm run build
# =========================================
# Stage 2: Prepare Nginx to Serve Static Files
# =========================================
FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner
# Copy custom Nginx config
COPY nginx.conf /etc/nginx/nginx.conf
# Copy the static build output from the build stage to Nginx's default HTML serving directory
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html
# Use a built-in non-root user for security best practices
USER nginx
# Expose port 8080 to allow HTTP traffic
# Note: The default NGINX container now listens on port 8080 instead of 80
EXPOSE 8080
# Start Nginx directly with custom config
ENTRYPOINT ["nginx", "-c", "/etc/nginx/nginx.conf"]
CMD ["-g", "daemon off;"]NoteWe are using nginx-unprivileged instead of the standard NGINX image to follow security best practices. Running as a non-root user in the final image:
- Reduces the attack surface
- Aligns with Docker’s recommendations for container hardening
- Helps comply with stricter security policies in production environments
Step 2: Create the compose.yaml file
Create a file named compose.yaml with the following contents:
services:
server:
build:
context: .
ports:
- 8080:8080Step 3: Create the .dockerignore file
The .dockerignore file tells Docker which files and folders to exclude when building the image.
NoteThis helps:
- Reduce image size
- Speed up the build process
- Prevent sensitive or unnecessary files (like
.env,.git, ornode_modules) from being added to the final image.To learn more, visit the .dockerignore reference.
Create a file named .dockerignore with the following contents:
# Ignore dependencies and build output
node_modules/
dist/
out/
.tmp/
.cache/
# Ignore Vite, Webpack, and React-specific build artifacts
.vite/
.vitepress/
.eslintcache
.npm/
coverage/
jest/
cypress/
cypress/screenshots/
cypress/videos/
reports/
# Ignore environment and config files (sensitive data)
*.env*
*.log
# Ignore TypeScript build artifacts (if using TypeScript)
*.tsbuildinfo
# Ignore lockfiles (optional if using Docker for package installation)
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Ignore local development files
.git/
.gitignore
.vscode/
.idea/
*.swp
.DS_Store
Thumbs.db
# Ignore Docker-related files (to avoid copying unnecessary configs)
Dockerfile
.dockerignore
docker-compose.yml
docker-compose.override.yml
# Ignore build-specific cache files
*.lockStep 4: Create the nginx.conf file
To serve your React.js application efficiently inside the container, you’ll configure NGINX with a custom setup. This configuration is optimized for performance, browser caching, gzip compression, and support for client-side routing.
Create a file named nginx.conf in the root of your project directory, and add the following content:
NoteTo learn more about configuring NGINX, see the official NGINX documentation.
worker_processes auto;
# Store PID in /tmp (always writable)
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Disable logging to avoid permission issues
access_log off;
error_log /dev/stderr warn;
# Optimize static file serving
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
# Gzip compression for optimized delivery
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_min_length 256;
gzip_vary on;
server {
listen 8080;
server_name localhost;
# Root directory where React.js build files are placed
root /usr/share/nginx/html;
index index.html;
# Serve React.js static files with proper caching
location / {
try_files $uri /index.html;
}
# Serve static assets with long cache expiration
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|map)$ {
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
# Handle React.js client-side routing
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}Step 5: Build the React.js application image
With your custom configuration in place, you're now ready to build the Docker image for your React.js application.
The updated setup includes:
- Optimized browser caching and gzip compression
- Secure, non-root logging to avoid permission issues
- Support for React client-side routing by redirecting unmatched routes to
index.html
After completing the previous steps, your project directory should now contain the following files:
├── docker-reactjs-sample/
│ ├── Dockerfile
│ ├── .dockerignore
│ ├── compose.yaml
│ └── nginx.confNow that your Dockerfile is configured, you can build the Docker image for your React.js application.
NoteThe
docker buildcommand packages your application into an image using the instructions in the Dockerfile. It includes all necessary files from the current directory (called the build context).
Run the following command from the root of your project:
$ docker build --tag docker-reactjs-sample .
What this command does:
- Uses the Dockerfile in the current directory (.)
- Packages the application and its dependencies into a Docker image
- Tags the image as docker-reactjs-sample so you can reference it later
Step 6: View local images
After building your Docker image, you can check which images are available on your local machine using either the Docker CLI or Docker Desktop. Since you're already working in the terminal, let's use the Docker CLI.
To list all locally available Docker images, run the following command:
$ docker images
Example Output:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-reactjs-sample latest f39b47a97156 14 seconds ago 75.8MBThis output provides key details about your images:
- Repository – The name assigned to the image.
- Tag – A version label that helps identify different builds (e.g., latest).
- Image ID – A unique identifier for the image.
- Created – The timestamp indicating when the image was built.
- Size – The total disk space used by the image.
If the build was successful, you should see docker-reactjs-sample image listed.
Run the containerized application
In the previous step, you created a Dockerfile for your React.js application and built a Docker image using the docker build command. Now it’s time to run that image in a container and verify that your application works as expected.
Inside the docker-reactjs-sample directory, run the following command in a
terminal.
$ docker compose up --build
Open a browser and view the application at http://localhost:8080. You should see a simple React.js web application.
Press ctrl+c in the terminal to stop your application.
Run the application in the background
You can run the application detached from the terminal by adding the -d
option. Inside the docker-reactjs-sample directory, run the following command
in a terminal.
$ docker compose up --build -d
Open a browser and view the application at http://localhost:8080. You should see a simple web application preview.
To confirm that the container is running, use docker ps command:
$ docker ps
This will list all active containers along with their ports, names, and status. Look for a container exposing port 8080.
Example Output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
88bced6ade95 docker-reactjs-sample-server "nginx -c /etc/nginx…" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp docker-reactjs-sample-server-1To stop the application, run:
$ docker compose down
NoteFor more information about Compose commands, see the Compose CLI reference.
Use containers for React.js development
Prerequisites
Complete Containerize React.js application.
Overview
In this section, you'll learn how to set up both production and development environments for your containerized React.js application using Docker Compose. This setup allows you to serve a static production build via Nginx and to develop efficiently inside containers using a live-reloading dev server with Compose Watch.
You’ll learn how to:
- Configure separate containers for production and development
- Enable automatic file syncing using Compose Watch in development
- Debug and live-preview your changes in real-time without manual rebuilds
Automatically update services (development mode)
Use Compose Watch to automatically sync source file changes into your containerized development environment. This provides a seamless, efficient development experience without needing to restart or rebuild containers manually.
Step 1: Create a development Dockerfile
Create a file named Dockerfile.dev in your project root with the following content:
# =========================================
# Stage 1: Develop the React.js Application
# =========================================
ARG NODE_VERSION=24.12.0-alpine
# Use a lightweight Node.js image for development
FROM node:${NODE_VERSION} AS dev
# Set the working directory inside the container
WORKDIR /app
# Copy package-related files first to leverage Docker's caching mechanism
COPY package.json package-lock.json* ./
# Install project dependencies
RUN --mount=type=cache,target=/root/.npm npm install
# Copy the rest of the application source code into the container
COPY . .
# Expose the port used by the Vite development server
EXPOSE 5173
# Use a default command, can be overridden in Docker compose.yml file
CMD ["npm", "run", "dev"]This file sets up a lightweight development environment for your React app using the dev server.
Step 2: Update your compose.yaml file
Open your compose.yaml file and define two services: one for production (react-prod) and one for development (react-dev).
Here’s an example configuration for a React.js application:
services:
react-prod:
build:
context: .
dockerfile: Dockerfile
image: docker-reactjs-sample
ports:
- "8080:8080"
react-dev:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "5173:5173"
develop:
watch:
- action: sync
path: .
target: /app- The
react-prodservice builds and serves your static production app using Nginx. - The
react-devservice runs your React development server with live reload and hot module replacement. watchtriggers file sync with Compose Watch.
NoteFor more details, see the official guide: Use Compose Watch.
Step 3: Update vite.config.ts to ensure it works properly inside Docker
To make Vite’s development server work reliably inside Docker, you need to update your vite.config.ts with the correct settings.
Open the vite.config.ts file in your project root and update it as follows:
/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
base: "/",
plugins: [react()],
server: {
host: true,
port: 5173,
strictPort: true,
},
});NoteThe
serveroptions invite.config.tsare essential for running Vite inside Docker:
host: trueallows the dev server to be accessible from outside the container.port: 5173sets a consistent development port (must match the one exposed in Docker).strictPort: trueensures Vite fails clearly if the port is unavailable, rather than switching silently.For full details, refer to the Vite server configuration docs.
After completing the previous steps, your project directory should now contain the following files:
├── docker-reactjs-sample/
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── .dockerignore
│ ├── compose.yaml
│ └── nginx.confStep 4: Start Compose Watch
Run the following command from your project root to start your container in watch mode:
$ docker compose watch react-dev
Step 5: Test Compose Watch with React
To verify that Compose Watch is working correctly:
Open the
src/App.tsxfile in your text editor.Locate the following line:
<h1>Vite + React</h1>Change it to:
<h1>Hello from Docker Compose Watch</h1>Save the file.
Open your browser at http://localhost:5173.
You should see the updated text appear instantly, without needing to rebuild the container manually. This confirms that file watching and automatic synchronization are working as expected.
Run React.js tests in a container
Prerequisites
Complete all the previous sections of this guide, starting with Containerize React.js application.
Overview
Testing is a critical part of the development process. In this section, you'll learn how to:
- Run unit tests using Vitest inside a Docker container.
- Use Docker Compose to run tests in an isolated, reproducible environment.
You’ll use Vitest — a blazing fast test runner designed for Vite — along with Testing Library for assertions.
Run tests during development
docker-reactjs-sample application includes a sample test file at location:
$ src/App.test.tsx
This file uses Vitest and React Testing Library to verify the behavior of App component.
Step 1: Install Vitest and React Testing Library
If you haven’t already added the necessary testing tools, install them by running:
$ npm install --save-dev vitest @testing-library/react @testing-library/jest-dom jsdom
Then, update the scripts section of your package.json file to include the following:
"scripts": {
"test": "vitest run"
}Step 2: Configure Vitest
Update vitest.config.ts file in your project root with the following configuration:
| |
NoteThe
testoptions invitest.config.tsare essential for reliable testing inside Docker:
environment: "jsdom"simulates a browser-like environment for rendering and DOM interactions.setupFiles: "./src/setupTests.ts"loads global configuration or mocks before each test file (optional but recommended).globals: trueenables global test functions likedescribe,it, andexpectwithout importing them.For more details, see the official Vitest configuration docs.
Step 3: Update compose.yaml
Add a new service named react-test to your compose.yaml file. This service allows you to run your test suite in an isolated containerized environment.
| |
The react-test service reuses the same Dockerfile.dev used for development and overrides the default command to run tests with npm run test. This setup ensures a consistent test environment that matches your local development configuration.
After completing the previous steps, your project directory should contain the following files:
├── docker-reactjs-sample/
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── .dockerignore
│ ├── compose.yaml
│ └── nginx.confStep 4: Run the tests
To execute your test suite inside the container, run the following command from your project root:
$ docker compose run --rm react-test
This command will:
- Start the
react-testservice defined in yourcompose.yamlfile. - Execute the
npm run testscript using the same environment as development. - Automatically remove the container after the tests complete
docker compose run --rmcommand.
NoteFor more information about Compose commands, see the Compose CLI reference.
Summary
In this section, you learned how to run unit tests for your React.js application inside a Docker container using Vitest and Docker Compose.
What you accomplished:
- Installed and configured Vitest and React Testing Library for testing React components.
- Created a
react-testservice incompose.yamlto isolate test execution. - Reused the development
Dockerfile.devto ensure consistency between dev and test environments. - Ran tests inside the container using
docker compose run --rm react-test. - Ensured reliable, repeatable testing across environments without relying on local machine setup.
Related resources
Explore official references and best practices to sharpen your Docker testing workflow:
- Dockerfile reference – Understand all Dockerfile instructions and syntax.
- Best practices for writing Dockerfiles – Write efficient, maintainable, and secure Dockerfiles.
- Compose file reference – Learn the full syntax and options available for configuring services in
compose.yaml. docker compose runCLI reference – Run one-off commands in a service container.