Slick Docker Deploys on Raspberry Pi
Tags: devops, docker, raspberrypi, ci
Published: 2020-02-29 15:13:01

In my spare moments this past week I’ve been working on a Telegram bot for basic image processing, and as it’s gotten closer and closer to the functionality I’m after, I’ve started to think about how I want to deploy it. This isn’t my first rodeo, mind. Around a year ago at a university hackathon I built [a very basic Telegram bot](https://github.com/idmyn/MainLibraryBusyBot) for querying the occupancy of the main library, which I ended up deploying on my Raspberry Pi (model 3B) with a `git clone`, `npm install`, and `npm start`. This time, however, I have bigger ambitions. I've decided that I’m going to containerise it with docker and deploy it that way. My mission is to minimise the time spent in `ssh`. As a dry-run of sorts (and a sweet excuse to procrastinate), I spent most of yesterday re-deploying my library bot with docker. There were some ups and downs, but it turned out alright in the end — the perfect recipe for a blog post, if you ask me. 

# How I would have liked to do it
Before containerising the library bot, I did a bit of research about docker on Raspberry Pi. I found out that [docker officially supports Raspbian]([Add support to install Docker on raspbian/jessie by DieterReuter · Pull Request #24815 · moby/moby · GitHub]https://github.com/moby/moby/pull/24815) (the official OS for Raspberry Pis), which was great news, so I moved on to the question of the easiest way to get a Raspberry Pi with docker up and running. I figured there would probably be an image or script to bootstrap a clean OS install with docker on a Pi, and I was right. [HypriotOS](https://github.com/hypriot/image-builder-rpi/releases) [sells itself](https://blog.hypriot.com/about/) as “a minimal Debian-based operating systems that is optimized to run Docker”, and makes the impressive claim that “from start to finish it takes a user less than 5 minutes to get started with Docker on Raspberry Pi”. It sounded perfect, so I gave it a try. I used their [flash](https://github.com/hypriot/flash) script to flash the HypriotOS image onto my microSD card and it seemed to work as advertised. 

It would have been negligent to leave the system running with the default username and password, though, so I looked into securing the system. More good news! I found a guest post on their blog titled [“Bootstrapping a Cloud with Cloud-Init and HypriotOS”](https://blog.hypriot.com/post/cloud-init-cloud-on-hypriot-x64/). It describes the process of flashing the HypriotOS image alongside a [cloud-init](https://cloudinit.readthedocs.io/en/latest/index.html) config file. It seemed like the perfect solution to my problem: I could change the username and hostname in the config file, and even add my public ssh key! Unfortunately, I couldn’t get the cloud-init config to work. After a good hour of trial, error, and [patience](https://github.com/hypriot/flash/issues/155), I decided to do things the old-fashioned way. After all, I’m not expecting to be purging my Pi and reinstalling a fresh OS all that often. 

# My steps to get a fresh, secure Raspbian install with docker on a Raspberry Pi:
1. Download the Raspbian Buster Lite image from [the Raspberry Pi website](https://www.raspberrypi.org/downloads/raspbian/)
2. Unzip it, double click to mount it (so it appears like a USB drive), and paste an empty file called `ssh` with no file extension into the root of the image (this enables you to connect to it in step 6)
3. Flash the image onto the MicroSD card you want to use for your Pi (I recommend [flash](https://github.com/hypriot/flash) for this)
4. Insert the flashed MicroSD into your Pi, connect it to your router via ethernet, and turn it on
5. Find the IP address of your Pi (I use [this terminal command](https://raspberrypi.stackexchange.com/a/13937))
6. SSH into your Pi (`ssh pi@IP_ADDRESS` with password `raspberry` when prompted)
7. Follow all of the steps from the [“Securing your Raspberry Pi”](https://www.raspberrypi.org/documentation/configuration/security.md) section of the Raspberry Pi documentation (including deleting the `pi` user)
8. As the new user you just created on your Pi, install docker with `curl -sSL https://get.docker.com | sh`, and install docker-compose with `sudo apt-get install docker-compose`

## Deploying containers
To preserve the beautiful clean slate of my fresh Raspbian install, I wanted a really slick deploy process for my docker containers. My solution was a `docker-compose.yml` looking a bit like this:
```yaml
version: "3"
services:
  busybot:
    image: "idmyn/busy-bot:arm"
    restart: always
    environment:
      - TOKEN=*********************************
  watchtower:
    image: containrrr/watchtower:latest
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
```

It’s wonderful. I just had to send that single file to my Pi with `scp docker-compose.yml USER@IP_ADDRESS:~/docker-compose.yml`, `ssh` into the Pi, and run `docker-compose up -d` to spin up the containers. The [watchtower](https://containrrr.github.io/watchtower/usage-overview/) container watches for image updates on [Docker Hub](https://hub.docker.com), and you can automate docker image builds on pushes to `master` with [GitHub Actions](https://github.com/features/actions) for a totally hands-off CI pipeline. Here’s my workflow for that:
```yaml
name: buildx

on:
  pull_request:
    branches: master
  push:
    branches: master

jobs:
  buildx:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v1
      -
        name: Set up Docker Buildx
        id: buildx
        uses: crazy-max/ghaction-docker-buildx@v1
        with:
          version: latest
      -   
        name: Docker Login
        env:
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
        run: |
          echo "${DOCKER_PASSWORD}" | docker login —username idmyn —password-stdin
      -
        name: Run Buildx
        run: |
          docker buildx build \
            -t idmyn/transparency-bot:arm \
            —platform linux/arm/v7 \
            —push .
```

**NOTE: for containers to run on a Raspberry Pi, they need to be built for its ARM architecture. There’s an informative post about this on [the Docker Blog](https://www.docker.com/blog/multi-arch-images/) but, for what’s it’s worth, my build command looks like this:** 
`docker buildx build -t idmyn/busy-bot:arm --platform linux/arm/v7 --push .`