Do you want to learn how to dockerize a React app and make it ready for deployment? In this article, I’ll show you how to do that with a practical example from my GitHub repo.
Prerequisites
Ensure you have a foundational understanding of React, Docker, and basic shell scripting. Ensure Docker and Node.js are installed on your machine, and you have a code editor ready for some hands-on action!
Understanding the React Application Structure
The React application in our repository is structured with various components, dependencies, and configurations. The app is a simple goal tracker that lets you add, edit, and delete your goals. It uses React Hooks, React Router, and Axios to create a dynamic and interactive UI. The app also communicates with a backend API that uses MongoDB as the database. The app’s structure is as follows:
package.json
: This file defines the dependencies and scripts for the app. We use libraries such as react, react-dom, react-router-dom, axios, and bootstrap.src/index.js
: This file renders the main App component and wraps it with a BrowserRouter component to enable routing.src/App.js
: This file defines the App component, which contains the main layout and logic of the app. It uses useState and useEffect hooks to manage the state and fetch data from the API. It also uses Switch and Route components to render different pages based on the URL path.src/components
: This directory contains the reusable components for the app, such as GoalList, GoalForm, GoalItem, Navbar, and Footer. Each component has its file with a .jsx extension.src/pages
: This directory contains the components for the different pages of the app, such as Home, About, and NotFound. Each page has its file with a .jsx extension.src/styles
: This directory contains the custom CSS styles for the app. We use Bootstrap as a base framework and override some styles in our files.
Crafting a Multi-Stage Dockerfile
The Dockerfile in the frontend
directory employs a multi-stage build to create an optimized Docker image:
# frontend/Dockerfile
FROM node:16.3.0-alpine AS prod
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
RUN npm run build
FROM nginx:alpine
WORKDIR /usr/local/bin
COPY --from=prod /app/build /usr/share/nginx/html
COPY generate-config.sh .
COPY custom-nginx.template /etc/nginx/conf.d/
RUN chmod +x generate-config.sh
EXPOSE 80
ENTRYPOINT [ "/bin/sh", "generate-config.sh"]
- Stage 1: Building the React Application
- Utilizes
node:16.3.0-alpine
for a lightweight build environment. Installs dependencies and builds the application usingnpm
. - Utilizes
node:16.3.0-alpine
for a lightweight build environment. - Installs dependencies and builds the application using
npm
. - Stage 2: Setting Up the Nginx Server
- Adopts
nginx:alpine
to serve the built React application. Copies the build artifacts and custom Nginx configuration for serving the application. - Adopts
nginx:alpine
to serve the built React application. - Copies the build artifacts and custom Nginx configuration for serving the application.
Parameterizing Nginx Configuration
The custom Nginx configuration (custom-nginx.template
) is designed to proxy API requests to a backend service and serve the React application for other routes. A shell script (generate-config.sh
) dynamically generates the final Nginx configuration by substituting environment variables, providing flexibility to define the backend host at runtime.
# frontend/custom-nginx.template
upstream backend {
server ${BACKEND_HOST};
}
server {
listen 80;
location /api/ {
proxy_pass http://backend$request_uri;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
# frontend/generate-config.sh
#!/bin/sh
envsubst '$BACKEND_HOST' < /etc/nginx/conf.d/custom-nginx.template > /etc/nginx/conf.d/default.conf;
exec nginx -g "daemon off;";
Orchestrating Containers with Docker Compose
Docker Compose is a tool that lets you define and run multiple containers using a YAML file. It simplifies the process of building, running, and connecting containers.
The docker-compose.yaml
file orchestrates the frontend and backend services, ensuring they can communicate and are deployed cohesively:
# docker-compose.yaml
version: '3.8'
services:
mongodb:
container_name: mongodb
image: mongo:6.0
volumes:
- dbdata:/data/db
networks:
- goals-net
env_file:
- .env
goals-backend:
image: goals-backend:1.0.0
container_name: goals-backend
build:
context: ./backend
dockerfile: Dockerfile
ports:
- 8000:8000
networks:
- goals-net
depends_on:
- mongodb
env_file:
- .env
goals-frontend:
image: goals-frontend:1.0.0
container_name: goals-frontend
build:
context: ./frontend
dockerfile: Dockerfile
networks:
- goals-net
ports:
- 3000:80
env_file:
- .env
depends_on:
- goals-backend
networks:
goals-net:
driver: bridge
volumes:
dbdata:
Local Development & Testing
To run the Dockerized React app locally, use docker-compose up
to build and start the containers. Ensure the application communicates with the backend API effectively and debug using Docker logs and browser developer tools.
CI/CD Considerations for Dockerized React Apps
Integrating CI/CD pipelines, like GitHub Actions, can automate the build, test, and deployment of the Dockerized React app. Define workflows in .github/workflows
to trigger on code pushes or pull requests, ensuring continuous delivery and integration.
Build Worfklow
This workflow triggers on every push or pull request to the main branch. It builds and tests both frontend and backend images using Docker commands. It also pushes the images to a Docker registry (such as Docker Hub or GitHub Packages) using secrets to store credentials. We have commented it out on our repo to avoid additional build runs. The code we've used is the following:
name: CI
on:
pull_request:
types:
- review_requested
branches:
- main
jobs:
backend:
name: Backend
runs-on: ubuntu-20.04
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Install and Test Backend
run: |
cd backend
npm install
npm test
frontend:
name: Frontend
runs-on: ubuntu-20.04
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Install and Test Frontend
run: |
cd frontend
npm install
npm test
- name: Build Frontend
run: |
cd frontend
npm run build
Best Practices & Tips
- Optimize Dockerfile: Leverage Docker layer caching by ordering instructions to install dependencies before copying all source files.
- Manage Images: Regularly prune unused Docker images and containers to manage disk usage.
- Security: Ensure only necessary ports are exposed and use trusted base images.
Conclusion
I hope you enjoyed this guide on how to dockerize a React app and make it deployable. You learned how to use Docker, Nginx, and Docker Compose to set up a uniform environment for your app in different phases of development, testing, and production. This will help you streamline your DevOps process and create better software.
If you liked this article, you can check my other blog posts and my mentorships plans at my MentorCruise Profile.
Thanks for reading!
References
Source code: GitHub repository