Introduction to container based development part 4/4 - Compose
Tags: docker
Published: 2020-03-26 20:15:34

This is the fourth part of our container based development series. Check the other posts at the end of this one.

This post is based on the container development [article]( https://github.com/drFabio/containerDevelopment/blob/master/article.pdf) created by me.

Running containers manually is useful but it quickly gets out of hand when you need to manage complex architectures, to make things easy we can use [docker-compose](https://docs.docker.com/compose/). It has a nice and centralized way to create multiple containers at the same time.

Let's port our old example with docker-compose and see how it looks.The [official documentation](https://docs.docker.com/compose/gettingstarted/) is full of more info and all the files for this step are on the projects repository on the [firstDockerCompose branch](https://github.com/drFabio/containerDevelopment/tree/firstDockerCompose).

Let's create on the same folder that our files are a folder to use as a volume. It is customary to keep all docker-compose related files under the same root, this folder will be called "ourLocalVolume". Then we need to create a [compose file](https://docs.docker.com/compose/compose-file/). This compose file is defining a single service called runner, building it from a local dockerFile and creating a volume called ourLocalVolume mounted on the container /app/ourApp/data.

```yml
version: '3'
services:
  runner:
    build: .
    volumes:
      - ./ourLocalVolume:/app/ourApp/data
```

To run our solution we need to do the following command on the terminal:

```shell
docker-compose up
```

If we make a change to our file we need to rebuild it with:
```shell
docker-compose build
```

Or we need to run and build with:

```shell
docker-compose up --build
```

Rebuilding every time is not very productive. Our code is also very simple we are not doing anything special with the node image we could easily be more productive if our code changes on the host machine would be transferred to the container. The way to achieve that is by using a volume with our code and our data , actually after all this changes we don't even need to make our own custom Dockerfile we can do everything using docker-compose. We are going to tidy our space a little so see the changes the [secondDockerCompose branch](https://github.com/drFabio/containerDevelopment/tree/secondDockerCompose).

Our compose volume changed and now we are going to use the node image directly:
```yml
version: '3'
services:
  runner:
    image: node:13-alpine3.10
    volumes:
      - ./src:/app
    working_dir: /app
    command: node ./nodeCounter.js
```
Since our code now lives on a volume along with our data we can update our code without needing to rebuild an image. Also since we are essentially only using a node image we do not need to create a custom image every time.

## Getting dangerous, multiple services!

Ok compose for a single service is nice but not as nice as managing an actual architecture.

We are going to maintain this counter but it will count to up to 20 and then reset and never stop.Also we are going to make a web frontend with php that is going to show the counter as it was at the moment somebody entered the page. Our file architecture will change again so check the 
[thirdDockerCompose branch](https://github.com/drFabio/containerDevelopment/tree/thirdDockerCompose).

We are going to change our node to run from a fixed place that is going to be our volume data
```javascript
const fs = require('fs')
const path = require('path')

const counterFile = path.resolve('/data/counter.txt')

const COUNTER_LIMIT = 20
const INTERVAL = 1000
let counterData = 0
if (fs.existsSync(counterFile)) {
  const fileData = fs.readFileSync(counterFile)
  counterData = parseInt(fileData, 10)
}

const stopCounter = counterData + COUNTER_LIMIT

const recursiveCounter = () => {
  if (counterData === stopCounter) {
    counterData = 0
  }
  console.log(counterData)
  counterData++
  fs.writeFileSync(counterFile, counterData)
  setTimeout(recursiveCounter, INTERVAL)
}

recursiveCounter()
```

And this will be our PHP code:

```php
<?php
    $counter = file_get_contents('/data/counter.txt');
?>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Counter <?=$counter ?></title>
</head>
<body>
  <h1>Welcome to the counter view</h1>
  <p>At this moment the counter is <?=$counter ?></p>
  <p>Reload for even more counter fun!</p>
</body>
</html>
```
Our docker-compose needs to change so it will have 2 services! Also we will link the volumes so the PHP can read what node is writing.

```yml
version: '3'
services:
  runner:
    image: node:13-alpine3.10
    volumes:
      - ./src/counter:/app
      - ./src/data:/data
    working_dir: /app
    command: node ./nodeCounter.js
  web:
    image: php:7.2-apache
    volumes:
      - ./src/web:/var/www/html/
      - ./src/data:/data
    ports:
      - 8081:80
```

If we run docker-compose up we should be able to see our page on http://localhost:8081. Reload and you will see changes.
Also Note that our system now never stop to exit we need to send a [SIGTERM](https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html) by typing "ctrl+c"

Let's take a moment to appreciate what is happening:

- We created 2 services based on 2 different images.
- We created 4 volumes being 2 of them shared between different containers.
- We create a port forwarding from a container to our host machine.
- We "installed" a php with apache and node with only 16 lines of code!

Now that you were introduced to containers see how they can help you be a more productive and happier dev. Also there is one bonus part on the PDF [article](https://github.com/drFabio/containerDevelopment/blob/master/article.pdf) when we add a Redis database to our Node, PHP project just for the laughs.

Have fun, be happy, be healthy be kind.