Learn step-by-step how to build and containerize a React app created with Vite using Docker and NGINX. Perfect for deploying a lightweight, production-ready React application.

Overview
- Project Structure: A typical React app with Vite, plus a custom
nginx.conf
. - Dockerfile Explained: We’ll use a multi-stage build:
- Stage 1 (
build
): Build the React app using Node.js. - Stage 2 (
deployer
): Copy the build artifacts to an NGINX container and serve them.
- Stage 1 (
- Environment Variables: Pass in environment variables during the build process to configure your app.
- Running the Container: Build and run your Docker container to serve the app on port 80 (or any port you map externally).
1. Prerequisites
- Node.js (for local development; though only Docker is strictly needed if you’re purely containerizing)
- Docker installed on your machine
- A basic understanding of Docker commands (
docker build
,docker run
, etc.) - A React app set up with Vite (e.g., created with
npm create vite@latest
)
2. The Dockerfile
Below is a sample Dockerfile
that uses two stages:
# Stage 1: Build React App
FROM node:20-alpine AS build
WORKDIR /app
ARG NODE_ENV
ARG VITE_USER_SERVICE_URL
ARG VITE_CATEGORY_SERVICE_URL
ARG VITE_PRODUCT_SERVICE_URL
ARG VITE_CART_SERVICE_URL
ARG VITE_ORDER_SERVICE_URL
ENV NODE_ENV=$NODE_ENV
ENV VITE_USER_SERVICE_URL=$VITE_USER_SERVICE_URL
ENV VITE_CATEGORY_SERVICE_URL=$VITE_CATEGORY_SERVICE_URL
ENV VITE_PRODUCT_SERVICE_URL=$VITE_PRODUCT_SERVICE_URL
ENV VITE_CART_SERVICE_URL=$VITE_CART_SERVICE_URL
ENV VITE_ORDER_SERVICE_URL=$VITE_ORDER_SERVICE_URL
COPY package*.json ./
COPY postinstall.js ./
RUN npm install
# RUN npm run postinstall
COPY . .
RUN npm run build
# Stage 2: Serve the App
FROM nginx:alpine AS deployer
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
What’s Happening Here?
FROM node:20-alpine AS build
We start with a lightweight Node.js Alpine image. This will be used only to install dependencies and compile our React app.- ARG and ENV
We define arguments (ARG
) and environment variables (ENV
) to pass external values (e.g., API endpoints, environment info). This allows you to build environment-specific versions of your app easily. - Copy and Build
COPY package*.json .
copies the package files.npm install
installs the dependencies.COPY . .
copies the entire React project into the container.npm run build
runs the Vite build command, producing an optimized production build in thedist
folder.
- Multi-stage
After building is done, we switch tonginx:alpine
. We take thedist
folder from the previous build stage and put it into NGINX’shtml
directory (/usr/share/nginx/html
). EXPOSE 80
Exposes port 80, which is where NGINX serves your React app by default.CMD ["nginx", "-g", "daemon off;"]
Starts NGINX in the foreground so the container doesn’t exit immediately.
3. Configuring NGINX
You’ll need an nginx.conf
(or similarly named config) in your project. For example:
Place this file in your project, and ensure your Dockerfile’s line:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
# If a file exists, serve it; otherwise, serve index.html
try_files $uri /index.html;
}
error_page 404 /index.html;
location /static/ {
# Serve static files
expires max;
add_header Cache-Control "public";
}
}
listen 80;
: We’re setting the server to listen on port 80.
try_files $uri /index.html;
: Ensures React can handle client-side routing properly (i.e., if the user navigates to /login
directly, NGINX will serve index.html
instead of returning a 404).
Place this file in your project, and ensure your Dockerfile’s line:
COPY nginx.conf /etc/nginx/conf.d/default.conf
matches the path to your custom configuration.
4. Building the Image
To build the Docker image locally, open your terminal in the root of your project and run:
docker build \
-t ecomui:v1 \
--build-arg NODE_ENV=development \
--build-arg VITE_USER_SERVICE_URL="http://localhost:6001/user" \
--build-arg VITE_CATEGORY_SERVICE_URL="http://localhost:6002/category" \
--build-arg VITE_PRODUCT_SERVICE_URL="http://localhost:6003/product" \
--build-arg VITE_CART_SERVICE_URL="http://localhost:6004/cart" \
--build-arg VITE_ORDER_SERVICE_URL="http://localhost:8000/order" \
.
Key points:
-t ecomui:v1
assigns a name and version tag to the image.--build-arg
allows passing in environment-specific URLs or keys for your microservices.
5. Running the Container
After the build completes, you can run the container:
docker run -p 9091:80 ecomui:v1
-p 9091:80
maps port 9091 on your machine to port 80 in the container.
If everything is successful, open http://localhost:9091 to see your React app running.
6. Working With Environment Variables in Vite
When you pass environment variables via --build-arg
, they become available in the build container. In your Vite + React app, you’d typically access them as import.meta.env.VITE_SOME_VARIABLE
. For example, if you have:
const userServiceUrl = import.meta.env.VITE_USER_SERVICE_URL;
…then you’d populate VITE_USER_SERVICE_URL
when building the image. Once the build is done, those URLs are baked into the final bundle.
7. Troubleshooting Tips
- Check the Build Logs: If your build fails, make sure that your
package*.json
andpostinstall.js
files are copied correctly before runningnpm install
. - Environment Variables: Ensure that your
ARG
andENV
lines match your usage within your React code. Misspellings can lead to undefined variables in your app. - Correct NGINX Configuration: If you see 404 errors for client-side routes, verify that your
nginx.conf
hastry_files $uri /index.html;
. - Ports: If you see an error on port 80, ensure you don’t have another service occupying that port on your machine. Use a different port mapping if needed (e.g.,
-p 8080:80
).
Conclusion
By following a multi-stage Docker build, you can keep your final container image small, ensure your React app is efficiently built, and serve it via NGINX. This setup is ideal for production environments where you want a straightforward, performant way to deploy static files.
Key Benefits:
- Efficiency: Only the necessary files (the
/dist
folder) end up in the final container. - Consistency: Your app runs exactly the same in any environment with Docker installed.
- Easy Configuration: Environment-specific variables can be passed at build time.
Feel free to adapt this Dockerfile for your own microservices, environment setups, or CI/CD pipelines. Happy containerizing!