A quick dive into MongoDB and Docker
In times where everyone talks about DevOps, containers and docker I decided that I, as a Engineer and Projectmanager can no longer wait to get my own view on this. Well and it was worth to do so. Let me share some of my experiences, and please do not take this as a deep dive.
Before we begin to set up a simple node.js / mongodb app which we dockerize later please make sure you have Node.js, npm, express and Docker installed.
The tryout Node app
Let’s start by creating the Node.js app. For this we use the express generator. If you have express not yet installed please do so via:
npm install -g express
npm install -g express-generator
Then cd into a directory you want and create the tryout-app:
$ express tryout-app
create : tryout-app
create : tryout-app/package.json
create : tryout-app/app.js
create : tryout-app/publiccreate : tryout-app/routes
create : tryout-app/routes/index.js
create : tryout-app/routes/users.js
create : tryout-app/views
create : tryout-app/views/index.jade
create : tryout-app/views/layout.jade
create : tryout-app/views/error.jade
create : tryout-app/bincreate : tryout-app/bin/www
create : tryout-app/public/javascripts
create : tryout-app/public/images
create : tryout-app/public/stylesheets
create : tryout-app/public/stylesheets/style.css
install dependencies:
$ cd tryout-app && npm install
run the app:
$ DEBUG=tryout-app:* npm start
Just go ahead and install the needed dependencies, as printed out and run the tryout-app
DEBUG=tryout-app:* npm start
> tryout-app@0.0.0 start /home/mi01/Projects/mongodb-docker/tryout-app
> node ./bin/www
tryout-app:server Listening on port 3000 +0ms
Head over to your browser and open up http://localhost:3000 You should see the “Welcome to Express” page. You should see the “Welcome to Express” page.
Let’s go to the dock
Ok, ok too early for a beer. So let’s get some work done... The absolute basic tryout.app is there, so next is to move this to docker.
The first thing to think about is to choose an image which we we’ll use to run our app container. There are many different choices. The most popular and easy choice might be the official Node.js image. I poked a little bit around and found that this is compressed already over a 190 MB large. Little bit much for this tryout. I came across a Dockerfile which is using the alpine image it uses a minimal linux so that some package need to be installed but a compressed size of 2 MB made me curious.
The Dockerfile
Go to the just created tryout-app folder and create a file named Dockerfile. This file contains the instructions order to build the project image. Please use the following content:
# the image is based on alpine-node
FROM mhart/alpine-node:latest
RUN rm -rf /tmp/node_modules
ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/
WORKDIR /opt/app
ADD . /opt/app
EXPOSE 3000
CMD ["npm", "start"]
Building the image, running the container
In order to build our Docker image we’ll use the docker build command. Head over to your console and in the project root directory run the following:
docker build -t tryout-app . Successfully built 8827c4c8874b Successfully tagged tryout-app:latest
You can check the built image in your local images list:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tryout-app latest 8827c4c8874b 28 seconds ago 87.6MB
Once you’ve built the image, you can run it on a container. Please stop the local server if it is still running and fire up the following command:
$ docker run -p 3000:3000 -ti tryout-app
> tryout-app@0.0.0 start /opt/app
> node ./bin/www
Super!! Seems to be ok. You want to see something? Just go to the browser and open up http://localhost:3000 again. You get the “Welcome to Express” page again. But this time out of your image! By the way 3000:3000 is a kind of port redirect. The container, which doesn’t know you mapped port 3000 of that container to 3000, making the correct URL http://localhost:3000. So you can have a different public port as the one you exposed in the dockerfile.
Docker Compose
For a simple webapp, it’s easy with a single Dockerfile and using docker build and docker run. But when we have multiple containers as we will later with MongoDB, it becomes a bit more complex.
Docker comes with a very useful tool called docker-compose. This tool allows us to define all of our container and the relationships between them in a single docker-compose.yml file. So let’s build the docker-compose file for the same configuration we ran before later we’ll add a MongoDB container and connect them. Create a docker-compose.yml file in the project root and put the following contents in it:
services:
web:
build: .
Ports:
- "3000:3000"
A quick glance on it:
- use version 2 of Docker Compose config syntax
- define all the services that will be run (for the beinning this is only a webservice)
- build in the current directory . is the target
- define the port mappings for the container, which are the same we used before with the -p option of the docker run command.
Now we can build and run the container via docker-compose. In case this is not installed please check this page
docker-compose build
This will build the image. Again you will get a message similar to this one:
Successfully built d077df0fb8a1 Successfully tagged tryoutapp web:latest
Then you can run the container with
docker-compose up
Bring MongoDB to the game
So, the basic stuff is working, up and running. Let’s add some database stuff to the app now. The containers will be able to communicate to each other at the end via some more configuration in the same docker-compose.yml file.
First we need the mongodb driver for our project, so let us add this to the package.json (the ^ will make sure we get the minor version updates)
"mongodb": "^2.2.9"
Let’s add a new rout to routes/index.js that will connect to a MongoDB and return some data from the dummy collection. The route file will look like this:
var express = require('express');
var router = express.Router();
var mongodb = require('mongodb');
var client = mongodb.MongoClient;
var uri = "mongodb://mongo/tryout-app";
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/data/from/db', function(req, res, next) {
client.connect(uri, function (err, db) {
if (err) return next(err);
var collection = db.collection('tryout');
collection.find({}).toArray(function(err, docs) {
if (err) return next(err);
return res.json(docs);
});
});
});
The URL is mongo to refer to the host of the database. This is because our MongoDB container will have that name and we’ll link it to our webapp container so that they know each other, more on this later.
We’ll add another route to create some data in the collection as well:
router.post('/data/into/db', function(req, res, next) {
client.connect(uri, function (err, db) {
if (err) return next(err);
var collection = db.collection('tryout');
collection.insertMany(req.body, function(err, result) {
return res.json({ result: "success" });
});
});
});
This route simply takes an array sent in the request body and inserts all the objects in the array into the tryout collection.
MongoDB container
Now we can connect to a MongoDB instance, so it is time to setup a container to run the MongoDB. Lets add some lines to the docker-compose.yml file:
version: "2"
services:
web:
build: .
Ports:
- "3000:3000"
links:
- mongo
mongo:
image: mongo
volumes:
- /data/mongodb/db:/data/db
ports:
- "27017:27017"
The “links” tag is where the containers get “connect” so that they can reference each other. This is what allows us to use a MongoDB connection URL like mongodb://mongo/tryout. The mongo in the URL references the mongo container that we define little bit further down of the docker-compose file.
Then we tell Docker to mount the host directory /data/mongodb/db into the container /data/db directory. This is not a must, you can even create another special container which is simply a volume you can mount in any container you want.
Finally we tell Docker to map the port 27017 on the host to the port 27017 on the container so that we can connect to the MongoDB server from any client we have in our local host.
Now we can build the and run the stuff via: docker-compose build and docker-compose up
Again you can see the welcome page in the browser on localhost:3000
Here comes the interesting part: we will now insert data to MongoDB by using the endpoint POST /data/into/db.
$ curl -X POST -H "Content-type: application/json" http://localhost:3000/data/into/db \
-d '[ { "a": 1 }, { "b": 2 }, { "c": 3 } ]'
You shoud get a {"result":"success"} back
Best of all let’s get the data back… by using our other endpoint GET /data/from/db
curl http://localhost:3000/data/from/db
[{"_id":"5a05baeabd0eba00109a9e81","a":1},{"_id":"5a05baeabd0eba00109a9e82","b":2},{"_id":"5a05baeabd0eba00109a9e83","c":3}]
That’s it. Just a quick note the code in this example is really “tryout” so please do not use this in any productive environment. But for the basic concepts this should be ok.
The complete code can be found here
Have fun with you next containers :-)
Michael
Thank you Michael! I will follow it.