Dockerize React app for dev and prod

Dockerize React app for dev and prod

Okay, you have a frontend React app and you want to serve it via Docker. Let's do that!

In this wiki, we will dockerize both the development and production environment via separate Dockerfiles.

Step 1: Project setup

Initialized a pretty standard react project using the default create react app (CRA) template. Or pick your existing React app. Below is the sample project folder structure.

├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── index.css
│   ├── index.js
│   └── logo.svg
├── package.json
└── yarn.lock

Step 2: Init .dockerignore

Add a .dockerignore file, this will help us ignore node_modules, .env etc

.git
.gitignore
**/node_modules
**/npm-debug.log
build

Step 3: Dockerize Development env

Init Dockerfile

Start by adding a Dockerfile.dev

FROM node:14-alpine AS development
ENV NODE_ENV development

# Add a work directory
WORKDIR /app

# Cache and Install dependencies
COPY package.json .
COPY yarn.lock .

#RUN yarn install
RUN npm i

# Copy app files
COPY . .

# Expose port
EXPOSE 3000

# Start the app
CMD [ "yarn", "start" ]

Init docker-compose

Create a docker-compose.dev.yml. Additionally, we will mount our code in a volume so that our code changes are in sync with the container during development.

version: "3.8"

services:
  app:
    container_name: app-dev-c
    image: app-dev-i
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - ./src:/app/src
    ports:
      - "3000:3000"

Let's start our React app for development!

docker-compose -f docker-compose.dev.yml up

To make life easier add docker-compose commands to package.json

"docker-dev-up": "docker-compose -f docker-compose.dev.yml up"
"docker-dev-down": "docker-compose -f docker-compose.dev.yml down"

Let's check our container!

> docker ps

REPOSITORY          TAG                   IMAGE ID       CREATED              SIZE
app-dev            latest                5867f4e40c98   About a minute ago    436MB

Visit the app at http://localhost:3000


Step 4: Dockerize Production env

Let's use nginx to serve our static assets and help resolve routes when we're using React Router or any kind of routing.

Configure nginx

Create a nginx.conf with the below content. This will help handle URI changes during routing.

server {
  listen 80;

  location / {
    root /usr/share/nginx/html/;
    include /etc/nginx/mime.types;
    try_files $uri $uri/ /index.html;
  }
}

Init Dockerfile

Start by adding a Dockerfile.prod

FROM node:14-alpine AS builder
ENV NODE_ENV production

# Add a work directory
WORKDIR /app

# Cache and Install dependencies
COPY package.json .
COPY yarn.lock .
RUN npm i

# Copy app files
COPY . .

# Build the app
RUN npm run build

# Bundle static assets with nginx
FROM nginx:1.21.0-alpine as production
ENV NODE_ENV production

# Copy built assets from builder
COPY --from=builder /app/build /usr/share/nginx/html

# Add your nginx.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port
EXPOSE 80

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

Init docker-compose

Add a docker-compose.prod.yml file

version: "3.8"

services:
  app:
    container_name: app-prod-c
    image: app-prod-i
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
        - "8080:80"

Build production image

docker-compose -f docker-compose.prod.yml build

Let's check out our built production image

> docker images

REPOSITORY          TAG                   IMAGE ID       CREATED              SIZE
app-prod           latest                c5db8d308bb9   About a minute ago   23.1MB

Start our production container on port 80 with the name react-app

docker run -p 8080:80 --name react-app app-prod

Visit the app at http://localhost:8080

Here is how the final project struct looks:

├── node_modules
├── public
│   ├── index.html
│   ...
│   └── manifest.json
├── src
│   ├── App.css
│   ...
│   └── index.js
├── package.json
├── yarn.lock
├── .dockerignore
├── Dockerfile.dev
├── Dockerfile.prod
├── docker-compose.dev.yml
└── docker-compose.prod.yml

Hurrayyy!! We can now use docker in our workflow and deploy our production images faster to any platform.