Skip to content

Homelab Production Deployment & Operations Guide

Comprehensive guide for deploying, configuring, maintaining, and updating the homelab stack on your production Ubuntu Server (HP ProDesk 600 G6 Tower, Intel Core i5-10400, 32GB RAM).


1. System Architecture & The 4 Core Layers

To keep operations intuitive and maintenance frictionless, the homelab ecosystem is composed of four distinct tools, each with one specific responsibility:

Layer Technology Primary Responsibility
1. The Vault HP ProDesk Ubuntu Server Hosts persistent storage, media engines (Immich, Navidrome, Calibre-Web), and intake bots behind a zero-open-ports firewall.
2. The Secret Entrance Tailscale Mesh (100.x.y.z) Point-to-point encrypted WireGuard mesh connecting your phone and laptop directly to your server from anywhere with MagicDNS.
3. The Privacy Mask ProtonVPN (WireGuard) Masks your home IP address: strictly containerized on the server (gluetun) for torrents, and on client devices for web privacy.
4. The Name Tag & SSL Cloudflare (devsaddress.net) Provides human-friendly domain names and automated wildcard SSL certificates via DNS-01 API with zero open router ports.

Master 4-Stage Deployment Roadmap

STAGE 1: Laptop Preparation (Current Stage)
└── Verify client mesh (laptop & phone pinging)
└── (Optional) Link docs.devsaddress.net to Cloudflare Pages
└── Draft Gluetun + ProtonVPN compose configuration

STAGE 2: Server Hardware Day (When ProDesk Arrives)
└── Install Ubuntu Server on HP ProDesk
└── Join Tailscale mesh with 1-liner: sudo tailscale up --hostname=homelab --ssh
└── Verify remote terminal access from laptop: ssh dev@homelab

STAGE 3: Deploy the Media Stack
└── Clone repository & configure production .env
└── Provision directories: make prod-prep
└── Launch 24/7 stack: make up (Immich, Navidrome, Audiobookshelf, Calibre-Web, Bots)

STAGE 4: Domain SSL & Protected Torrents
└── Configure Cloudflare DNS API token in .env for *.devsaddress.net
└── Add ProtonVPN WireGuard credentials to Gluetun container
└── Verify torrent kill switch and IP masking (make vpn-ip)

2. Hardware & OS Prerequisites

Hardware Requirements

  • Platform: Ubuntu Server 24.04 / 22.04 LTS (x86_64).
  • Storage Devices:
  • Fast NVMe / SATA SSD: Hosts / (OS) and /srv/docker/ (container configs, SQLite/PostgreSQL databases, Valkey cache, AI model weights).
  • Bulk Storage: /srv/storage/ (media libraries, downloads, intake drop-chutes, restricted partition). Starts on SSD; later migrates to dedicated 3.5" HDD.
  • GPU: Intel UHD Graphics 630 (/dev/dri/renderD128) for hardware-accelerated video transcoding (QuickSync).

Host Package Prerequisites

Before cloning, ensure the host system has Docker, Docker Compose, Git, Python 3, and FFmpeg:

# 1. Update apt repositories
sudo apt update && sudo apt upgrade -y

# 2. Install core utilities & media tools
sudo apt install -y curl wget git nano ffmpeg python3 python3-venv

# 3. Install official Docker CE and Compose plugin
curl -fsSL https://get.docker.com | sh

# 4. Add your user to the docker group (avoids sudo for container commands)
sudo usermod -aG docker $USER

# 5. Install Tailscale & join mesh
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --hostname=homelab --advertise-exit-node --ssh

[!IMPORTANT] Group Membership Refresh: After running usermod -aG docker $USER, log out and SSH back in for group permissions to take effect. Verify with docker ps without sudo.


3. Initial Production Bootstrap

Follow these 5 steps to deploy the production stack from scratch:

Step 1: Pre-Create /srv Storage Roots

In Linux, /srv is owned by root:root by default. Pre-creating the roots and transferring ownership to your non-root user prevents Docker from auto-creating host directories as root:root (which causes Permission Denied in containers running as UID 1000):

sudo mkdir -p /srv/docker /srv/storage
sudo chown -R $USER:$USER /srv/docker /srv/storage

Step 2: Clone Repository

Clone the repository into your home directory (e.g., ~/homelab):

git clone https://github.com/pandeydevanshuh/homelab.git ~/homelab
cd ~/homelab

Step 3: Initialize Production Environment (.env)

Generate the .env configuration file from the template:

make init-env
nano .env

Key variables to review in .env: * PUID & PGID: Set to 1000 (check via id -u and id -g). * TZ: Set to your local timezone (e.g. America/New_York). * DATA_ROOT: /srv/docker (default). * STORAGE_ROOT: /srv/storage (default). * DISCORD_BOT_TOKEN & DISCORD_GUILD_ID: Production Discord bot credentials. * TELEGRAM_BOT_TOKEN & TELEGRAM_ALLOWED_USER_IDS: Private Telegram intake credentials. * DB_PASSWORD: Strong database password for Immich PostgreSQL.

Step 4: Run Production Provisioning (make prod-prep)

Run the automated preparation target:

make prod-prep

This single command: 1. Provisions all media library directories (/srv/storage/media/{music,audiobooks,podcasts,books,photos,video}). 2. Provisions intake chutes (/srv/storage/downloads/intake/ and /srv/storage/downloads/scratch/). 3. Provisions the restricted partition (/srv/storage/restricted/). 4. Provisions SSD container database paths (/srv/docker/{navidrome,calibre-web,audiobookshelf,immich}). 5. Automatically initializes and seeds the Calibre SQLite database (metadata.db) under /srv/storage/media/books.

Step 5: Start the 24/7 Core Stack

Pull production container images and launch the stack in detached mode:

make pull
make up

Verify service status and healthchecks:

make ps

All core containers (audiobookshelf, calibre-web, navidrome, immich-server, immich-postgres, immich-valkey, discord-bot, telegram-bot) should display status healthy or running.


4. Day-2 Operations & Update Workflows

Server-Side Update (make update)

When you push bug fixes, documentation, or configuration changes to the GitHub repository, update the running server with a single command:

cd ~/homelab
make update

What make update does automatically: 1. Fetches and merges the latest git commits (git pull origin main). 2. Pulls updated container images from GHCR and Docker Hub (docker compose pull). 3. Rebuilds custom bot container images if code changed (docker compose build). 4. Restarts only the containers whose configuration or images changed, preserving uptime on untouched services (docker compose up -d --remove-orphans).

Laptop-to-Server Remote Deploy (make deploy)

You don't even need to SSH into the server to deploy updates. From your laptop (over Tailscale):

# Deploys to dev@homelab (or specify host=user@ip)
make deploy

# Or specify a custom host or Tailscale node:
make deploy host=dev@homelab

5. On-Demand Immich Machine Learning

To maintain the Zero-Idle resource profile (~1.3 GB total RAM across the 24/7 stack), facial recognition and CLIP search ML models are completely dormant until invoked:

  • Activate ML Sidecar for Photo Batch:
    make ml-on
    
    Spawns immich-machine-learning. Immich will process newly uploaded photo albums and facial clustering.
  • Dismantle ML Sidecar:
    make ml-off
    
    Stops and removes the container, restoring the zero-idle baseline.

6. Backups & Disaster Recovery

Because database and state data (/srv/docker) are strictly separated from heavy media files (/srv/storage), fast daily backups take seconds and require very little storage space.

What to Back Up Daily

  1. Repository & Secrets: ~/homelab/.env and config/
  2. Container State & Databases: /srv/docker/
  3. immich/postgres: Immich database and VectorChord embeddings
  4. navidrome/: Subsonic SQLite library and playlist state
  5. audiobookshelf/: Playback progress, user bookmarks, and metadata
  6. calibre-web/: E-reader sync states
#!/bin/bash
BACKUP_DEST="/mnt/backup/homelab-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DEST"

# 1. Back up .env secrets
cp ~/homelab/.env "$BACKUP_DEST/.env"

# 2. Snapshot /srv/docker (stop DB briefly or use postgres pg_dump)
rsync -aAX --delete /srv/docker "$BACKUP_DEST/"
echo "✅ Backup complete at $BACKUP_DEST"

7. Future Migration to Dedicated HDD Storage

When you add a 3.5" bulk HDD (e.g. 4TB–16TB WD Red / Seagate IronWolf) to your HP ProDesk, moving storage off the SSD requires zero changes to Docker or application configurations:

  1. Partition & Format the New HDD:
    sudo mkfs.ext4 -m 1 -L homelab-storage /dev/sdX1
    
  2. Copy Existing Media & Data:
    # Temporarily mount to /mnt/newdisk
    sudo mkdir -p /mnt/newdisk
    sudo mount /dev/sdX1 /mnt/newdisk
    sudo rsync -avP /srv/storage/ /mnt/newdisk/
    sudo umount /mnt/newdisk
    
  3. Mount HDD to /srv/storage in /etc/fstab:
    # Get disk UUID
    sudo blkid /dev/sdX1
    
    # Add to /etc/fstab:
    UUID=xxxx-xxxx-xxxx    /srv/storage    ext4    defaults,noatime    0    2
    
    # Mount
    sudo mount -a
    
  4. Restart Containers:
    make restart
    
    All containers continue running without knowing their storage moved to a high-capacity physical HDD!