Setting up a React project with Docker and Nginx

Matthew Leng
7 min readMar 6, 2025

--

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
What the output should look like

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:

  1. No security checks: There’s no one to stop people from entering or causing trouble.
  2. Direct access: Your app is fully exposed, making it easier for attackers to exploit vulnerabilities.
  3. 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:

`docker ps` output

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 use bash if your image includes it).

Now your terminal prompt should look something like this:

terminal prompt

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:

npm list react-router-dom

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!

Yay!

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:

LinkedIn

--

--

Matthew Leng
Matthew Leng

Written by Matthew Leng

Pre-School teacher turned software developer 👨‍🏫 👾.

No responses yet