Breadcrumb Build: Raspberry Pi and Docker Fun: Dependencies and Docker Compose

Last time we did the base build of a Raspberry Pi with Docker hosting a bunch of different services and we left Docker looking like this (as seen via Portainer)

We also walked through setting up an external Kodi client to reference the MySQL server.

That was pretty neat in that we did a bunch of consolidation, but each of the services on the docker host were more or less standalone in that there were no dependencies between them, and therefore the startup order was more or less irrelevant. All of the containers had a restart policy of always (meaning they would restart when crashed or when the docker host booted). So what would happen if we were to add a dependent container (e.g a Kodi client) to our Docker configuration?

Adding Kodi in a Container

  • SSH into Pi
  • sudo mkdir -p /storage/docker/kodi-headless-datadir/userdata
  • Copy source.xml and advancedsettings.xml configuration files to /storage/docker/kodi-headless-datadir/userdata
    • We created these files in the base build post and stored them for future use
  • docker pull codafog/kodi-rpi
  • docker run –name kodi-client –restart always –network=”host” -d –device=”/dev/tty0″ –device=”/dev/tty2″ –device=”/dev/fb0″ –device=”/dev/input” –device=”/dev/snd” –device=”/dev/vchiq” -v /var/run/dbus:/var/run/dbus -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v /storage/docker/kodi-headless-datadir:/config/kodi -p 8080:8080 -p 9777:9777/udp codafog/kodi-rpi

Now you should have a setup that looks like this in Portainer

And our Kodi client, available on http://[pi-static-ip]:8080 is showing the data we scraped in using our external Kodi client

But now we have two problems

Problem One: We have a dependency between the kodi-client container and the mysql-server container, in that Kodi needs MySQL to be running when it launches. But if you look at the configuration of the Kodi client, you will see the following startup command, which isn’t going to wait on anything.

Problem Two: We have a really complex docker run command to remember if we need to rebuild the Kodi container with a newer image

So I’m going to use Docker Compose to solve both of our problems. Docker Compose allows you to coordinate a related set of Docker containers, either from existing images or from ones that you want to build on the fly. We will do both in this case.

Installing Docker Compose

  • SSH into Pi
  • sudo apt-get update
  • sudo apt-get install -y python python-pip
  • sudo pip install docker-compose

Obtain wait-for-it.sh

We are going to use this utility to have our Kodi container wait on the MySQL server to be ready before launching Kodi itself

Stop and Remove Containers

To build our dependent containers we need to remove the original ones. All of our data is saved externally, so we wont lose anything by rebuilding the containers. In fact, this is one of the selling points of Docker to begin with

You can stop and remove the containers through Portainer or you can do it via command line as follows

  • docker stop kodi-client
  • docker stop mysql-server
  • docker container rm kodi-client
  • docker container rm mysql-server

Create Dockerfile

We need to modify the standard Kodi image as we want to inject our our wait-for-it.sh script into it. We will use a Dockerfile as follows:

  • SSH into Pi
  • cd /compose/media-center
  • sudo vi Dockerfile
  • Enter the following contents:
FROM codafog/kodi-rpi
COPY wait-for-it.sh .

This will pull down the codafog/kodi-rpi image and inject the wait-for-it.sh into it creating a new custom image.

Create Docker Compose File

  • SSH into Pi
  • cd /compose/media-center
  • sudo vi docker-compose.yml
  • Enter the following contents:
version: '3'
services:
  mysql-server:
    image: hypriot/rpi-mysql
    container_name: mysql-server
    restart: always
    network_mode: host
    environment:
        - "MYSQL_ROOT_PASSWORD=raspberry"
        - "MYSQL_USER=pi"
        - "MYSQL_PASSWORD=raspberry"
    volumes:
        - /mount/infrastructure/docker_data/mysql_data:/var/lib/mysql
  kodi-client:
    build: .
    container_name: kodi-client
    restart: always
    network_mode: host
    depends_on:
        - mysql-server
    devices:
        - "/dev/tty0"
        - "/dev/fb0"
        - "/dev/input"
        - "/dev/snd"
        - "/dev/vchiq"
    volumes:
        - /etc/localtime:/etc/localtime:ro
        - /etc/timezone:/etc/timezone:ro
        - /storage/docker/kodi-headless-datadir:/config/kodi
        - /usr/bin/tvservice:/usr/bin/tvservice
    command: ["./wait-for-it.sh", "[pi-static-ip]:3306", "-t", "60", "--", "bash", "/usr/bin/kodi-standalone"]

Several things of note in the above file:

  • Its YAML, so you need to be real careful about spacing and tabs.
  • We are recreating the mysql-server and the kodi-client containers with more or less all the same inputs as before but in a much nicer format
  • mysql-server is grabbing a standard image using the image flag
  • kodi-client is building a custom image using the build flag to combine a base image and our custom injected script
  • The ‘build .‘ under the kodi service is using the contents of the Dockerfile in the current working directory to pull down the image, inject our script, create a new image and spin out a container called kodi-client from it
  • We are using the depends_on flag to make starting the kodi-client container dependent on the mysql-server container being started
    • This does nothing more than start the mysql-server container before the kodi-client container. It doesn’t actually wait for mysql-server itself to be available.
  • Because of this we are overriding the default startup command for the kodi-client container and replacing it with one that uses our wait-for-it.sh script to successfully test the availability of MySQL before starting Kodi as normal

Starting Up the configuration

  • SSH into Pi
  • cd /compose/media-center
  • docker-compose up -d

See how much easier that is vs remembering those docker run commands? If you want to force a rebuild of the images (maybe you have newer base images for the docker containers) then just do this instead

  • docker-compose up -d –build

If you look at the configuration of the kodi-client container now it looks like this. Note the new startup command.

And the overall containers list looks like this. See how we have media-center in the stacks column. This isn’t a true stack as that’s intended for docker swarm, but thats how Portainer views it. The media-center name comes from the folder name docker-compose was run from.

If you check the logs on the kodi-container itself, you will see this

You can also see the same from the command line by

  • SSH into Pi
  • cd /compose/media-center
  • docker-compose logs

Summary

Now we have a system with dependencies, but have controlled the start order. Even if the docker host reboots suddenly, the startup configuration of the kodi-client container will ensure that it will wait for MySQL to be running before it attempts to a launch Kodi.

Technically this could all be done without Docker Compose, but I think you’ll agree it’s an elegant way to handle the setup. Docker Compose can bring many other advantages which we haven’t used here, and you can also setup the docker-compose.yml to run as a service, but thats a topic for another time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s