In this post, I will walk you through how you can use docker to set up a development environment for a React project.
- What is Docker?
- Why should you use Docker to set up the environment?
- Pre-requisites
- Overview of the setup
- Setting up a development environment for a create-react-app project
- Adjustments for a Vite project
- Notes
What is Docker?
Docker is a containerization platform that allows us to create isolated containers to run our code in. We can pack all the dependencies needed for the code to run into a container image which can then be used to run on any machine that has docker installed with no special setup. I have written about how you can dockerize a React app for deployment in this post.
Why should you use Docker to set up the environment?
By using docker to set up the development environment, you will be able to start working on a project with just a few commands instead of spending hours getting the tools to work on new machines or new OSes.
Moreover, if you need to work on multiple projects at the same time, it can be quite tedious to set up and maintain several development environments. Especially if the projects have conflicting requirements such as different versions of Node.js. By running the development environments inside Docker containers, the environments will not interfere with each other and you can switch between them quite easily.
Pre-requisites
You need to have Docker installed on your computer to create and run Docker images and containers. You can install Docker Desktop by following the instructions here.
Make sure that you can run the docker command before moving on to the next step
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Overview of the setup
To set up the development environment, we will first create a docker image containing Node.js that is capable of starting the development server for the project.
To create this docker image, we need to
- Create a
Dockerfile
which outlines the instructions that Docker needs to follow to create the image - Create a
.dockerignore
file to prevent docker from copying thenode_modules
folder into this image - Run the
docker build
command to execute the instructions and build the image
Once the image has been built, we can ask Docker to create a container with it so that we can run development commands on it. To access the dev server from the host machine, we will publish the port on which the dev server is running.
To allow the dev server to keep track of the changes to the code on the host machine, we will also use a bind mount to "share" the source code folder on the host machine with the container.
You can find some of the limitations of this setup in the notes section.
Setting up a development environment for a create-react-app project
Place the following code into a file named Dockerfile
at the root of your project. (Note: A Dockerfile has no file extension) If you already have a Dockerfile
for deploying your React app, you can name it Dockerfile.develop
.
# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm i
ENTRYPOINT ["npm"]
Place the following into a .dockerignore
file and place it alongside the Dockerfile
.
node_modules
Now you can run the docker build
command to build the image. react-app
is the name or "tag" that will be assigned to the image, you can use whichever name you like. Don't miss the dot at the end of this command.
$ docker build -t react-app .
If you used a different name for the Dockerfile such as Dockerfile.deploy
, you need to specify this in the build command with -f
flag like so,
$ docker build -t react-app -f Dockerfile.develop .
Once you have built the image, you can start a container and run commands in it with a docker run
command. By default, you start the dev server on a create-react-app
project with the npm start
command. You can now run the same command like so and the dev server will be accessible at http://localhost:3000.
$ docker run -p "3000:3000" react-app start
With the above command, the dev server will not update when the code is changed on the host machine. To fix this, we can use a bind mount with the -v
flag to mount the source code folder inside the container.
$ docker run -p "3000:3000" -v "$(pwd)/src:/app/src" react-app start
This command assumes that you follow the create-react-app convention of storing the .js/.jsx/.ts/.tsx files in the src
folder. If you use a different convention in your project, you need to replace /src
in the path that is passed to the -v
flag (e.g. -v "$(pwd)"/source:/app/source
). You can also use multiple -v
flags to share several folders.
Adjustments for a Vite project
If you are using Vite, you should need to follow the same process as above but with a few changes.
The Vite dev server is not accessible from the local network by default. Docker can only publish ports that are accessible from the local network of the container. To overcome this, adjust the vite configuration in the vite.config.js
so that the dev server is accessible from the local network.
export default defineConfig({
plugins: [react()],
+ server: {
+ host: true
+ }
})
The vite dev server runs on port 5173 rather than port 3000 used by create-react-app. Moreover, the command to run the dev server is npm run dev
rather than npm start
. So the run command should be adjusted like so.
$ docker run -p "5173:5173" -v "$(pwd)"/src:/app/src react-app run dev
Now the dev server will be accessible at http://localhost:5173.
Notes
- You can also run other npm commands with this setup. Instead of
start
, you can use any other command and it will be passed into npm. For example, to runnpm list
you can do
$ docker run -p "3000:3000" -v "$(pwd)"/src:/app/src react-app list
- Running
npm install
will not work since node_modules are part of the container image and any changes to the image will be lost since docker starts a new container every time you rundocker run
. So you need to manually edit thepackage.json
file and you need to build the image withdocker build
to install the new packages.
$ docker build -t react-app .
- Docker does not automatically delete unused containers and images. So if you often run
docker build
, it can take up a lot of disk space as each version of the image will remain on the disk. You can clean up unused containers and images by using the following commands in the given order.
$ docker container prune
$ docker image prune -a
- The Dockerfile above uses the Alpine variant of the Node.js docker image because it needs significantly less disk space than the standard, Debian-based image. In some cases, especially if you use npm packages that use native binaries, it might fail on the Alpine version of the image. In those cases, try using the standard, Debian-based version of the Node.js image by changing the base image in the
Dockerfile
.
- FROM node:18-alpine
+ FROM node:18