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.
As an example, we’ll take a very simple Node.js / Express / MongoDB aplication. Here is the package.json of this app:
"dev": "nodemon ./index.js",
"start": "node ./index.js"
It has two scripts:
We need to have MongoDB installed globally.
So, let’s use Docker for a better isolation.
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:
command: npm run dev mongo:
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 the
mongoimages 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 the
commandinstruction 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 the
devscript 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 with
mongoose.connect('mogodb://mongo:27017'). Unlike the
portsinstruction, this does not make the port available on the host.
Start the services
In the terminal:
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
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