• Shopping Cart Shopping Cart
    0Shopping Cart
London App Developer
  • Courses
  • Consulting
  • Tutorials
  • About
  • Click to open the search input field Click to open the search input field Search
  • Menu Menu
Docker, React

How to Dockerize a React Project

In this tutorial I’ll explain how to set up a new React project using Docker and Vite so that auto reloading works.

Prerequisites

You’ll need the following:

  1. Docker and Docker Compose – I recommend using Docker Desktop or Colima
  2. A code editor – I use VSCode

Resources

The full and final version of this tutorial can be found here: https://github.com/LondonAppDeveloper/dockerize-react

Creating React Project

First, we’ll create a new React project.

Open your terminal and create a new directory you wish to use for your project.

Note: I will be creating the react project as a subdirectory in the root of the project directory. This is so we can use the root directory for the Docker configuration. Also, in many projects the root directory is used for the backend.

Next, run the following command to create a new React project called “frontend”.

macOS / Linux:

docker run --rm -it -v $(pwd):/code -w /code node:22.11 sh -c "npm create vite@5.4 frontend -- --template react && cd frontend/ && npm install"

Windows:

docker run --rm -it -v $(PWD):/code -w /code node:22.11 sh -c "npm create vite@5.4 frontend -- --template react && cd frontend/ && npm install"

When asked to install the create-vite package, type y and hit enter to proceed:

Let me explain this command section by section:

docker run: the command for running containers in Docker

--rm: tells docker to remove the container after it’s finished running (this prevents containers lingering on the system once they are used).

-it: Tells Docker to run the command “interactively” – this is so we can provide inputs to the command we run in Docker in the terminal.

-v $(pwd):/code (macOS) or -v $(PWD):/code (Windows): Maps the current working directory from your local machine to the running container – this basically shares our current directory with the /code directory in our running containers filesystem.

-w /code – Tells Docker to set the working directory to /code (similar to running cd /code).

node:22.11 – The image we want to run, with the version tag (to keep these steps consistent).

sh -c – This forms the first part of the command we run. It allows us to provide a “shell” command that will run in the container inside quotations after this command. It’s optional, but I find it useful to separate the Docker syntax from the command we want to run in Docker.

npm create vite@5.4 frontend -- --template react: This is the command for creating a new vite project with a react template as per the vite documentation.

&& cd frontend/ – This moves us into the directory of the new project inside Docker (&& is used to chain multiple commands together)

&& npm install – This installs the dependencies defined in the package.json that is created by the npm create command. This is done so we get the package-lock.json file for these dependencies.

Why the long command?

I imagine you’re asking: why on earth do you use such a huge command for creating a simple project? Why not just run npm directly?

It’s a fair question, and feel free to npm create vite@5.4 frontend -- --template react if you already have npm setup and installed.

The reason I use Docker is because while most people understand the value of using it for deployment, few utilise it’s full potential for local development.

Running the command using Docker ensures that everyone following this tutorial is running exactly the same command, with the same versions of node and vite.

Hopefully this means that if you’re following these steps in 2 weeks or 2 years, it should still work for you the same way.

Also, it means it doesn’t matter which version of node, vite, or anything else you have installed directly on your machine, because we are running it through a controlled Docker environment.

This is not only applicable for tutorials, but also when you’re working in teams and companies.

How many times have you started a new job, only to open up the project to find the README.md is 5 years old and none of the “getting started” steps work anymore? (for those new to the field, you have that to look forward to!)

Let’s keep going…

Configure Docker

Next we’ll create a Docker configuration.

All of these files will go in the root of the project (not inside frontend/).

If you prefer, you can see the code changes here on GitHub.

Create .dockerignore

Add a new file called .dockerignore.

README.md
frontend/node_modules/

This tells Docker which files to ignore when it gathers up files from the context to build our image. It’s mostly beneficial from a performance standpoint, as it makes the build process faster because it doesn’t need to transfer unnecessary files to the Docker builder.

Then, add a new file called Dockerfile

FROM node:22.11

COPY ./frontend /frontend
RUN rm -rf /frontend/node_modules

WORKDIR /frontend
RUN npm ci

CMD ["npm", "run", "dev"]

This provides Docker with the instructions on how to build our image.

Each line is a new step in the build process.

I’ll break it down line-by-line below:

FROM node:22.11 – Defines the base image (starting point) for our image. In this case we are building on top of the node image, version 22.11 (currently LTS version at the time of writing this tutorial).

COPY ./frontend /frontend – This copies all the files in the frontend directory in our project into the image we are building.

RUN rm -rf /frontend/node_modules – In-case we have the node_modules/ installed locally, we want to clean them out from our Docker image. This is because we will be installing them fresh inside the image in a future step.

WORKDIR /frontend – Tells docker that the default working directory of containers built from this image should be the /frontend directory in the container. This means all commands run from /frontend, as if you had run cd /frontend before the command.

RUN npm ci – Installs the NPM packages defined in package.json and package-lock.json. Why do we not use npm install? Great question. The ci command is similar to running npm install, except it doesn’t update the package-lock.json. This is because when the image is being built, it can’t sync files back to our project, so we don’t want a situation where the package-lock.json in our Docker image is different from the one in our project code, as we may run into unexpected issues that are hard to debug.

CMD ["npm", "run", "dev"] – Sets the default command that runs for this image (in this case, it will start the vite development server which is set in the package.json “dev” line under the scripts block).

Create docker-compose.yml

Now create a file called docker-compose.yml in the root of the project:


services:
  frontend:
    build:
      context: .
    ports:
      - 5173:5173
    develop:
      watch:
        - action: sync
          path: ./frontend/
          target: /frontend
          ignore:
            - node_modules

Let me break that down…

We define a services block with frontend as our first (and only) service.

Then, we specify the build context as the root directory using the period character. This tells Docker Compose that we want to use the Dockerfile found in the root of the project for this service.

We map ports 5173 on our local machine to the same port in the Docker container. This ensures we can access the vite server running inside the Docker containers isolated network.

Next, we have the develop block.

This utilises the new watch feature of Docker Compose.

It’s designed to watch files in our local machine and sync them to the running container when they change.

Previously, we’ve had to use volumes for this. The problem with volumes is it’s not optimised for syncing file changes for node projects. Also, you can’t exclude files like node_modules which always is a pain when Dockerizing node.

The downside is that it only provides one way syncing (from the host to the container) and not reverse. We’ll talk more about that later.

I’ll explain each line of the watch block below:

action: sync tells Docker Compose to sync the files every time something changes in the watched path. Other options for this include restart (restart the container) and rebuild (rebuild the docker image). You can see more in the official docs. We only need to sync, because this is enough for vite to detect changes and reload the development server.

path: ./frontend/ tells Docker to watch files in the frontend/ directory of our project

target: /frontend – Tells docker to sync the files in our path to the /frontend directory inside our running container.

ignore: – Tells Docker to ignore the node_modules/ directory – this is important because this directory often contains billions of files (I’m exaggerating a bit but it’s not far off), which make syncing very inefficient. Also, the files in our node_modules/ might vary since we have a different OS to the Docker Container. It’s best to keep the node_modules/ inside the container separate from the one on our local machine.

Configure vite

Next we need to make a minor change to our vite config.

You can see the full diff here on GitHub.

Open the frontend/vite.config.js file and modify the contents to look like this:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: { host: "0.0.0.0" },
});

This updates the server to listen on host: 0.0.0.0.

We need to do this because vite will only bind to localhost, which means it’s only accessible from the local network inside the docker container network (which is not accessible from our local machine).

Setting the host to 0.0.0.0 tells the server to listen on all network interfaces of the docker container’s network. This ensures we can access it via the mapped port of 5173.

Running the Server

Now we can finally run the server!

Open the Terminal/PowerShell and run the following:

docker compose up --watch --build

This will build our container and start the image.

Then, open http://127.0.0.1:5173 and you should see the sample vite placeholder app.

Test by making a change to any file in the React code, and you should see the server auto-reload.

Adding Node Packages

As mentioned above, the downside of using the “watch” functionality in Docker Compose is that it only syncs files one way – from your system to the container.

This means, if you want to install NPM packages using pure docker (and not by running npm install PACKAGE directly on your machine) there is an additional step to follow.

If you want to add node packages, you will need to run the following command:

macOS:

docker compose run --rm -v $(pwd):/frontend frontend sh -c "npm install PACKAGE"

docker compose run –rm -v $(pwd):/app frontend sh -c “npm install PACKAGE”

Windows:

docker compose run --rm -v "$(PWD):/frontend" frontend sh -c "npm install PACKAGE"

docker compose run –rm -v “$(PWD):/app” frontend sh -c “npm install PACKAGE”

This runs a command through our configured frontend service, which will install the package and update package-lock.json. The -v $(pwd):/frontend ensures that a volume is mapped, which (unlike the watch config) ensures two way syncing is possible. This allows the package-lock.json in our project to be updated by the command running inside Docker.

After you run this, you’ll need to rebuild the container again.

Simply use CMD + C (macOS) or CTRL + C to stop our running service.

Then run the docker compose up --watch --build command again.

This ensures that the new dependency is installed in the running docker container.

Summary

That’s how you create and configure a React app using Docker Compose.

Thanks for reading!

Share this entry
  • Share on Facebook
  • Share on X
  • Share on WhatsApp
  • Share on Pinterest
  • Share on LinkedIn
  • Share on Tumblr
  • Share on Vk
  • Share on Reddit
  • Share by Mail
https://i0.wp.com/londonappdeveloper.com/wp-content/uploads/2024/11/9.png?fit=888%2C715&ssl=1 715 888 mark https://londonappdeveloper.com/wp-content/uploads/2018/12/lon_website_logo-300x110.png mark2024-11-12 11:55:562024-12-13 11:14:01How to Dockerize a React Project
1 reply
  1. Damilare emmanuel
    Damilare emmanuel says:
    August 5, 2025 at 5:33 am

    Dear Mark Winterbottom,

    My name is Damin, and I’m a full-stack developer (MERN stack) currently exploring Django for backend development. A few months ago, I came across your video while learning Django, which led me to enroll in your course(Build a Backend REST API with Python & Django). I truly enjoyed your clear and insightful teaching style, and it has been incredibly helpful in my learning journey.

    I’m currently working on a small project using Django for the backend and Next.js for the frontend. I’d like to dockerize this application to streamline development and deployment. While I’ve watched several YouTube videos on dockerizing Django and Next.js, I found the explanations lacking in depth and clarity. I also tried applying your tutorial on dockerizing Django with React, but noticed that the setup for Next.js differs significantly.

    Would you consider creating a tutorial (blog or video) on how to dockerize a Django and Next.js application? A step-by-step guide from you would be invaluable, given your ability to break down complex topics clearly. Thank you for your time and for the amazing content you produce. I look forward to your response!

    Best regards,
    Damin

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Pages

  • About
  • Consulting
  • Home
  • Tutorials

Categories

  • AI
  • Android
  • Android Studio
  • Angular
  • Aptana Studio
  • AWS
  • Bootstrap
  • Career advice
  • Celery
  • Databases
  • Deployment
  • DevOps
  • Django
  • Django REST Framework
  • Docker
  • Eclipse
  • Getting Help
  • Git
  • Git Bash
  • Git Flow
  • GitHub Actions
  • Google App Engine
  • IDE
  • Ionic Framework
  • iOS
  • JavaScript
  • Job Search
  • Links
  • Linux
  • Mac OS X
  • macOS
  • Node.JS
  • OpenAI
  • PGAdmin
  • Postgres
  • Productivity
  • Python
  • React
  • Salt Stack
  • Stack Overflow
  • Tools
  • Travis-CI
  • Tutorials
  • Ubuntu
  • Uncategorized
  • Vagrant
  • VSCode
  • WebSockets
  • Windows 10
  • WordPress

London App Developer Ltd,

71-75 Shelton Street, Covent Garden, London, United Kingdom, WC2H 9JQ

Company registration number: 09718346

© London App Developer - London App Developer Ltd, Company Registration Number: 09718346, VAT: GB224061842
  • Link to X
  • Link to Youtube
  • Link to Facebook
  • Link to Reddit
Link to: Django Docker Deployment with HTTPS using Letsencrypt Link to: Django Docker Deployment with HTTPS using Letsencrypt Django Docker Deployment with HTTPS using Letsencrypt Link to: Add Backend to React with Django REST Framework and Docker Link to: Add Backend to React with Django REST Framework and Docker Add Backend to React with Django REST Framework and Docker
Scroll to top Scroll to top Scroll to top

This site uses cookies. By continuing to browse the site, you are agreeing to our use of cookies.

OKLearn more

Cookie and Privacy Settings



How we use cookies

We may request cookies to be set on your device. We use cookies to let us know when you visit our websites, how you interact with us, to enrich your user experience, and to customize your relationship with our website.

Click on the different category headings to find out more. You can also change some of your preferences. Note that blocking some types of cookies may impact your experience on our websites and the services we are able to offer.

Essential Website Cookies

These cookies are strictly necessary to provide you with services available through our website and to use some of its features.

Because these cookies are strictly necessary to deliver the website, refusing them will have impact how our site functions. You always can block or delete cookies by changing your browser settings and force blocking all cookies on this website. But this will always prompt you to accept/refuse cookies when revisiting our site.

We fully respect if you want to refuse cookies but to avoid asking you again and again kindly allow us to store a cookie for that. You are free to opt out any time or opt in for other cookies to get a better experience. If you refuse cookies we will remove all set cookies in our domain.

We provide you with a list of stored cookies on your computer in our domain so you can check what we stored. Due to security reasons we are not able to show or modify cookies from other domains. You can check these in your browser security settings.

Google Analytics Cookies

These cookies collect information that is used either in aggregate form to help us understand how our website is being used or how effective our marketing campaigns are, or to help us customize our website and application for you in order to enhance your experience.

If you do not want that we track your visit to our site you can disable tracking in your browser here:

Other external services

We also use different external services like Google Webfonts, Google Maps, and external Video providers. Since these providers may collect personal data like your IP address we allow you to block them here. Please be aware that this might heavily reduce the functionality and appearance of our site. Changes will take effect once you reload the page.

Google Webfont Settings:

Google Map Settings:

Google reCaptcha Settings:

Vimeo and Youtube video embeds:

Other cookies

The following cookies are also needed - You can choose if you want to allow them:

Privacy Policy

You can read about our cookies and privacy settings in detail on our Privacy Policy Page.

Privacy Policy
Accept settingsHide notification only