Skip to main content

React Docker Deployment

Introduction

Docker has revolutionized the way we develop, ship, and run applications by introducing containerization. For React developers, Docker provides a consistent environment to build and deploy applications across different platforms without worrying about "it works on my machine" problems. This guide will walk you through the process of containerizing your React application with Docker, making it ready for deployment to any environment that supports Docker containers.

Why Use Docker for React Deployments?

Deploying React applications with Docker offers several advantages:

  • Environment Consistency: The same container runs identically in development and production
  • Isolation: Application dependencies are isolated from the host system
  • Portability: Containers can run on any platform that supports Docker
  • Scalability: Easily scale your application horizontally
  • Version Control: Track changes to your runtime environment alongside code changes

Prerequisites

Before getting started, make sure you have:

  • A React application ready for deployment
  • Docker installed on your machine
  • Basic understanding of the terminal/command line
  • Node.js and npm/yarn (for development purposes)

Step 1: Create a Dockerfile

The first step in containerizing your React application is creating a Dockerfile. This file contains instructions for building a Docker image of your app.

Create a file named Dockerfile (no extension) in the root directory of your React project:

dockerfile
# Build stage
FROM node:18-alpine as build

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy all files
COPY . .

# Build the app
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy built files from build stage to nginx serve directory
COPY --from=build /app/build /usr/share/nginx/html

# Copy custom nginx config if needed
# COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port 80
EXPOSE 80

# Start nginx
CMD ["nginx", "-g", "daemon off;"]

This Dockerfile uses a multi-stage build process:

  1. The first stage uses a Node.js image to build your React application
  2. The second stage uses a lightweight Nginx image to serve the built static files

Step 2: Create a .dockerignore File

Similar to .gitignore, a .dockerignore file specifies which files and directories should be excluded when copying files to the Docker image. This helps keep your image size small and prevents unnecessary files from being included.

Create a .dockerignore file in your project root:

node_modules
npm-debug.log
build
.git
.gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local

Step 3: Configure Nginx (Optional)

For more control over your web server configuration, you can create a custom Nginx configuration file. This step is optional but recommended for production deployments.

Create a file named nginx.conf in your project root:

server {
listen 80;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

# Add any additional configuration as needed
# For example, API proxying:
# location /api {
# proxy_pass http://api-server;
# }
}

The try_files directive is crucial for single-page applications like React, as it redirects all requests to index.html, allowing React Router to handle client-side routing.

If you add this file, uncomment the corresponding line in the Dockerfile:

dockerfile
COPY nginx.conf /etc/nginx/conf.d/default.conf

Step 4: Build the Docker Image

Now it's time to build your Docker image. Open your terminal, navigate to your project directory, and run:

bash
docker build -t my-react-app .

Here's what this command does:

  • docker build: Command to build a Docker image
  • -t my-react-app: Tags your image with the name "my-react-app"
  • .: Specifies the build context (current directory)

If successful, you'll see something like:

Successfully built abc123def456
Successfully tagged my-react-app:latest

Step 5: Run the Docker Container Locally

To test your Docker image locally, run:

bash
docker run -p 8080:80 -d my-react-app

Breaking down this command:

  • docker run: Command to start a new container
  • -p 8080:80: Maps port 8080 on your host to port 80 in the container
  • -d: Runs the container in detached mode (in the background)
  • my-react-app: The name of your image

Now you can access your React application by opening http://localhost:8080 in your browser.

Step 6: Configuring Environment Variables

React applications often need environment variables for API URLs, feature flags, or other configuration. There are several ways to handle environment variables in a Docker container:

Option 1: Build-time Environment Variables

For variables that are known and fixed at build time:

dockerfile
# Build stage
FROM node:18-alpine as build

WORKDIR /app

COPY package*.json ./
RUN npm ci
COPY . .

# Set build-time environment variables
ARG API_URL=https://api.example.com
ENV REACT_APP_API_URL=$API_URL

RUN npm run build

# Production stage
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Then, when building the image:

bash
docker build --build-arg API_URL=https://api.production.com -t my-react-app .

Option 2: Runtime Environment Configuration

For more flexibility, especially in different environments, you can inject environment variables at runtime using a script that generates a configuration file when the container starts:

  1. Add a env.js file to your public directory:
javascript
window.env = {
API_URL: '__API_URL__',
OTHER_VAR: '__OTHER_VAR__'
}
  1. Create a script to update this file at container startup:
bash
#!/bin/sh
# save as env-config.sh

# Replace environment variables in env.js
envsubst < /usr/share/nginx/html/env.js.template > /usr/share/nginx/html/env.js

# Start nginx
exec nginx -g 'daemon off;'
  1. Update your Dockerfile:
dockerfile
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
# Copy env.js to a template file
COPY --from=build /app/build/env.js /usr/share/nginx/html/env.js.template
# Copy the shell script
COPY env-config.sh /
RUN chmod +x /env-config.sh
EXPOSE 80
CMD ["/env-config.sh"]
  1. Reference these variables in your React code:
jsx
// Instead of process.env.REACT_APP_API_URL
const apiUrl = window.env.API_URL;
  1. Run your container with environment variables:
bash
docker run -p 8080:80 -e API_URL=https://api.staging.com -d my-react-app

Step 7: Deploying to Production

There are multiple ways to deploy your Docker container to production:

Docker Registry Deployment

  1. Tag your image with your registry information:
bash
docker tag my-react-app username/my-react-app:v1.0.0
  1. Push to a registry (Docker Hub, GitHub Container Registry, etc.):
bash
docker push username/my-react-app:v1.0.0
  1. On your server, pull and run the image:
bash
docker pull username/my-react-app:v1.0.0
docker run -p 80:80 -d username/my-react-app:v1.0.0

Using Docker Compose

For more complex deployments with multiple services:

Create a docker-compose.yml file:

yaml
version: '3'

services:
react-app:
image: username/my-react-app:v1.0.0
ports:
- "80:80"
environment:
- API_URL=https://api.production.com
restart: always

Deploy using:

bash
docker-compose up -d

Using Container Orchestration

For large-scale deployments, consider using Kubernetes or other container orchestration platforms. Here's a basic Kubernetes deployment example:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app
spec:
replicas: 3
selector:
matchLabels:
app: react-app
template:
metadata:
labels:
app: react-app
spec:
containers:
- name: react-app
image: username/my-react-app:v1.0.0
ports:
- containerPort: 80
env:
- name: API_URL
value: "https://api.production.com"
---
apiVersion: v1
kind: Service
metadata:
name: react-app-service
spec:
selector:
app: react-app
ports:
- port: 80
targetPort: 80
type: LoadBalancer

Real-world Example: CI/CD Pipeline with GitHub Actions

Here's how to set up a GitHub Actions workflow for automatically building and deploying your React app with Docker:

Create a file .github/workflows/docker-deploy.yml:

yaml
name: Build and Deploy React App

on:
push:
branches: [ main ]

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: username/my-react-app:latest,username/my-react-app:${{ github.sha }}
build-args: |
API_URL=${{ secrets.API_URL }}

# Optional: Deploy to your server
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull username/my-react-app:latest
docker stop react-app || true
docker rm react-app || true
docker run -d --name react-app -p 80:80 username/my-react-app:latest

This workflow:

  1. Builds your Docker image when you push to the main branch
  2. Pushes the image to Docker Hub
  3. Connects to your deployment server via SSH
  4. Pulls the latest image and replaces the running container

Performance Optimization

To optimize your Docker image for production React deployments:

  1. Minimize image size:

    • Use the Alpine variants of base images
    • Clean up npm cache in the same RUN statement
    dockerfile
    RUN npm ci && npm run build && npm cache clean --force
  2. Implement proper caching:

    • Copy package files first before other files to leverage Docker's build cache
  3. Set up compression in Nginx: Add to your nginx.conf:

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;

Security Best Practices

  1. Use non-root users: Modify your Dockerfile to run as a non-root user:

    dockerfile
    FROM nginx:alpine
    # Add a non-root user
    RUN adduser -D reactuser
    # Change ownership of the application files
    COPY --from=build /app/build /usr/share/nginx/html
    RUN chown -R reactuser:reactuser /usr/share/nginx/html
    # Switch to the non-root user
    USER reactuser
  2. Scan for vulnerabilities: Use tools like Docker Scout or Snyk to scan your containers:

    bash
    docker scout cves my-react-app:latest
  3. Use secrets management: Never hardcode sensitive information in your Dockerfile.

Troubleshooting Common Issues

1. "Module not found" errors in the build stage

Problem: The Docker build fails with module not found errors.

Solution: Make sure your .dockerignore file isn't excluding necessary files and that all dependencies are properly listed in package.json.

2. White page/blank screen after deployment

Problem: The app loads but shows a blank page.

Solution: Check browser console errors. It's often caused by:

  • Missing the try_files $uri $uri/ /index.html; directive in Nginx config
  • Incorrect base path configuration in React

3. Cannot connect to the Docker daemon

Problem: docker build or docker run commands fail with "Cannot connect to the Docker daemon" error.

Solution: Make sure Docker is running on your system:

bash
# On Linux/macOS
sudo systemctl start docker
# On macOS (with Docker Desktop)
open -a Docker

4. Cannot access the React app on the specified port

Problem: You can't access your app at http://localhost:8080

Solution: Check if the container is running and the ports are correctly mapped:

bash
docker ps

Summary

In this guide, you've learned how to:

  1. Create a Dockerfile for a React application using a multi-stage build
  2. Build and run a Docker container locally
  3. Configure environment variables for different deployment environments
  4. Deploy your containerized React app to production
  5. Set up CI/CD with GitHub Actions
  6. Optimize and secure your Docker containers
  7. Troubleshoot common issues

Containerizing your React applications with Docker provides a consistent, portable, and scalable deployment solution. By following the steps outlined in this guide, you can ensure that your React applications deploy reliably across different environments.

Additional Resources

Exercises

  1. Containerize an existing React application using the multi-stage build pattern.
  2. Set up a development Docker configuration that supports hot reloading.
  3. Create a Docker Compose file that runs your React app alongside a backend API service.
  4. Implement a GitHub Actions workflow to automatically build and deploy your Docker image.
  5. Optimize your Docker image to reduce its size and improve security.

By mastering Docker deployments for your React applications, you'll gain flexibility in how and where you deploy your apps while ensuring consistency across environments.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)