Compose the containers
- 1 - Docker - Introduction
- 2 - Compose the containers
- 3 - The Dockerfile
- 4 - Docker image optimization
- 5 - Multi-stage dockerfiles
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
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!