How to Dockerize a Node.js/Mongo App with Live Reload (nodemon)
I’ve been working on a Node.js application that interacts with a local Mongo instance. I needed a way to live reload my application on code changes.
Usually, nodemon
does this very easily for us. We could install nodemon
in devDependencies
and then just run nodemon index.js
.
However, inside a Docker container, it’s a little different.
Suppose this is my project structure:
📂project
┣ 📜docker-compose.yml
┣ 📜Dockerfile
┣ 📜package.json
┗ 📂server
┣ 📜index.js
┣ 📂views
┗ 📜every other file in my project...
Let’s go through each file.
Our Dockerfile will build and start just our Node.js application. This one is fairly straightforward. I commented the lines if you’re unfamiliar with how a Dockerfile
works.
# Dockerfile
# Pull official Node.js image from Docker Hub
FROM node:12
# Create app directory
WORKDIR /usr/src/app
# Install dependencies
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
# Expose container port 3000
EXPOSE 3000
# Run "start" script in package.json
CMD ["npm", "start"]
This docker-compose.yml
allows us to communicate between containers.
The app
service contains our Node.js application.
The build
entry is calling our Dockerfile
from above and creating a custom image for us.
We are also mounting our local source code using volumes
. In this case, we are stating that the container’s folder /usr/src/app/server
should simply reference my local copy of ./server
. In other words, the host machine’s ./server
will override the container’s copy of that entire folder.
The ports
entry is telling us to map our http://localhost:8080
to the container’s exposed port 3000
. This means that we can access our application through port 8080
(not 3000
).
We are also using the mongo
image as seen below.
# docker-compose.yml
version: "3"
services:
app:
container_name: node-app
build: .
volumes:
- ./server:/usr/src/app/server
command: npm start
restart: always
ports:
- "8080:3000"
external_links:
- mongo
mongo:
container_name: mongo
image: mongo
ports:
- "27017:27017"
The main thing to notice in our package.json
is that we are running the -L
flag when running nodemon
, which uses legacyWatch
(Chokidar polling).
Since we mounted ./server
, nodemon
will be able to see code changes on our local machine, and then update the Node.js application in the container.
{
"name": "my-package",
"version": "1.0.0",
"main": "server/index.js",
"scripts": {
"start": "nodemon -L server/index.js"
},
"dependencies": {
"express": "^4.17.1",
"mongoose": "^5.10.0"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
In index.js
, we’ll initialize an express
app.
If we’re using express
and we have our own views
, then we may need to specify the location of our views
folder. The default location is /usr/src/app/views
inside the container, but we have our views
folder inside /usr/src/app/server/views
.
We’ll also make a connection to MongoDB using mongoose
.
// index.js
const express = require("express");
const mongoose = require("mongoose");
const app = express();
// Specify location of views
app.set("views", "./server/views");
// Connect to MongoDB
mongoose
.connect("mongodb://mongo:27017/node-app", { useNewUrlParser: true })
.then(() => console.log("MongoDB Connected"))
.catch((err) => console.log(err));
// Start up the server on port 3000
const port = 3000;
app.listen(port, () => console.log(`Server running on http://localhost:8080`));