Getting started with Docker for local Node.js development
How to setup Docker-compose to isolate a web application
Each computer has a specific environment: some tools and specific version of languages are globally installed, our applications share databases… When we work on a web project, the production server might be different from our computer. This is error prone.
Docker solves this problem by running applications in isolated environments.
In this article, we are going to
- Take an existing Node.js / MongoDB application and make it work inside Docker containers.
- Share content between the host and the container to avoid restarting the container after each modification.
Prerequisites
- install Docker on your computer.
The application
As an example, we’ll take a very simple Node.js / Express / MongoDB aplication. Here is the package.json of this app:
{
"name": "test-app",
"version": "0.1.0",
"main": "index.js",
"scripts": {
"dev": "nodemon ./index.js",
"start": "node ./index.js"
},
"dependencies": {
"express": "^4.16.3",
"mongoose": "^5.0.16"
},
"devDependencies": {
"nodemon": "^1.17.3"
}
}
It has two scripts:
dev
: usesnodemon
for development,start
: usesnode
for production.
Right now we start the dev server with npm run dev
. Express is configured to listen to incoming requests at https://localhost:3000
.
We need to have MongoDB installed globally.
So, let’s use Docker for a better isolation.
Docker
Docker runs any process in isolation in their own container.
The advantages of Docker are:
- Apps always run in the same environment (local computer, live server).
- Apps are sandboxed to keep them separate and avoid potential conflicts.
- Apps are easy to share with all their dependencies.
The scope of Docker tools is really large including:
- Docker (docker-cli) and the related Dockerfiles to run single containers.
- Docker-compose cli and the related docker-compose.yml files to run services (multi-containers application).
Docker-compose can be used together with dockerfiles, but not necessary. Also, the scopes of Docker and Docker-compose often overlap but use different syntaxes…
Create Docker containers
Let’s create two containers with docker-compose:
- One for the Node.js app
- One for MongoDB
At the root of the project, create a docker-compose.yml
file with the following content:
version: "3"services: app:
image: node:alpine
volumes:
- ./:/app
working_dir: /app
depends_on:
- mongo
environment:
NODE_ENV: development
ports:
- 3000:3000
command: npm run dev mongo:
image: mongo
expose:
- 27017
volumes:
- ./data/db:/data/db
This files creates two services: app
(the Node.js application) and mongo
(the database):
image
: a set of instructions to build a container with a specific environment. Here we use thenode:alpine
andmongo
images which are downloaded from the Docker hub.volumes
: share content between the local host and the container. This is handy for development to reflects local changes without having to restart the container. I wouldn’t do that in production. Instead, I would copy the content from the host to the container for a better isolation.working_dir
: the directory where thecommand
instruction is executed.depends_on
: defines dependency between services.environment
: env variables.ports
: share ports between the host and the container. Here the port 3000 on the host is configured to point to the port 3000 in the container.command
: an instruction that start the application. Here, this is thedev
script from the package.json file.expose
: expose a port from the container to a docker network. Here, we don’t declare an explicit network, so our two services are bound the default network. This means that inside the Node app, mongo is referenced withmongoose.connect('mogodb://mongo:27017')
. Unlike theports
instruction, this does not make the port available on the host.
Start the services
In the terminal:
docker-compose up
Now, the app runs in isolated containers with their own version of Node.js and MongoDB. Also, mongo is only available in the docker-network. The app still listens at http://localhost:3000
.
Useful commands
Hide logs
Start the container in detached mode:
docker-compose up -d
List all running containers
docker ps -a
Start a shell in one container
Get the name of the container from the command above, then:
docker exec -ti <container-name> /bin/bash