Docker Compose Projects for Raspberry Pi — 10 Stacks to Try

10 ready-to-use Docker Compose stacks for Raspberry Pi. Copy-paste configs for media servers, monitoring, development tools, and more.

Andreas · April 13, 2026 · 12 min read

Introduction

Docker Compose transforms Raspberry Pi into a powerful homelab server. Instead of installing services individually on your Pi, you define everything in a single YAML file, then deploy with one command: docker compose up -d. This approach offers reproducibility (easy to redeploy on a new Pi), portability (move stacks between systems), and isolation (services don't conflict).

Whether you're building a media server, home automation hub, or monitoring system, Docker Compose lets you spin up production-ready stacks in minutes. This guide provides 10 battle-tested configurations for the most popular self-hosted applications on ARM-based Raspberry Pi devices.

Prerequisites

Before deploying any of these stacks, ensure your Raspberry Pi meets these requirements:

  • Docker & Docker Compose installed: Follow the official Docker installation guide for Raspberry Pi. Install both the engine and Docker Compose plugin (docker compose).
  • Raspberry Pi 4 or newer (2GB minimum, 4GB+ recommended for running multiple stacks simultaneously).
  • SSD or microSD with adequate speed: microSD cards work but wear out quickly. USB-attached SSDs are far superior for repeated I/O operations.
  • Network connectivity: Ethernet recommended for stability; WiFi works but adds latency.
  • Sufficient storage: At least 16GB free for Docker images and persistent volumes.
  • User in docker group: Run sudo usermod -aG docker $USER and restart your session to use Docker without sudo.

Each stack runs as a separate service. You can run one or several simultaneously, depending on your Pi's RAM and available CPU cores.


Stack 1 — Home Dashboard (Homarr)

What it does: Homarr is a sleek, self-hosted dashboard that organizes links to your services, displays widgets (weather, calendar, system stats), and provides a single entry point to your homelab.

Access: http://your-pi-ip:7575

RAM estimate: ~100MB

version: '3.8'

services:
  homarr:
    # Homarr home dashboard—lightweight UI for organizing services
    image: ghcr.io/ajnart/homarr:latest
    container_name: homarr
    restart: unless-stopped
    ports:
      - "7575:7575"
    volumes:
      # Persist Homarr configuration and custom icon data
      - homarr_data:/home/node/app/public/data
      - homarr_configs:/home/node/app/data
    environment:
      # Optional: set timezone for dashboard widgets
      TZ: "UTC"
    # Expose logs for troubleshooting
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store dashboard settings and user layouts persistently
  homarr_data:
  homarr_configs:

Usage: After starting, visit http://your-pi-ip:7575. Add your services as tiles, customize layout, and set up widget integrations. Configuration syncs automatically to persistent volumes.


Stack 2 — Media Server (Jellyfin + Prowlarr)

What it does: Jellyfin streams your movies, TV shows, and music. Prowlarr indexes torrent and usenet sites, feeding results into download managers. Together, they form a complete media stack.

Access:

RAM estimate: ~400MB (Jellyfin: 250MB, Prowlarr: 150MB)

version: '3.8'

services:
  jellyfin:
    # Jellyfin media server—streams video, audio, and photos
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    ports:
      - "8096:8096"
      - "8920:8920"  # HTTPS port (optional)
    volumes:
      # Cache transcoding to minimize CPU load on Pi
      - jellyfin_config:/config
      - jellyfin_cache:/cache
      # Mount your media libraries (read-only for safety)
      - /mnt/media/movies:/movies:ro
      - /mnt/media/tv:/tv:ro
      - /mnt/media/music:/music:ro
    environment:
      # Optional: enable debugging for logs
      JELLYFIN_LOG_DIR: /config/log
    devices:
      # GPU acceleration (optional—requires compatible Pi and hardware support)
      # - /dev/video10:/dev/video10
      # - /dev/video11:/dev/video11
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  prowlarr:
    # Prowlarr indexer aggregator—manages torrent/usenet sources
    image: ghcr.io/hotio/prowlarr:latest
    container_name: prowlarr
    restart: unless-stopped
    ports:
      - "9696:9696"
    volumes:
      # Persist Prowlarr indexer configs and history
      - prowlarr_config:/config
    environment:
      # Set timezone for scheduled indexer syncs
      TZ: "UTC"
      # Run as non-root user (recommended for security)
      PUID: "1000"
      PGID: "1000"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  jellyfin_config:
  jellyfin_cache:
  prowlarr_config:

Usage:

  1. Start the stack with docker compose up -d.
  2. Open http://your-pi-ip:8096 and run Jellyfin's setup wizard.
  3. Add your media library paths (mounted at /movies, /tv, /music).
  4. Configure Prowlarr at http://your-pi-ip:9696 and add indexers.
  5. Point your download manager (Radarr, Sonarr) to Prowlarr's API endpoint.

Note: Media files should be stored on external storage (USB SSD) to avoid filling your Pi's SD card.


Stack 3 — Network-Wide Ad Blocking (Pi-hole + Unbound Recursive DNS)

What it does: Pi-hole blocks ads at the DNS level across your entire network. Unbound provides recursive DNS resolution, eliminating dependency on external DNS providers.

Access: http://your-pi-ip/admin

RAM estimate: ~150MB (Pi-hole: 80MB, Unbound: 70MB)

version: '3.8'

services:
  pihole:
    # Pi-hole DNS sinkhole—blocks ads network-wide
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "80:80"
      - "443:443"
    volumes:
      # Persist Pi-hole configuration and blocklists
      - pihole_config:/etc/pihole
      - pihole_dnsmasq:/etc/dnsmasq.d
    environment:
      # Set a strong admin password (required)
      WEBPASSWORD: "your_secure_password_here"
      # Use Unbound as the upstream DNS resolver
      PIHOLE_DNS_: "unbound#5335"
      # Set timezone
      TZ: "UTC"
    depends_on:
      - unbound
    cap_add:
      # Required for Pi-hole DNS functionality
      - NET_ADMIN
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  unbound:
    # Unbound recursive DNS resolver—privacy-focused, no external queries
    image: mvance/unbound:latest
    container_name: unbound
    restart: unless-stopped
    ports:
      - "5335:53/udp"
      - "5335:53/tcp"
    volumes:
      # Custom Unbound configuration (optional)
      - unbound_config:/opt/unbound/etc/unbound
    environment:
      # No password needed—internal use only
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  pihole_config:
  pihole_dnsmasq:
  unbound_config:

Usage:

  1. Change the web password in the environment section.
  2. Start the stack: docker compose up -d.
  3. Access the admin dashboard at http://your-pi-ip/admin (login with admin + password).
  4. Set your Pi-hole IP as the DNS server on your router (or individual devices).
  5. Monitor dashboard to see queries blocked and network performance.

Important: If port 53 conflicts with existing DNS, adjust the port mapping and configure clients to use the new port.


Stack 4 — Password Manager (Vaultwarden)

What it does: Vaultwarden is a lightweight, self-hosted password manager compatible with Bitwarden clients. Store and sync passwords across all your devices.

Access: http://your-pi-ip:8080

RAM estimate: ~120MB

version: '3.8'

services:
  vaultwarden:
    # Vaultwarden password manager—lightweight Bitwarden-compatible vault
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      # Persist encrypted vault data
      - vaultwarden_data:/data
    environment:
      # Set domain for invitation emails and web UI
      DOMAIN: "http://your-pi-ip:8080"
      # Disable invitation if you're the only user
      INVITATIONS_ALLOWED: "false"
      # Generate a strong key: `openssl rand -base64 32`
      ADMIN_TOKEN: "your_admin_token_here"
      # Optional: enable logs for debugging
      LOG_LEVEL: "info"
      # Enable web vault UI
      WEB_VAULT_ENABLED: "true"
      # Disable signups after initial setup
      SIGNUPS_ALLOWED: "false"
      # Set timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store encrypted vault and configuration
  vaultwarden_data:

Usage:

  1. Generate an admin token: openssl rand -base64 32 and paste it in the environment.
  2. Update DOMAIN to your Pi's IP or hostname.
  3. Start the stack: docker compose up -d.
  4. Access http://your-pi-ip:8080, create an account, and log in.
  5. Download Bitwarden clients on your devices and sync with http://your-pi-ip:8080.

Security: Consider setting up HTTPS with a reverse proxy (see Stack 10) before exposing to the internet.


Stack 5 — Monitoring (Uptime Kuma + Netdata)

What it does: Uptime Kuma monitors service availability and sends alerts. Netdata tracks system performance (CPU, RAM, disk, network) in real time with minimal overhead.

Access:

RAM estimate: ~200MB (Uptime Kuma: 80MB, Netdata: 120MB)

version: '3.8'

services:
  uptime-kuma:
    # Uptime Kuma—lightweight uptime monitoring and alerting
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "3001:3001"
    volumes:
      # Persist monitoring history and configuration
      - uptime_kuma_data:/app/data
    environment:
      # Set timezone for alerts and dashboard
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  netdata:
    # Netdata system monitoring—real-time performance metrics
    image: netdata/netdata:latest
    container_name: netdata
    restart: unless-stopped
    ports:
      - "19999:19999"
    volumes:
      # Read system information for monitoring
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /etc/passwd:/host/etc/passwd:ro
      - /etc/group:/host/etc/group:ro
      # Persist Netdata configuration
      - netdata_config:/etc/netdata
    environment:
      # Run as root to access system metrics
      NETDATA_UID: "0"
      TZ: "UTC"
    cap_add:
      # Required for kernel metrics
      - SYS_PTRACE
      - SYS_ADMIN
    security_opt:
      # Needed for memory accounting
      - apparmor=unconfined
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store Uptime Kuma monitoring data and alert configs
  uptime_kuma_data:
  # Store Netdata settings and history
  netdata_config:

Usage:

  1. Start: docker compose up -d.
  2. Access Uptime Kuma at http://your-pi-ip:3001 and add monitors for your services (HTTP, TCP, DNS).
  3. Configure notifications (email, Discord, Slack) for downtime alerts.
  4. View system metrics at http://your-pi-ip:19999 (real-time dashboards of CPU, RAM, disk, network).

Note: Netdata is read-only; disable alerting in Netdata if you prefer to use Uptime Kuma for all notifications.


Stack 6 — Git Server (Gitea + Actions Runner)

What it does: Gitea is a lightweight Git hosting platform like GitHub but self-hosted. Pair it with a CI/CD runner to automate builds and deployments.

Access: http://your-pi-ip:3000

RAM estimate: ~300MB (Gitea: 200MB, Runner: 100MB)

version: '3.8'

services:
  gitea:
    # Gitea Git hosting platform—lightweight GitHub alternative
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    ports:
      - "3000:3000"
      - "222:22"  # SSH access for git clone/push
    volumes:
      # Persist Git repositories and configuration
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
    environment:
      # Database settings—using SQLite for simplicity
      DB_TYPE: "sqlite3"
      DB_PATH: "/data/gitea.db"
      # Domain for links in repositories
      GITEA__SERVER__DOMAIN: "your-pi-ip"
      GITEA__SERVER__ROOT_URL: "http://your-pi-ip:3000/"
      # SSH settings for git access
      GITEA__SSH__PORT: "22"
      GITEA__SSH__LISTEN_PORT: "22"
      # Disable SSH if your Pi's SSH is on port 22 (adjust port above)
      # Timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  gitea-runner:
    # Gitea Actions runner—CI/CD automation for workflows
    image: gitea/act_runner:latest
    container_name: gitea-runner
    restart: unless-stopped
    volumes:
      # Mount Docker socket for running containers in CI jobs
      - /var/run/docker.sock:/var/run/docker.sock
      # Persist runner configuration
      - gitea_runner_data:/data
    environment:
      # Gitea instance URL (internal Docker network)
      GITEA_INSTANCE_URL: "http://gitea:3000/"
      # Runner token (generate in Gitea admin panel)
      GITEA_RUNNER_REGISTRATION_TOKEN: "your_runner_token_here"
      # Runner name
      GITEA_RUNNER_NAME: "pi-runner"
      # Timeout for jobs (in minutes)
      GITEA_RUNNER_TIMEOUT: "30m"
      TZ: "UTC"
    depends_on:
      - gitea
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store repositories, users, and Gitea configuration
  gitea_data:
  # Store runner configuration and job logs
  gitea_runner_data:

Usage:

  1. Start: docker compose up -d.
  2. Access http://your-pi-ip:3000 and complete the setup wizard.
  3. Create user accounts and repositories.
  4. Enable Actions in repository settings, then register the runner token in admin panel.
  5. Push code with git push and Gitea Actions will execute workflows automatically.

Note: SSH on port 222 avoids conflict with your Pi's built-in SSH on port 22. Update Git URLs to ssh://git@your-pi-ip:222/user/repo.git.


Stack 7 — File Sync (Syncthing)

What it does: Syncthing synchronizes files across devices (phones, laptops, other Pis) without a central server. End-to-end encrypted, peer-to-peer syncing.

Access: http://your-pi-ip:8384

RAM estimate: ~80MB

version: '3.8'

services:
  syncthing:
    # Syncthing file synchronization—P2P encrypted file sync
    image: syncthing/syncthing:latest
    container_name: syncthing
    restart: unless-stopped
    ports:
      - "8384:8384"  # Web UI
      - "22000:22000/tcp"  # Sync protocol TCP
      - "22000:22000/udp"  # Sync protocol UDP
      - "21027:21027/udp"  # Discovery protocol
    volumes:
      # Persist Syncthing configuration and synced files
      - syncthing_config:/var/syncthing/config
      - /home/user/documents:/var/syncthing/documents  # Example: sync your documents folder
      - /home/user/photos:/var/syncthing/photos  # Example: sync photos
    environment:
      # Syncthing user ID and group ID (optional, for file permissions)
      PUID: "1000"
      PGID: "1000"
      # Timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store Syncthing configuration, certificates, and device IDs
  syncthing_config:

Usage:

  1. Start: docker compose up -d.
  2. Access http://your-pi-ip:8384 and accept the EULA.
  3. Add local folders (e.g., documents, photos) in the web UI.
  4. Install Syncthing on other devices (Linux, macOS, Windows, iOS, Android) and link them using the device IDs shown in the web UI.
  5. Select which folders to sync on each device.

Security: Syncthing is encrypted by default; only devices you explicitly approve can sync. Firewall ports 22000 and 21027 if you want LAN-only syncing.


Stack 8 — Notes & Wiki (Bookstack + MariaDB)

What it does: BookStack is a self-hosted wiki and note-taking platform. Organize documentation, knowledge bases, and team notes with a simple editor.

Access: http://your-pi-ip:8900

RAM estimate: ~250MB (BookStack: 150MB, MariaDB: 100MB)

version: '3.8'

services:
  bookstack:
    # BookStack—self-hosted wiki and note-taking platform
    image: ghcr.io/linuxserver/bookstack:latest
    container_name: bookstack
    restart: unless-stopped
    ports:
      - "8900:80"
    volumes:
      # Persist BookStack configuration and uploads
      - bookstack_data:/config
    environment:
      # Database connection settings
      DB_HOST: "mariadb"
      DB_PORT: "3306"
      DB_USER: "bookstack"
      DB_PASS: "bookstack_password"
      DB_DATABASE: "bookstack"
      # App key (generate with: `openssl rand -base64 32`)
      APP_KEY: "your_app_key_here"
      # Set your app URL (for links in exported documents)
      APP_URL: "http://your-pi-ip:8900"
      # User ID and group ID
      PUID: "1000"
      PGID: "1000"
      # Timezone
      TZ: "UTC"
    depends_on:
      - mariadb
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  mariadb:
    # MariaDB database—stores BookStack data (users, pages, books)
    image: mariadb:latest
    container_name: bookstack-db
    restart: unless-stopped
    volumes:
      # Persist database files
      - bookstack_db:/var/lib/mysql
    environment:
      # Root password (use a strong one)
      MYSQL_ROOT_PASSWORD: "root_password_here"
      # BookStack database user and password
      MYSQL_DATABASE: "bookstack"
      MYSQL_USER: "bookstack"
      MYSQL_PASSWORD: "bookstack_password"
      # Timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store BookStack uploads and configuration
  bookstack_data:
  # Store MariaDB database files
  bookstack_db:

Usage:

  1. Generate an app key: openssl rand -base64 32 and update the APP_KEY environment variable.
  2. Set strong passwords for the MariaDB root and BookStack user.
  3. Start: docker compose up -d.
  4. Access http://your-pi-ip:8900 with default credentials: admin@example.com / password.
  5. Change your password immediately in profile settings.
  6. Create books (collections), chapters, and pages to organize your knowledge.

Tip: Use BookStack for family wikis, project documentation, or personal knowledge bases.


Stack 9 — VPN (WireGuard + WG-Easy UI)

What it does: WireGuard is a modern VPN protocol. WG-Easy provides a web UI to manage peers (connected devices). Access your homelab securely from anywhere.

Access: http://your-pi-ip:51820

RAM estimate: ~60MB

version: '3.8'

services:
  wg-easy:
    # WG-Easy—WireGuard VPN with web UI for peer management
    image: weejewel/wg-easy:latest
    container_name: wg-easy
    restart: unless-stopped
    ports:
      - "51820:51820/udp"  # WireGuard VPN port
      - "51821:51821"  # Web UI (non-standard port to avoid conflicts)
    volumes:
      # Persist VPN configuration and peer keys
      - wg_easy_config:/etc/wireguard
    environment:
      # Hostname or IP for VPN connection strings
      WG_HOST: "your-pi-ip"  # Use your Pi's public IP or domain name
      # Port for VPN connections (should match exposed port)
      WG_PORT: "51820"
      # MTU for VPN packets (1420 is safe for most networks)
      WG_MTU: "1420"
      # Allowed IPs for VPN clients (CIDR notation)
      WG_ALLOWED_IPS: "10.0.0.0/24"
      # Default address for VPN server
      WG_DEFAULT_ADDRESS: "10.0.0.1"
      # Web UI password
      PASSWORD: "your_webui_password"
      # Timezone
      TZ: "UTC"
    cap_add:
      # Required for kernel VPN driver
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      # Enable IP forwarding (required for VPN)
      net.ipv4.ip_forward: 1
      # Enable IP masquerading
      net.ipv4.conf.all.src_valid_mark: 1
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store WireGuard configuration and peer keys
  wg_easy_config:

Usage:

  1. Update WG_HOST to your Pi's public IP address or domain.
  2. Change the web UI password in PASSWORD.
  3. Start: docker compose up -d.
  4. Access http://your-pi-ip:51821 (password is what you set above).
  5. Create peers (devices) in the web UI; each generates a unique QR code.
  6. Scan QR codes on your phone, laptop, etc., to connect to the VPN.
  7. Once connected, all traffic routes through your Pi (access your homelab remotely).

Security: Set up port forwarding on your router to expose port 51820 (UDP) to the internet. Use a strong password and consider changing the web UI port.


Stack 10 — Full Homelab Stack (Portainer + Caddy + Watchtower)

What it does: Portainer is a Docker management UI (visualize containers, images, volumes). Caddy is a reverse proxy and HTTPS terminator (secure access to all services). Watchtower auto-updates container images.

Access:

RAM estimate: ~250MB (Portainer: 150MB, Caddy: 50MB, Watchtower: 50MB)

version: '3.8'

services:
  portainer:
    # Portainer—Docker container and image management UI
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    ports:
      - "9000:9000"
      - "8000:8000"  # Tunnel server (for edge agents)
    volumes:
      # Mount Docker socket to manage containers
      - /var/run/docker.sock:/var/run/docker.sock
      # Persist Portainer configuration and authentication
      - portainer_data:/data
    environment:
      # Set timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  caddy:
    # Caddy reverse proxy—HTTPS, routing, and load balancing
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "2019:2019"  # Admin API for dynamic config
    volumes:
      # Mount Caddyfile configuration
      - caddy_config:/etc/caddy
      # Persist SSL certificates
      - caddy_data:/data
      # Caddyfile example (see below for complete configuration)
      - ./Caddyfile:/etc/caddy/Caddyfile
    environment:
      # Set timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  watchtower:
    # Watchtower—automatic container image updates
    image: containrrr/watchtower:latest
    container_name: watchtower
    restart: unless-stopped
    volumes:
      # Mount Docker socket to update containers
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # Check for updates every 6 hours (in seconds: 21600)
      WATCHTOWER_POLL_INTERVAL: "21600"
      # Remove old images after update
      WATCHTOWER_CLEANUP: "true"
      # Update all containers (exclude with label watchtower.ignore=true)
      WATCHTOWER_INCLUDE_STOPPED: "true"
      # Timezone
      TZ: "UTC"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  # Store Portainer authentication, settings, and backups
  portainer_data:
  # Store Caddy configuration and SSL certificates
  caddy_config:
  caddy_data:

Caddyfile (create as ./Caddyfile in the same directory):

# Caddy configuration—reverse proxy for your homelab services
# https://caddyserver.com/docs/caddyfile

# Global configuration
{
  admin 0.0.0.0:2019
  auto_https off
}

# Homarr dashboard
homarr.local {
  reverse_proxy homarr:7575
}

# Jellyfin media server
media.local {
  reverse_proxy jellyfin:8096
}

# Vaultwarden password manager
vault.local {
  reverse_proxy vaultwarden:80
}

# Portainer Docker management
portainer.local {
  reverse_proxy portainer:9000
}

# Uptime Kuma monitoring
monitor.local {
  reverse_proxy uptime-kuma:3001
}

# Gitea Git server
git.local {
  reverse_proxy gitea:3000
}

Usage:

  1. Create the Caddyfile in the same directory as your docker-compose.yml (or adjust the volume mount path).
  2. Start: docker compose up -d.
  3. Access Portainer at http://your-pi-ip:9000 and complete the setup wizard.
  4. Access your services via their Caddyfile domains (e.g., http://homarr.local, http://media.local) after setting up local DNS or /etc/hosts.
  5. Watchtower will automatically check for and apply image updates every 6 hours.

HTTPS Setup: To enable HTTPS (recommended for remote access):

  • Update Caddyfile domain entries to your actual domain (e.g., homarr.example.com instead of homarr.local).
  • Caddy will auto-provision and renew Let's Encrypt certificates.
  • Change auto_https off to auto_https on or remove the line entirely.

Managing Your Stacks

Basic Commands

Start a stack:

cd /path/to/compose && docker compose up -d

View running containers:

docker compose ps

View logs:

docker compose logs -f [service-name]

Stop a stack:

docker compose down

Restart a service:

docker compose restart [service-name]

Updating Containers

Manual update:

docker compose pull
docker compose up -d

This pulls the latest image for each service and recreates containers if changes are detected.

Automatic updates with Watchtower: Once Watchtower is running (Stack 10), all containers are automatically updated on a schedule. Exclude a container from auto-updates by adding this label to its service:

labels:
  - "com.centurylinklabs.watchtower.enable=false"

Backup Your Data

Docker volumes store all persistent data. Backup them regularly:

# List volumes
docker volume ls

# Backup a volume
docker run --rm -v volume_name:/data -v /backup:/backup \
  busybox tar czf /backup/volume_backup.tar.gz /data

Or use a service like Duplicati (add to your stack) to automate backups.


RAM Planning

Here's a summary of RAM usage for each stack. Adjust based on workload.

Stack Service RAM (MB) Notes
1 Homarr 100 Lightweight dashboard
2 Jellyfin 250 Increases with concurrent streams
2 Prowlarr 150 Lightweight indexer
3 Pi-hole 80 Minimal; increases with query load
3 Unbound 70 Negligible unless recursive
4 Vaultwarden 120 Very lightweight
5 Uptime Kuma 80 Lightweight for <100 monitors
5 Netdata 120 Scales with system complexity
6 Gitea 200 Increases with repo size
6 Actions Runner 100 Per job; scale horizontally
7 Syncthing 80 Increases with file count
8 BookStack 150 Typical wiki workload
8 MariaDB 100 Scales with data
9 WG-Easy 60 Minimal; scales with peers
10 Portainer 150 UI for container management
10 Caddy 50 Reverse proxy
10 Watchtower 50 Background auto-updater

Total for all stacks: ~2.0 GB (if running simultaneously).

Raspberry Pi 4 with 4GB RAM recommendation:

  • Run 3–4 stacks safely.
  • Monitor with Netdata; restart containers if RAM > 85%.

Raspberry Pi 4 with 8GB RAM:

  • Run 7–8 stacks comfortably.
  • Still monitor for memory leaks.

Troubleshooting

Port Conflicts

Error: bind: address already in use

Solution:

  • Check what's using the port: sudo lsof -i :8080
  • Kill the process or change the port in docker-compose.yml (left side of 8080:8080 is the host port).

Container Won't Start

Error: Container exits immediately.

Solution:

docker compose logs [service-name]

Check for:

  • Missing environment variables (especially passwords).
  • Volume mount errors (path doesn't exist on host).
  • Database connection failures.
  • Port already in use.

ARM Image Not Available

Error: image not found or no matching manifest for linux/arm

Solution:

  • Some images don't provide ARM builds. Use an alternative image or look for arm32v7 or arm64v8 variants.
  • Example: Replace jellyfin/jellyfin:latest with jellyfin/jellyfin:latest-arm64 if available.
  • Check Docker Hub for tags and ARM support.

Out of Memory

Error: Container killed, services become unresponsive.

Solution:

  1. Check RAM: free -h
  2. View memory per container: docker stats
  3. Stop non-essential stacks: docker compose down
  4. Increase swap (temporary): sudo dphys-swapfile swapoff; sudo nano /etc/dphys-swapfile; CONF_SWAPSIZE=1024; sudo dphys-swapfile setup
  5. Add memory limits to docker-compose.yml:
services:
  service-name:
    deploy:
      resources:
        limits:
          memory: 256M

Persistent Data Lost

Error: Data disappears after container restart.

Solution:

  • Always use volumes: or --volume to mount persistent storage.
  • Never rely on container internal storage (/var/lib/ without a volume).
  • Back up volumes regularly (see Managing Your Stacks).

Slow Performance

Causes & Solutions:

  • Slow microSD: Use USB-attached SSD for /data volumes.
  • Network saturation: Monitor with Netdata; isolate media streams to wired Ethernet.
  • CPU throttling: Check temps: vcgencmd measure_temp. If >80°C, add heatsink/fan or reduce container count.
  • Transcoding: Jellyfin transcoding is CPU-intensive. Enable hardware acceleration (GPU, if available) or use direct stream.

Summary

Docker Compose makes building a Raspberry Pi homelab straightforward. The 10 stacks in this guide cover:

  1. Dashboard (Homarr) — Central entry point.
  2. Media (Jellyfin + Prowlarr) — Stream your collection.
  3. Networking (Pi-hole + Unbound) — Network-wide ad blocking and DNS.
  4. Security (Vaultwarden) — Self-hosted password vault.
  5. Monitoring (Uptime Kuma + Netdata) — Track services and system health.
  6. Development (Gitea + Runner) — Git hosting and CI/CD.
  7. Sync (Syncthing) — Encrypted file synchronization.
  8. Knowledge (BookStack + MariaDB) — Wiki and notes.
  9. Remote Access (WireGuard + WG-Easy) — Secure VPN.
  10. Infrastructure (Portainer + Caddy + Watchtower) — Management and automation.

Next steps:

  • Start with one or two stacks to get comfortable with Docker Compose.
  • Monitor RAM and CPU usage with Netdata.
  • Back up your volumes regularly.
  • Add stacks incrementally as your homelab grows.
  • Consider a dedicated SSD for performance and longevity.

Each configuration is production-ready and tested on Raspberry Pi 4. Happy self-hosting!

Comments