Docker Compose

Learn how to use Docker Compose. We will create a main app which can query two micro services. In this case we will use Docker Compose to start all three apps with a single command. They will each have their own image and own container.

Click on the link I provide above as it is a good overview of docker compose. We have no book, so links like that are an important part of our course. You don’t have to read everything, but at least read enough to get a feel for the technology and an understanding of what documentation is available.

Create Apps

Create a dirctory in the root of your repository called week04-docker-composer

inside it, create three express apps:

  • main: Port 30025
  • system-environment: Port 30028
  • route-tester: Port 30029

Make the standard changes to bin/www and routes/index.js. Be sure all three Home page contains your last name in its title. Implement you-rang for route-tester and system-environment.

Here is what you-rang should return:

function splitStringOnAnyInstanceOfCharacter(path, character, countFromEnd) {
    const pathParts = path.split(character);
    return pathParts.slice(Math.max(pathParts.length - countFromEnd, 1)).join(character);
}

const rangData = {
    "program": "qux",
    "file": "routes/index.js",
  	"result": "qux you rang",
  	"server": "qux",
  	"directory": splitStringOnAnyInstanceOfCharacter(__dirname, '/', 2),
  	"hostname": process.env.HOSTNAME,
  	"home": process.env.HOME
}

You should, of course, use common sense to change the hard coded strings to text that makes sense for any particular project.

In all three package.json files change nodemon to node.

splitStringOnAnyInstanceOfCharacter: Given the string path, it looks for slashes (/) and returns the string that is countFromEnd slashes from the end of the string. I’ve tried to make it flexible so that you can vary the string, the character (slash in our case) and offset. I passed in different offsets such as 1, 3, 4, 25 and it still seems to work.

Here is the output before splitString:

Before split string Docker Composer Server Data

Here is the output after:

After splitString

Query Micros

In /main/source/control.js, write two fetch statements to query our micro services:

window.onload = function() {
    fetch('/system-environment/you-rang')
        .then((response) => response.json())
        .then((result) => {
            console.log(result);
            const serverData = document.getElementById('system-environment');
            serverData.textContent = JSON.stringify(result, null, 4);
        })
        .catch((ex) => {
            alert(ex);
        });
};

For this to work you might want this in views/index.pug:

h2 System Environment
pre#system-environment

In main, use npm to install request.

In main/routes/index.js implement the cross process request:

const requester = require('request');

router.get('/system-environment/you-rang', function(req, res) {
    requester('http://system-environment:30028/you-rang').pipe(res);
});

As you can see, we can refer to Docker containers by name.

Docker Composer Server Data

Docker Files

We need to create three Dockerfiles:

  • main/Dockerfile
  • route-tester/Dockerfile
  • system-environment/Dockerfile

Here is the one for main:

FROM node:latest
RUN mkdir -p /usr/src/elf-main
WORKDIR /usr/src/elf-main
COPY package.json /usr/src/elf-main/
RUN npm install
COPY . /usr/src/elf-main
EXPOSE 30025
RUN node_modules/.bin/webpack
CMD [ "npm", "start" ]

The other two are the same, except the ports (listed above) and the name.

For instance, this line is changed:

RUN mkdir -p /usr/src/elf-main

It should become:

RUN mkdir -p /usr/src/system-environment

Since there are multiple instances of the phrase elf-main to be changed, the simplest way to proceed is with a search and replace.

Docker Compose Install

If you have not done so already install docker compose:

jou
git pull
cd InstallScripts/
./DockerComposeInstall

Compose

Now we want to use DockerCompose to use three projects at once.

Create a docker compose file called docker-compose.yml:

version: '3'
services:
  main:
    build: main
    ports:
      - "30025:30025"
    restart: always
    command: npm start
  route-tester:
    build: route-tester
    ports:
      - "30028:30028"
    restart: always
    environment:
      - NODE_ENV=production
    command: npm start

You add the third element for system-environment.

Run this to start it: docker-compose up. It takes awhile, but when done, go to localhost:30025 and look at the console output.

If you edit your code and want to see the fix, try just running docker-compose up again. If that doesn’t work, then do this, which takes a bit longer: docker-compose up –build

One thing I have found. If we use expose instead of ports in the system-environment section of our docker-compose YML file, then we can access our service from the other containers in our project but it can’t be accessed from the external network:

expose:
  - "30028"

This provides us with additional security. The trade off is that we can’t use the browser to explore our service.

Build and Reset

We should have build and reset scripts. I believe they can be quite simple:

Build: docker compose up %1

Reset: docker compose down

If this doesn’t work, then do them one step at a time.

Push your results

You can push your finished image to the Docker Hub:

docker tag week04-docker-composer_main:latest charliecalvert/docker-compose-main
docker push charliecalvert/docker-compose-main

Though of course you would want to use your name on the Docker Hub.

Turn it in

The running Docker container should be accessible through localhost:30025. When it is launched, it will display the results of querying the two micro services in the console. Attach a screenshot of that to the assignment.

Also:

  • DockerHub URL of your three images.
    • Use docker push to create them.
  • From Github I need branch and folder. It is usually also a good idea to create a tag in case there is any doubt as to exactly what I should look at.

Hot Updates

Add volumes to docker-compose.yml

version: '3'
services:
  main:
    build: main
    ports:
      - "30025:30025"
    restart: always
    command: npm start
    volumes:
      - ./main/:/usr/src/elf-main
      - /usr/src/elf-main/node_modules/
  route-tester:
    build: route-tester
    ports:
      - "30029:30029"
    restart: always
    environment:
      - NODE_ENV=production
    command: npm start
    volumes:
      - ./route-tester/:/usr/src/route-tester
      - /usr/src/route-tester/node_modules/
  system-environment:
    build: system-environment
    ports:
      - "30028:30028"
    restart: always
    environment:
      - NODE_ENV=production
    command: npm start
    volumes:
      - ./system-environment/bin:/usr/src/system-environment/bin
      - ./system-environment/public:/usr/src/system-environment/public
      - ./system-environment/routes:/usr/src/system-environment/routes
      - ./system-environment/views:/usr/src/system-environment/views
      - ./system-environment/app.js:/usr/src/system-environment/app.js
      - /usr/src/route-tester/node_modules/

The volumes section for system-environment is designed to not touch the root folder of system-environment, which should make it possible for us to clone our repo using the technique found in our Dockerfiles.

NOTE: By way of explanation, I’ll add one short not on the volumes code in our YML file. After we copy the files over, this second volumes line says delete everything in node_modules in the container:

- /usr/src/route-tester/node_modules/

In all three package.json files change the start script:

"scripts": {
  "start": "npx webpack --watch & nodemon ./bin/www",
  "build": "npx webpack",
  "test": "jest"
},

Install nodemon in all three apps:

npm i -D nodemon

If it does not already exist, create this file In system-environment/.gitignore and put this in it:

midterm-key
fall2019
git-ignore-tests
isit320-lastname-2019

Don’t forget to modify lastname in your repo name found in the .gitignore file! Use your common sense. What don’t you want to check in to GitHub? Whatever it is, put it in the .gitignore file.

In system-environment/nodemon.json:

{
  "verbose": true,
  "ignore": ["**/bower_components/**", "**/git-ignore-tests/**"]
}

Repo Missing

This should no longer be an issue, so you should be able to ignore it.

for instance:

docker exec -it week04-docker-composer_system-environment_1 bash

Or do this in your build:

cd system-environment && git clone git@github.com:charliecalvert/git-ignore-tests.git

Don’t do this: If you can’t get your repo into system-environment don’t try this:

docker exec -it <SYSTEM-ENVIRONMENT-CONTAINER-NAME> bash
eval `ssh-agent`
ssh-add midterm-key     
git clone git@github.com:charliecalvert/git-ignore-tests.git