Build a dotnet project for Docker on Raspberry Pi

  5 mins read  

I have a couple of Raspberry Pi’s running on my home network. I find them useful to run small utilities like pi-hole. They’re physically small, and they have low power consumption.

The utilities I run on the rpi are all docker images. I find using docker instead of installing on to the OS makes the utilities easier to maintain. Docker ensures the dependencies of one utility I run is contained from the other.

My rpi’s are a couple of years old. When I first got them, I installed the latest version of the recommended OS at the time.

I like to write code in dotnet, and containerise the code I write as docker images. That way, I can be confident I could pick up the piece of code and run it in a different location. For example, I have 3 different working machines at home:

  • my personal laptop (a MacBook Pro with M2 chip)
  • my work laptop (a SurfaceBook laptop with Windows Subsystem for Linux WSL installed)
  • dedicated Linux desktop running Ubuntu 22
  • Raspberry Pi’s located in the server cupboard

Docker images can of course also be deployed to the cloud.

The utility I wanted to write is a dotnet 7.0 worker process called “knockknock”. It’s not complex, it looks at an Azure Storage Queue and processes messages in the queue.

In this blog post I wanted to focus on what you need to do to ensure your dotnet docker container will run on the system you intend it to run on. The team at Microsoft have done a lot of the work for you, but you need to join the dots to make sure you get it right.

My worker process has the following Dockerfile:

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["KnockKnock.Worker.csproj", "background-service/"]
RUN dotnet restore "background-service/KnockKnock.Worker.csproj"
COPY . "background-service/"
WORKDIR "/src/background-service"
RUN dotnet build "KnockKnock.Worker.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "KnockKnock.Worker.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "KnockKnock.Worker.dll"]
  • FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base indicates the worker process itself will run on the dotnet 7.0 runtime
  • FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build indicates when I build the app, I want to use the standard dotnet 7.0 sdk

All is good. With the above Dockerfile I can execute docker build -t knockknock-worker . on my MacBook Pro M2, and docker will dutifully download the dotnet/sdk:7.0 and dotnet/runtime:7.0 containers and their depdendencies. If my project is set up correctly, the build completes as expected.

I can then run the built docker image on the same machine by executing docker run docker.io/library/knockknock-worker or just docker run knockknock-worker.

But what if I want to run this amazing worker process on my Raspberry Pi?

First I execute the following docker command: docker save -o knockknock-worker.tar knockknock-worker.

Then I copy it over to my Raspberry Pi using the ssh copy command (scp): scp -r knockknock-worker.tar pi@pi-hostname:/home/pi/knockknock

I already have docker installed on the Raspberry Pi, so I can ssh in to the rpi, try to load the new docker image, and then try to run it:

sudo docker load -i ~/knockknock/knockknock-worker.tar
sudo docker run knockknock-worker

But this is where it fails. It turns out, my rpi host is running a version of Raspberry Pi OS that is not supported by the dotnet 7.0 runtime.

Knowing the answer to a couple of questions is important when looking up the tags to reference in your solution’s Dockerfile

It took me some time to work this out, because I needed to “join the dots” on a couple of things:

  • What version of rpi OS am I on?
  • What version of Debian is that version of rpi OS based on?
  • Am I using the 32bit or 64bit OS?
  • Which dotnet 7 runtime image should I reference in my Dockerfile?

What version of rpi OS am I on?

The answer to the first three questions can be answered by ssh-ing in to the Raspberry Pi host, and running the following commands: cat /etc/os-release

What version of Debian is that version of rpi OS based on?

The Debian Linux distro has names for their versions. For examplem, Debian v10 is named Buster, Debian v11 is named Bullseye, v12 is named Bookworm.

Am I using the 32bit or 64bit OS?

You can execute the following command to find out the definition of what a LONG variable is: getconf LONG_BIT

Which dotnet 7 runtime image should I reference in my Dockerfile?

The .NET runtime tags are published to the Docker Hub here. These tags can be added to the Dockerfile FROM directives, for example:

FROM mcr.microsoft.com/dotnet/runtime:7.0-bookworm-slim-arm32v7 AS base

This means the dotnet runtime included in my container will work on Debian Bookworm (Debian v12) 32 bit arm architecture (Raspberry Pi’s processor is an arm processor).

Another example:

FROM mcr.microsoft.com/dotnet/runtime:7.0.10-bookworm-slim AS base

Some tags published in the Docker Hub are more generic, for example the tag 7.0.10-bullseye-slim-arm64v8 can also be referenced (at the time of writing this blog) as 7.0.10, 7.0, or even latest.