Companion to “Your Own Netflix, Google Photos, and Dropbox on a Raspberry Pi” (The Pi Stack Lab)
Everything from the video: the commands, the full docker-compose.yml, and the fix for every gotcha. Paste this into a GitHub Gist / your site and link it in the description.
Tested pattern for Raspberry Pi 5 (8GB) on Raspberry Pi OS 64-bit (Bookworm). Software moves fast — if an image changes, check the app’s official docs (linked per app).
0) Before you start
- Hardware: Raspberry Pi 5 (8GB), official 27W USB-C PSU, active cooler, and a USB 3.0 SSD (boot + storage — not a microSD).
- Give the Pi a fixed address: set a DHCP reservation in your router (or a static IP) so services stay reachable.
- This guide assumes you’re SSH’d into the Pi and your SSD is mounted at /mnt/ssd (see step 2).
1) Flash Raspberry Pi OS
- In Raspberry Pi Imager, choose Raspberry Pi OS Lite (64-bit).
- Click the gear icon → set hostname, enable SSH, set username + password, and your Wi‑Fi/locale.
- Write to the SSD (or card), boot, and SSH in:
ssh youruser@your-pi-ip
sudo apt update && sudo apt full-upgrade -y
2) Mount the SSD at /mnt/ssd (persists across reboots)
lsblk -f # find your SSD partition (e.g. sda1) and its UUID
sudo mkdir -p /mnt/ssd
# add to fstab (replace YOUR-UUID and filesystem type, e.g. ext4):
echo 'UUID=YOUR-UUID /mnt/ssd ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab
sudo mount -a
# create the data folders we'll use:
sudo mkdir -p /mnt/ssd/{media/movies,media/tv,nextcloud-data,immich}
3) Install Docker + Docker Compose
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# log out and back in (or: newgrp docker), then verify:
docker --version && docker compose version
n4) Project layout
mkdir -p ~/stack && cd ~/stack
# the docker-compose.yml below lives here; each app keeps its config in a subfolder
5) The combined docker-compose.yml (5 apps)
Put this in ~/stack/docker-compose.yml. Immich runs in its own stack (step 7) because it ships its own database + machine-learning containers — bundling it here would be fragile.
Port plan (avoids conflicts): Nginx Proxy Manager owns 80/443. Pi-hole’s web UI is moved to 8081. Everything else sits on a high port and is reached either directly or via the proxy.
Just you? You can skip the proxy. If you’ll access these services yourself via Tailscale (step 9 — recommended), you don’t need Nginx Proxy Manager or a domain at all. Delete the npm service below and reach each app directly at http://your-pi-ip:PORT (or a Tailscale address). Keep NPM only if you want to share with family or want public HTTPS URLs (step 8).
services:
# --- Pi-hole: network-wide ad blocking (replaces paid ad blockers) ---
pihole:
container_name: pihole
image: pihole/pihole:latest
ports:
- "53:53/tcp"
- "53:53/udp"
- "8081:80/tcp" # web UI on 8081 (80 is used by the proxy)
environment:
TZ: "America/New_York"
FTLCONF_webserver_api_password: "CHANGE-ME-admin-pass" # Pi-hole v6 syntax
FTLCONF_dns_listeningMode: "all"
volumes:
- ./pihole/etc-pihole:/etc/pihole
cap_add:
- SYS_NICE
restart: unless-stopped
# --- Jellyfin: your own Netflix (replaces streaming) ---
jellyfin:
container_name: jellyfin
image: lscr.io/linuxserver/jellyfin:latest
environment:
PUID: "1000"
PGID: "1000"
TZ: "America/New_York"
ports:
- "8096:8096"
volumes:
- ./jellyfin/config:/config
- /mnt/ssd/media:/media
restart: unless-stopped
# --- Nextcloud: your own Dropbox (files/sync/calendar) ---
nextcloud:
container_name: nextcloud
image: lscr.io/linuxserver/nextcloud:latest
environment:
PUID: "1000"
PGID: "1000"
TZ: "America/New_York"
ports:
- "8443:443"
volumes:
- ./nextcloud/config:/config
- /mnt/ssd/nextcloud-data:/data
restart: unless-stopped
# --- Vaultwarden: self-hosted passwords (Bitwarden-compatible) ---
vaultwarden:
container_name: vaultwarden
image: vaultwarden/server:latest
environment:
SIGNUPS_ALLOWED: "true" # TRUE only to create your first account, then set false (see gotcha)
DOMAIN: "https://vault.yourdomain.com" # if Tailscale-only, use your Tailscale HTTPS URL instead (see step 9)
volumes:
- ./vaultwarden:/data
ports:
- "8082:80"
restart: unless-stopped
# --- Nginx Proxy Manager: secure gateway + free SSL ---
npm:
container_name: nginx-proxy-manager
image: jc21/nginx-proxy-manager:latest
ports:
- "80:80"
- "443:443"
- "81:81" # admin UI
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
restart: unless-stopped
Start it:
cd ~/stack
docker compose up -d
docker compose ps # check everything is 'running'
docker compose logs -f pihole # tail logs if one won't start
First-load URLs (on your LAN):
- Pi-hole: http://your-pi-ip:8081/admin
- Jellyfin: http://your-pi-ip:8096
- Nextcloud: https://your-pi-ip:8443 (self-signed until you proxy it)
- Vaultwarden: http://your-pi-ip:8082
- Nginx Proxy Manager: http://your-pi-ip:81
6) The gotchas — and the exact fixes
Pi-hole won’t start: “address already in use” (port 53)
Raspberry Pi OS runs systemd-resolved, which holds port 53. Free it:
sudo sed -i 's/#\?DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved
docker compose up -d pihole
Killer setting: in your router’s DHCP/DNS settings, set the primary DNS to the Pi’s IP so every device is covered. (Note: it can’t block YouTube ads — those come from the same domain as the video.)
Nextcloud: uploads fail on big files (default limit ~512MB)
With the linuxserver image, raise PHP + nginx limits, then restart:
# PHP limits
printf 'upload_max_filesize=10G\npost_max_size=10G\nmemory_limit=1G\n' | \
sudo tee ~/stack/nextcloud/config/php/php-local.ini
# nginx body size: in ~/stack/nextcloud/config/nginx/site-confs/default.conf
# add inside the server { } block: client_max_body_size 10G;
docker compose restart nextcloud
Behind the proxy, also add your domain to Nextcloud’s trusted domains (in ~/stack/nextcloud/config/www/nextcloud/config/config.php) and set ‘overwriteprotocol’ => ‘https’.
Vaultwarden: lock down signups (do this immediately)
- With SIGNUPS_ALLOWED: “true”, open Vaultwarden and create your account.
- Edit the compose file → set SIGNUPS_ALLOWED: “false”.
- Apply: docker compose up -d vaultwarden Killer setting: the browser extensions need HTTPS before you store real passwords. Simplest way with no domain: Tailscale HTTPS (step 9) — sudo tailscale serve gives Vaultwarden a trusted https:// URL on your tailnet. Or, if you’re sharing publicly, put it behind the proxy (step 8).
Nginx Proxy Manager: change the default admin login
Default is admin@example.com / changeme — these are public. Log into http://your-pi-ip:81 and change email + password on first login.
7) Immich (your own Google Photos) — its own stack
Immich is Docker-only and ships its own compose + .env:
mkdir -p ~/immich && cd ~/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
# edit .env:
# UPLOAD_LOCATION=/mnt/ssd/immich
# DB_DATA_LOCATION=/mnt/ssd/immich/postgres
# set a strong DB_PASSWORD
nano .env
docker compose up -d
Open http://your-pi-ip:2283, create the admin user, then install the Immich mobile app, point it at the Pi, and enable automatic backup. Gotcha: the machine-learning (face/object search) is heavy on a Pi — the first library scan is slow. To lighten it, you can comment out the immich-machine-learning service in Immich’s compose. Full docs: https://docs.immich.app/install/docker-compose/
8) (OPTIONAL) Nginx Proxy Manager — only for public / family sharing
Skip this whole step if it’s just you — Tailscale (step 9) already gives you secure remote access with no domain and no proxy. Do this only if you want clean public URLs or to share with people who won’t install Tailscale. It requires a domain you own (see the note at the end of this step).
For each service, in NPM → Proxy Hosts → Add Proxy Host:
- Domain: e.g. jellyfin.yourdomain.com
- Forward Hostname/IP: the container name; Forward Port: the internal port
- Jellyfin → jellyfin : 8096
- Nextcloud → nextcloud : 443 (scheme: https)
- Vaultwarden → vaultwarden : 80
- Pi-hole → pihole : 80
- Immich → your-pi-ip : 2283 (separate stack)
- SSL tab: request a Let’s Encrypt certificate, force SSL. (All services in ~/stack share one Docker network, so NPM reaches them by container name.)
What is yourdomain.com? A domain name you own — replace it with your real one (e.g. you buy pistacklab.com, so jellyfin.yourdomain.com becomes jellyfin.pistacklab.com). Get one for ~$10–12/yr at Cloudflare or Namecheap. You’ll also point its DNS at your home IP (add dynamic DNS, since home IPs change) and either forward ports 80/443 or use a Cloudflare Tunnel to avoid opening ports. Prefer a free option? DuckDNS gives you a free yourname.duckdns.org subdomain. Again — none of this is needed if you just use Tailscale.
9) (RECOMMENDED) Tailscale — reach everything from anywhere, no domain, no open ports
This is the main remote-access method for a personal setup. No domain, no port-forwarding, no proxy.
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
Install the Tailscale app on your phone/laptop, log in with the same account, and use the Pi’s 100.x.x.x address to reach any service remotely — fully encrypted, nothing exposed on your router. Example: http://100.x.x.x:8096 for Jellyfin from anywhere.
Want trusted HTTPS + nice hostnames without a domain? Turn on MagicDNS in the Tailscale admin, then let Tailscale terminate HTTPS for a service:
sudo tailscale serve --bg --https=443 localhost:8082 # e.g. Vaultwarden → https://your-pi.tailXXXX.ts.net
That gives Vaultwarden the HTTPS URL its browser extensions require — no domain, no Let’s Encrypt, no NPM.
10) Update & back up
# update any stack:
cd ~/stack && docker compose pull && docker compose up -d
cd ~/immich && docker compose pull && docker compose up -d
# back up (config + data). At minimum, save these off the Pi:
# ~/stack (all compose + app configs), ~/immich/.env,
# and your data on /mnt/ssd (nextcloud-data, immich, media)
Savings recap
Netflix ~15+Dropbox~12 + Google Photos ~3–10+passwordmanager~5 ≈ 40+/month(~500/yr). Hardware is a one-time ~$120–160. You trade a monthly bill for owning your data.
Questions? Drop them on the video — and tell me which app you want a full deep-dive on next.
Sources: Immich Docker Compose docs · Pi-hole Docker configuration

