Deploy a Node.js With Couchbase Web Application as Docker Containers
In this post, we'll look at how to use Node.js, Couchbase, and Docker to create, launch, and containerize a web application.
Join the DZone community and get the full member experience.
Join For FreeDocker 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, the experience will be the same. Take Node.js for example. There are many versions of Node.js and while they generally work regardless of the web application scenario, you can’t truly be sure.
We’re going to see how to build a custom Couchbase NoSQL container and deploy it alongside a custom Node.js web application container that makes use of the Couchbase container.
If this is your first time being exposed to Docker, 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
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"]
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
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. The configuration includes creating a cluster, enabling Couchbase services, defining administration credentials, creating a Bucket, and creating an 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
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
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. First, 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
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
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 + "...");
});
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 can 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
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
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 run.
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
Anything you want to be 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
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
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, and 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
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
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
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 a 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.
Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments