Privacy-First Homelab
A complete self-hosted stack focused on privacy, ad blocking, and replacing cloud services with local alternatives. Every component is A3/T2 – fully autonomous and open source.
This recipe extends the Minimal Autonomous Server with password management, reverse proxy, cloud platform, and smart home automation.
Goal
Create a homelab that:
- Blocks ads and trackers for every device on your network
- Manages passwords without cloud dependency
- Replaces Google Drive / Dropbox with self-hosted file sync and cloud platform
- Provides HTTPS access to all services through a single reverse proxy
- Automates your home locally – no Alexa, no Google Home
- Backs up everything automatically
Components
| Component | Catalog Card | Role |
|---|---|---|
| Debian 12 | (OS) | Base operating system |
| Docker | compute/container | Container runtime |
| WireGuard | network/vpn | Secure remote access |
| Pi-hole | network/dns | Network-wide ad blocking |
| Nginx Proxy Manager | network/proxy | Reverse proxy with GUI |
| Vaultwarden | security/passwords | Password manager |
| Syncthing | storage/sync | P2P file sync |
| Nextcloud | applications/cloud | Cloud platform (files, calendar, contacts) |
| Kopia | storage/backup | Encrypted backups |
| Home Assistant | applications/automation | Smart home automation |
| Uptime Kuma | observability/monitoring | Monitoring dashboard |
All components are A3/T2.
Prerequisites
- A server or mini PC (Intel NUC, old laptop, Raspberry Pi 4+ with 4GB RAM minimum)
- Debian 12 installed
- Docker and Docker Compose installed (see Minimal Server recipe, steps 1-3)
- A local domain or IP address for accessing services
- External USB drive or NAS for backups (recommended)
Step 1: Directory structure
mkdir -p /opt/homelab/{config,data,backups}
cd /opt/homelab
Step 2: Docker Compose
Create docker-compose.yml in /opt/homelab/:
services:
# --- Network ---
pihole:
image: pihole/pihole:latest
container_name: pihole
ports:
- "53:53/tcp"
- "53:53/udp"
- "8053:80/tcp"
volumes:
- ./config/pihole/etc:/etc/pihole
- ./config/pihole/dnsmasq:/etc/dnsmasq.d
environment:
WEBPASSWORD: ${PIHOLE_PASSWORD}
restart: unless-stopped
npm:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- ./data/npm:/data
- ./data/npm-letsencrypt:/etc/letsencrypt
restart: unless-stopped
# --- Security ---
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
ports:
- "8222:80"
volumes:
- ./data/vaultwarden:/data
environment:
SIGNUPS_ALLOWED: "false"
ADMIN_TOKEN: ${VW_ADMIN_TOKEN}
restart: unless-stopped
# --- Storage ---
syncthing:
image: syncthing/syncthing:latest
container_name: syncthing
ports:
- "8384:8384"
- "22000:22000"
volumes:
- ./config/syncthing:/var/syncthing/config
- ./data/syncthing:/var/syncthing/data
restart: unless-stopped
nextcloud-db:
image: postgres:15
container_name: nextcloud-db
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${NC_DB_PASSWORD}
volumes:
- ./data/nextcloud-db:/var/lib/postgresql/data
restart: unless-stopped
nextcloud:
image: nextcloud:latest
container_name: nextcloud
ports:
- "8080:80"
volumes:
- ./data/nextcloud:/var/www/html
environment:
POSTGRES_HOST: nextcloud-db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${NC_DB_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NC_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD}
depends_on:
- nextcloud-db
restart: unless-stopped
# --- Automation ---
homeassistant:
image: homeassistant/home-assistant:latest
container_name: homeassistant
network_mode: host
volumes:
- ./config/homeassistant:/config
restart: unless-stopped
# --- Monitoring ---
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
ports:
- "3001:3001"
volumes:
- ./data/uptime-kuma:/app/data
restart: unless-stopped
Step 3: Environment file
Create .env in the same directory:
# Pi-hole
PIHOLE_PASSWORD=change-me
# Vaultwarden
VW_ADMIN_TOKEN=change-me-generate-with-openssl-rand-hex-32
# Nextcloud
NC_DB_PASSWORD=change-me-generate-random
NC_ADMIN_USER=admin
NC_ADMIN_PASSWORD=change-me-strong-password
Generate real secrets:
openssl rand -hex 32 # for VW_ADMIN_TOKEN
openssl rand -hex 16 # for NC_DB_PASSWORD
Step 4: Start everything
docker compose up -d
Step 5: Configure WireGuard
Follow the same WireGuard setup as the Minimal Server recipe, step 5. This gives you secure remote access to all services.
Step 6: Configure Nginx Proxy Manager
Open http://your-server:81. Default login: admin@example.com / changeme.
Add proxy hosts for each service:
| Service | Internal URL | Suggested subdomain |
|---|---|---|
| Pi-hole | http://pihole:80 | pihole.home.local |
| Vaultwarden | http://vaultwarden:80 | vault.home.local |
| Nextcloud | http://nextcloud:80 | cloud.home.local |
| Syncthing | http://syncthing:8384 | sync.home.local |
| Uptime Kuma | http://uptime-kuma:3001 | status.home.local |
| Home Assistant | http://localhost:8123 | ha.home.local |
For local-only access, use self-signed certificates or mkcert. For external access, enable Let’s Encrypt in NPM.
Step 7: Configure Pi-hole as network DNS
Point your router’s DNS to your server’s IP address. All devices on the network will now use Pi-hole for DNS resolution and ad blocking.
If you can’t change router DNS, configure individual devices to use your server as DNS.
Step 8: Set up Vaultwarden
Open https://vault.home.local (or the IP with port 8222).
- Create your account (signups are disabled after first user by default).
- Install the Bitwarden browser extension and mobile app.
- Point them to your Vaultwarden URL.
- Import existing passwords from your current password manager.
Step 9: Set up Nextcloud
Open https://cloud.home.local (or the IP with port 8080). Log in with the admin credentials from .env.
Install recommended apps: Calendar, Contacts, Notes, Talk. Install the Nextcloud desktop and mobile sync clients and point them to your server.
Step 10: Configure backups with Kopia
# Install Kopia
wget https://github.com/kopia/kopia/releases/latest/download/kopia-linux-amd64.tar.gz
tar -xzf kopia-linux-amd64.tar.gz
sudo mv kopia /usr/local/bin/
# Create repository on external drive
sudo kopia repository create filesystem --path /mnt/backup
# Backup everything
sudo kopia snapshot create /opt/homelab
# Set up daily cron
sudo crontab -e
# Add: 0 3 * * * /usr/local/bin/kopia snapshot create /opt/homelab
Step 11: Pause and resume
# Pause everything
cd /opt/homelab
docker compose stop
# Resume
docker compose start
Verification
Run through the three structural criteria:
Pause. Stop all services with docker compose stop. Verify they stop cleanly. Resume with docker compose start. Nothing should be lost.
Exit. All data is in /opt/homelab/config/ and /opt/homelab/data/. Copy these directories to another machine, run docker compose up -d, and you have the same stack. Passwords export via Bitwarden clients. Nextcloud files are ordinary files. No cloud lock-in anywhere.
Recoverability. Restore a Kopia snapshot to verify backups work:
sudo kopia snapshot list
sudo kopia restore <snapshot-id> /tmp/homelab-test
What you replaced
| Cloud service | What you had | What you have now | Autonomy change |
|---|---|---|---|
| Google DNS / ISP DNS | No filtering, tracked | Pi-hole – filtered, private | A0 to A3 |
| LastPass / 1Password | Cloud passwords | Vaultwarden – local passwords | A0 to A3 |
| Google Drive / Dropbox | Cloud files | Syncthing + Nextcloud – local files | A0 to A3 |
| Google Home / Alexa | Cloud automation | Home Assistant – local automation | A0 to A3 |
| No monitoring | Hope nothing breaks | Uptime Kuma – you’ll know | – to A3 |
Next steps
- Add Immich for photo backup (replace Google Photos)
- Add Jellyfin for media streaming (replace Plex)
- Add Paperless-ngx for document management
- Add Forgejo for Git hosting
- Add Authentik for single sign-on across all services
- Set up Grafana + Prometheus for advanced monitoring
This stack achieves A3/T2 across all components. Every service can be paused, exited, and recovered. Every piece of data is yours. If full transparency about how any of these tools work would make you stop using them – they would fail their own seventh question. They don’t.