Dockerize Your React App
A complete, step-by-step tutorial on how to wrap your React application into a Docker container. We will build a custom image, run it locally, and push it to Docker Hub so anyone can pull and run it.
1 Prerequisites
Ensure you have the following installed on your machine before we begin.
Docker Desktop
Must be running
Node.js (LTS)
v18 or higher
Docker Hub
Free Account
2 Application Setup
First, let's create a basic Vite React application. Open your terminal and run:
npm create vite@latest react-docker-demo -- --template react
cd react-docker-demo
npm install lucide-react
npm install -D tailwindcss@3 postcss autoprefixer
npx tailwindcss init -p
Now, replace the content of your src/App.jsx with the
code below. This creates our interactive "Container World" demo
page.
import React, { useState, useEffect } from 'react';
import { Box, Package, Cloud, RefreshCw, Trash2, Github, Globe, Linkedin, Youtube, Instagram, Mail } from 'lucide-react';
const ContainerBox = ({ id, color, removeContainer }) => {
const [isHovered, setIsHovered] = useState(false);
return (
<div
className={`transform transition-all duration-500 ease-out hover:scale-105 animate-in fade-in slide-in-from-bottom-4`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div
className={`relative w-32 h-32 ${color} rounded-lg shadow-lg border-4 border-black/20 flex flex-col items-center justify-center p-2 m-2 cursor-pointer group`}
onClick={() => removeContainer(id)}
>
<div className="absolute top-0 left-0 w-full h-full bg-white/5 bg-[linear-gradient(45deg,transparent_25%,rgba(255,255,255,0.2)_50%,transparent_75%,transparent_100%)] bg-[length:10px_10px]" />
{/* Container Ribs */}
<div className="absolute inset-0 flex justify-between px-4 py-2 opacity-30 pointer-events-none">
<div className="w-1 h-full bg-black/20"></div>
<div className="w-1 h-full bg-black/20"></div>
<div className="w-1 h-full bg-black/20"></div>
</div>
<Package size={32} className="text-white drop-shadow-md z-10" />
<span className="text-xs font-mono font-bold text-white mt-2 z-10 bg-black/30 px-2 rounded">
{id.slice(0, 8)}
</span>
{isHovered && (
<div className="absolute inset-0 flex items-center justify-center bg-red-500/80 rounded-sm z-20 backdrop-blur-sm transition-opacity">
<Trash2 className="text-white" />
</div>
)}
</div>
</div>
);
};
export default function App() {
const [containers, setContainers] = useState([]);
const [count, setCount] = useState(0);
const colors = [
'bg-blue-500', 'bg-blue-600', 'bg-indigo-500',
'bg-cyan-500', 'bg-teal-500', 'bg-sky-500'
];
const authorLinks = [
{ icon: Globe, label: 'Portfolio', url: 'https://thanvirassif.com', color: 'hover:text-purple-400' },
{ icon: Linkedin, label: 'LinkedIn', url: 'https://linkedin.com/in/thanvir-assif-1b3435203', color: 'hover:text-blue-400' },
{ icon: Youtube, label: 'YouTube', url: 'https://youtube.com/@thanvirassif731', color: 'hover:text-red-500' },
{ icon: Github, label: 'GitHub', url: 'https://github.com/thanvirassif731', color: 'hover:text-white' },
{ icon: Instagram, label: 'Instagram', url: 'https://instagram.com/iamassif7', color: 'hover:text-pink-500' },
{ icon: Mail, label: 'Email', url: 'mailto:contact@thanvirassif.com', color: 'hover:text-yellow-400' },
];
const spawnContainer = () => {
const newContainer = {
id: crypto.randomUUID(),
color: colors[Math.floor(Math.random() * colors.length)],
timestamp: new Date().toLocaleTimeString()
};
setContainers(prev => [...prev, newContainer]);
setCount(c => c + 1);
};
const removeContainer = (id) => {
setContainers(prev => prev.filter(c => c.id !== id));
};
const clearAll = () => {
setContainers([]);
setCount(0);
};
return (
<div className="min-h-screen bg-slate-900 text-slate-100 font-sans selection:bg-blue-500 selection:text-white overflow-x-hidden">
{/* Navbar */}
<nav className="border-b border-slate-800 bg-slate-950/50 backdrop-blur-md sticky top-0 z-50">
<div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<img src="/v2.png" alt="Logo" className="w-10 h-10 rounded-full" />
<span className="font-bold text-xl tracking-tight">DockerHub<span className="text-blue-500">Demo</span></span>
</div>
<div className="flex gap-4 text-sm font-medium text-slate-400">
<div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
<span>v1.0.0</span>
</div>
<div className="hidden md:flex items-center gap-1">
<Cloud size={16} />
<span>Running on Port 3000</span>
</div>
</div>
</div>
</nav>
<main className="max-w-6xl mx-auto px-4 py-12">
{/* Hero Section */}
<div className="text-center mb-16 space-y-6">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-blue-500/10 text-blue-400 text-sm font-mono border border-blue-500/20 mb-4">
<Globe size={14} />
<span>Hello from the Container World</span>
</div>
<h1 className="text-5xl md:text-7xl font-extrabold tracking-tight text-white mb-4">
Ship it like a <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-cyan-300">Pro</span>.
</h1>
<p className="text-xl text-slate-400 max-w-2xl mx-auto leading-relaxed">
This is a <span className="text-white font-semibold">Docker image code</span> that is hosted in Docker Hub.
It's immutable, stateless, and ready to scale.
</p>
<div className="bg-slate-800/50 p-6 rounded-xl border border-slate-700 max-w-3xl mx-auto mt-8 shadow-2xl">
<div className="flex items-center gap-3 mb-4 border-b border-slate-700 pb-4">
<div className="flex gap-2">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
</div>
<span className="text-sm text-slate-500 font-mono">terminal — -zsh</span>
</div>
<div className="font-mono text-left space-y-2 text-sm md:text-base">
<div className="flex gap-2">
<span className="text-green-400">➜</span>
<span className="text-cyan-300">~</span>
<span className="text-slate-300">docker pull student-repo/react-demo:latest</span>
</div>
<div className="text-slate-500 pl-4">Detailed info... Download complete.</div>
<div className="flex gap-2">
<span className="text-green-400">➜</span>
<span className="text-cyan-300">~</span>
<span className="text-slate-300">docker run -p 3000:80 student-repo/react-demo</span>
</div>
<div className="text-slate-500 pl-4">Server running at http://localhost:3000</div>
</div>
</div>
</div>
{/* Interactive Demo Section */}
<div className="bg-slate-800 rounded-2xl p-8 border border-slate-700 shadow-xl relative overflow-hidden mb-16">
<div className="absolute top-0 right-0 p-32 bg-blue-500/10 rounded-full blur-3xl -mr-16 -mt-16 pointer-events-none"></div>
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between mb-8 gap-6">
<div>
<h2 className="text-3xl font-bold text-white mb-2">Container Factory</h2>
<p className="text-slate-400">
You can recreate as many containers as you want!
</p>
</div>
<div className="flex gap-3">
<button
onClick={clearAll}
className="px-4 py-2 rounded-lg font-bold text-slate-400 hover:text-white hover:bg-slate-700 transition-colors flex items-center gap-2"
disabled={containers.length === 0}
>
<Trash2 size={18} />
Prune All
</button>
<button
onClick={spawnContainer}
className="px-6 py-3 rounded-lg bg-blue-600 hover:bg-blue-500 text-white font-bold shadow-lg shadow-blue-500/25 active:scale-95 transition-all flex items-center gap-2"
>
<RefreshCw size={20} className={containers.length > 0 ? "animate-spin-once" : ""} />
Spawn Container
</button>
</div>
</div>
{/* Container Grid */}
<div className="bg-slate-900/50 rounded-xl min-h-[300px] border-2 border-dashed border-slate-700 p-6 flex flex-wrap content-start items-start justify-center md:justify-start gap-2 relative">
{containers.length === 0 && (
<div className="absolute inset-0 flex flex-col items-center justify-center text-slate-600">
<Box size={48} className="mb-2 opacity-50" />
<p>No containers running. Click Spawn to scale up!</p>
</div>
)}
{containers.map((container) => (
<ContainerBox
key={container.id}
{...container}
removeContainer={removeContainer}
/>
))}
</div>
<div className="mt-4 flex justify-between items-center text-xs font-mono text-slate-500">
<div>Running Instances: <span className="text-blue-400">{containers.length}</span></div>
<div>Total Spawned: {count}</div>
</div>
</div>
{/* Author Details Section */}
<div className="border-t border-slate-800 pt-12 animate-in slide-in-from-bottom-8 fade-in duration-700">
<div className="flex flex-col items-center justify-center text-center space-y-6">
<div className="space-y-2">
<h3 className="text-2xl font-bold text-white">Built by Thanvir Assif 👨💻</h3>
<p className="text-slate-400 max-w-lg mx-auto">
Got questions about Docker or this code? Hit me up for discussions & clarification!
</p>
</div>
<div className="flex flex-wrap justify-center gap-4">
{authorLinks.map((link, index) => (
<a
key={index}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={`flex items-center gap-2 px-4 py-2 bg-slate-800 rounded-full border border-slate-700 text-slate-400 transition-all duration-300 hover:scale-105 hover:bg-slate-700 hover:border-slate-600 hover:shadow-lg ${link.color}`}
>
<link.icon size={18} />
<span className="font-medium text-sm">{link.label}</span>
</a>
))}
</div>
</div>
</div>
</main>
<footer className="border-t border-slate-800 mt-12 py-8 text-center text-slate-500 text-sm">
<p>Built for educational purposes. Host this on Docker Hub.</p>
</footer>
<style>{`
@keyframes spin-once {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin-once {
animation: spin-once 0.5s ease-in-out;
}
`}</style>
</div>
);
}
Also, update your src/index.css to include Tailwind:
@import "tailwindcss";
3 The Dockerfile
Create a new file named Dockerfile (no extension) in
your root folder. This tells Docker how to build your application.
FROM node:18-alpine as build
# Set working directory
WORKDIR /app
# Install Node.js dependencies
COPY package*.json ./
RUN npm install
# Copy application source code
COPY . .
# Build the application
RUN npm run build
# Expose the application port
EXPOSE 3000
# Start the application
CMD ["npx", "serve", "-s", "dist", "-l", "3000"]
What's happening here?
- We use Node.js 18 as our base image.
- We copy our code and install dependencies.
-
We run
npm run buildto create the production files. -
Finally, we use
serveto host thedistfolder on port 3000.
4 Build & Run Locally
Now it's time to build the "Image" (the blueprint) and run the "Container" (the running instance).
# Format: docker build -t [image-name] .
docker build -t react-docker-demo .
# Format: docker run -p [host-port]:[container-port] [image-name]
docker run -p 3000:3000 react-docker-demo
🚀 Verification
Open http://localhost:3000 in your browser. You should see the Container Factory app!
5 Push to Docker Hub
Once your app is working, share it with the world by pushing it to Docker Hub.
Step A: Login to Docker
docker login
Enter your Docker Hub username and password when prompted.
Step B: Tag the Image
You must tag your local image with your Docker Hub username.
# Format: docker tag [local-image] [username]/[repo-name]:[tag]
docker tag react-docker-demo thanvirassif/react-demo:v1
Step C: Push the Image
# Format: docker push [username]/[repo-name]:[tag]
docker push thanvirassif/react-demo:v1
docker run -p 3000:3000 thanvirassif/react-demo:v1