← Self-Hosting · Foundations 1 of 8 · Replaces: renting cloud services
What You'll Build
Every self-hosted app on this site runs on one small, always-on Linux computer in your home — your home server. This guide sets it up once, properly, so every later guide is just "create a folder, paste a file, run one command".
By the end you'll have:
- A server running Ubuntu Server LTS with SSH access
- A fixed (static) local IP address, so apps are always at the same address
- Docker and Docker Compose installed
- A tidy folder layout and a firewall
Time: 1–2 hours · Difficulty: Medium
Step 1: Choose Your Hardware
| Option | Good for | Watch out for |
|---|---|---|
| Old laptop / desktop | Free, lots of RAM and storage. A laptop's battery acts as a built-in UPS. | Power use (desktops can draw 40–80 W all day). |
| Mini PC (Intel N100/N150 class) | The sweet spot: quiet, ~6–15 W, fast enough for 20+ apps, hardware video transcoding for Jellyfin. | Usually one drive bay — add a USB drive for bulk storage. |
| Raspberry Pi 5 (8 GB) | Tiny and very low power. Great for Pi-hole, Vaultwarden, Navidrome, Home Assistant. | Struggles with Immich machine learning and video transcoding. Boot from an SSD, not the SD card. |
Minimum for the full Self-Hosting section: 4 CPU cores, 16 GB RAM, a 500 GB+ SSD for apps, and a separate larger drive for photos and media. 8 GB RAM works if you skip Immich's AI features.
Step 2: Install Ubuntu Server LTS
Ubuntu Server has no desktop, so all the memory goes to your apps. (Debian works identically if you prefer it.)
- Download Ubuntu Server LTS from ubuntu.com/download/server and write it to a USB stick — see USB installation. (Raspberry Pi: use Raspberry Pi Imager → Other general-purpose OS → Ubuntu Server, or Raspberry Pi OS Lite 64-bit.)
- Boot the server from the USB stick and follow the installer:
- Network: plug in Ethernet. Leave it on DHCP for now.
- Storage: "Use an entire disk" on your SSD. Untick LVM only if you know you don't want it.
- Profile: server name
homeserver, your username, a strong password. - SSH: tick Install OpenSSH server. If you have a GitHub account with SSH keys, you can import them here.
- Featured snaps: skip them all — especially the Docker snap. We'll install Docker properly in Step 4.
- Reboot, remove the USB stick, and log in from your own computer:
# From your laptop/desktop
ssh yourname@homeserver.local # or ssh yourname@<the IP shown on the server's screen>
# First job on any new server: update everything
sudo apt update && sudo apt full-upgrade -y
Step 3: Give the Server a Fixed IP Address
Your phone and apps need to find the server at the same address forever. The easiest, safest way is a DHCP reservation in your router:
- Find the server's current IP and its network card's MAC address:
ip -brief address # e.g. enp1s0 UP 192.168.1.50/24 ip link # the "link/ether" value is the MAC address - Log in to your router (often
192.168.1.1or192.168.0.1), find DHCP reservations / Static leases / Address reservation, and reserve that IP for that MAC address. - Reboot the server and confirm it gets the same IP.
Throughout the self-hosting guides we'll use 192.168.1.50 as the server's address. Replace it with yours everywhere.
Step 4: Install Docker and Docker Compose
Docker runs each app in its own sealed "container" with everything it needs. Docker Compose lets you describe a container in a small text file (compose.yaml) instead of long commands.
# Docker's official install script (adds Docker's own apt repository)
curl -fsSL https://get.docker.com | sudo sh
# Let your user run docker without sudo
sudo usermod -aG docker $USER
# Log out and back in for the group change to apply
exit
ssh yourname@homeserver.local
# Check both work
docker --version
docker compose version
docker run --rm hello-world
Security note: being in the docker group is effectively root access. Only add users you trust with the whole server.
Step 5: The Folder Layout Every Guide Uses
# One folder per app, all under ~/stacks
mkdir -p ~/stacks
~/stacks/
├── portainer/
│ └── compose.yaml
├── immich/
│ ├── compose.yaml
│ └── .env ← secrets and settings, never shared
└── vaultwarden/
├── compose.yaml
└── data/ ← the app's saved data (back this up!)
And the four commands you'll use for every app, always run from inside that app's folder:
| Command | What it does |
|---|---|
docker compose up -d | Create/start the app in the background (also applies any changes you made to compose.yaml). |
docker compose logs -f | Watch the app's log output (Ctrl+C to stop watching). |
docker compose pull && docker compose up -d | Update the app to the newest image. |
docker compose down | Stop and remove the containers (your data folders are kept). |
Step 6: Basic Security
# Firewall: allow SSH, then turn it on
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
# Automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Docker and UFW: ports published by Docker (ports: in compose files) bypass UFW's rules. That's fine on a home network behind a router, but it means never forward ports on your router except the ones a guide explicitly tells you to. For remote access, use Tailscale.
Then work through First-Time Server Hardening for SSH keys and fail2ban.
Troubleshooting
homeserver.localdoesn't resolve — use the IP address instead, or install mDNS:sudo apt install avahi-daemon.- "permission denied while trying to connect to the Docker daemon socket" — you haven't logged out and back in since
usermod. Or runnewgrp docker. - Installed the Docker snap by mistake —
sudo snap remove docker, then run Step 4. - Server's IP changed — the DHCP reservation didn't save, or it's reserved for the Wi-Fi MAC instead of the Ethernet one.
What's Next?
Manage your containers from a web page instead of the terminal:
Foundations 2: Portainer →