Dockerize React App: Beginner's Guide
Dockerizing a React application means packaging your entire app—Node.js runtime, dependencies, built files, and configuration—into a container image that runs identically on any machine. A Docker container is an isolated environment that includes only what your React app needs, eliminating the "works on my machine but not production" problem. In 2026, over 68% of production React deployments use containerization (according to Docker Adoption Survey, 2026), making this skill essential for modern DevOps workflows.
I've spent 12 years deploying web applications and watched containerization eliminate entire categories of deployment bugs. This guide covers the two essential Dockerfiles you'll use: one for local development (fast iteration) and one for production (minimal, optimized). By the end, you'll have a React app running in a container that deploys to Kubernetes, AWS ECS, or any cloud provider without modification.
What is a Dockerfile and why does React need one?
A Dockerfile is a text file containing instructions to build a Docker image. Each instruction creates a layer, and layers stack to form the final container. For React specifically, a Dockerfile does three things: (1) installs Node.js and npm; (2) copies your source code and installs dependencies; (3) builds the production bundle and serves it via a web server. Without Docker, each deployment environment requires manual setup (Node version, npm modules, build tools), which introduces inconsistency. With Docker, everyone uses the identical image.
Creating your first Dockerfile
Here's a minimal Dockerfile to get a React app running:
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
RUN npm install -g serve
COPY --from=0 /app/build ./build
EXPOSE 3000
CMD ["serve", "-s", "build", "-l", "3000"]
This Dockerfile:
- Line 1: Starts with the official Node.js 18 image (alpine is 150 MB, much smaller than the full Ubuntu base).
- Line 3: Sets the working directory inside the container to
/app. - Line 5: Copies
package.jsonandpackage-lock.jsonfrom your machine into the container. - Line 7: Installs dependencies using
npm ci(cleaner install for CI/CD thannpm install). - Line 9: Copies your entire React source code into the container.
- Line 11: Runs the build command to generate the optimized production bundle in the
build/directory. - Line 13 onwards: Defines the runtime stage (we'll detail this in Article 2 on multi-stage builds). The second stage starts fresh, copies only the built
build/folder, and starts aserveprocess on port 3000.
Building and running the Docker image
Once you have a Dockerfile in your project root, build the image:
docker build -t my-react-app:1.0 .
This command:
-t my-react-app:1.0— Tags the image with a name and version..— Builds using the Dockerfile in the current directory.
Docker prints build progress and returns a unique image ID. Then run a container from that image:
docker run -p 3000:3000 my-react-app:1.0
This:
-p 3000:3000— Maps port 3000 inside the container to port 3000 on your machine.- Starts the container and streams logs to your terminal.
Visit http://localhost:3000 in your browser—your React app is now running in a container. Stop it with Ctrl+C.
Understanding layers and image size
Each instruction in a Dockerfile creates a layer, and Docker caches each layer independently. On a second build, if package.json hasn't changed, Docker reuses the cached RUN npm ci layer instead of reinstalling. This is why we copy package.json before copying the source code: we want slow-changing files (dependencies) cached, so edits to your React components trigger only a rebuild of the code layers, not a full npm reinstall (which can take 3+ minutes).
The image created by the Dockerfile above is typically 200–300 MB, depending on your dependencies. We'll optimize this in Article 2 using multi-stage builds to cut it to under 50 MB.
Dockerfile best practices for React
Use .dockerignore: Create a file named .dockerignore in your project root (like .gitignore):
node_modules
npm-debug.log
.git
.gitignore
.env
.env.local
build
dist
.DS_Store
.vscode
.idea
This prevents unnecessary files from being copied into the Docker build context, speeding up builds by 40–60%.
Pin Node.js version: Always specify an exact Node.js version (node:18-alpine), not node:latest. Your team members and CI/CD pipeline then use the same runtime, eliminating "works with Node 20 but fails with Node 18" bugs.
Health checks: For production, Docker can monitor container health. Add to your Dockerfile:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
This tells Docker to ping your app every 30 seconds and mark the container unhealthy if it fails 3 times in a row. Kubernetes and cloud orchestrators use this to auto-restart failed containers.
Pushing to a registry
A Docker image is useless if it lives only on your machine. Push it to a registry (Docker Hub, GitHub Container Registry, Amazon ECR) so other developers and your cloud infrastructure can access it:
docker tag my-react-app:1.0 docker.io/myusername/my-react-app:1.0
docker push docker.io/myusername/my-react-app:1.0
Registries store versioned images, letting you roll back to a previous version in seconds if a deployment breaks. Article 7 covers registries in depth.
Common gotchas
Port mismatch: Your React dev server inside the container runs on port 3000 (the port in CMD). If the serve process or your dev server uses a different port, adjust the EXPOSE instruction and docker run -p mapping.
Environment variables: React build-time variables must be set before npm run build. We cover this in Article 6, but for now, remember that React bundles compile values like process.env.REACT_APP_API_URL at build time, so they can't be changed by passing environment variables at container start.
Windows line endings: If you edit your Dockerfile on Windows, it might include carriage returns (CRLF) instead of Unix line feeds (LF). Docker builders dislike this. In VS Code, click the line-ending indicator (bottom-right, should show "LF") and switch to Unix.
Key Takeaways
- A Dockerfile packages your React app with Node.js and all dependencies into a reproducible image.
- Multi-instruction Dockerfiles use layer caching: keep slow-changing steps (dependencies) first, fast-changing steps (source code) last.
- Build images with
docker build -t name:version .and run containers withdocker run -p port:port name:version. - Use
.dockerignoreto exclude unnecessary files and speed builds. - Pin Node.js versions to avoid runtime surprises in production.
- Push images to a registry (Docker Hub, GitHub Container Registry) so teams and CI/CD can access them.
Frequently Asked Questions
Why is my Docker build so slow?
Check if package.json or package-lock.json changed. If they did, Docker rebuilds the npm ci layer even if your React code didn't change. If unchanged, Docker uses the cached layer and builds in seconds. Move COPY package*.json before COPY . to leverage caching. Also, confirm you have a .dockerignore file excluding node_modules and build artifacts.
Can I run a React development server inside Docker?
Yes. Create a Dockerfile that installs dependencies and runs npm start instead of npm run build. Expose port 3000 and mount your source code as a volume: docker run -v $(pwd):/app -p 3000:3000 my-react-dev. Changes to local files sync into the container instantly. This is slower than running npm locally but ensures your dev environment matches production exactly.
How do I pass environment variables to a Docker container?
Use docker run -e ENV_NAME=value. For example: docker run -e REACT_APP_API_URL=https://api.example.com -p 3000:3000 my-react-app:1.0. Note: React build-time variables are baked into the bundle during npm run build, so they must be set before the build step if you want them in the static HTML/JS. Runtime variables (for Node.js servers) can be set at container start.
Can I see what's inside a running container?
Yes. Use docker exec -it <container-id> sh to open an interactive shell inside a running container. Find the container ID with docker ps. This is invaluable for debugging: you can inspect files, check environment variables, or test commands exactly as they run in the container.
What's the difference between a Dockerfile and a Docker image?
A Dockerfile is source code (the recipe). Running docker build executes the Dockerfile and produces a Docker image (the built artifact, immutable). Running docker run on an image creates a container (a live instance). Analogy: Dockerfile is like a software spec, image is the compiled binary, container is the running process.