raspberry-pi intermediate 30 min

Raspberry Pi: install Docker the right way and run multi-service stacks

Set up Docker on a Raspberry Pi 4 or 5, run multi-container stacks with docker-compose, and avoid the arm64 vs amd64 trap that wastes a weekend.

Code available for: Python
Published Aug 26, 2026

A Raspberry Pi running a few services with systemctl and a pile of apt install commands works. It also gets unwieldy fast. The next service you add conflicts with a Python version, the upgrade path becomes “rebuild the SD card from scratch,” and you cannot tell which service owns which file.

Docker fixes this. Each service is in its own container with its own filesystem, its own dependencies, and its own lifecycle. You upgrade one without touching the others. The Pi becomes a small server, not a pile of hacks.

This tutorial covers the install, the arm64 trap, the multi-container pattern with docker-compose, and the RAM ceiling that catches people running a Pi 4 with too many services.

What you need

  • Raspberry Pi 4 (4 GB or 8 GB) or Pi 5 (4 GB or 8 GB) running Raspberry Pi OS Bookworm (64-bit)
  • A class 10 SD card or, ideally, a USB SSD (Docker pulls a lot of I/O)
  • About 30 minutes
  • A working internet connection

Why Docker on a Pi

The benefits are the same as Docker on a server:

  • Isolation. Each service has its own filesystem. A bad config in one does not corrupt the others.
  • Reproducibility. A docker-compose.yml is a complete spec for the service. Rebuild the Pi, copy the file, run docker compose up, and you are back where you were.
  • Upgradability. Upgrade a service by changing a tag in the compose file. Roll back by changing it back.
  • Disposable. A docker rm removes the service entirely, no leftover config files.

The costs are the same as Docker on a server:

  • Slight CPU overhead. Negligible on a Pi 4 or 5.
  • Disk space. Each image is a few hundred MB. Plan for at least 16 GB of free space.
  • Debugging. A bug inside a container is harder to debug than a bug on the host. The tooling helps, but it is one more layer.

For “I am running 3-4 services on a Pi,” the trade is worth it.

Install Docker

The convenience script is the standard install path:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

The “convenience script” warning is real. It runs as root, it pulls from Docker’s apt repo, and you are trusting that the script does what its README says. For a production server, I would review the script before running it. For a home Pi, the risk is “Docker Inc goes rogue,” which is a low-probability event.

After the install, add your user to the docker group so you do not need sudo for every command:

sudo usermod -aG docker $USER

Log out and back in. Verify:

docker run hello-world

If you see “Hello from Docker!” you are good.

The arm64 vs amd64 gotcha

Docker images are built for specific CPU architectures. The Pi 4 and Pi 5 are arm64 (aarch64). Most desktop and server software is amd64 (x86_64). An amd64 image will not run on a Pi.

The good news: most popular services (Node-RED, Mosquitto, InfluxDB, Home Assistant, Pi-hole, Nextcloud) have official arm64 images. They work on the Pi.

The bad news: some niche services do not. The error looks like:

exec format error

The fix is to find an arm64 image or to rebuild from source. The quickest check is to look at the image’s Docker Hub page for the “Platforms” or “Architectures” section. If it only lists linux/amd64, you are stuck.

If you are picking a service, prefer the ones with multi-arch images. If you are writing a Dockerfile for your own service, build it with --platform linux/arm64 from the start.

docker-compose for multi-container stacks

The pattern for “I run Node-RED + Mosquitto + InfluxDB on the same Pi” is a docker-compose.yml file. One file, all the services, one command to start them.

# docker-compose.yml
version: "3.9"

services:
  nodered:
    image: nodered/node-red:latest
    restart: unless-stopped
    ports:
      - "1880:1880"
    volumes:
      - nodered-data:/data

  mosquitto:
    image: eclipse-mosquitto:2
    restart: unless-stopped
    ports:
      - "1883:1883"
    volumes:
      - mosquitto-conf:/mosquitto/config
      - mosquitto-data:/mosquitto/data

  influxdb:
    image: influxdb:2
    restart: unless-stopped
    ports:
      - "8086:8086"
    volumes:
      - influxdb-data:/var/lib/influxdb2

volumes:
  nodered-data:
  mosquitto-conf:
  mosquitto-data:
  influxdb-data:

Save as docker-compose.yml and run:

docker compose up -d

The -d runs in the background. The three services start, expose their ports, and persist their data to the named volumes.

The restart: unless-stopped line means the services come back up after a reboot. Without it, you have to docker compose up -d again after every restart.

The “Pi 4 has limited RAM” gotcha

A Pi 4 with 4 GB of RAM runs about 4-6 typical services before the OOM killer starts reaping things. A Pi 5 with 8 GB runs about 8-12.

The OOM killer does not announce itself. Your service just disappears from docker ps and shows up in docker ps -a as “exited.” The dmesg log has the murder.

The fix is to monitor RAM and either:

  • Upgrade to the 8 GB Pi.
  • Move the heavy services (InfluxDB, Grafana with a big database) to another machine.
  • Use SQLite instead of InfluxDB for small time-series datasets. SQLite is plenty for most home projects.

A Pi 4 with 4 GB is enough for “Pi-hole + Mosquitto + a small Node-RED flow.” It is not enough for “Pi-hole + Mosquitto + InfluxDB + Grafana

  • Home Assistant + Nextcloud + Jellyfin.” Pick the right workload for the hardware.

The bind mount pattern for persistent config

Docker volumes are managed by Docker, and you cannot easily get a file in or out of them. For service config files you want to edit on the host, use a bind mount:

services:
  mosquitto:
    image: eclipse-mosquitto:2
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro
      - mosquitto-data:/mosquitto/data

The ./mosquitto.conf path is on the host, mounted read-only into the container. Edit the file on the host, restart the container, the new config is in effect.

The rule: data you want to back up, edit, or version-control lives in bind mounts on the host. Data the service owns (databases, runtime state) lives in named volumes.

Pi 5 vs Pi 4 differences

The Pi 5 is a meaningful upgrade over the Pi 4 for Docker workloads:

  • 2-3x the CPU performance, depending on the workload.
  • Better I/O. The Pi 5 has a real PCIe lane for NVMe SSDs (via the official HAT), which is much faster than the Pi 4’s USB 3.0 SSD.
  • 8 GB is now a sensible default, not a luxury.
  • It runs hotter. A passive heatsink is necessary; an active cooler is recommended.

The Pi 4 is still good for light workloads. The Pi 5 is the right choice for “I am running 5+ containers and I want them to be fast.”

When to use Docker on Pi vs a full x86 server

A Pi is a good Docker host for:

  • A handful of small services.
  • A home network with low traffic.
  • Anything you do not want to pay $30/month to host.

A Pi is not a good Docker host for:

  • Many concurrent users (the network is the bottleneck, 1 Gbit ethernet is shared with USB).
  • Heavy I/O workloads (databases that need NVMe speeds).
  • Anything that needs more than 16 GB of RAM.
  • A 24/7 production service that cannot tolerate a Pi SD card failure.

The rule: a Pi is a good “home lab” or “small business” host. It is not a replacement for a real server in a production environment.

What you learned

  • Docker on a Pi gives you service isolation, reproducibility, and easy upgrade paths.
  • The arm64 architecture constraint is the most common Docker gotcha on the Pi.
  • docker-compose.yml is the spec for a multi-service stack. One file, one command to bring it up.
  • The Pi 4 with 4 GB is enough for a small stack, the Pi 5 with 8 GB is enough for a medium one.
  • Bind mounts are for config, named volumes are for data.

When something breaks

exec format error. The image is amd64-only. Find an arm64 image or rebuild from source.

The service keeps restarting. OOMKilled in docker ps. Out of RAM. Either reduce the workload, move heavy services elsewhere, or upgrade the Pi.

docker compose up says “port is already allocated”. Another service on the host (or another container) is using the port. Change the host-side mapping in the ports: block.

The bind-mounted config file is not picked up. The container has it cached, or the file is in the wrong path. Restart the container after editing the file.

The Pi runs out of disk space. Old Docker images. Run docker system prune to remove unused ones.

What to build next

  • A full home automation stack (Node-RED + Mosquitto + InfluxDB + Grafana).
  • A Pi-hole + Unbound recursive DNS combo.
  • A Nextcloud instance for personal file sync.
  • A monitoring stack (Prometheus + Grafana + node-exporter).

The Pi-hole + Unbound stack is the easiest win. The Nextcloud instance is the most useful for a household.