Docker is becoming increasingly popular and I’ve been slowly introducing it into my projects. It makes it easy to distribute your applications because regardless of where you deploy your containers to, the experience will be the same. Let’s see how this works using Docker and Node, which has many variations. While they generally work regardless of the web application scenario, you can’t truly be sure when using the Node.js runtime.

We’re going to see how to build a custom NoSQL container and deploy it alongside a custom web application container that makes use of Node and Docker to encapsulate the needed Couchbase functions.

If this is your first time being exposed to Docker containers, let’s break down what we’re hoping to accomplish. Couchbase has published an official Docker image to Docker Hub, but the image is not pre-provisioned. That is not a bad thing and it is definitely what we would hope to expect in a database container. This means that we need to create a custom image based on the official image, otherwise when we deploy Couchbase, it won’t have been set up. When it comes to Node.js, we’re going to create an application and package it into a Docker container. These two Couchbase and Node.js Docker containers will be able to communicate with each other.

Creating a Custom Couchbase Server Docker Image and Container

Create a directory somewhere on your computer and include the following two files:

Dockerfile configure.sh 1 2 Dockerfile configure.sh

The Dockerfile file will represent our custom image, and the configure.sh file will be a runtime script for when we deploy our container.

Open the Dockerfile file and include the following:

FROM couchbase COPY configure.sh /opt/couchbase CMD ["/opt/couchbase/configure.sh"] 1 2 3 4 5 FROM couchbase COPY configure.sh /opt/couchbase CMD ["/opt/couchbase/configure.sh"]

Our custom image will use the official Couchbase image as the base. At build time, the configuration script will be copied to the image. When run, the script will be executed. Let’s take a look at that script.

Open the configure.sh file and include the following:

set -m /entrypoint.sh couchbase-server & sleep 15 curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=512 -d indexMemoryQuota=512 curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2cn1ql%2Cindex curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=$COUCHBASE_ADMINISTRATOR_USERNAME -d password=$COUCHBASE_ADMINISTRATOR_PASSWORD curl -i -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized' curl -v -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/pools/default/buckets -d name=$COUCHBASE_BUCKET -d bucketType=couchbase -d ramQuotaMB=128 -d authType=sasl -d saslPassword=$COUCHBASE_BUCKET_PASSWORD sleep 15 curl -v http://127.0.0.1:8093/query/service -d "statement=CREATE PRIMARY INDEX ON `$COUCHBASE_BUCKET`" fg 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 set -m /entrypoint.sh couchbase-server & sleep 15 curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=512 -d indexMemoryQuota=512 curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2cn1ql%2Cindex curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=$COUCHBASE_ADMINISTRATOR_USERNAME -d password=$COUCHBASE_ADMINISTRATOR_PASSWORD curl -i -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized' curl -v -u $COUCHBASE_ADMINISTRATOR_USERNAME:$COUCHBASE_ADMINISTRATOR_PASSWORD -X POST http://127.0.0.1:8091/pools/default/buckets -d name=$COUCHBASE_BUCKET -d bucketType=couchbase -d ramQuotaMB=128 -d authType=sasl -d saslPassword=$COUCHBASE_BUCKET_PASSWORD sleep 15 curl -v http://127.0.0.1:8093/query/service -d "statement=CREATE PRIMARY INDEX ON `$COUCHBASE_BUCKET`" fg 1

Again, the point of the configure.sh script is to provision the server after it is launched. To do this, we can make use of the Couchbase RESTful API. Configuration includes creating a cluster, enabling Couchbase services, defining administration credentials, creating a Bucket, and creating a N1QL index on the bucket.

In the configuration script you’ll notice several environment variables being used, for example the $COUCHBASE_ADMINISTRATOR_USERNAME variable. This will save us from hard-coding potentially sensitive information into the image. Instead we can define these variables at deployment.

Now let’s build the custom Couchbase image for our project. From the Docker Shell, execute the following:

docker build -t couchbase-custom /path/to/directory/with/dockerfile 1 docker build -t couchbase-custom /path/to/directory/with/dockerfile

In the above command, the couchbase-custom text is the name of our image, while the path is the path to our Dockerfile and configure.sh files.

With the custom image created for Couchbase, execute the following command to run it:

docker run -d \ -p 8091-8093:8091-8093 \ -e COUCHBASE_ADMINISTRATOR_USERNAME=Administrator \ -e COUCHBASE_ADMINISTRATOR_PASSWORD=password \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ --network="docker_default" \ --name couchbase \ couchbase-custom 1 2 3 4 5 6 7 8 9 docker run -d \ -p 8091-8093:8091-8093 \ -e COUCHBASE_ADMINISTRATOR_USERNAME=Administrator \ -e COUCHBASE_ADMINISTRATOR_PASSWORD=password \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ --network="docker_default" \ --name couchbase \ couchbase-custom

The above command says we want to deploy a container, mapping each of the necessary Couchbase ports. We pass in the environment variables that are found in our configure.sh script and we define which network we want the container to run on. The container will be named couchbase , which is also going to be the container’s host name.

Creating a Node.js RESTful API Web Application

With Couchbase up and running, we can develop a simple Node.js application. Create a directory somewhere on your computer to represent our project. Within that project, create an app.js file. From the Command Prompt or Terminal, execute the following:

npm init --y 1 npm init --y

The above command will create a very basic package.json file which will be the foundation of our Node.js application. There are a few dependencies that must be installed, so let’s execute the following to obtain them:

npm install express couchbase body-parser uuid --save 1 npm install express couchbase body-parser uuid --save

We’ll be using express for building our API, couchbase as our SDK, body-parser for working with POST bodies, and uuid for generating unique id values that will represent document keys.

With the application foundation in place, we can start developing the application. Open the project’s app.js file and include the following code:

var Couchbase = require("couchbase"); var Express = require("express"); var BodyParser = require("body-parser"); var UUID = require("uuid"); var app = Express(); var N1qlQuery = Couchbase.N1qlQuery; var bucket = (new Couchbase.Cluster("couchbase://" + process.env.COUCHBASE_HOST)).openBucket(process.env.COUCHBASE_BUCKET, process.env.COUCHBASE_BUCKET_PASSWORD); app.use(BodyParser.json()); app.get("/", function(request, response) { response.send("Try using the `/get` or `/save` endpoints!"); }); app.get("/get", function(request, response) { var query = N1qlQuery.fromString("SELECT `" + bucket._name + "`.* FROM `" + bucket._name + "`"); bucket.query(query, function(error, result) { if(error) { return response.status(500).send(error); } response.send(result); }); }); app.post("/save", function(request, response) { bucket.insert(UUID.v4(), request.body, function(error, result) { if(error) { return response.status(500).send(error); } response.send(result); }); }); var server = app.listen(process.env.APPLICATION_PORT || 3000, function() { console.log("Listening on port " + server.address().port + "..."); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var Couchbase = require("couchbase"); var Express = require("express"); var BodyParser = require("body-parser"); var UUID = require("uuid"); var app = Express(); var N1qlQuery = Couchbase.N1qlQuery; var bucket = (new Couchbase.Cluster("couchbase://" + process.env.COUCHBASE_HOST)).openBucket(process.env.COUCHBASE_BUCKET, process.env.COUCHBASE_BUCKET_PASSWORD); app.use(BodyParser.json()); app.get("/", function(request, response) { response.send("Try using the `/get` or `/save` endpoints!"); }); app.get("/get", function(request, response) { var query = N1qlQuery.fromString("SELECT `" + bucket._name + "`.* FROM `" + bucket._name + "`"); bucket.query(query, function(error, result) { if(error) { return response.status(500).send(error); } response.send(result); }); }); app.post("/save", function(request, response) { bucket.insert(UUID.v4(), request.body, function(error, result) { if(error) { return response.status(500).send(error); } response.send(result); }); }); var server = app.listen(process.env.APPLICATION_PORT || 3000, function() { console.log("Listening on port " + server.address().port + "..."); });

So what is happening in the above code?

First we import all the downloaded dependencies and then we establish a connection to Couchbase. You’ll notice that we are using process.env throughout the code. This allows us to read environment variables. We’re going to be using a similar approach to what we saw in the Couchbase image.

The API has two endpoints that actually do anything. The /save endpoint will insert whatever is in the request body and the /get endpoint will query for whatever is saved in Couchbase.

Containerizing the Node.js Web Application

With the application built, we need to containerize it so it can be deployed easily with Docker. In the same directory as your package.json file, add the following two files:

Dockerfile .dockerignore 1 2 Dockerfile .dockerignore

The plan is to create a custom image based on the official Node.js Docker image. Within the Dockerfile file, include the following:

FROM node:6-alpine COPY . /srv/ WORKDIR /srv RUN /usr/local/bin/npm install CMD /usr/local/bin/node app.js 1 2 3 4 5 6 7 8 FROM node:6-alpine COPY . /srv/ WORKDIR /srv RUN /usr/local/bin/npm install CMD /usr/local/bin/node app.js

During the image build time, we are going to copy our project into the image filesystem and change the working directory. Then we are going to install all the dependencies found in the package.json file. When the container is deployed, the app.js file will be ran.

You might be wondering why we are doing an install when we build the image. We are doing this because we don’t want to copy the dependencies into the image as there could be OS and architecture incompatibilities. To exclude the node_modules directory from our image, include the following in the .dockerignore file:

node_modules 1 node_modules

Anything you want excluded from the image can go in that file.

To build this image, execute the following from the Docker Shell:

docker build -t nodejs-custom /path/to/directory/with/dockerfile 1 docker build -t nodejs-custom /path/to/directory/with/dockerfile

The image name for our Node.js application will be called nodejs-custom and it will be based on the directory that contains the Dockerfile file. More information on building custom Docker images can be found in a previous article that I wrote.

With the image available, we need to run it. From the Docker Shell, execute the following:

docker run -d \ -p 3000:3000 \ -e COUCHBASE_HOST=couchbase \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ -e APPLICATION_PORT=3000 \ --network="docker_default" \ --name nodejs \ nodejs-custom 1 2 3 4 5 6 7 8 9 docker run -d \ -p 3000:3000 \ -e COUCHBASE_HOST=couchbase \ -e COUCHBASE_BUCKET=default \ -e COUCHBASE_BUCKET_PASSWORD= \ -e APPLICATION_PORT=3000 \ --network="docker_default" \ --name nodejs \ nodejs-custom

The above command should look familiar to what we saw in the Couchbase deployment. We are defining the port mapping of our application, passing in the environment variables that are used in the app.js file, defining the container network and the image name.

If you go to your web browser and navigate to any of the endpoints of the web application, it should work fine.

Using a Compose File for Deploying Docker Containers

Having to remember all the environment variables and everything in the command for deploying the containers can be painful. Creating a Compose file can make things a lot simpler.

After you’ve created your two custom images, create a docker-compose.yml file somewhere on your computer. This file should contain the following:

version: '2' services: couchbase: image: couchbase-custom ports: - 8091:8091 - 8092:8092 - 8093:8093 environment: - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator - COUCHBASE_ADMINISTRATOR_PASSWORD=password - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= nodejs: image: nodejs-custom ports: - 3000:3000 environment: - COUCHBASE_HOST=couchbase - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= - APPLICATION_PORT=3000 restart: always 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 version: '2' services: couchbase: image: couchbase-custom ports: - 8091:8091 - 8092:8092 - 8093:8093 environment: - COUCHBASE_ADMINISTRATOR_USERNAME=Administrator - COUCHBASE_ADMINISTRATOR_PASSWORD=password - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= nodejs: image: nodejs-custom ports: - 3000:3000 environment: - COUCHBASE_HOST=couchbase - COUCHBASE_BUCKET=default - COUCHBASE_BUCKET_PASSWORD= - APPLICATION_PORT=3000 restart: always

When using Compose, you don’t have to worry about defining the network as everything in the file will be on the same network. To launch either of the containers, you can execute the following:

docker-compose run -d --service-ports --name couchbase couchbase 1 docker-compose run -d --service-ports --name couchbase couchbase

With Couchbase, you can’t spin everything up together because Couchbase doesn’t have a method of telling you when it is ready. Because of this, the Node.js application might try to connect before Couchbase is ready to accept connections.

docker-compose up -d 1 docker-compose up -d

The above command usually spins up all containers in the Compose file, but we can’t use it in this scenario.

Conclusion

If you’re wondering how you can deploy your Node.js with Couchbase web application, Docker is an easy solution. After building images of either Couchbase or Node.js, you can trust that it will work the same on any server or computer running the Docker Engine.

For more information on using the Couchbase Node.js SDK, check out the Couchbase Developer Portal.