7 minute read

This article is part 2 in a series: Docker



In this post I intend to introduce you to docker compose.
Compose is a tool which lets you structure up your containers in a neat little yaml file.
Yaml is a “language” that is quite easy to read and quite easy to write, so I’m sure that it wont be any issues keeping up even without prior experience with it.

To start of, we should decide on a project to run in docker.
A good one to start with could be a very simple php page that just says “hello world” but runs through nginx, so that is where we start!

The images

The images we will be using are

  • nginx (we go with the official package)
  • php-fpm (in this case I’ll be using one I’ve made (jitesoft/php-fpm))
  • busybox (which is a container that people often use as a data only container, I’ll describe this in a bit)

The compose file

When we know what containers we want to run, we create our docker-compose file.
The file is (usually) named docker-compose.yml and the following is a “first step” version of the image, before we connect all services and add volumes and such.

version: '2'

services:

  shared-data:
    image: busybox
    volumes:
      - "./nginx:/etc/nginx/conf.d"
      - "./src:/app"

  nginx:
    image: nginx:latest
    restart: on-failure
    ports:
      - 8080:80
    mem_limit: 512m
    
  fpm:
    image: jitesoft/fpm:latest
    restart: on-failure
    mem_limit: 512m
    expose:
      - 9000
  

Description

Initially we tell docker-compose that it is a version 3 type of compose file.
We then define a set of services.

Our first service is called shared-data, it uses the image busybox which is a super tiny image, the container will be used only to hold data, nothing more.
A data-only container is a container that is stopped as soon as it is created but due to it sharing data with other containers, it will be accessed (and in a sense started) on each access.
Yes, I know that it sounds like this would be a very heavy bottleneck, but it isn’t. If you REALLY don’t want to do it as above, you can just move the volumes lines to the other two services and skip the volumes_from thing I describe later.

The second service is the nginx service, it uses the official nginx image (in this case the latest version on docker hub).
We have a option here called restart which is for now set to on-failure, that means that if the nginx service crashes, it will be restarted automatically by docker.
After the restart option we define what ports we want mapped to which on the host computer, its done in a : pattern, and in this case we map the port 80 to 8080 on our local machine. We also add a `mem_limit` option to make sure that the container don't eat all of our memory!

The third and in this case last service is called fpm. I prefer to use FPM for php cause its very easy to use and its fast and sturdy.
If you wish to read more about fpm, I would recommend reading about it on their homepage.
We use the latest jitesoft/fpm image, which in moment of writing is php 7.1.
This container also has a restart option set to on-failure so that it restarts on crash.
We also include a mem_limit arg, just as with the nginx service, we don’t want this one to eat all our memory either.
At the end of the service we have a property called expose. Now, most of the images that uses ports already set their exposed ports inside the Dockerfile, but I prefer to also add it in the compose file so that its very obvious what is exposed.
Then what is expose? Well, within a docker-compose file, all services shares a default network (automatically set up by docker-compose). This means that they have access to each-other through their exposed ports, if we for example expose the fpm containers port 9000, the nginx container can access it through port 9000!
And we really want that!

Whats then is the difference between expose and port properties? you may ask. Well, expose opens up a port for its network-connected siblings, while ports opens up the ports for the host.
Exposing a port will not allow the host computer access to the container through that port, only the network connected containers.

If we start the docker containers we will have a few containers running, but they share no data nor any settings or configurations…
We need to fix this!

Adding data!

The files we want to serve will have to reside in the correct folders for docker-compose to add them to the data only container.
The container specifies ./src and ./nginx, which means that the directories it looks for are in the folder same folder as the compose file.
We might as well just create those two folders and then go on to adding files to them.

php file

Inside the src folder we create a index.php file with the following (this is not forced, you may do whatever you want!) code:

<?php
echo "<h1>HELLO WORLD!</h1>";

Save and close!

Now we have a php file, awesome!

For the php-fpm container to be able to reach this file, we will need to add it to the container as a volume of some sort.
In this example, we will use the shared-data container to give data to the other containers.
To let the fpm container use the data that the shared-data container provides, we need to use the property volumes_from in the docker-compose file.
That is, we change the fpm service to this:

  fpm:
    image: jitesoft/fpm:latest
    restart: on-failure
    mem_limit: 512m
    expose:
      - 9000
    volumes_from: 
      - shared-data

That’s it! Now all the data that the shared-data container has is included at the same place in the fpm container!
We also have to do the same to the nginx server, because it also need to be able to access some files (both the php file and its settings that we will set up next).

nginx config file

I’m not going to go in to deep into the swamp of nginx.
I like nginx, but to go through all its configuration possibilities… well, that would turn my blog into a very nginx focused site!

The configuration we will use could look something like this:

upsteam phpfpm_backend {
  server fpm:9000;
}

server {
  listen 80;
  
  root /src;
  index index.php;
  
  location ~ \.php$ {
    fastcgi_pass  phpfpm_backend;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include       fastcgi_params;
  }
}

In short, this configuration file will pass all calls to a php file to the index.php file served by the upstream, which is our fpm service.

Save the configuration file as nginx.conf and add it to the nginx directory.
Now what? well, we start the container of course!

docker-compose up

When running up with docker-compose, we will start docker compose itself, it will look for a suitable docker-compose file and it will start all the services in it.
When we are done with the services we can close them down by docker-compose down and it will stop all the services until we next need them!
Now, if you run up with out any arguments, you are kind of stuck in the terminal with the output of the docker containers.
This could be a good thing when you run your containers, especially locally so that you can watch all the logs and output while it happens, but if you for some reason do not want it to be like that, just supply the -d argument to the command and it will be detached from all containers when they are started.

So:

docker-compose up -d

Open browser and enter: localhost:8080 and you should be able to see the very beautiful site we just made available to your localhost!

End notes

There is more to docker-compose than this, of course. I would recommend browsing the docker-compose docs for more information.
I will likely post more information about compose in future docker posts too, but it’s always a good idea to read the docs!

Compose is production friendly and its possible to use overridable files and environment variables etc, I will post about this at a later time, but I thought it was worth noting!