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.
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 $USERand restart your session to use Docker withoutsudo.
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:
- Jellyfin: http://your-pi-ip:8096
- Prowlarr: http://your-pi-ip:9696
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:
- Start the stack with
docker compose up -d. - Open http://your-pi-ip:8096 and run Jellyfin's setup wizard.
- Add your media library paths (mounted at
/movies,/tv,/music). - Configure Prowlarr at http://your-pi-ip:9696 and add indexers.
- 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:
- Change the web password in the environment section.
- Start the stack:
docker compose up -d. - Access the admin dashboard at http://your-pi-ip/admin (login with admin + password).
- Set your Pi-hole IP as the DNS server on your router (or individual devices).
- 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:
- Generate an admin token:
openssl rand -base64 32and paste it in the environment. - Update
DOMAINto your Pi's IP or hostname. - Start the stack:
docker compose up -d. - Access http://your-pi-ip:8080, create an account, and log in.
- 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:
- Uptime Kuma: http://your-pi-ip:3001
- Netdata: http://your-pi-ip:19999
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:
- Start:
docker compose up -d. - Access Uptime Kuma at http://your-pi-ip:3001 and add monitors for your services (HTTP, TCP, DNS).
- Configure notifications (email, Discord, Slack) for downtime alerts.
- 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:
- Start:
docker compose up -d. - Access http://your-pi-ip:3000 and complete the setup wizard.
- Create user accounts and repositories.
- Enable Actions in repository settings, then register the runner token in admin panel.
- Push code with
git pushand 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:
- Start:
docker compose up -d. - Access http://your-pi-ip:8384 and accept the EULA.
- Add local folders (e.g., documents, photos) in the web UI.
- Install Syncthing on other devices (Linux, macOS, Windows, iOS, Android) and link them using the device IDs shown in the web UI.
- 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:
- Generate an app key:
openssl rand -base64 32and update theAPP_KEYenvironment variable. - Set strong passwords for the MariaDB root and BookStack user.
- Start:
docker compose up -d. - Access http://your-pi-ip:8900 with default credentials:
admin@example.com/password. - Change your password immediately in profile settings.
- 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:
- Update
WG_HOSTto your Pi's public IP address or domain. - Change the web UI password in
PASSWORD. - Start:
docker compose up -d. - Access http://your-pi-ip:51821 (password is what you set above).
- Create peers (devices) in the web UI; each generates a unique QR code.
- Scan QR codes on your phone, laptop, etc., to connect to the VPN.
- 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:
- Portainer: http://your-pi-ip:9000
- Caddy Dashboard: http://your-pi-ip:2019
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:
- Create the
Caddyfilein the same directory as yourdocker-compose.yml(or adjust the volume mount path). - Start:
docker compose up -d. - Access Portainer at http://your-pi-ip:9000 and complete the setup wizard.
- Access your services via their Caddyfile domains (e.g., http://homarr.local, http://media.local) after setting up local DNS or /etc/hosts.
- 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.cominstead ofhomarr.local). - Caddy will auto-provision and renew Let's Encrypt certificates.
- Change
auto_https offtoauto_https onor 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 of8080:8080is 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
arm32v7orarm64v8variants. - Example: Replace
jellyfin/jellyfin:latestwithjellyfin/jellyfin:latest-arm64if available. - Check Docker Hub for tags and ARM support.
Out of Memory
Error: Container killed, services become unresponsive.
Solution:
- Check RAM:
free -h - View memory per container:
docker stats - Stop non-essential stacks:
docker compose down - Increase swap (temporary):
sudo dphys-swapfile swapoff; sudo nano /etc/dphys-swapfile; CONF_SWAPSIZE=1024; sudo dphys-swapfile setup - 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--volumeto 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
/datavolumes. - 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:
- Dashboard (Homarr) — Central entry point.
- Media (Jellyfin + Prowlarr) — Stream your collection.
- Networking (Pi-hole + Unbound) — Network-wide ad blocking and DNS.
- Security (Vaultwarden) — Self-hosted password vault.
- Monitoring (Uptime Kuma + Netdata) — Track services and system health.
- Development (Gitea + Runner) — Git hosting and CI/CD.
- Sync (Syncthing) — Encrypted file synchronization.
- Knowledge (BookStack + MariaDB) — Wiki and notes.
- Remote Access (WireGuard + WG-Easy) — Secure VPN.
- 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!