Docker Tips : Development With Nodemon
Tracking changes in real time
TL;DR

I have delivered several Docker trainings during the last few weeks and some questions come back quite often. For example:
Q: When developing my application, how can I get my changes to be taken into account automatically in the containers?
The idea behind this question is, for a developer, to make changes in his/her local IDE (Atom, Visual Studio Code, Eclipse, vi…) and then see the changes taken into account in real time in the running application.
Usually, I respond:
R: While in development, you need to mount the local source code in the services’ containers and start the main process through a utility like nodemon, which will watch files and restart the application when some changes are observed.
I realized this answer deserved some additional details and examples. This is what this post is all about.
Example Application
Illustration on the result service

Let’s consider the Docker Voting Application. This one is used very often for demos and presentations.
This application follows a micro-services architecture. It is made of 5 services as illustrated below.
- vote: front end that enables a user to choose between a cat and a dog
- redis: database where votes are stored
- worker: gets votes from redis and stores the results in a postgres database
- db: the postgres database in which vote’s results are stored
- result: front end displaying the results of the vote
Let’s clone the Voting App repository:
git clone https://github.com/dockersamples/example-voting-app
As we can see, several Docker Compose files are defined here. We’ll use the default, named docker-compose.yml
, in this article.
version: "3.3"
services:
vote:
build: ./vote
command: python app.py
volumes:
- ./vote:/app
ports:
- "5000:80"
networks:
- front-tier
- back-tier
result:
build: ./result
command: nodemon server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
networks:
- front-tier
- back-tier
worker:
build:
context: ./worker
depends_on:
- "redis"
networks:
- back-tier
redis:
image: redis:alpine
container_name: redis
ports: ["6379"]
networks:
- back-tier
db:
image: postgres:9.4
container_name: db
volumes:
- "db-data:/var/lib/postgresql/data"
networks:
- back-tier
volumes:
db-data:
networks:
front-tier:
back-tier:
Illustration on the result service
Let’s have a closer look at the way the result service is defined in this file.
result:
build: ./result
command: nodemon server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
networks:
- front-tier
- back-tier
Several interesting things here:
- the local
result
folder is bind-mounted into the/app
folder - the command used to run the service is
nodemon server.js
Bind-mounting the source folder
The local source code, the one we continuously change within our favorite IDE, will be available as-is within the service’s container. In other words, every change we will do locally will be reflected in the running application. But, if we want it to be taken into account by the application, this one needs to be reloaded. Enter nodemon!
Running the service with nodemon
Nodemon is a great piece of software. Take a look at the official description.
Nodemon is a utility that will monitor for any changes in your source and automatically restart your server. Perfect for development. — Nodemon
In other words, this guy is there to supervise the main process running in the container, and to restart it if it detects some changes on surrounding files.
The result service is developed with Node.js so it’s very easy to run it with nodemon instead of with the default node command.
As we can see, the command defined in in the docker-compose.yml
file overrides the one defined in the Dockerfile.
// Command instruction in the Dockerfile
CMD [“node”, “server.js”]// Command instruction in the docker-compose.yml file
command: nodemon server.js
For nodemon to react on changes made on html files located in the views
folder, we will change the command specified in the docker-compose.yml
file a little bit and make it look like the following.
// Slight modification of the command instruction
command: nodemon --watch views -e js,html server.js
Of course, in order for the result service to start with nodemon, we need to have it available within the container. The instruction to install it in the Dockerfile of the result service is the following one.
RUN npm install -g nodemon
Not a Node.js application ?
Nodemon integrates very well with a Node.js application, but what if the application we use is developed with another language ?
Not a problem… nodemon can run several types of applications. It also provides out of the box several options to watch changes into specific folders and allows to provide file extension as well. Let’s illustrate this on the vote service, this one is a python flask application.
- The first thing to do is to make sure nodemon is available in the service’s image. To do so, we start by changing the Dockerfile of the service adding the following instruction:
# Install nodemon
RUN apk update && apk add nodejs && npm i -g nodemon
Note1: Since Alpine 3.8, npm is not installed with Node.js, the nodejsnpm
package needs to be added in the above command next to nodejs
. Thanks Omar Quiroz for pointing this out.
Note2: As nodemon will not be needed inside the production image, it could be good to add a condition based on a build arg; it could be installed in dev only.
- The second thing is to override the command used to run the service so it uses nodemon. The definition of the vote service can be changed to:
services:
vote:
build: ./vote
command: nodemon --watch template --exec "python" app.py
volumes:
— ./vote:/app
ports:
— "5000:80"
networks:
— front-tier
— back-tier
We provide some additional options to nodemon
- --exec “python”: indicates the type of application we need to run
- --watch template: on top of the app.py script run, we want to watch changes done in the template folder
It is also possible to provide the list of extensions we need to watch through an -e flag. The nodemon documentation provides a list of the available options.
Let’s test
We can now build the vote service so the changes we have done in the Dockerfile are taken into account, and then start the application.
$ docker-compose build vote
$ docker-compose up
The vote interface is available on port 5000, the result one on port 5001.
- Modify the source code of the vote service
Let’s modify the selection options and change Cats into Kitten in the app.py
file
option_a = os.getenv(‘OPTION_A’, “Kitten”)
option_b = os.getenv(‘OPTION_B’, “Dogs”)
We can observe the automatic reload of vote in the Compose logs.
vote_1 | * Detected change in ‘/app/app.py’, reloading
vote_1 | * Restarting with stat
vote_1 | * Debugger is active!
vote_1 | * Debugger PIN: 161–189–800
Reloading the web interface shows the changes
Summary
In this post, we have seen the setup needed for a developer to be able to work on the source code and have it automatically be taken into account into the application running in container.
Once we are happy with the code changes, we can use docker-compose to build and push the images to the registry and thus trigger the CI pipeline.