Raspberry Pi Home Server: NAS, VPN, Pi-hole, and Docker in One Box
Turn a Raspberry Pi 4 into a powerful self-hosted home server running a NAS, network-wide ad blocker, VPN, and all your favourite services in Docker -- for under $80.
Contents
Hardware Requirements
| Component | Recommended | Notes |
|---|---|---|
| Board | Raspberry Pi 4 (4GB or 8GB) | Pi 5 also works and is faster |
| Boot drive | 32GB+ quality microSD or USB SSD | SSD strongly recommended -- SD cards fail under write load |
| Storage | 1-4 TB USB 3.0 HDD or SSD | USB-powered HDD needs a powered hub |
| Power supply | Official 5V/3A USB-C PSU | Cheap PSUs cause instability |
| Case | Argon ONE or similar with cooling | Active cooling critical for sustained loads |
| Network | Wired Ethernet (not WiFi) | WiFi adds latency and instability to NAS |
OS Setup and Hardening
# 1. Flash Raspberry Pi OS Lite (64-bit) using Raspberry Pi Imager
# Enable SSH and set hostname/username in Imager's advanced settings
# 2. First login -- update everything
sudo apt update && sudo apt full-upgrade -y
# 3. Set static IP (edit /etc/dhcpcd.conf)
sudo nano /etc/dhcpcd.conf
# Add to /etc/dhcpcd.conf:
interface eth0
static ip_address=192.168.1.10/24
static routers=192.168.1.1
static domain_name_servers=127.0.0.1 1.1.1.1 # Pi-hole first
# 4. Secure SSH -- disable password login, use keys only
ssh-keygen -t ed25519 -C "pi-homeserver"
ssh-copy-id pi@192.168.1.10
sudo nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
# Set: PermitRootLogin no
# Set: PubkeyAuthentication yes
sudo systemctl restart sshd
# 5. Enable UFW firewall
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 53 # DNS (Pi-hole)
sudo ufw allow 51820/udp # WireGuard VPN
sudo ufw enable
# 6. Set timezone
sudo timedatectl set-timezone Asia/Kolkata # change to your TZ
# 7. Enable automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
External Storage and Automount
# Identify your USB drive
lsblk
# Format as ext4 (do this ONCE -- erases the drive)
sudo mkfs.ext4 -L NASHDD /dev/sda1
# Create mount point
sudo mkdir -p /mnt/nas
# Get UUID for reliable mounting
sudo blkid /dev/sda1
# -> UUID="a1b2c3d4-..."
# Add to /etc/fstab for automount on boot
echo 'UUID=a1b2c3d4-xxxx /mnt/nas ext4 defaults,nofail,noatime 0 2' \
| sudo tee -a /etc/fstab
# Mount now
sudo mount -a
df -h /mnt/nas
# Set permissions
sudo chown -R $USER:$USER /mnt/nas
sudo chmod 775 /mnt/nas
Docker and Portainer
# Install Docker (official script)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker run hello-world
# Install Portainer (web UI for Docker management)
docker volume create portainer_data
docker run -d \
--name portainer \
--restart=always \
-p 9443:9443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
# Access at https://192.168.1.10:9443
Docker Compose Base Stack
# /opt/homeserver/docker-compose.yml
version: "3.9"
networks:
homelab:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
volumes:
pihole_config:
pihole_dnsmasq:
samba_config:
grafana_data:
prometheus_data:
Pi-hole: Network-Wide Ad Blocking
# Add to docker-compose.yml
services:
pihole:
image: pihole/pihole:latest
container_name: pihole
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "8080:80" # admin UI
environment:
TZ: Asia/Kolkata
WEBPASSWORD: "your-strong-password"
PIHOLE_DNS_: "1.1.1.1;8.8.8.8"
DNSSEC: "true"
volumes:
- pihole_config:/etc/pihole
- pihole_dnsmasq:/etc/dnsmasq.d
networks:
homelab:
ipv4_address: 172.20.0.2
# After starting -- point your router's DNS to 192.168.1.10
# Or set per-device DNS to 192.168.1.10
# Recommended blocklists to add in Pi-hole admin:
# https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
# https://adaway.org/hosts.txt
# https://v.firebog.net/hosts/AdguardDNS.txt
docker compose up -d pihole
# Access admin: http://192.168.1.10:8080/admin
Samba NAS
# Add to docker-compose.yml
samba:
image: dperson/samba:latest
container_name: samba
restart: unless-stopped
ports:
- "139:139"
- "445:445"
volumes:
- /mnt/nas:/share
command: >
-u "pi;password123"
-s "NAS;/share;yes;no;no;pi"
-g "log level = 1"
networks:
- homelab
# Windows: map network drive -> \\192.168.1.10\NAS
# macOS: Finder -> Go -> Connect to Server -> smb://192.168.1.10/NAS
# Linux: mount -t cifs //192.168.1.10/NAS /mnt/pi-nas -o user=pi
WireGuard VPN -- Access Home from Anywhere
# Use wg-easy -- the simplest WireGuard setup
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: wg-easy
restart: unless-stopped
ports:
- "51820:51820/udp"
- "51821:51821/tcp" # web UI
environment:
WG_HOST: "your.public.ip.or.ddns.hostname"
PASSWORD: "your-admin-password"
WG_DEFAULT_DNS: "172.20.0.2" # route DNS through Pi-hole!
WG_ALLOWED_IPS: "0.0.0.0/0" # route ALL traffic through VPN
volumes:
- /opt/homeserver/wireguard:/etc/wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
networks:
- homelab
# Access admin UI: http://192.168.1.10:51821
# Create a client -> download QR code -> scan with WireGuard mobile app
# Now your phone has full access to your home network from anywhere
Monitoring with Grafana + Prometheus
# Add to docker-compose.yml
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
networks: [homelab]
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command: >
--path.procfs=/host/proc
--path.rootfs=/rootfs
--path.sysfs=/host/sys
--collector.filesystem.mount-points-exclude='^/(sys|proc|dev|host|etc)($$|/)'
networks: [homelab]
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "your-password"
volumes:
- grafana_data:/var/lib/grafana
networks: [homelab]
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "node"
static_configs:
- targets: ["node-exporter:9100"]
# Start everything
cd /opt/homeserver
docker compose up -d
# Import Grafana dashboard for Pi monitoring:
# Grafana -> Import -> Dashboard ID: 1860 (Node Exporter Full)
# Access: http://192.168.1.10:3000
Keeping Everything Updated
# Install Watchtower to auto-update Docker images
docker run -d \
--name watchtower \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower \
--schedule "0 0 4 * * *" # update at 4 AM daily
[YES] Boot from USB SSD: Use Raspberry Pi Imager to flash the OS to a USB SSD instead of a microSD. Then in
raspi-config -> Advanced -> Boot Order, set USB boot priority. SSD gives 5-10x better I/O performance and dramatically better longevity for a 24/7 server.Key Takeaways: Use a quality SSD, not microSD, for 24/7 server reliability. Hardwire Ethernet -- never run a NAS on WiFi. Deploy everything in Docker Compose for easy management and updates. Route VPN clients through Pi-hole DNS for ad-blocking on mobile too.
Raspberry PiHome ServerNASPi-holeWireGuardDockerSelf-Hosted
0 Comments