The Raspberry Pi is the ideal platform for learning Docker and running self-hosted services. With the Pi 5's quad-core ARM Cortex-A76 at 2.4 GHz and up to 8 GB of RAM, it has enough power to run a dozen or more containers simultaneously while consuming under 10 watts. This guide covers everything from initial Docker installation to deploying a complete home lab stack with monitoring and management.

Hardware Recommendations

Component Raspberry Pi 4 Raspberry Pi 5 Notes
CPU Cortex-A72 1.8 GHz Cortex-A76 2.4 GHz Pi 5 is ~2-3x faster per core
RAM 2/4/8 GB 4/8 GB Get 8 GB for Docker workloads
Storage microSD, USB 3.0 microSD, USB 3.0, NVMe (HAT) NVMe highly recommended for Pi 5
Network Gigabit Ethernet Gigabit Ethernet Use wired for servers, not Wi-Fi
Power 5V/3A USB-C 5V/5A USB-C Use official power supply
Warning: Do not use cheap microSD cards for Docker workloads. The constant write operations will wear out low-quality cards within months. Use an NVMe drive (Pi 5) or a USB 3.0 SSD (Pi 4) for the Docker data directory. An A2-rated microSD card is acceptable for the boot partition only.

Installing Docker

Start with a fresh Raspberry Pi OS (64-bit) installation. The 64-bit version is essential for running ARM64 Docker images, which have much better availability than 32-bit ARM variants.

# Update the system
sudo apt update && sudo apt upgrade -y

# Install Docker using the official convenience script
curl -fsSL https://get.docker.com | sh

# Add your user to the docker group (avoids sudo for every command)
sudo usermod -aG docker $USER

# Log out and back in for group changes to take effect
# Then verify the installation
docker version
docker run --rm hello-world

Install Docker Compose

# Docker Compose v2 is included with Docker Engine as a plugin
# Verify it works
docker compose version

# If not present, install the plugin
sudo apt install docker-compose-plugin

Move Docker Storage to SSD

If you are using an external SSD, move Docker's data directory off the microSD card:

# Stop Docker
sudo systemctl stop docker

# Mount your SSD (example: /dev/sda1 formatted as ext4)
sudo mkdir -p /mnt/ssd
sudo mount /dev/sda1 /mnt/ssd

# Add to /etc/fstab for persistence
echo "/dev/sda1 /mnt/ssd ext4 defaults,noatime 0 2" | sudo tee -a /etc/fstab

# Move Docker data
sudo rsync -aP /var/lib/docker/ /mnt/ssd/docker/

# Configure Docker to use the new location
sudo tee /etc/docker/daemon.json <<EOF
{
  "data-root": "/mnt/ssd/docker",
  "storage-driver": "overlay2",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

# Start Docker
sudo systemctl start docker

# Verify
docker info | grep "Docker Root Dir"
# Expected: /mnt/ssd/docker

ARM64 Image Compatibility

Most popular Docker images now support ARM64 natively through multi-architecture manifests. When you docker pull an image, Docker automatically selects the correct architecture.

# Check if an image supports ARM64
docker manifest inspect library/nginx:alpine | \
  jq '.manifests[] | select(.platform.architecture=="arm64")'

# Common images with ARM64 support:
# nginx, postgres, redis, mariadb, node, python, golang,
# traefik, grafana, prometheus, pihole, homeassistant,
# nextcloud, vaultwarden, jellyfin, caddy
Tip: If an image does not support ARM64, check for community-maintained ARM64 variants on Docker Hub, or build the image yourself from source using the project's Dockerfile.

Essential Self-Hosted Services

Pi-hole: Network-Wide Ad Blocking

# docker-compose.yml for Pi-hole
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    environment:
      TZ: "America/New_York"
      WEBPASSWORD: "${PIHOLE_PASSWORD}"
      FTLCONF_LOCAL_IPV4: "192.168.1.100"
    volumes:
      - pihole_data:/etc/pihole
      - pihole_dnsmasq:/etc/dnsmasq.d
    restart: unless-stopped
    dns:
      - 127.0.0.1
      - 1.1.1.1

volumes:
  pihole_data:
  pihole_dnsmasq:

Home Assistant: Smart Home Hub

services:
  homeassistant:
    container_name: homeassistant
    image: ghcr.io/home-assistant/home-assistant:stable
    volumes:
      - ha_config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped
    privileged: true
    network_mode: host
    # network_mode: host is needed for mDNS discovery
    # of smart home devices on the local network

volumes:
  ha_config:

Nextcloud: Personal Cloud Storage

services:
  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    ports:
      - "8443:443"
    environment:
      MYSQL_HOST: nextcloud-db
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: "${NEXTCLOUD_DB_PASS}"
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD: "${NEXTCLOUD_ADMIN_PASS}"
      NEXTCLOUD_TRUSTED_DOMAINS: "nextcloud.local 192.168.1.100"
    volumes:
      - nextcloud_data:/var/www/html
      - /mnt/ssd/nextcloud-files:/var/www/html/data
    depends_on:
      - nextcloud-db
    restart: unless-stopped

  nextcloud-db:
    image: mariadb:11
    container_name: nextcloud-db
    environment:
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASS}"
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: "${NEXTCLOUD_DB_PASS}"
    volumes:
      - nextcloud_db:/var/lib/mysql
    restart: unless-stopped

volumes:
  nextcloud_data:
  nextcloud_db:

Media Server Stack

services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    ports:
      - "8096:8096"
    volumes:
      - jellyfin_config:/config
      - jellyfin_cache:/cache
      - /mnt/ssd/media:/media:ro
    environment:
      JELLYFIN_PublishedServerUrl: "http://192.168.1.100:8096"
    restart: unless-stopped
    # Hardware transcoding on Pi 5 (V4L2)
    devices:
      - /dev/video10:/dev/video10
      - /dev/video11:/dev/video11
      - /dev/video12:/dev/video12

volumes:
  jellyfin_config:
  jellyfin_cache:

Complete Home Lab Stack

Here is a curated Docker Compose stack that runs well on a Raspberry Pi 5 with 8 GB RAM:

# homelab/docker-compose.yml
services:
  # Reverse Proxy
  traefik:
    image: traefik:v3.0
    container_name: traefik
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_certs:/certs
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
    restart: unless-stopped

  # Password Manager
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    environment:
      DOMAIN: "https://vault.local"
      SIGNUPS_ALLOWED: "false"
    volumes:
      - vaultwarden_data:/data
    labels:
      traefik.enable: "true"
      traefik.http.routers.vault.rule: "Host(`vault.local`)"
    restart: unless-stopped

  # Monitoring
  uptime-kuma:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    ports:
      - "3001:3001"
    volumes:
      - uptime_kuma:/app/data
    restart: unless-stopped

  # Bookmarks
  linkding:
    image: sissbruecker/linkding:latest
    container_name: linkding
    ports:
      - "9090:9090"
    volumes:
      - linkding_data:/etc/linkding/data
    restart: unless-stopped

volumes:
  traefik_certs:
  vaultwarden_data:
  uptime_kuma:
  linkding_data:

Performance Expectations

Scenario Pi 4 (8GB) Pi 5 (8GB)
Idle Docker daemon ~80 MB RAM ~80 MB RAM
5 lightweight containers ~500 MB RAM, 5% CPU ~500 MB RAM, 3% CPU
10 mixed containers ~2-3 GB RAM, 15% CPU ~2-3 GB RAM, 10% CPU
Postgres under load ~100 queries/sec ~250 queries/sec
Docker image build (Go app) ~4 minutes ~1.5 minutes
Jellyfin transcoding (1080p) Struggles (software only) Acceptable (V4L2 HW)

Storage Considerations

  • microSD cards: Only for boot. Write endurance is too low for Docker's overlay filesystem
  • USB 3.0 SSD: Best option for Pi 4. 500 GB SATA SSD with a USB adapter provides excellent performance at low cost
  • NVMe SSD: Best option for Pi 5 with the official NVMe HAT. Provides the fastest I/O and lowest latency
  • External HDD: Acceptable for media storage and backups, but too slow for Docker data directories or databases

Clustering with Docker Swarm

Multiple Raspberry Pis can form a Docker Swarm cluster for high availability and load distribution:

# On Pi 1 (manager)
docker swarm init --advertise-addr 192.168.1.100

# Join token will be displayed, use it on other Pis:
# On Pi 2 and Pi 3 (workers)
docker swarm join --token SWMTKN-1-xxx 192.168.1.100:2377

# Verify cluster
docker node ls
# ID           HOSTNAME    STATUS   AVAILABILITY   MANAGER STATUS
# abc123 *     pi-01       Ready    Active         Leader
# def456       pi-02       Ready    Active
# ghi789       pi-03       Ready    Active

# Deploy a service across the cluster
docker service create --name web --replicas 3 -p 80:80 nginx:alpine

Monitoring Your Pi

Monitor your Raspberry Pi's Docker environment with a lightweight Prometheus and Grafana stack:

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
      - '--storage.tsdb.retention.size=2GB'
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    restart: unless-stopped

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    ports:
      - "8081:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"
    restart: unless-stopped

volumes:
  prometheus_data:
  grafana_data:
Tip: Monitor your Pi's CPU temperature. Under sustained Docker workloads, the Pi can thermal throttle without a heatsink or active cooling. Add a simple check: cat /sys/class/thermal/thermal_zone0/temp (divide by 1000 for Celsius). Keep it below 80 degrees C.

The Raspberry Pi's low cost, low power consumption, and ARM64 architecture make it an excellent platform for running Docker-based self-hosted services. Start with one or two services, monitor resource usage, and expand your stack as you gain confidence. With proper storage configuration and resource management, a single Pi can comfortably run your personal infrastructure for years.