Setting up a React project with Docker and Nginx
What is Docker?
Docker is a powerful tool that simplifies building, sharing, and running applications by packaging them into containers. Containers are lightweight, portable, and ensure that your app runs the same way everywhere — no more “it works on my machine” issues!
When I first started using Docker, I found it super confusing. Terms like “images,” “volumes,” and “containers” felt overwhelming. But once I got the hang of it, I realized how simple and powerful Docker can be.
- Images: Blueprints for containers. They contain the app code, runtime, and dependencies.
- Containers: Running instances of images. They are isolated and portable.
- Volumes: Persistent storage for containers, used to share data between the host and container.
In this tutorial, I’ll walk you through setting up a React/TypeScript project using Vite and Docker. By the end, you’ll have a fully containerized app that’s ready to deploy!
Requirements for the Tutorial
1. Operating System
- Windows 10/11, macOS, or Linux (Ubuntu, Debian, etc.).
- Docker runs on all major operating systems, but the installation process may vary slightly.
2. Docker
- Docker Desktop (for Windows and macOS) or Docker Engine (for Linux).
- Download and install Docker from the official website: https://www.docker.com/get-started.
- Ensure Docker is running before starting the tutorial.
3. Node.js and npm
- Node.js (version 22 or higher recommended).
- Download and install Node.js from the official website: https://nodejs.org.
- npm (Node Package Manager) is included with Node.js.
4. Code Editor
- A code editor like Visual Studio Code (VS Code), or any editor of your choice.
- VS Code is highly recommended for its Docker and terminal integration.
- Download VS Code: https://code.visualstudio.com.
5. Terminal or Command Line
- A terminal or command-line interface (CLI) to run commands.
- Windows: Use PowerShell, Command Prompt, or Windows Terminal.
- macOS/Linux: Use the built-in terminal.
Step 1: Creating the Vite Project
Vite is a modern build tool that provides a faster and leaner development experience for web projects. It’s a great alternative to tools like Create React App, which has been deprecated.
npm create vite@latest
# you will be prompted for a project name
docker-example
cd docker-example
npm install
Step 2: Setting up Docker
We need to create a Dockerfile
at the root of our project. This file defines how the Docker image is built.
# Use an official Node.js runtime as the base image
FROM node:22.11-alpine
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and install dependencies
COPY package.json .
RUN npm install
# Copy the rest of the application code
COPY . .
# Expose port 8080 for the app
EXPOSE 8080
# Start the development server
CMD ["npm", "run", "dev"]
Step 3: Setting up a .dockerignore file
The .dockerignore
file tells Docker which files or directories to exclude when building the image. Ignoring node_modules
ensures that the dependencies are installed fresh inside the container, avoiding conflicts with your local environment.
node_modules
Step 4: Create our docker-compose.yml file
The docker-compose.yml
file defines how the containers are run. Here’s what it looks like:
services:
frontend:
platform: linux/amd64
build:
dockerfile: Dockerfile
container_name: docker-example
ports:
- "8080:8080" # Map port 8080 on your host to port 8080 in the container
volumes:
- .:/app # Mount the current directory to /app in the container
- /app/node_modules # Ensure node_modules is not overwritten by the host
stdin_open: true # Keep the container open for input
tty: true # Allocate a pseudo-TTY
Step 5: Updating the Vite configurations
By default, Vite binds to localhost
, which means it’s only accessible from within the container. To allow external access (e.g., from your host machine), we need to bind it to 0.0.0.0
. Update your vite.config.ts
:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: "0.0.0.0", //Allow external access
port: 8080,
},
});
Disclaimer: Security Considerations
Exposing your app on 0.0.0.0
is not secure for production environments. It allows anyone on your network (or the internet, if your machine is publicly accessible) to access your app. To make it secure, we’ll use Nginx as a reverse proxy.
Why is this not secure?
Our Vite application is exposed on 0.0.0.0
, which means the app is listening on all network interfaces on your machine. Think of it like leaving the front door of your house wide open. Anyone who knows your address (your machine's IP address) can walk right in, whether they’re a friendly neighbor or a stranger with bad intentions.
This is risky because:
- No security checks: There’s no one to stop people from entering or causing trouble.
- Direct access: Your app is fully exposed, making it easier for attackers to exploit vulnerabilities.
- No control: You can’t filter or manage who gets in or what they do.
How can we make it secure?
We can use Nginx to act like a bouncer for your house. Here’s how it works:
A) Nginx listens at the door (port 80):
- Instead of letting people walk straight into your house (your Vite app), they first interact with the bouncer (Nginx). Nginx stands at the door and decides who gets in and how they should behave.
B) Nginx forwards requests to your app:
- If the bouncer (Nginx) decides the visitor is allowed, they guide them to the right place inside your house (your Vite app running on
localhost:8080
).
C) Nginx adds security:
- The bouncer (Nginx) can enforce rules, like checking IDs (authentication), limiting how many people can enter at once (rate limiting), or even turning away suspicious visitors (blocking malicious requests).
To make this safer, let us configure our project a bit more.
Step 6: Add Nginx for security
Nginx acts as a bouncer for your app, controlling who can access it and adding an extra layer of security.
a) We need to create a nginx.conf
file:
server {
listen 80; # Listen on port 80
location / {
proxy_pass http://localhost:8080; # Forward requests to the Vite app
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
b) Update your docker-compose.yml
to include Nginx:
services:
frontend:
platform: linux/amd64
build:
dockerfile: Dockerfile
container_name: docker-example
ports:
- "8080:8080"
volumes:
- .:/app
- /app/node_modules
stdin_open: true
tty: true
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- frontend
Step 7: Running the Docker application
Run the app using: docker-compose up -d --build
This command builds the Docker images (if they don’t exist) and starts the containers in detached mode (-d
), meaning they run in the background.
*Note: Also please make sure your Docker Desktop is running or else you will get an error!
Bonus: Installing dependencies inside Docker
nstead of installing dependencies on your local machine, you can install them directly inside the Docker container. This keeps your local environment clean and ensures consistency.
Step 1: Find your running container
To list all running containers: docker ps
You will see an output like this:
We are looking for our container named docker-example
Step 2: Open the shell inside the container: We will use docker exec
to open a shell: docker exec -it docker-example sh
docker exec
: Runs a command inside a running container.-it
: Opens an interactive terminal.docker-example
: The name of your container.sh
: The shell to use (you can also usebash
if your image includes it).
Now your terminal prompt should look something like this:
Step 3: Installing the Dependency
We will install React Router in this example: npm install react-router-dom
This will install React Router and add it into your node_modules
folder inside the container.
To verify if it was installed, you can run this command: npm list react-router-dom
Your output will look similar to this:
Step 4: Update your App to use the dependency
In our example, we can update our application to utilize React Router now!
We can update our src/App.tsx
file
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import "./App.css";
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
export default App;
Step 5: Test your App
Open your browser and navigate to http://localhost:8080
. You should see your app with working navigation between the Home and About pages!
Final notes
Congratulations! You’ve successfully set up a React/TypeScript project with Docker and Nginx. Your app is now containerized, secure, and ready for development or deployment!
And that’s a wrap! Now you’re ready to ship your containers like a pro — no more dock-ing around! 🚢🐳
Feel free to reach out: