How do you start up your multi-container application? Lots of the time these multi-container setups have dependencies between them and you need to start them in a specific order? How can you get this done automatically on bootup? Today I’m going over how to use Docker Compose and systemd to automatically launch all your containers in the correct order on bootup leveraging systemd on a Debian host.
So if you’ve got an MQTT broker (or another service) that must start up before your Home Assistant service and you’re already using Docker Compose this is an article for you! This method works great on a Raspberry Pi but should also work on anything using systemd as the initilization system.
First off, there are probably dependencies between services in your docker-compose.yml
file. For the purposes of this article I’m going to detail the method I used for my Home Assistant configuration. My compose file looks like:
image: homeassistant/raspberrypi3-homeassistant:0.76.2 - /home/pi/home-assistant-config/configuration:/config - /etc/localtime:/etc/localtime:ro - "/dev/ttyACM0:/dev/zwave:rmw" image: martin211/rpi-influxdb:1.5.0-1 - "PRE_CREATE_DB=home_assistant" image: fg2it/grafana-armhf:v5.0.4 - /var/grafana:/var/lib/grafana image: leojrfs/mosquitto-arm:latest - /var/mosquitto/data:/mosquitto/data - /var/mosquitto/log:/mosquitto/log - ./mosquitto.conf:/mosquitto/config/mosquitto.conf |
If you’re interested in learning more about some of these services, check out my other articles:
Looking at this compose file you can see that both the grafana
and homeassistant
service depend on the influxdb
service. When our system boots up we want to make sure they get started in the correct order and the next one doesn’t start until the previous is up and running. Ideally, our application logic takes care of all this but it’s a good idea to built-in checks to the startup logic.
To take care of the service dependency mechanism, I’m going to be using wait-for-it, a bash script that will wait until it sees data on a port. Go ahead and download that script and mark it as executable.
wget "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" |
Next, we’ll create the service script that systemd will call to start and stop our multi-container Docker application.
I created a new shell script called “service” (no file extension).
# Bash script to startup all components of home assistant, mostly through # docker and checking if services are available using wait-for-it declare -r DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" declare -r WAIT="${DIR}/wait-for-it.sh" docker-compose up -d influxdb docker-compose up -d grafana docker-compose up -d mosquitto docker-compose up -d homeassistant ${WAIT} --timeout=120 localhost:8123 *) echo "Usage: ./service start|stop" |
Let’s break this down:
DIR
variable to the directory that holds this script. I’m assuming this is in the same directory as the wait-for-it.sh
script mentioned earlier as well as your docker-compose.yml
file.WAIT
variable to the path of the wait-for-it.sh
scriptstart
is where the startup happens. First a service is started using the docker-compose
command and then the wait-for-it.sh
script is called with the IP address and port to listen on. That script blocks until that port becomes active signaling the service is up and running. Change this section to match the services, ports, and order that make sense for your application.stop
just defines how to stop the application. In my case, I’m just letting docker-compose
manage this for me. You could shut down services in a particular order if that makes sense for your setup.After creating this file you should mark it as executable (chmod +x service
). To test it out run ./service start
to start the application and ./service stop
to bring it down.
Debian and many other Linux distributions use systemd as their initialization system. We can create a systemd service for our docker application so that the operating system can start it up on a reboot.
To create a systemd service you need a .service
file. For this example, I created a new file called ha.service
. See the contents below:
# systemd service for Home Assistant using docker-compose Description=Home Assistant ExecStart=/home/pi/home-assistant-config/service start ExecStop=/home/pi/home-assistant-config/service stop WantedBy=multi-user.target |
What does this do?
docker-compose.yml
is bringing upstart
is passed in as the first argument so that it brings up the application. You need to change this to wherever you are storing your service script.stop
is passed into the script so it shuts stuff down correctly.yes
. This tells systemd that even though my service script exited the service is still active. Our service
script exits after all the docker containers are spun up.multi-user.target
tells systemd to only start the service once the machine is basically done booting up. Then we know network connections and other services we depend on have already been started.After creating that file we need to enable it. I symlinked it into the /etc/systemd/system
directory and then enabled it through systemctl
.
# Symlink the service file to the correct location sudo ln -s /home/pi/home-assistant-config/ha.service /etc/systemd/system/ha.service sudo systemctl enable ha.service |
Everything should now be set up for a proper systemd
service. Reboot your server to make sure that everything starts up correctly. You can also use systemctl
to start and stop the service manually.
If you need help debugging, you can use journalctl
to view the systemd
logs. The following command usually works for me (make sure you scroll to the bottom):
At first, systemd
looks like it’s a really complicated solution for getting your application running after reboot, but hopefully breaking it down like this has cleared up some of the mystery. Let me know in the comments of other great systemd
best practices or how you bootup your docker applications.