loading...

Slick Docker Deploys on Raspberry Pi

idmyn profile image David Mynors ・4 min read

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 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 (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 sells itself 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 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”. It describes the process of flashing the HypriotOS image alongside a cloud-init 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, 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
  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 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)
  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” 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:

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 container watches for image updates on Docker Hub, and you can automate docker image builds on pushes to master with GitHub Actions for a totally hands-off CI pipeline. Here’s my workflow for that:

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 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 .

Posted on Feb 29 by:

idmyn profile

David Mynors

@idmyn

Religious Studies MA @ Edinburgh Uni => Software Engineering @ Nando's 🔥

Discussion

markdown guide