Docker / Docker Compose on a Pi

Been playing with a few things at home, and as part of that was trying to get Docker and Docker Compose running on a Raspberry Pi. Docker Compose if you aren’t familiar with, allows one to run multi-container apps, and is very handy when building multi-tier layered applications – which are quite common.

I was running it docker on my (Synology) NAS, but a recent update from them broke docker – specifically environment variables. That in turn broke the ability to run Docker Compose, and of course a bunch of stuff; and the opportunity to experiment.

First, we need to install docker – which these days is quite simple. You need the ability to ssh into the pi (or if you are connected to a display, then via a terminal prompt). And in some cases if things fail then you might need to run them as root (via sudo). To install docker, run the following:

curl -sSL https://get.docker.com | sh

And once you are done installing docker, then test it by running the classic hello world image. To so that you run the following command – this will get the Hello World image, and once run will automatically remove it (which is because of the –rm option)

docker run --rm hello-world

If everything is installed OK, then you should see a output that looks something like this the shown below. And this is good – means everything is up and running as expected.

pi@pi-server2:~ $ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1eda109e4da: Pull complete
Digest: sha256:b8ba256769a0ac28dd126d584e0a2011cd2877f3f76e093a7ae560f2a5301c00
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm32v7)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

To make like more simple, you should add the user you are logged in as to the ‘docker’ group. In my case it is the default ‘pi’ user, so that command would look like this. And for this to take effect, you would need to logout – I just reboot the machine – old habits. 🙂

sudo usermod -aG docker pi

OK, now that docker is installed, lets get to docker-compose. For that we first install pip, and use that to install docker-compose. And don’t forget the apt-get update in the end.

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && sudo python3 get-pip.py
sudo pip3 install docker-compose
sudo apt-get update

Now before anything else, lets try and make sure all dependencies are there. Create a file called ‘docker-compose.yml’ with the following. You can put this file anywhere, but I like to create a separate folder and save it in that.

In this example I expose port 6666 to the host which is mapped to port 8000 internally on the image. If your port 6666 is taken you can choose another port – it doesn’t matter. Spacing and indent, do matter in a yml file, so you would want to pay extra attention to that.

version: '3'
services:
  webapp:
    ports:
      - 6666:8000
    image: python:3.7-alpine
    command: "python -m http.server 8000"

Once the file is saved you run it with the following command. The image handles you would see are very likely going to be different and that is OK.

pi@pi-server2:~/docker/docker-test $ docker-compose up
Creating network "docker-test_default" with the default driver
Pulling webapp (python:3.7-alpine)...
3.7-alpine: Pulling from library/python
33b18ff7f9b7: Pull complete
0c1f90421c3a: Pull complete
91543a0ba590: Pull complete
913b1310b79e: Pull complete
6b545e90ee55: Pull complete                                                                                             Digest: sha256:9363cb46e52894a22ba87ebec0845d30f4c27efd6b907705ba9a27192b45e797
Status: Downloaded newer image for python:3.7-alpine
Creating docker-test_webapp_1 ... done                                                                                  Attaching to docker-test_webapp_1

At this point, the image is running in attached mode and it seems like it is waiting, when in reality it is running. If you open another ssh terminal and type in the following command – change the port to whatever you used earlier in the yml file.

pi@pi-server2:~ $ curl -iv 0.0.0.0:6666

And if everything is working then you will see a output something like this. And if you see towards the top you got a HTTP 200 – that is all that mattes in this case.

* Expire in 0 ms for 6 (transfer 0x1b097c0)
*   Trying 0.0.0.0...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x1b097c0)
* Connected to 0.0.0.0 (127.0.0.1) port 6666 (#0)
> GET / HTTP/1.1
> Host: 0.0.0.0:6666
> User-Agent: curl/7.64.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.7.4
Server: SimpleHTTP/0.6 Python/3.7.4
< Date: Thu, 26 Sep 2019 22:10:28 GMT
Date: Thu, 26 Sep 2019 22:10:28 GMT
< Content-type: text/html; charset=utf-8
Content-type: text/html; charset=utf-8
< Content-Length: 915
Content-Length: 915

<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".dockerenv">.dockerenv</a></li>
<li><a href="bin/">bin/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="lib/">lib/</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin/</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
</ul>
<hr>
</body>
</html>
* Closing connection 0

You can go back to the first ssh session and hit Ctrl + C to shutdown the image. Once you do that you will see something like:

^CGracefully stopping... (press Ctrl+C again to force)
Stopping docker-test_webapp_1 ... done                                                                                  pi@pi-server2:~/docker/docker-test $

Now you know docker-compose and all the dependencies are installed. Next I would want docker to auto start whenever the pi boots up, and for that we will use the following two commands.

sudo systemctl enable docker
sudo systemctl start docker

And that should be it. If you are running low on space you might want to clean up the images we downloaded in testing this installation.