
Run 15 self-hosted services on an Intel N100 mini PC using a single Docker Compose file. Jellyfin, Nextcloud, Pi-hole, Immich, Home Assistant, Vaultwarden, Grafana, and more โ all under 12W idle.
Running a home server doesn't have to mean managing a dozen separate install scripts, hunting for config files, or debugging broken dependencies. Docker Compose solves all of that โ one YAML file defines your entire stack, and docker compose up -d brings everything online in minutes.
This guide covers 15 essential self-hosted services organized into a single composable stack, tested on an Intel N100 mini PC running Ubuntu 24.04. Total idle power draw with all 15 services: under 12W.

Docker Compose gives you:
rsync + docker compose updocker compose pull && docker compose up -d updates everythingOn an Intel N100 mini PC (6Wโ10W idle), running 15 Docker containers adds roughly 2โ4W to baseline power draw. That's essentially free at $0.12/kWh โ less than $5/year for the entire stack overhead.

# Ubuntu 24.04 LTS
sudo apt update && sudo apt upgrade -y
# Install Docker + Docker Compose (official method)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version # Docker version 26.x.x
docker compose version # Docker Compose version v2.x.x
Create your stack directory:
mkdir -p ~/homeserver
cd ~/homeserver
# Shared network and volumes directory
mkdir -p data/{nginx,portainer,pihole,uptime-kuma,vaultwarden,nextcloud,jellyfin,immich,home-assistant,grafana,prometheus,navidrome,mealie,freshrss,paperless}

Here's a complete docker-compose.yml organized into functional groups. Copy it all into ~/homeserver/docker-compose.yml.
Before anything else, set up a shared Docker network so containers can reach each other by name:
networks:
homeserver:
driver: bridge
volumes:
# Define named volumes here for cleaner config
portainer_data:
pihole_data:
pihole_dnsmasq:
version: "3.9"
networks:
homeserver:
driver: bridge
services:
# ============================================================
# 1. Portainer โ Docker Management UI
# ============================================================
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
networks:
- homeserver
# ============================================================
# 2. Nginx Proxy Manager โ Reverse Proxy with SSL
# ============================================================
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81" # Admin UI
volumes:
- ./data/nginx/data:/data
- ./data/nginx/letsencrypt:/etc/letsencrypt
networks:
- homeserver
# ============================================================
# 3. Pi-hole โ Network-Wide Ad Blocking
# ============================================================
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80" # Web UI
environment:
TZ: "America/New_York"
WEBPASSWORD: "${PIHOLE_PASSWORD}"
volumes:
- ./data/pihole/etc:/etc/pihole
- ./data/pihole/dnsmasq:/etc/dnsmasq.d
cap_add:
- NET_ADMIN
networks:
- homeserver
# ============================================================
# 4. Uptime Kuma โ Service Monitoring
# ============================================================
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./data/uptime-kuma:/app/data
networks:
- homeserver
# ============================================================
# 5. Vaultwarden โ Self-Hosted Password Manager
# ============================================================
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
ports:
- "8180:80"
environment:
WEBSOCKET_ENABLED: "true"
SIGNUPS_ALLOWED: "false"
volumes:
- ./data/vaultwarden:/data
networks:
- homeserver
# ============================================================
# 6. Nextcloud โ Private Cloud Storage
# ============================================================
nextcloud:
image: nextcloud:28-apache
container_name: nextcloud
restart: unless-stopped
ports:
- "8090:80"
environment:
NEXTCLOUD_ADMIN_USER: "${NC_ADMIN_USER}"
NEXTCLOUD_ADMIN_PASSWORD: "${NC_ADMIN_PASSWORD}"
NEXTCLOUD_TRUSTED_DOMAINS: "${NC_DOMAIN}"
MYSQL_HOST: nextcloud-db
MYSQL_USER: nextcloud
MYSQL_PASSWORD: "${NC_DB_PASSWORD}"
MYSQL_DATABASE: nextcloud
volumes:
- ./data/nextcloud:/var/www/html
depends_on:
- nextcloud-db
networks:
- homeserver
nextcloud-db:
image: mariadb:10.11
container_name: nextcloud-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "${NC_DB_ROOT_PASSWORD}"
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: "${NC_DB_PASSWORD}"
volumes:
- ./data/nextcloud-db:/var/lib/mysql
networks:
- homeserver
# ============================================================
# 7. Jellyfin โ Media Server (with Intel QSV hardware transcoding)
# ============================================================
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096"
environment:
JELLYFIN_PublishedServerUrl: "http://${SERVER_IP}:8096"
volumes:
- ./data/jellyfin/config:/config
- ./data/jellyfin/cache:/cache
- /mnt/media:/media:ro # Mount your media drive
devices:
- /dev/dri:/dev/dri # Intel Quick Sync GPU access
group_add:
- "render"
- "video"
networks:
- homeserver
# ============================================================
# 8. Immich โ Self-Hosted Google Photos Alternative
# ============================================================
immich-server:
image: ghcr.io/immich-app/immich-server:release
container_name: immich-server
restart: unless-stopped
ports:
- "2283:2283"
environment:
DB_HOSTNAME: immich-db
DB_USERNAME: immich
DB_PASSWORD: "${IMMICH_DB_PASSWORD}"
DB_DATABASE_NAME: immich
REDIS_HOSTNAME: immich-redis
volumes:
- ./data/immich/upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
depends_on:
- immich-db
- immich-redis
networks:
- homeserver
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:release
container_name: immich-machine-learning
restart: unless-stopped
volumes:
- ./data/immich/model-cache:/cache
networks:
- homeserver
immich-db:
image: tensorchord/pgvecto-rs:pg16-v0.2.0
container_name: immich-db
restart: unless-stopped
environment:
POSTGRES_USER: immich
POSTGRES_PASSWORD: "${IMMICH_DB_PASSWORD}"
POSTGRES_DB: immich
volumes:
- ./data/immich/db:/var/lib/postgresql/data
networks:
- homeserver
immich-redis:
image: redis:7
container_name: immich-redis
restart: unless-stopped
networks:
- homeserver
# ============================================================
# 9. Home Assistant โ Smart Home Automation
# ============================================================
home-assistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: home-assistant
restart: unless-stopped
network_mode: host # Required for device discovery
environment:
TZ: "America/New_York"
volumes:
- ./data/home-assistant:/config
- /etc/localtime:/etc/localtime:ro
privileged: true # Required for Bluetooth/Zigbee/Z-Wave dongles
# ============================================================
# 10. Grafana + Prometheus โ Server Monitoring Dashboard
# ============================================================
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./data/prometheus:/prometheus
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
networks:
- homeserver
grafana:
image: grafana/grafana-oss:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ./data/grafana:/var/lib/grafana
networks:
- homeserver
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
network_mode: host
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
- "--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)"
# ============================================================
# 11. Navidrome โ Self-Hosted Music Streaming
# ============================================================
navidrome:
image: deluan/navidrome:latest
container_name: navidrome
restart: unless-stopped
ports:
- "4533:4533"
environment:
ND_SCANSCHEDULE: "1h"
ND_LOGLEVEL: "info"
ND_SESSIONTIMEOUT: "24h"
volumes:
- ./data/navidrome:/data
- /mnt/media/music:/music:ro
networks:
- homeserver
# ============================================================
# 12. Mealie โ Self-Hosted Recipe Manager
# ============================================================
mealie:
image: ghcr.io/mealie-recipes/mealie:latest
container_name: mealie
restart: unless-stopped
ports:
- "9925:9000"
environment:
PUID: "1000"
PGID: "1000"
TZ: "America/New_York"
MAX_WORKERS: "1"
WEB_CONCURRENCY: "1"
BASE_URL: "http://${SERVER_IP}:9925"
volumes:
- ./data/mealie:/app/data
networks:
- homeserver
# ============================================================
# 13. FreshRSS โ Self-Hosted RSS Reader
# ============================================================
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
ports:
- "8092:80"
environment:
TZ: "America/New_York"
CRON_MIN: "*/20"
volumes:
- ./data/freshrss/data:/var/www/FreshRSS/data
- ./data/freshrss/extensions:/var/www/FreshRSS/extensions
networks:
- homeserver
# ============================================================
# 14. Paperless-ngx โ Document Management
# ============================================================
paperless-ngx:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
container_name: paperless-ngx
restart: unless-stopped
ports:
- "8000:8000"
environment:
PAPERLESS_REDIS: "redis://paperless-redis:6379"
PAPERLESS_DBENGINE: "sqlite"
PAPERLESS_OCR_LANGUAGE: "eng"
PAPERLESS_SECRET_KEY: "${PAPERLESS_SECRET}"
PAPERLESS_TIME_ZONE: "America/New_York"
PAPERLESS_URL: "http://${SERVER_IP}:8000"
volumes:
- ./data/paperless/data:/usr/src/paperless/data
- ./data/paperless/media:/usr/src/paperless/media
- ./data/paperless/export:/usr/src/paperless/export
- ./data/paperless/consume:/usr/src/paperless/consume
depends_on:
- paperless-redis
networks:
- homeserver
paperless-redis:
image: redis:7
container_name: paperless-redis
restart: unless-stopped
networks:
- homeserver
# ============================================================
# 15. Watchtower โ Automatic Container Updates
# ============================================================
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_SCHEDULE: "0 4 * * 0" # Weekly Sunday 4AM
WATCHTOWER_NOTIFICATIONS: "slack"
WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: "${SLACK_WEBHOOK}"
networks:
- homeserver
volumes:
portainer_data:
Create ~/homeserver/.env:
# Server
SERVER_IP=192.168.1.100
# Pi-hole
PIHOLE_PASSWORD=changeme123
# Nextcloud
NC_ADMIN_USER=admin
NC_ADMIN_PASSWORD=strongpassword
NC_DOMAIN=nextcloud.yourdomain.com
NC_DB_PASSWORD=dbpassword
NC_DB_ROOT_PASSWORD=rootpassword
# Immich
IMMICH_DB_PASSWORD=immichpassword
# Grafana
GRAFANA_PASSWORD=grafanapassword
# Paperless
PAPERLESS_SECRET=yoursecretkey
# Watchtower (optional)
SLACK_WEBHOOK=https://hooks.slack.com/services/...
Create ~/homeserver/config/prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: "node"
static_configs:
- targets: ["localhost:9100"]
- job_name: "cadvisor"
static_configs:
- targets: ["cadvisor:8080"]
cd ~/homeserver
# Start everything
docker compose up -d
# Check all services are running
docker compose ps
# Follow logs for a specific service
docker compose logs -f jellyfin
# Stop everything
docker compose down
# Update all containers
docker compose pull && docker compose up -d
Measured with a Kill-A-Watt on a Beelink EQ12 (Intel N100, 16GB DDR5):
| State | Power Draw |
|---|---|
| Idle (no Docker) | 6.2W |
| 15 containers running, no load | 9.8W |
| Jellyfin transcoding 1080p | 18โ22W |
| Jellyfin 4K HEVC (QSV hardware) | 14โ16W |
| Immich face recognition scan | 22โ28W |
The N100's Intel Quick Sync hardware transcoder means Jellyfin 4K transcoding draws less power than software 1080p transcoding on older CPUs.
Annual electricity cost at $0.12/kWh:
| Service | RAM (typical) |
|---|---|
| Portainer | 80MB |
| Nginx Proxy Manager | 120MB |
| Pi-hole | 80MB |
| Uptime Kuma | 60MB |
| Vaultwarden | 20MB |
| Nextcloud + MariaDB | 400MB |
| Jellyfin (idle) | 300MB |
| Immich (all 4 containers) | 600MB |
| Home Assistant | 150MB |
| Prometheus + Grafana | 200MB |
| Navidrome | 50MB |
| Mealie | 120MB |
| FreshRSS | 40MB |
| Paperless-ngx | 200MB |
| Watchtower | 20MB |
| Total | ~2.4GB |
On a 16GB N100 system, this leaves 13.5GB free for caching and additional services. Even an 8GB system handles this stack with headroom.
| Service | URL | Port |
|---|---|---|
| Portainer | http://server:9443 | 9443 |
| Nginx Proxy Manager | http://server:81 | 81 |
| Pi-hole | http://server:8080 | 8080 |
| Uptime Kuma | http://server:3001 | 3001 |
| Vaultwarden | http://server:8180 | 8180 |
| Nextcloud | http://server:8090 | 8090 |
| Jellyfin | http://server:8096 | 8096 |
| Immich | http://server:2283 | 2283 |
| Home Assistant | http://server:8123 | 8123 |
| Grafana | http://server:3000 | 3000 |
| Navidrome | http://server:4533 | 4533 |
| Mealie | http://server:9925 | 9925 |
| FreshRSS | http://server:8092 | 8092 |
| Paperless-ngx | http://server:8000 | 8000 |
Don't open all these ports to the internet. Use one of:
Option A: Tailscale (easiest)
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Access services via Tailscale IP from anywhere
Option B: Nginx Proxy Manager + Cloudflare
Option C: Cloudflare Tunnel (no port forwarding)
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token YOUR_TOKEN
Back up the data/ directory with an automated script:
#!/bin/bash
# backup.sh โ run daily via cron
BACKUP_DIR="/mnt/backup/homeserver"
DATE=$(date +%Y%m%d)
# Stop services (optional but safer)
docker compose -f ~/homeserver/docker-compose.yml stop
# Sync data
rsync -av --delete ~/homeserver/data/ "$BACKUP_DIR/$DATE/"
# Restart
docker compose -f ~/homeserver/docker-compose.yml start
echo "Backup complete: $BACKUP_DIR/$DATE"
Add to cron: 0 3 * * * /home/user/homeserver/backup.sh
Pi-hole conflicts with port 53:
Some systems run systemd-resolved on port 53. Disable it:
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
echo "DNS=8.8.8.8" | sudo tee -a /etc/systemd/resolved.conf
Jellyfin Intel QSV not working:
# Check if /dev/dri exists
ls -la /dev/dri
# Install Intel media driver
sudo apt install intel-media-va-driver-non-free vainfo
vainfo # Should show iHD driver
Nextcloud slow performance: Add Redis as a memory cache. The compose file above uses MariaDB โ add Redis:
nextcloud-redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
networks:
- homeserver
Then in Nextcloud's config.php:
'memcache.local' => '\OC\Memcache\Redis',
'redis' => ['host' => 'nextcloud-redis', 'port' => 6379],
If this is your first home server, don't start all 15 services at once. Recommended order:
The beauty of Docker Compose is that you can comment out services you don't need yet and add them later with zero disruption.
Q: Can I run all 15 services on 8GB RAM?
Yes. Total RAM for all 15 services is approximately 2.4GB, leaving 5.6GB free for caching on an 8GB system. Immich machine learning during photo import spikes to 1.5โ2GB temporarily, but this is short-lived. Consider increasing Docker's swap space if you hit limits: --memory-swap=4g.
Q: Do I need a domain name?
No. All services work fine on local IPs and ports (http://192.168.1.100:8096). A domain with Nginx Proxy Manager + Let's Encrypt SSL is better UX but optional. Tailscale gives you remote access without a domain.
Q: How do I update all services to the latest version?
cd ~/homeserver
docker compose pull # Pull latest images
docker compose up -d # Restart with new images
docker image prune -f # Clean up old images
Or let Watchtower handle it automatically (it's service 15 in the stack above).
Q: What's the minimum hardware to run all 15 services?
An Intel N100 mini PC with 16GB RAM handles this stack at under 10W idle. You could also use a Raspberry Pi 5 with 8GB RAM, but the Pi lacks hardware video transcoding, so Jellyfin will use software transcoding at higher power draw. See our hardware comparison guide for recommendations.

Builds
Complete docker-compose.yml to run Jellyfin, Nextcloud, Home Assistant, Pi-hole, Vaultwarden, Immich, Homepage, Uptime Kuma, Tailscale, and Portainer on an Intel N100 mini PC under 15W.
Use Cases
From replacing Netflix to running local AI, a home server running on 8W can do far more than you'd expect. Here are 15 real uses with app recommendations โ and the cost breakdown that makes it worth it.

Use Cases
Complete Nextcloud setup guide with Docker Compose for 2026. Replace Google Drive with a self-hosted cloud on an Intel N100 mini PC. Includes Redis caching, MariaDB, and remote access via Tailscale.
Check out our build guides to get started with hardware.
View Build Guides