DEV Community

Michelle
Michelle

Posted on

30-Day Cloud & DevOps Challenge: Day 6 — Dockerizing My React Frontend

Yesterday, I packaged my backend into a Docker container. Today, I did the same for my React frontend.

But there's a twist, frontend containers are DIFFERENT. No Node.js running. No npm start. Just pure, static files served by nginx.

And I learned why nginx is the king of web servers.


First: How Frontend Docker is Different

Backend Container Frontend Container
Runs Node.js server Runs nginx (web server)
Needs npm start Just serves static files
Code changes often Build once, serve forever
Port 5000 Port 80 (standard web port)
~135MB image ~23MB image

Analogy:

  • Backend = Restaurant kitchen (active cooking, handling requests)
  • Frontend = Dining room + menu (just shows what's ready)

Step 1: Building React for Production

Right now, I run React with npm start (development mode). For Docker, I need a production build, optimized, minified, and ready to serve.

Navigate to frontend folder

cd ~/Desktop/Production-Ready-Microservices-Platform/frontend
Enter fullscreen mode Exit fullscreen mode

Build the production version

npm run build
Enter fullscreen mode Exit fullscreen mode

What this command does:

  • Takes all my React code
  • Optimizes it (removes comments, shortens variable names)
  • Minifies files (makes them smaller)
  • Creates a build folder with everything needed

What I saw:

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:
  40.21 kB  build/static/js/main.xxxxx.js
  1.78 kB   build/static/css/main.xxxxx.css
Enter fullscreen mode Exit fullscreen mode

Verify the build folder

ls
Enter fullscreen mode Exit fullscreen mode

I could see the build folder (blue or highlighted in terminal).

ls build
Enter fullscreen mode Exit fullscreen mode

Output:

index.html  static  asset-manifest.json
Enter fullscreen mode Exit fullscreen mode

What's inside:

  • index.html — The main HTML file
  • static/ — JavaScript and CSS files
  • asset-manifest.json — Map of all assets

Step 2: Creating the Dockerfile

Why nginx?

nginx (pronounced "engine-x") is the most popular web server in the world. It's:

Feature Why It Matters
Fast Can handle 10,000+ connections simultaneously
Lightweight Alpine version is only 23MB
Reliable Used by Netflix, Airbnb, Uber, Dropbox
Simple Just point it to your files and it works

Create the Dockerfile

nano Dockerfile
Enter fullscreen mode Exit fullscreen mode

The Complete Dockerfile

FROM nginx:alpine
COPY build /usr/share/nginx/html
EXPOSE 80
Enter fullscreen mode Exit fullscreen mode

That's it! Three lines. Frontend containers are much simpler than backend.

Understanding Each Line

Line What it does Why
FROM nginx:alpine Start with nginx on Alpine Linux Tiny base image (23MB)
COPY build /usr/share/nginx/html Put React files where nginx looks nginx serves from this folder by default
EXPOSE 80 Document the port Standard web port

What is /usr/share/nginx/html?

This is nginx's default document root — the folder where it looks for files to serve. When you put index.html there, nginx automatically serves it.

Save and verify

# Save: Ctrl+O, Enter, Ctrl+X

# Verify the file
cat Dockerfile
Enter fullscreen mode Exit fullscreen mode

Output:

FROM nginx:alpine
COPY build /usr/share/nginx/html
EXPOSE 80
Enter fullscreen mode Exit fullscreen mode

Step 3: Building the Frontend Image

sudo docker build -t frontend-app .
Enter fullscreen mode Exit fullscreen mode

Breaking down the command:

Part Meaning
sudo docker build Build an image
-t frontend-app Tag it as "frontend-app"
. Use Dockerfile in current directory

What I saw:

[+] Building 0.5s (5/5) FINISHED
 => [1/2] FROM nginx:alpine
 => [2/2] COPY build /usr/share/nginx/html
 => exporting to image
 => => naming to docker.io/library/frontend-app:latest
Enter fullscreen mode Exit fullscreen mode

Verify the image

sudo docker images
Enter fullscreen mode Exit fullscreen mode

Output:

REPOSITORY     TAG       IMAGE ID       CREATED         SIZE
frontend-app   latest    xxxxxxx        1 minute ago    23MB
backend-app    latest    e0e0904815be   1 day ago       135MB
nginx          alpine    xxxxxxx        2 weeks ago     23MB
Enter fullscreen mode Exit fullscreen mode

Notice the size difference:

  • Backend image: 135MB (Node.js + dependencies)
  • Frontend image: 23MB (just static files + nginx)

Step 4: Running the Frontend Container

sudo docker run -p 8080:80 frontend-app
Enter fullscreen mode Exit fullscreen mode

Understanding the port mapping:

Part Meaning
-p 8080:80 Map port 8080 (my computer) → port 80 (container)
frontend-app Which image to use

Why port 80? Port 80 is the standard web port. Browsers use it by default when you visit http://example.com.

Why map to 8080? Port 80 is often already in use. 8080 is a common alternative.

What I saw: Nothing in the terminal. That's normal! nginx runs silently.


Step 5: Testing the Container

Open browser

I went to: http://localhost:8080

My React app appeared! The same one I built on Day 3.

Check running containers

In a new terminal:

sudo docker ps
Enter fullscreen mode Exit fullscreen mode

Output:

CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS
b8194975c784   frontend-app   "/docker-entrypoint.…"   3 minutes ago   Up 3 minutes   0.0.0.0:8080->80/tcp
Enter fullscreen mode Exit fullscreen mode

What this shows:

  • Container is running
  • Port 8080 on my computer → port 80 inside container
  • Status: Up (healthy!)

Running Both Containers

Now I have TWO containers running:

Container Image Port Purpose
frontend-app nginx:alpine 8080 Serves React app
backend-app node:18-alpine 3001 API endpoints

To see both:

sudo docker ps
Enter fullscreen mode Exit fullscreen mode

Output:

CONTAINER ID   IMAGE           COMMAND                  PORTS
b8194975c784   frontend-app    "/docker-entrypoint.…"   0.0.0.0:8080->80/tcp
a1b2c3d4e5f6   backend-app     "docker-entrypoint.s…"   0.0.0.0:3001->5000/tcp
Enter fullscreen mode Exit fullscreen mode

Step 6: Stopping the Container

To stop the frontend container, I went to its terminal and pressed:

Ctrl + C
Enter fullscreen mode Exit fullscreen mode

The container stopped gracefully.


What I Learned

Why nginx for Frontend?

Alternative Pros Cons
serve (npm package) Simple Needs Node.js installed
http-server Easy Not production-ready
nginx Fast, reliable, tiny Slight learning curve

Production Build vs Development

Aspect npm start (dev) npm run build (prod)
File size Large (unminified) Small (minified)
Source maps Yes (debugging) No (smaller)
Hot reload Yes No
Performance Slow Fast
Use case Development Production/Docker

The Build Process Explained

React Source Code (JSX, Components)
        ↓
    npm run build
        ↓
Optimized Files (minified, bundled)
        ↓
   build/ folder
        ↓
    COPY to nginx
        ↓
     Served to users
Enter fullscreen mode Exit fullscreen mode

Mistakes I Made (And Fixed)

Mistake 1: Trying to use npm start in Docker

What I thought: I could just do CMD ["npm", "start"] like backend

Why it's wrong: React's dev server isn't meant for production containers

Fix: Use nginx to serve static files

Mistake 2: Wrong COPY destination

What I tried: COPY build /var/www/html

Why it's wrong: nginx default folder is /usr/share/nginx/html

Fix: Use the correct nginx document root

Mistake 3: Forgetting to run npm run build

What happened: Docker couldn't find the build folder

Error: COPY failed: stat build: file does not exist

Fix: Always run npm run build before building the Docker image


Docker Commands Cheat Sheet (Updated)

Command What it does
npm run build Create production build of React app
docker build -t name . Build image from Dockerfile
docker images List all images
docker run -p HOST:CONTAINER image Run container from image
docker ps List running containers
docker ps -a List ALL containers (including stopped)
docker stop container_id Stop running container
docker rm container_id Remove stopped container
docker rmi image_id Remove image

Key Takeaways

  1. Frontend containers are simpler — Just static files + web server
  2. nginx is the industry standard — 40% of all websites use it
  3. Production builds are essentialnpm run build creates optimized files
  4. Alpine images are tiny — My frontend image is only 23MB
  5. Port mapping is still important — Container port 80 → host port 8080

Resources

Let's Connect!

Have you containerized a frontend app before? What web server do you prefer? nginx, Apache, or something else?

Drop a comment or connect on LinkedIn. Let's learn together!


This is Day 6 of my 30-Day Cloud & DevOps Challenge. Follow along as I build a complete microservices platform from scratch!





Enter fullscreen mode Exit fullscreen mode

Top comments (0)