Summary
Here, I define a group of related services (in this case, nginx and php-fpm) using the 'docker-compose' tool. This allows us to start (and restart) collections of related containers at once. I also define a systemd 'unit' that will start services defined by docker-compose definitions.
Deets
First, a brief excursion: my data drive is getting a little messy now with docker build configurations, container configuration files, and then the legacy data. So I made some directories:
- /mnt/datadrive/srv/config
- /mnt/datadrive/srv/data
I moved my legacy data directories (formerly directly under 'srv') into 'data', and the existing container configuration file stuff under 'config'. OK, back to the story.
I got the two containerized services working together, but I started those things up manually, and had to create some other resources (i.e. the network) beforehand as well. There is a tool for automating this called 'docker-compose'. Try 'which docker-compose' to see if you already have it, and if not get it installed on the host:
# Install required packages
sudo apt update
sudo apt install -y python3-pip libffi-dev
sudo apt install -y libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev zlib1g-dev
# Install Docker Compose from pip (using Python3)
# This might take a while
sudo pip3 install docker-compose
So, docker-compose uses YAML to define the collection of stuff. The containers to run are called 'services', and the body defines the various parameters that would passed on the docker command line as I have been doing up to this point. But it's formatted in YAML! So get used to it.
Authoring the Compose File
Now I'm going to get a little ahead of myself and tell you that the end goal is to have this docker-compose definition be hooked up as a systemd service, and so I am going to put the file in the place that makes sense for that from the start. But don't think these files have to be in this location; you could have them in your home directory while you develop them, and maybe this is easier. You could move them into final position later.
You can find deets on the tool at the normative location https://docs.docker.com/compose/gettingstarted/ ; I am only going to discuss the parts that are meaningful in this project.
First, create the definition in the right place:
sudo mkdir -p /etc/docker/compose/myservices
Ultimately, I am going to create a 'generic' systemd unit definition that will work with any docker-compose yaml, not just the one I am creating here. If I wind up making more compose scripts for other logical service groups, then I would create a new directory under /etc/docker/compose with a separate name, and store its docker-compose.yml in that separate directory. So, basically, the directory names under /etc/docker/compose will become part of the systemd service name.
But again I'm getting ahead of myself (sorry). Onward...
Create the docker-compose definition:
/etc/docker/compose/myservices/docker-compose.yml
version: '3'
services:
#PHP-FPM service (must be FPM for nginx)
php:
image: php:fpm-alpine
container_name: php
restart: unless-stopped
tty: true
#don't need to specify ports here, because nginx will access from services-network
#ports:
# - "9000:9000"
volumes:
- /mnt/datadrive/srv/config/php/www.conf:/usr/local/etc/php-fpm.d/www.conf
- /mnt/datadrive/srv/data/www:/srv/www
networks:
- services-network
#nginx
www:
depends_on:
- php
image: nginx-certbot
container_name: www
restart: unless-stopped
tty: true
ports:
- "80:80"
volumes:
- /mnt/datadrive/srv/config/nginx/default.conf:/etc/nginx/conf.d/default.conf
- /mnt/datadrive/srv/data/www:/srv/www
networks:
- services-network
#Docker Networks
networks:
services-network:
driver: bridge
If you take a moment to read it, it should be intelligible if you were able to follow along up to this point regarding all those docker command line invocations and understand the switches I used. The particularly interesting things here (at least I think) are:
- the first line specified the docker-compose format version, and it simply is a requirement that this line be present
- the 'services' have names, and these become the names of the containers (rather than the docker-generated random two-part names). This is particularly useful because these will also become the container's machine name.
- the 'depends_on' key tells docker-compose that 'www' must be started after 'php', so I can control start order
- at the bottom I defined explicitly a network, named 'services-network' (can be whatever you like), using the bridge driver, and I explicitly connected www and php onto that network. Now www can communicate with php. Because of that, I don't need to have php publish its ports on the host machine -- no one needs to mess with those other than nginx.
There are other things that can be defined here, such as persistent docker volumes, but I am using the 'bind' method to make available storage resources on the host, so I don't have a 'volumes' section in this example.
You can get the thing running:
docker-compose -f /etc/docker/compose/myservices/docker-compose.yml up
(it's usually easier to be in the directory with the docker-compose.yml, then you can omit the -f option and simply issue 'docker-compose up')
and to bring them down:
docker-compose -f /etc/docker/compose/myservices/docker-compose.yml down
If all that is working, then I'm ready to wire it into systemd. (If it's not working, you might consider 'docker-compose logs'.)
systemd
OK, if you did perchance create the 'docker.www' systemd service from before, it's time to replace it, so first:
sudo service docker.www stop
sudo systemctl disable docker.www
sudo rm /etc/systemd/system/docker.www.service
Now I will make a fancier one:
/etc/systemd/system/docker-compose@.service:
[Unit]
Description=%i service with docker compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/etc/docker/compose/%i
# Remove old containers, images and volumes
ExecStartPre=/usr/local/bin/docker-compose down -v
ExecStartPre=/usr/local/bin/docker-compose rm -fv
ExecStartPre=-/bin/bash -c 'docker volume ls -qf "name=%i_" | xargs docker volume rm'
ExecStartPre=-/bin/bash -c 'docker network ls -qf "name=%i_" | xargs docker network rm'
ExecStartPre=-/bin/bash -c 'docker ps -aqf "name=%i_*" | xargs docker rm'
ExecStart=/usr/local/bin/docker-compose up -d --remove-orphans
ExecStop=/usr/local/bin/docker-compose down
[Install]
WantedBy=multi-user.target
This fancier one defines a general class of services that are all named 'docker-compose' followed by the particular service name, and separate by '@'. In our case, I've created 'docker-compose@myservices'. The 'myservices' part gets passed in as '%i', and the rest of the definition does the magicry of things in the working directory 'etc/docker/compose/%i', which has the particular docker-compose stuff for that service.
After creating that, I need to do the usual enable and first-time start:
sudo systemctl enable docker-compose@myservices
sudo systemctl start docker-compose@myservices
Now the www and php service should be up and running. And of course you can stop it or restart it the usual systemctl ways.
All this is nifty, but in this modern era who does unencrypted HTTP anymore? And especially since you can get TLS certificates for free from Let's Encrypt, I really should try to move towards that.
Next
Modifying the base nginx image to also include tools needed to use Let's Encrypt certificates, and renew them automatically.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.