To get started with a new technology it’s great to have a small, yet non-trivial example. Docker is an open-source project to easily create lightweight, portable, and self-sufficient containers from any application. We are using Docker as our core technology. With this tutorial we demonstrate how to use Docker by dockerizing MeanJS, a popular Node.JS Webstack.
One of the great features of Docker is the low barrier of entry. With the proper Dockerfile,
docker-compose installed it’s basically a simple
docker-compose up to get everything up and running.
Here’s an ASCII cast for an overview:
Now let’s take a step back and see what this is all about.
MEAN is an abbreviation for:
- MongoDB: a document database,
- Express: a web application framework for node,
The term MEAN was coined in the MongoDB blog and it characterizes the collection of these tools. So there is no actual “THE Mean stack”. With its growing popularity there have been various projects, which glue these different components together and make it very easy to get started. The most prominent are a Yeoman MEAN generator, MEAN.io, and it’s fork MEAN.JS.
This tutorial discusses the dockerization of MEANJS.org but it should be easily applicable to the other MEAN stacks.
Motivation for Dockerization
When starting out with a web application, it’s common to start with one machine. But as the application grows the demand for the different parts changes. And as many software developers know: once something is mixed up it’s hard to split it up again. So why not start with different components from the get go?
When looking at the example MEAN.JS: Why not build a container for the database and the web server and have each running in their own container respectively? In development they could still run on the same host, but when going into production these containers could be put on different machines.
The greatest objection is the increased effort in creating, starting, and maintaining these containers. We believe that the initial effort is very low and the long run benefit very high. Let us show you how easy it is to get started.
Ensure that both tools are installed:
$ docker version $ docker-compose --version
Build and run
The easiest way to get started is to clone the MEAN.JS fork with the proper Dockerfile:
$ git clone https://github.com/luebken/mean.git $ cd mean
The easiest way to build and and run our two containers is docker-compose. This tool looks at the project’s
docker-compose.yml and builds and runs the Docker containers appropriately:
Build the Docker containers with docker-compose:
$ docker-compose build
Run the Docker containers with docker-compose:
$ docker-compose up
Building and running your containers with pure Docker is just a little more complicated. Only the mean container needs to be built since the mongo container uses a predefined image.
Build the container with mean installed:
$ docker build -t mean .
-t mean names the built image “mean”.
To get the application running, the MongoDB container needs be started first:
$ docker run -p 27017:27017 -d --name db mongo
-p 27017:27017exposes the MongoDB port so the mean container can connect to it
-druns it as a background process
--name dbgives this cointainer a name so it can be referenced
mongois the image name that should be run
Next the mean container has to be started and connected to the db:
$ docker run -p 3000:3000 --link db:db_1 mean
-p 3000:3000exposes the web server port 3000
-druns it as a background process
--link db:db_1links the db container
Again. That’s it.
docker-compose actually does the same thing. And to recapitulate, let’s examine docker-compose’s YAML file
web: build: . links: - db ports: - "3000:3000" db: image: mongo ports: - "27017:27017"
For a detailed explanation see the docker-compose reference.
To the virtual box / boot2docker users: don’t forget to enable port forwarding for the appropriate ports.
VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port3000,tcp,,3000,,3000";
Wether we started with docker-compose or with docker, opening the browser at
localhost:3000 should show the MEAN.JS starting site:
Now, let’s examine what the two containers are about.
The database container
The database container doesn’t require custom code. This is because it uses the official predefined mongo image: [registry.hub.docker.com//mongo](https://registry.hub.docker.com//mongo/). Currently the Dockerfile, from which the image is built is not linked but can be found here: github.com/docker-library/mongo.
Here are some commands for fooling around with the mongo image:
# Pull the official image: $ docker pull mongo # Run a mongo container in open a bash shell in it: $ docker run -i -t mongo /bin/bash # Inspect the different layers $ docker history mongo # Show low-level information on a container $ docker inspect mongo
With databases, the foremost question is: where does it store its data? An examination of the MongoDB Dockerfile shows near the end that it marks /data/db as a volume:
For detailed information on volumes see the userguide but to get started, you might want to examine the volume and backup the data:
# Run docker inspect and filter for the volumes mounted $ docker inspect --format '' mean_db_1 # To run a simple backup command start $ docker run --volumes-from db -v $(pwd):/backup \ ubuntu tar cvf /backup/backup.tar /data/db
The container with the mean application is a little more interesting:
FROM node WORKDIR /home/mean # Install Mean.JS Prerequisites RUN npm install -g grunt-cli RUN npm install -g bower # Install Mean.JS packages ADD package.json /home/mean/package.json RUN npm install # Manually trigger bower. ADD .bowerrc /home/mean/.bowerrc ADD bower.json /home/mean/bower.json RUN bower install --config.interactive=false --allow-root # Make everything available for start ADD . /home/mean # currently only works for development ENV NODE_ENV development # Port 3000 for server # Port 35729 for livereload EXPOSE 3000 35729 CMD ["grunt"]
A Dockerfile is a set of instructions to build and create an image. After each instruction the docker build commits a new layer. If a layer hasn’t changed, it doesn’t need to be rebuilt the next time the build runs. Instead the cached layers are used. This layering system is one of the reasons why Docker is so fast.
Let’s go through a brief overview of the instructions used in this Dockerfile:
FROMtells the Docker with which image to start. All Docker images need to have one root image. In this case it is the official node image.
WORKDIRsets the current working directory for subsequent instructions
RUNstarts arbitrary commands during the build process. The first two are installing the tools bower and grunt.
ADDadds files from the host file system into the Docker container. In the first instance the
package.jsonis added so that in a subsequent step the packages can be installed via
- Note that there are several
RUNinstructions. The reason for this are the different layers. For example as long as the
package.jsonis not changed the appropriate layer is not changed. Docker can use the existing layer. So new NPM packages only get installed if needed.
ENVsets environment variables.
EXPOSEexposes PORTS from the container.
CMDsets the command that is executed once the container is run.
We hope that this tutorial gives a good impression on how to use Docker with a small web project. In particular with MEAN.JS. A Dockerfile gives an impressive head-start since a developer now only needs a
docker-compose up to provision and start it`s containers. At the same time the Dockerfile comprehensively shows how this is achieved and best practices can be shared within a team.
The next steps are to further modularize our application and to get the containers running on a server. This might be part of a future tutorial. Meanwhile get your hands dirty, fool around with the Dockerfile, and leave comments and pull requests on improvement suggestions.
Bringing this setup into production requires some more thought. We at Giant Swarm are working hard to make this next step as seamless as the first one.