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
Build the production version
npm run build
What this command does:
- Takes all my React code
- Optimizes it (removes comments, shortens variable names)
- Minifies files (makes them smaller)
- Creates a
buildfolder 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
Verify the build folder
ls
I could see the build folder (blue or highlighted in terminal).
ls build
Output:
index.html static asset-manifest.json
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
The Complete Dockerfile
FROM nginx:alpine
COPY build /usr/share/nginx/html
EXPOSE 80
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
Output:
FROM nginx:alpine
COPY build /usr/share/nginx/html
EXPOSE 80
Step 3: Building the Frontend Image
sudo docker build -t frontend-app .
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
Verify the image
sudo docker images
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
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
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
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
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
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
Step 6: Stopping the Container
To stop the frontend container, I went to its terminal and pressed:
Ctrl + C
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
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
- Frontend containers are simpler — Just static files + web server
- nginx is the industry standard — 40% of all websites use it
-
Production builds are essential —
npm run buildcreates optimized files - Alpine images are tiny — My frontend image is only 23MB
- 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!
Top comments (0)