Files
ServerInfrastructure/README.md
Alexander Doerflinger 2ffcbb5f60 Initial commit
2026-02-20 09:10:27 +01:00

21 KiB

🏠 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
  2. Prerequisites — Ubuntu 24.04 Setup
  3. IONOS DynDNS Setup
  4. FritzBox Port Forwarding
  5. Project Setup
  6. Launch the Stack
  7. Nginx Proxy Manager Configuration
  8. Service-Specific Setup
  9. WireGuard Client Setup
  10. Backup & Restore
  11. Migration to New Hardware
  12. Troubleshooting
  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

sudo apt update && sudo apt upgrade -y

2.2 Install Essential Packages

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.

# 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

sudo usermod -aG docker $USER
newgrp docker

Verify the installation:

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:

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:

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

# 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.

# 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

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.

# Edit the root crontab
sudo crontab -e

Add this line at the bottom:

*/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

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

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

# If using git
git clone <your-repo-url> ~/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

cp .env.example .env

Edit .env with your preferred editor:

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

chmod +x scripts/backup.sh scripts/restore.sh

6. Launch the Stack

6.1 Start All Services

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

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)

# 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://<laptop-local-ip>: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.comha.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://<laptop-ip>: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:

# 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:

# 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 on Play Store
iOS WireGuard on App Store
Windows WireGuard official installer
macOS WireGuard 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 <peername>
  • Desktop: Copy the peer_<name>.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

# 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:

# 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

# 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:

    docker compose down
    ./scripts/backup.sh
    
  2. Copy to new server:

    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:

    # 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

docker compose logs <service-name>

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.

# 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

# 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

# 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/<backup-file>.tar.gz
docker compose up -d