# 🏠 Home Server β€” Docker Compose Stack Self-hosted infrastructure running **Home Assistant**, **Gitea**, and **WireGuard VPN** behind **Nginx Proxy Manager** with **Let's Encrypt SSL** and **IONOS DynDNS**. | Service | Domain | Internal Port | Purpose | |---------|--------|--------------|---------| | **Nginx Proxy Manager** | β€” (admin: `:81`) | 80, 443, 81 | Reverse proxy + SSL | | **Home Assistant** | `ha.doerflingers.com` | 8123 | Smart home | | **Gitea** | `git.doerflingers.com` | 3000 (web), 22 (SSH) | Git hosting | | **WireGuard** | `home.doerflingers.com` | 51820/UDP | VPN | --- ## Table of Contents 1. [Hardware Requirements](#1-hardware-requirements) 2. [Prerequisites β€” Ubuntu 24.04 Setup](#2-prerequisites--ubuntu-2404-setup) 3. [IONOS DynDNS Setup](#3-ionos-dyndns-setup) 4. [FritzBox Port Forwarding](#4-fritzbox-port-forwarding) 5. [Project Setup](#5-project-setup) 6. [Launch the Stack](#6-launch-the-stack) 7. [Nginx Proxy Manager Configuration](#7-nginx-proxy-manager-configuration) 8. [Service-Specific Setup](#8-service-specific-setup) 9. [WireGuard Client Setup](#9-wireguard-client-setup) 10. [Backup & Restore](#10-backup--restore) 11. [Migration to New Hardware](#11-migration-to-new-hardware) 12. [Troubleshooting](#12-troubleshooting) 13. [Architecture Overview](#13-architecture-overview) --- ## 1. Hardware Requirements This stack is designed to run on modest hardware. Here's the assessment for the target laptop: | Resource | Available | Used (est.) | Status | |----------|-----------|-------------|--------| | CPU | i5-3210M (2C/4T, 2.5 GHz) | ~5-15% idle | βœ… | | RAM | 3.7 GB + 3.7 GB Swap | ~1.3 GB | βœ… | | Disk | 500 GB HDD | ~5 GB base + data | βœ… | > **All services coexist without interference.** HTTP traffic is multiplexed by hostname through the reverse proxy, WireGuard uses a separate UDP port. --- ## 2. Prerequisites β€” Ubuntu 24.04 Setup Since this is a fresh Ubuntu 24.04 install, we need to install all required packages from scratch. ### 2.1 System Update ```bash sudo apt update && sudo apt upgrade -y ``` ### 2.2 Install Essential Packages ```bash sudo apt install -y \ ca-certificates \ curl \ gnupg \ lsb-release \ python3 \ python3-pip \ python3-venv \ git \ cron ``` ### 2.3 Install Docker Engine Follow the official Docker installation for Ubuntu. Do **not** use the `docker.io` snap package. ```bash # Add Docker's official GPG key sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${VERSION_CODENAME}") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker Engine + Compose plugin sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` ### 2.4 Post-Installation β€” Run Docker Without `sudo` ```bash sudo usermod -aG docker $USER newgrp docker ``` Verify the installation: ```bash docker --version docker compose version ``` ### 2.5 Prevent Laptop Suspend on Lid Close Since this is a laptop acting as a server, you want it to stay on when the lid is closed: ```bash sudo sed -i 's/#HandleLidSwitch=suspend/HandleLidSwitch=ignore/' /etc/systemd/logind.conf sudo sed -i 's/#HandleLidSwitchExternalPower=suspend/HandleLidSwitchExternalPower=ignore/' /etc/systemd/logind.conf sudo systemctl restart systemd-logind ``` ### 2.6 (Optional) Enable WireGuard Kernel Module WireGuard often needs the kernel module pre-loaded: ```bash sudo apt install -y wireguard-tools sudo modprobe wireguard # Make it persistent across reboots echo "wireguard" | sudo tee /etc/modules-load.d/wireguard.conf ``` --- ## 3. IONOS DynDNS Setup Your domains are registered at IONOS. Since your home internet has a dynamic IP address, you need DynDNS to keep the DNS records updated automatically. ### 3.1 Install the IONOS DynDNS Client ```bash # Create a virtual environment (required on Ubuntu 24.04 which uses PEP 668) sudo python3 -m venv /opt/dyndns-venv sudo /opt/dyndns-venv/bin/pip install domain-connect-dyndns ``` ### 3.2 Register Each Domain You need to run the setup command **once per domain/subdomain**. Each will give you a URL to open in your browser to authorize IONOS. ```bash # Domain for Home Assistant sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain ha.doerflingers.com # β†’ A URL is printed. Open it in your browser. # β†’ Log in to IONOS, click "Erlauben" (Allow). # β†’ Copy the code shown and paste it back into the terminal. # Domain for Gitea sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain git.doerflingers.com # β†’ Same process: open URL, authorize, paste code. # Domain for Home / VPN sudo /opt/dyndns-venv/bin/domain-connect-dyndns setup --domain home.doerflingers.com # β†’ Same process: open URL, authorize, paste code. ``` ### 3.3 Test the Update ```bash sudo /opt/dyndns-venv/bin/domain-connect-dyndns update --all ``` You should see output like: ``` Read ha.doerflingers.com config. IP x.x.x.x found in A record New IP: x.x.x.x A record up to date. ``` ### 3.4 Set Up Automatic Updates via Cron The cron job runs every 5 minutes to check for IP changes and update DNS if needed. ```bash # Edit the root crontab sudo crontab -e ``` Add this line at the bottom: ```cron */5 * * * * /usr/bin/flock -n /tmp/dyndns-update.lck /opt/dyndns-venv/bin/domain-connect-dyndns update --all >> /var/log/dyndns-update.log 2>&1 ``` > **Note:** We use every 5 minutes instead of every 1 minute β€” it's unlikely your IP changes more frequently, and it reduces system load. The `flock` prevents overlapping runs. ### 3.5 Verify Cron is Running ```bash sudo systemctl enable cron sudo systemctl start cron # Check it's scheduled sudo crontab -l ``` --- ## 4. FritzBox Port Forwarding You need to forward specific ports from your FritzBox router to the laptop. ### 4.1 Find the Laptop's Local IP Address ```bash ip addr show | grep "inet " | grep -v 127.0.0.1 ``` Note the IP address (e.g., `192.168.178.XX`). > **Tip:** In FritzBox, go to **Heimnetz β†’ Netzwerk β†’ [Your Laptop]** and enable **"Diesem NetzwerkgerΓ€t immer die gleiche IPv4-Adresse zuweisen"** (always assign the same IP) to prevent the IP from changing. ### 4.2 Configure Port Forwarding In the FritzBox admin UI (`http://fritz.box`), go to **Internet β†’ Freigaben β†’ Portfreigaben** and add the following rules: | Service | Protocol | External Port | Internal Port | Internal IP | Required | |---------|----------|---------------|---------------|-------------|----------| | HTTP | TCP | 80 | 80 | 192.168.178.XX | βœ… Yes (Let's Encrypt HTTP-01 challenge) | | HTTPS | TCP | 443 | 443 | 192.168.178.XX | βœ… Yes (all web services) | | WireGuard | UDP | 51820 | 51820 | 192.168.178.XX | βœ… Yes (VPN) | | Gitea SSH | TCP | 2222 | 2222 | 192.168.178.XX | ⚑ Optional (for `git clone` via SSH) | > **⚠️ Security Note:** Do **NOT** forward port 81. The Nginx Proxy Manager admin UI should only be accessible from your local network (or via WireGuard VPN). --- ## 5. Project Setup ### 5.1 Clone or Copy This Repository ```bash # If using git git clone ~/home-server cd ~/home-server # Or simply copy the files to your laptop # and navigate to the directory cd ~/home-server ``` ### 5.2 Create Your Environment File ```bash cp .env.example .env ``` Edit `.env` with your preferred editor: ```bash nano .env ``` **Required changes:** | Variable | What to set | |----------|-------------| | `TZ` | Your timezone (default: `Europe/Berlin`) | | `WG_SERVERURL` | `home.doerflingers.com` (already set) | | `WG_PEERS` | Comma-separated list of your VPN client names | | `GITEA_SECRET_KEY` | Run `openssl rand -hex 32` and paste result | | `GITEA_INTERNAL_TOKEN` | Run `openssl rand -hex 32` and paste result | ### 5.3 Set Script Permissions ```bash chmod +x scripts/backup.sh scripts/restore.sh ``` --- ## 6. Launch the Stack ### 6.1 Start All Services ```bash docker compose up -d ``` This will pull all images (first run may take 5-10 minutes on a slow connection) and start the containers. ### 6.2 Verify Everything is Running ```bash docker compose ps ``` Expected output: ``` NAME STATUS PORTS gitea Up 0.0.0.0:2222->22/tcp, 3000/tcp homeassistant Up 8123/tcp nginx-proxy-manager Up (healthy) 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:81->81/tcp wireguard Up 0.0.0.0:51820->51820/udp ``` ### 6.3 Check Logs (if something goes wrong) ```bash # All services docker compose logs -f # Specific service docker compose logs -f homeassistant docker compose logs -f gitea docker compose logs -f wireguard docker compose logs -f nginx-proxy-manager ``` --- ## 7. Nginx Proxy Manager Configuration ### 7.1 Access the Admin UI Open your browser and go to: ``` http://:81 ``` Default credentials: | Field | Value | |-------|-------| | Email | `admin@example.com` | | Password | `changeme` | > **You will be prompted to change these on first login. Do it immediately.** ### 7.2 Add Proxy Host for Home Assistant 1. Go to **Hosts β†’ Proxy Hosts β†’ Add Proxy Host** 2. Fill in the **Details** tab: | Field | Value | |-------|-------| | Domain Names | `ha.doerflingers.com` | | Scheme | `http` | | Forward Hostname / IP | `homeassistant` | | Forward Port | `8123` | | Websockets Support | βœ… **Enable** (required for HA) | 3. Go to the **SSL** tab: | Field | Value | |-------|-------| | SSL Certificate | Request a new SSL Certificate | | Force SSL | βœ… Enable | | HTTP/2 Support | βœ… Enable | | Email for Let's Encrypt | your-email@example.com | | Agree to ToS | βœ… Yes | 4. Click **Save**. ### 7.3 Add Proxy Host for Gitea Same process, with these values: | Field | Value | |-------|-------| | Domain Names | `git.doerflingers.com` | | Scheme | `http` | | Forward Hostname / IP | `gitea` | | Forward Port | `3000` | | Websockets Support | βœ… Enable | SSL tab: same as above (request new cert, force SSL, HTTP/2). ### 7.4 Add Proxy Host for Home / Landing Page (Optional) If you want `home.doerflingers.com` to point somewhere (e.g., a landing page or redirect to Home Assistant): | Field | Value | |-------|-------| | Domain Names | `home.doerflingers.com` | | Scheme | `http` | | Forward Hostname / IP | `homeassistant` | | Forward Port | `8123` | Or use a **Redirection Host** to redirect `home.doerflingers.com` β†’ `ha.doerflingers.com`. > **Note:** `home.doerflingers.com` is primarily used as the WireGuard server URL. It doesn't need a proxy host unless you want it to serve a web page too. --- ## 8. Service-Specific Setup ### 8.1 Home Assistant β€” First Launch 1. Navigate to `https://ha.doerflingers.com` (or `http://:8123` from LAN). 2. The onboarding wizard will guide you through: - Creating your admin account - Setting your home location - Discovering devices on your network 3. Home Assistant is ready to use! > **Note on device discovery:** In container mode (non-host network), some discovery protocols (mDNS, SSDP) may not work. You can add integrations manually via **Settings β†’ Devices & Services β†’ Add Integration**. ### 8.2 Gitea β€” First Launch 1. Navigate to `https://git.doerflingers.com`. 2. The initial configuration page will appear. Most settings are pre-configured via environment variables. Verify: | Setting | Value | |---------|-------| | Site Title | Your choice | | Repository Root Path | `/data/git/repositories` | | SQLite Database Path | `/data/gitea/gitea.db` | | SSH Server Domain | `git.doerflingers.com` | | SSH Port | `2222` | | Gitea Base URL | `https://git.doerflingers.com/` | 3. Create your admin account in the **Administrator Account Settings** section. 4. Click **Install Gitea**. --- ## 9. WireGuard Client Setup ### 9.1 Locate Client Configurations After the WireGuard container starts, it automatically generates config files for each peer defined in `WG_PEERS`: ```bash # List generated peer configs ls wireguard/config/peer_*/ # View a specific peer's config cat wireguard/config/peer_phone/peer_phone.conf ``` Each peer folder also contains a **QR code** image for easy mobile setup: ```bash # Display QR code in the terminal (for mobile scanning) docker exec wireguard /app/show-peer phone ``` ### 9.2 Install WireGuard on Your Client | Platform | App | |----------|-----| | **Android** | [WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android) on Play Store | | **iOS** | [WireGuard](https://apps.apple.com/app/wireguard/id1441195209) on App Store | | **Windows** | [WireGuard](https://www.wireguard.com/install/) official installer | | **macOS** | [WireGuard](https://apps.apple.com/app/wireguard/id1451685025) on App Store or `brew install wireguard-tools` | | **Linux** | `sudo apt install wireguard-tools` | ### 9.3 Import the Configuration - **Mobile:** Scan the QR code shown by `docker exec wireguard /app/show-peer ` - **Desktop:** Copy the `peer_.conf` file to the client and import it in the WireGuard app ### 9.4 Test the VPN Connection 1. Activate the WireGuard tunnel on your client. 2. Try accessing `https://ha.doerflingers.com` β€” it should work even from a mobile network. 3. You can also access the Nginx Proxy Manager admin UI at `http://192.168.178.XX:81` through the VPN. --- ## 10. Backup & Restore ### 10.1 Create a Backup ```bash # Backup all service data to ./backups/ ./scripts/backup.sh # Or specify a custom backup directory ./scripts/backup.sh /mnt/usb-drive/backups ``` The script creates a timestamped archive like `home-server-backup_20260220_080000.tar.gz`. > **Tip:** For automated backups, add a cron job: > ```cron > # Daily backup at 3 AM > 0 3 * * * /home/YOUR_USER/home-server/scripts/backup.sh /mnt/backup-drive/ >> /var/log/home-server-backup.log 2>&1 > ``` ### 10.2 Restore from Backup ```bash # Stop all services first! docker compose down # Restore ./scripts/restore.sh backups/home-server-backup_20260220_080000.tar.gz # Restart services docker compose up -d ``` --- ## 11. Migration to New Hardware Moving everything to a new server is straightforward thanks to Docker: 1. **On the old server:** ```bash docker compose down ./scripts/backup.sh ``` 2. **Copy to new server:** ```bash scp backups/home-server-backup_*.tar.gz user@new-server:/tmp/ scp -r . user@new-server:~/home-server/ # or just clone the repo ``` 3. **On the new server:** ```bash # Install prerequisites (Section 2 of this README) cd ~/home-server ./scripts/restore.sh /tmp/home-server-backup_*.tar.gz # Review .env and adjust if needed (e.g., new timezone) docker compose up -d ``` 4. **Update DynDNS** β€” Run the DynDNS setup again on the new server (Section 3). 5. **Update FritzBox** β€” If the new server has a different local IP, update the port forwarding rules (Section 4). --- ## 12. Troubleshooting ### Container won't start ```bash docker compose logs ``` ### Home Assistant shows `400 Bad Request` This means the reverse proxy is not trusted. Verify that `homeassistant/configuration.yaml` contains the correct `trusted_proxies` subnet (`172.20.0.0/24`) matching the Docker network in `docker-compose.yml`. ```bash # Find the actual Docker network subnet docker network inspect proxy-network | grep Subnet ``` ### Let's Encrypt certificate fails - **Port 80 must be forwarded** in FritzBox (HTTP-01 challenge requires it). - DNS for the domain must already point to your IP. Check: `dig +short ha.doerflingers.com` - Wait a few minutes after setting up DynDNS for DNS propagation. ### WireGuard clients can't connect - Verify port `51820/UDP` is forwarded in FritzBox. - Check if the WireGuard kernel module is loaded: `lsmod | grep wireguard` - View WireGuard logs: `docker compose logs wireguard` ### Gitea SSH clone doesn't work - Verify port `2222/TCP` is forwarded in FritzBox. - Clone syntax: `git clone ssh://git@git.doerflingers.com:2222/user/repo.git` ### High memory usage ```bash # Check per-container memory usage docker stats --no-stream ``` If running low on RAM, consider: - Reducing `WG_PEERS` count - Moving Gitea to a lighter database (SQLite is already the lightest) - Adding more swap: `sudo fallocate -l 4G /swapfile2 && sudo chmod 600 /swapfile2 && sudo mkswap /swapfile2 && sudo swapon /swapfile2` ### Laptop suspends when lid is closed See Section 2.5 β€” make sure `HandleLidSwitch=ignore` is set. --- ## 13. Architecture Overview ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ INTERNET β”‚ β”‚ β”‚ β”‚ ha.doerflingers.com ──┐ β”‚ β”‚ git.doerflingers.com ────→ Your Public IP (DynDNS-updated) β”‚ β”‚ home.doerflingers.com β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FritzBox β”‚ β”‚ Port Forwarding: β”‚ β”‚ 80 β†’ Laptop:80 β”‚ β”‚ 443 β†’ Laptop:443 β”‚ β”‚ 51820/UDP β†’ :51820 β”‚ β”‚ 2222 β†’ Laptop:2222 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Ubuntu 24.04 Laptop β”‚ β”‚ (Docker Host) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Docker Network β”‚ β”‚ (proxy-network) β”‚ β”‚ 172.20.0.0/24 β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Nginx Proxy Manager β”‚ β”‚ β”‚ β”‚ :80, :443, :81 β”‚ β”‚ β”‚ β”‚ SSL termination β”‚ β”‚ β”‚ β”‚ Let's Encrypt certs β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”˜ └────┐ β”‚ β”‚ β–Ό β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Home β”‚ β”‚ Gitea β”‚ β”‚ β”‚ β”‚ Assistantβ”‚ β”‚ :3000 β”‚ β”‚ β”‚ β”‚ :8123 β”‚ β”‚ :22β†’2222β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ WireGuard VPN β”‚ β”‚ β”‚ β”‚ :51820/UDP β”‚ β”‚ β”‚ β”‚ Subnet: 10.13.13.0/24 β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Data Persistence All service data is stored in local directories (bind mounts), making backups and migration simple: | Service | Data Directory | Contains | |---------|---------------|----------| | Nginx Proxy Manager | `./nginx-proxy-manager/data/` + `./nginx-proxy-manager/letsencrypt/` | Proxy configs, SSL certs | | Home Assistant | `./homeassistant/` | Configuration, automations, database | | Gitea | `./gitea/data/` | Repositories, database, config | | WireGuard | `./wireguard/config/` | Server + peer keys, configs | --- ## Quick Reference ```bash # Start all services docker compose up -d # Stop all services docker compose down # Restart a single service docker compose restart homeassistant # View logs docker compose logs -f # Check resource usage docker stats # Update all images to latest docker compose pull docker compose up -d # Backup ./scripts/backup.sh # Restore docker compose down ./scripts/restore.sh backups/.tar.gz docker compose up -d ```