Set Up a Raspberry Pi as a Home Server for Self-Hosting Everything
A Raspberry Pi is one of the best investments you can make if you want to self-host services at home. For under $100, you get a silent, low-power computer that can run 24/7, serving as a file server, media center, ad blocker, VPN endpoint, and much more — all without the electricity bill of a full-size server.
This guide walks you through setting up a Raspberry Pi 4 or 5 as a proper home server with Docker, so you can easily deploy and manage dozens of services.
What You Need
| Component | Recommendation | Approx. Cost |
|---|---|---|
| Raspberry Pi 4 (4GB) or Pi 5 (8GB) | Pi 5 preferred for better performance | $60–$80 |
| MicroSD card (32GB+) or USB SSD | SSD strongly recommended for reliability | $15–$40 |
| USB-C power supply (5V 3A / 5V 5A for Pi 5) | Official Raspberry Pi PSU | $10 |
| Ethernet cable | Cat5e or Cat6 | $5 |
| Case with passive cooling | Argon ONE or Flirc | $15–$25 |
Total: ~$100–$170
An SSD connected via USB 3.0 dramatically improves performance and longevity over a microSD card. For the Pi 5, an NVMe HAT with an M.2 drive is the best option.
Step 1: Install Raspberry Pi OS
Download and install the Raspberry Pi Imager from raspberrypi.com/software.
- Insert your microSD card (or USB SSD) into your computer
- Open Raspberry Pi Imager
- Click Choose OS → Raspberry Pi OS (other) → Raspberry Pi OS Lite (64-bit)
- Click Choose Storage and select your card/drive
- Click the gear icon (⚙️) to configure:
- Set hostname:
piserver - Enable SSH (use password authentication)
- Set username and password
- Configure Wi-Fi (optional — Ethernet is preferred)
- Set locale settings
- Set hostname:
- Click Write
Raspberry Pi OS Lite is headless (no desktop environment), which saves resources for server tasks.
Step 2: First Boot and SSH Access
- Insert the card/drive into the Pi and plug in Ethernet + power
- Wait 1–2 minutes for it to boot
- Find the Pi’s IP address from your router’s admin panel, or use:
# From another machine on the same network
ping piserver.local
- SSH into the Pi:
ssh youruser@piserver.local
Step 3: Initial System Configuration
Update the system and install essentials:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git vim htop
Set a Static IP Address
Edit the network configuration so your Pi always gets the same IP:
sudo nmcli con mod "Wired connection 1" \
ipv4.addresses 192.168.1.100/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns "1.1.1.1,8.8.8.8" \
ipv4.method manual
sudo nmcli con up "Wired connection 1"
Replace the IP addresses with values appropriate for your network.
Enable Automatic Security Updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Step 4: Boot from USB SSD (Recommended)
If you’re using a USB SSD alongside a microSD card, you can boot directly from the SSD for much better performance:
- Flash Raspberry Pi OS to the SSD using Raspberry Pi Imager
- Update the Pi’s bootloader to prefer USB boot:
sudo raspi-config
Navigate to Advanced Options → Boot Order → USB Boot.
- Shut down, remove the microSD card, and power on — the Pi should boot from the SSD.
Step 5: Install Docker
Docker makes it simple to run isolated services without dependency conflicts:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Log out and back in, then verify:
docker --version
docker run hello-world
Install Docker Compose
Docker Compose comes with modern Docker installations. Verify:
docker compose version
Step 6: Create a Docker Compose Stack
Create a directory for your server configuration:
mkdir -p ~/server && cd ~/server
Create docker-compose.yml:
version: "3.8"
services:
# 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/tcp"
environment:
TZ: "Asia/Kolkata"
WEBPASSWORD: "your-secure-password"
volumes:
- ./pihole/etc:/etc/pihole
- ./pihole/dnsmasq:/etc/dnsmasq.d
dns:
- 127.0.0.1
- 1.1.1.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
# 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"
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
# File Browser: Web-based file manager
filebrowser:
image: filebrowser/filebrowser:latest
container_name: filebrowser
restart: unless-stopped
ports:
- "8082:80"
volumes:
- /home:/srv
- ./filebrowser/database.db:/database.db
environment:
PUID: 1000
PGID: 1000
# Uptime Kuma: Service monitoring
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./uptime-kuma:/app/data
Start everything:
docker compose up -d
Step 7: Access Your Services
After the containers start, access them from any device on your network:
| Service | URL | Purpose |
|---|---|---|
| Pi-hole | http://192.168.1.100:8080/admin | Ad blocking DNS |
| Portainer | https://192.168.1.100:9443 | Docker management |
| Nginx Proxy Manager | http://192.168.1.100:81 | Reverse proxy + SSL |
| File Browser | http://192.168.1.100:8082 | Web file manager |
| Uptime Kuma | http://192.168.1.100:3001 | Uptime monitoring |
Pi-hole Setup
After logging into Pi-hole, set your router’s DNS to 192.168.1.100 so all devices on your network get ad blocking automatically. Alternatively, configure individual devices to use the Pi’s IP as their DNS server.
More Services to Add
Once your base stack is running, you can add more services to docker-compose.yml:
Jellyfin (Media Server)
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
ports:
- "8096:8096"
volumes:
- ./jellyfin/config:/config
- ./jellyfin/cache:/cache
- /media:/media
Nextcloud (Cloud Storage)
nextcloud:
image: nextcloud:latest
container_name: nextcloud
restart: unless-stopped
ports:
- "8083:80"
volumes:
- ./nextcloud:/var/www/html
environment:
SQLITE_DATABASE: nextcloud
Home Assistant (Smart Home)
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
restart: unless-stopped
network_mode: host
volumes:
- ./homeassistant:/config
environment:
TZ: "Asia/Kolkata"
WireGuard (VPN Server)
wireguard:
image: linuxserver/wireguard:latest
container_name: wireguard
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "51820:51820/udp"
environment:
PUID: 1000
PGID: 1000
TZ: "Asia/Kolkata"
SERVERURL: your.ddns.domain
PEERS: 3
volumes:
- ./wireguard:/config
Step 8: External Access with Cloudflare Tunnels
To access your services from outside your home without opening router ports, use Cloudflare Tunnels:
docker run -d \
--name cloudflared \
--restart unless-stopped \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
Get your tunnel token from one.dash.cloudflare.com → Networks → Tunnels.
Maintenance and Monitoring
Update All Containers
cd ~/server
docker compose pull
docker compose up -d
Monitor Resource Usage
# System overview
htop
# Docker container stats
docker stats
# Disk usage
df -h
# Temperature (important for Pi!)
vcgencmd measure_temp
Automatic Container Updates with Watchtower
Add this to your docker-compose.yml:
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 0 4 * * *"
This automatically updates containers daily at 4 AM.
Backup Strategy
Create a simple backup script at ~/backup.sh:
#!/bin/bash
BACKUP_DIR="/media/backup/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
cd ~/server
docker compose down
tar -czf "$BACKUP_DIR/server-data.tar.gz" .
docker compose up -d
echo "Backup completed: $BACKUP_DIR"
Schedule it with cron:
crontab -e
# Add: 0 3 * * 0 /home/youruser/backup.sh
Performance Tips
- Use an SSD — The single biggest performance improvement over microSD
- Allocate swap space — Even with 8GB RAM, swap prevents OOM kills:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab - Overclock the Pi 5 — Add to
/boot/firmware/config.txt:arm_freq=2800 gpu_freq=900 - Monitor temperature — Keep it below 80°C with proper cooling
- Use arm64 images — Ensure Docker images support
linux/arm64
Security Hardening
- Change the default SSH port:
sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config sudo systemctl restart ssh - Install fail2ban to block brute-force attacks:
sudo apt install -y fail2ban sudo systemctl enable fail2ban - Set up a firewall with UFW:
sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 2222/tcp # SSH sudo ufw allow 53 # DNS (Pi-hole) sudo ufw allow 80,443/tcp # Web sudo ufw enable - Use SSH keys instead of passwords (see our SSH key authentication guide)
- Keep the system updated with unattended-upgrades
Conclusion
A Raspberry Pi home server gives you complete control over your data and services. With Docker Compose, you can deploy and manage dozens of self-hosted applications with minimal effort. Whether you’re blocking ads network-wide with Pi-hole, streaming media with Jellyfin, or running your own cloud storage with Nextcloud, the Pi handles it all while consuming just 5–15 watts of power.