Common usage

In this section, we are going to experience the power of the orchestration feature provided by the Docker Compose framework with the help of an example. For this purpose, we are going to build a two-tiered web application that will receive your inputs through a URL and respond with the associated response text. This application is built using the following two services, as enumerated here:

  • Redis: This is a key-value database used to store a key and its associated value
  • Node.js: This is a JavaScript runtime environment used to implement the web server functionality as well the application logic

Each of these services is packed inside two different containers that are stitched together using the docker-compose tool. The following is the architectural representation of the services:

Here, in this example, we begin with implementing the example.js module, a Node.js file to realize the web server, and the key lookup functionality. Further, we will craft the Dockerfile on the same directory as example.js to package the Node.js runtime environment, and then, define the service orchestration using a docker-compose.yml file in the same directory as example.js.

The following is the example.js file, which is a Node.js implementation of the simple request/response web application. For demonstration, in this sample code, we restrict the request and response for just two docker-compose commands (build and kill). For the code to be self-explanatory, we added comments in the code:

// A Simple Request/Response web application 

// Load all required libraries
var http = require('http');
var url = require('url');
var redis = require('redis');

// Connect to redis server running
// createClient API is called with
// -- 6379, a well-known port to which the
// redis server listens to
// -- redis, is the name of the service (container)
// that runs redis server
var client = redis.createClient(6379, 'redis');

// Set the key value pair in the redis server

// Here all the keys proceeds with "/", because
// URL parser always have "/" as its first character
client.set("/", "Welcome to Docker-Compose helpernEnter the docker-compose command in the URL for helpn", redis.print);
client.set("/build", "Build or rebuild services", redis.print);
client.set("/kill", "Kill containers", redis.print);

var server = http.createServer(function (request, response) {
var href = url.parse(request.url, true).href;
response.writeHead(200, {"Content-Type": "text/plain"});

// Pull the response (value) string using the URL
client.get(href, function (err, reply) {
if ( reply == null ) response.write("Command: " +
href.slice(1) + " not supportedn");
else response.write(reply + "n");
response.end();
});
});

console.log("Listening on port 80");
server.listen(80);

The following text is the content of Dockerfile that packs the Node.js image, the redis driver for Node.js, and the example.js file, as defined earlier:

############################################### 
# Dockerfile to build a sample web application
###############################################

# Base image is node.js
FROM node:latest

# Author: Dr. Peter
MAINTAINER Dr. Peter <[email protected]>

# Install redis driver for node.js
RUN npm install redis

# Copy the source code to the Docker image
ADD example.js /myapp/example.js

The following text is from the docker-compose.yml file that defines the services that the Docker Compose tool orchestrates:

version: "3.1" 
services:
web:
build: .
command: node /myapp/example.js
depends_on:
- redis
ports:
- 8080:80
redis:
image: redis:latest

We defined two services in this docker-compose.yml file, wherein these services serve the following purposes:

  • The service named web is built using the Dockerfile in the current directory. Also, it is instructed that you launch the container by running the node (the Node.js runtime) with /myapp/example.js (web application implementation), as its argument. Since this Node.js application uses the redis database, the web service is forced to start after the redis service using the depends_on instruction. Besides, the 80 container port is mapped to the 8080 Docker host's port.
  • The service named redis is instructed to launch a container with the redis:latest image. If the image is not present in the Docker host, the Docker Engine will pull it from the central repository or the private repository.

Now, let's continue with our example by building the Docker images using the docker-compose build command, launch the containers using the docker-compose up command, and connect with a browser to verify the request/response functionality, as explained step by step here:

  1. The docker-compose commands must be executed from the directory in which the docker-compose.yml file is stored. Besides, docker-compose considers each docker-compose.yml file as a project, and it assumes the project name from the docker-compose.yml file's directory. Of course, this can be overridden using the -p option. So, as a first step, let's change the directory, wherein the docker-compose.yml file is stored:
      $ cd ~/example
  1. Build the services using the docker-compose build command:
      $ sudo docker-compose build
  1. Pull the images from the repository using the docker-compose pull command:
      $ sudo docker-compose pull
  1. Proceed to bring up the services as indicated in the docker-compose.yml file using the docker-compose up command:
      $ sudo docker-compose up
Creating network "example_default" with the default
driver
Creating example_redis_1
Creating example_web_1
Attaching to example_redis_1, example_web_1
redis_1 | 1:C 03 Feb 18:09:40.743 # Warning: no
config file specified, using the default config.
In order to specify a config file use redis-server
/path/to/redis.conf
. . . TRUNCATED OUTPUT . . .
redis_1 | 1:M 03 Feb 18:03:47.438 * The server
is now ready to accept connections on port 6379
web_1 | Listening on port 80
web_1 | Reply: OK
web_1 | Reply: OK
web_1 | Reply: OK

Since the directory name is example, the docker-compose tool has assumed that the project name is example. If you pay attention to the first line of the output, you will notice the example_default network being created. The Docker Compose tool creates this bridge network by default and this network is used by the service for IP address resolution. Thus the services can reach the other services by just using the service names defined in the compose file.

  1. Having successfully orchestrated the services using the docker-compose tool, let's invoke the docker-compose ps command from a different Terminal to list the containers associated with the example docker-compose project:
      $ sudo docker-compose ps
Name Command
State Ports
--------------------------------------------------
-------------------------
example_redis_1 /entrypoint.sh redis-server
Up 6379/tcp
example_web_1 node /myapp/example.js
Up 0.0.0.0:8080->80/tcp

Evidently, the two example_redis_1 and example_web_1 containers are up and running. The container name is prefixed with example_, which is the docker-compose project name.

  1. Explore the functionality of our own request/response web application on a different Terminal of the Docker host, as illustrated here:
      $ curl http://localhost:8080
Welcome to Docker-Compose helper
Enter the docker-compose command in the URL for help
$ curl http://localhost:8080/build
Build or rebuild services
$ curl http://localhost:8080/something
Command: something not supported
Here, we are directly connecting to the web service using http://localhost:8080 because the web service is bound to the Docker host on port 8080. You can also access the service externally using the Docker host IP address and port 8080 (https://<docker host ip>:8080), provided the IP address and the port is reachable from the external system.

Cool, isn't it? With very minimal effort and with the help of the docker-compose.yml file, we are able to compose two different services together and offer a composite service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset