Properly Setting Up React Development Environment Using Docker
Solving the node modules synchronization problem
It’s amazing to use just one command docker-compose up
to start your development workflow in any host operating system. But it may become a daunting task to set up a perfect workflow especially when we have to sync our node_modules
folder across the host machine and docker container.
In this blog, I will show how to properly set up the development and test containers of your react application. I will also show how to solve the empty node_modules folder problem in your host machine so that your linters do not yell at you anymore.
I’ve also written an angular version of this blog. If you are using Angular then follow this link.
Initial setup with Docker
Create a React project using the following command.
npx create-react-app dockerize-react
create a folder for saving docker-related files in the project root. I like to name the folder as .docker
Your directory will look something like the following.
├──.docker
│ ├── ci
│ ├── dev
│ └── Dockerfile # our development dockerfile
│ ├── prod
│ └── scripts
└── ...
Create a Dockerfile
in the .docker/dev/
directory.
This is a conventional docker file. We did the following in the Dockerfile
- We took node:14 as our base image
- Set the current working directory inside the docker container to
app
- We set the node environment variable.
- Then we copy
package.josn
andpackage-lock.json
- Install node modules using
npm install
- Finally, Copy everything from the current working directory of the host machine to the working directory inside the container ( which is
app
in this case ). This step is not necessary for the developmentDockerfile
though. We will map the host working directory to the containers working directory in thedocker-compose.yml
file anyway. But I like to keep this line here because in the future we may use thisDockerfile
for production or take thisDockerfile
as an inspiration to create the productionDockerfile
. In that case, this line will work as a reminder that we need to copy the source code into the container.
Now let’s create the docker-compose.yml
Create a file name docker-compose.yml
in the project's root directory. Following is my docker-compose.yml
- we set the build
context
to the current folder.
of the host machine - we provide the
Dockerfile
location which we want to use for building this(web)
service. - we map the port so that we can access our react app running on port
3000
inside the container from outside i.e host machine. - The volume maps everything in the host machine to the app folder inside the container.
- Here we map
node_modules
folder inside the container to the host machine. Remember we didnpm install
in the app folder inside the docker container. So we have anode_modules
inside the docker container which we can map back to the host machine.
This is necessary because we want to actively develop our application while running the environment using docker. If we do not map
node_modules
from the container to the host then we will have to generate anothernode_modules
locally so that our IDEs can work properly.Also In our particular case, we are mapping everything inside the current context
.
to theapp
folder inside the container. If we do not map backnode_modules
from the container to the host then the host’s file will replace everything inside thecontainr's app
folder. In this way, ournode_modules
folder generated earlier in theapp
folder inside the container will also get removed because we do not have any node module folder in our host machine. So we must map backnode_modules
from the container to the host. Otherwise, our volume mapping.:/app
will removenode_modules
folder inside the container. Hence our setup will not work.
Now run docker-compose build
and then docker-compose up
to start the environment.
Now let’s quickly set up the test suit for our react app. We will use a different container for running our tests. Following is the docker-compose file with the test setup configuration added.
The Problem
There is a small problem with this setup. The node_mouldes
folder is empty in the host machine. Our app will work perfectly because we have node_modules
inside the docker container. That means synchronization did not happen properly.
As a result, different IDEs and code editors will give linting errors because they won’s find required node_modules
locally to work with.
A simple workaround for this problem is to enter into that particular docker container and run npm install
one more time.
First, delete the existing empty node_module
folder. Then run the following command.
docker-compose exec web npm install
But I do not like running npm install again!!
I personally do not like to run npm install multiple times because it hinders smooth development workflow. My teammates want to just pull the repository and run docker-compose up --build
and everything should work.
To solve this problem let’s modify the dockerfile
a little bit.
Here we installed node_modules
inside another temporary directory namely cache
. Later we will copy this node_module
to the app
directory once the image is built.
Let’s modify the docker-compose.yml
file and add commands to copy node_modules
at the startup
with the cp -rfu /cache/node_modules/. /app/node_modules/
command, we copy node_modules
from the temporary cache directory to the working directory after the image is built. The -u
flag is important because we want to copy files only if anything is changed in the node_modules
folder. In this way, we won’t copy files every time we run docker-compose up
to start our development.
Development workflow for developers
Pull the source code
If pulled for the first time or the package.json changed run
docker-compose build
Then, run the following command to start the environment.
docker-compose up -d
To see the logs of your app
docker-compose logs -f web
To see and follow the logs of your tests
docker-compose logs -f test
If you need to install any npm package.
docker-compose exec web npm install pacakge-name
stop the containers
docker-compose down
Now developers can work smoothly without having to run npm install
multiple times.
Here is the final code GitHub