Ash-Docs
Beginner's Guide 2026

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

js

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:

Terminal
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.

src/App.jsx
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:

src/index.css
@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.

Dockerfile
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 build to create the production files.
  • Finally, we use serve to host the dist folder on port 3000.

4 Build & Run Locally

Now it's time to build the "Image" (the blueprint) and run the "Container" (the running instance).

1. Build the Image
# Format: docker build -t [image-name] .
docker build -t react-docker-demo .
2. Run the Container
# 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
💡 Pro Tip: Anyone in the world can now run your app with just one command:
docker run -p 3000:3000 thanvirassif/react-demo:v1
Thanvir Assif

Created by Thanvir Assif

Cloud & DevOps Engineer | AWS Certified | Full Stack Developer. Helping professionals and enthusiasts master cloud deployments.

© 2026 Thanvir Assif. All rights reserved.