No description
Find a file
2026-06-13 17:25:27 +02:00
caddy Add Raspberry Pi Caddy deployment 2026-06-13 17:25:27 +02:00
docker-compose-files Add Raspberry Pi Caddy deployment 2026-06-13 17:25:27 +02:00
docs Add Raspberry Pi Caddy deployment 2026-06-13 17:25:27 +02:00
.env.sample Add Raspberry Pi Caddy deployment 2026-06-13 17:25:27 +02:00
.gitignore Update Technitium deployment documentation 2026-06-13 16:15:32 +02:00
ipfconfig cleanup: remove retired service routes and references 2026-03-06 21:59:29 +02:00
README.md Add Raspberry Pi Caddy deployment 2026-06-13 17:25:27 +02:00
setup-homeassistant.sh fix: use tail -1 in get_env_value so appended secrets are read correctly 2026-04-05 16:30:27 +02:00
setup-node-red.sh fix: use tail -1 in get_env_value so appended secrets are read correctly 2026-04-05 16:30:27 +02:00
substitute_env.sh fix(substitute_env): ignore commented YAML placeholders and prevent unresolved-token loop hang 2026-03-31 22:22:46 +02:00

Homelab Docker Services

Docker Compose templates for home lab services, featuring security best practices, resource management, and comprehensive health monitoring.

🚀 Quick Start

# 1. Clone the repository
git clone https://github.com/brianpooe/homelab-blueprint.git
cd homelab-blueprint

# 2. Copy and configure environment
cp .env.sample .env
nano .env  # Fill in your configuration

# 3. Generate Docker Compose files
./substitute_env.sh docker-compose-files/arr-stack/template.yaml docker-compose.arr-stack.yml
./substitute_env.sh docker-compose-files/postgres/template.yaml docker-compose.postgres.yml
./substitute_env.sh docker-compose-files/vault/template.yaml docker-compose.vault.yml
./substitute_env.sh docker-compose-files/socket-proxy/template.yaml docker-compose.beszel-proxy.yml
./substitute_env.sh docker-compose-files/beszel-agent/template.yaml docker-compose.beszel-agent.yml
./substitute_env.sh caddy/Caddyfile_template caddy/Caddyfile .env
./substitute_env.sh docker-compose-files/caddy/template.yaml docker-compose.caddy.yml .env

# 4. Deploy
docker-compose -f docker-compose.arr-stack.yml up -d
docker-compose -f docker-compose.postgres.yml up -d
docker-compose -f docker-compose.vault.yml up -d
docker-compose -f docker-compose.beszel-proxy.yml up -d
docker-compose -f docker-compose.beszel-agent.yml up -d

📦 Available Stacks

🎬 Media Stack (arr-stack/)

Complete automated media management with VPN protection and security hardening.

Services:

  • Gluetun - VPN container (WireGuard/OpenVPN)
  • qBittorrent - Torrent client (through VPN)
  • SABnzbd - Usenet client (through VPN)
  • Prowlarr - Indexer manager
  • Radarr - Movie collection manager
  • Sonarr - TV series collection manager
  • Bazarr - Subtitle manager
  • Emby - Media server
  • Seerr - Request management
  • FlareSolverr - Captcha solver
  • Recyclarr - Quality profile sync (TRaSH Guides)

Features:

  • VPN kill-switch for download clients
  • Health checks and dependency management
  • Resource limits to prevent system exhaustion
  • Security hardening (no-new-privileges, internal networks)

📈 Monitoring Stack (socket-proxy/ + beszel-agent/)

Host-level telemetry stack for Beszel Hub with restricted Docker API access.

Services:

  • Beszel Socket Proxy - Restricted Docker API proxy on localhost
  • Beszel Agent - Host metrics and Docker telemetry collector

Features:

  • Official Beszel agent container setup
  • Restricted Docker API access via socket-proxy (instead of direct socket mount)
  • Host networking for interface metrics visibility
  • Persistent state volume and constrained memory limit

🗄️ Database Stack (postgres/)

PostgreSQL with pgAdmin web interface.

Services:

  • PostgreSQL 17 - Alpine-based database
  • pgAdmin 4 - Web-based administration

Features:

  • Health-aware dependency management
  • Automated backups ready
  • Resource limits configured
  • Persistent data volumes

🔐 Secrets Stack (vault/)

HashiCorp Vault for secrets management.

Services:

  • Vault 1.19.2 - Secrets management

Features:

  • Production-ready configuration
  • Pinned Vault version for predictable upgrades
  • IPC_LOCK capability for security
  • Health monitoring

🏠 Home Automation Stack (homeassistant/)

Home Assistant with MQTT broker and Zigbee2MQTT for local smart home control.

Services:

  • Home Assistant - Home automation platform
  • Eclipse Mosquitto - MQTT broker (v2.x)
  • Zigbee2MQTT - Zigbee coordinator bridge

Features:

  • All services on a shared bridge network (container-name routing)
  • SMLIGHT SLZB-06U over PoE+ (TCP socket, no USB passthrough needed)
  • Mosquitto 2.x with persistence and WebSocket listener
  • Zigbee2MQTT 2.x with Home Assistant MQTT discovery
  • Health checks and dependency ordering

Config templates (copy and fill in before first run):

  • homeassistant/config/mosquitto.conf./appdata/mqtt/config/mosquitto.conf
  • homeassistant/config/zigbee2mqtt_configuration.yaml./appdata/zigbee2mqtt/configuration.yaml
  • homeassistant/config/ha_configuration.yaml./appdata/homeassistant/configuration.yaml (via substitute_env.sh — sets Caddy trusted proxy)

Caddy Reverse Proxy (caddy/)

Dedicated Raspberry Pi reverse proxy with Cloudflare DNS-01 support.

Features:

  • Custom Caddy image with the Cloudflare DNS module
  • 256 MB runtime limit suitable for a 1 GB Raspberry Pi
  • Persistent certificate and ACME state
  • DMZ deployment at 10.0.15.5

🔴 Node-RED (node-red/)

Standalone Node-RED with the Home Assistant websocket integration pre-installed. Joins the HA network so it can reach Home Assistant by container name without going through a reverse proxy.

Services:

  • Node-RED - Flow-based automation editor (custom image built locally)

Features:

  • node-red-contrib-home-assistant-websocket baked into the image at build time
  • Joins ha-network — reaches Home Assistant at http://homeassistant:8123
  • Credential secret substituted at setup time (not a runtime env var)
  • Persistent flow and credential storage under ./appdata/node-red

First-time HA connection: After starting the container, open http://<host>:1880, drag in any Home Assistant node, add a server config with:

  • URL: $(HA_URL)
  • Token: $(HA_TOKEN)

Node-RED expands these from the container env vars at runtime. The actual token stays in .env and never gets written into your flow files — updating it only requires changing .env and restarting.


🔒 Security Features

Additional Security

  • No-new-privileges on all containers
  • Read-only filesystems where applicable
  • Internal networks for service isolation
  • Resource limits to prevent DoS
  • Health checks for reliability

🗂️ Repository Structure

docker-compose-files/
  arr-stack/
    template.yaml             # Compose template
    config/
      recyclarr_template.yml  # Recyclarr app config template
  beszel-agent/
    template.yaml
  caddy/
    Dockerfile
    README.md
    template.yaml
  gramps-web/
    template.yaml
    config/
      config_template.cfg
  homeassistant/
    template.yaml
    config/
      mosquitto.conf                    # Mosquitto 2.x config (copy to host before first run)
      zigbee2mqtt_configuration.yaml    # Zigbee2MQTT config template
  node-red/
    template.yaml
    Dockerfile                          # Custom image — installs HA websocket node at build time
    config/
      settings.js                       # Node-RED settings template (credentialSecret substituted by setup script)
  postgres/
    template.yaml
  socket-proxy/
    template.yaml
  technitium/
    README.md                 # Deployment and Raspberry Pi troubleshooting
    template.yaml
  vault/
    template.yaml

Each stack lives in its own folder alongside any config files it needs. The workflow is always the same:

# 1. Generate the compose file from the template
./substitute_env.sh docker-compose-files/<stack>/template.yaml docker-compose.<stack>.yml

# 2. (If the stack has config templates) copy or substitute them into place
cp docker-compose-files/<stack>/config/<file> ./appdata/<service>/
# or substitute variables into them first
./substitute_env.sh docker-compose-files/<stack>/config/<file> ./appdata/<service>/<file>

# 3. Deploy
docker-compose -f docker-compose.<stack>.yml up -d

📋 Prerequisites

Required

  • Linux host with Docker & Docker Compose installed
  • SSH access enabled
  • Basic knowledge of Docker and command line
  • Static IP for your host
  • Domain name for reverse proxy (optional)
  • VPN subscription for Gluetun (AirVPN, Mullvad, etc.)

⚙️ Configuration

1. Environment Variables

Edit .env file with your settings:

# arr-stack only
DOCKERCONFDIR=/opt/arr-stack/appdata
DOCKERSTORAGEDIR=/mnt/unas/arr-data
ARR_DATA_MARKER=.arr-stack-storage
PUID=1000
PGID=1000
LXC_IDMAP_BASE=100000
TZ=Africa/Johannesburg

# Docker Logging
DOCKERLOGGING_MAXFILE=3
DOCKERLOGGING_MAXSIZE=10m

# VPN Configuration
VPN_SERVICE_PROVIDER=airvpn
WIREGUARD_PRIVATE_KEY=your_private_key
WIREGUARD_PRESHARED_KEY=your_preshared_key
WIREGUARD_ADDRESSES=10.x.x.x/32
FIREWALL_VPN_INPUT_PORTS=12345
SERVER_COUNTRIES=Netherlands

# Beszel Socket Proxy
BESZEL_SOCKET_PROXY_IMAGE_TAG=latest
BESZEL_SOCKET_PROXY_BIND_IP=127.0.0.1
BESZEL_SOCKET_PROXY_PORT=2375
BESZEL_SOCKET_PROXY_LOG_LEVEL=info
BESZEL_SOCKET_PROXY_MEM_LIMIT=64m

# Beszel Agent
BESZEL_AGENT_KEY=<public_key_from_hub>
BESZEL_AGENT_TOKEN=<token_from_hub>
BESZEL_AGENT_HUB_URL=https://beszel.example.com
BESZEL_AGENT_LISTEN=45876
BESZEL_AGENT_IMAGE_TAG=latest
BESZEL_AGENT_MEM_LIMIT=128m

# Caddy reverse proxy template
CADDY_TLS_EMAIL=admin@example.com
CADDY_BASE_DOMAIN=home.example.com
CADDY_HOST_IP=10.0.15.5
CLOUDFLARE_DNS_TOKEN=restricted_cloudflare_api_token
CADDY_MEM_LIMIT=256m
CADDY_LAN_PREFIX=10.0.1
CADDY_IOT_PREFIX=10.0.30
CADDY_OFFICE_PREFIX=10.0.10

# DNS
DNS_BIND_IP=10.0.10.3

# Home Automation
SLZB06_HOST=10.0.30.X

# Node-RED
HA_TOKEN=your_long_lived_access_token   # HA Profile → Security → Long-lived access tokens
HA_URL=http://homeassistant:8123        # optional — this is the default
NODE_RED_CREDENTIAL_SECRET=             # auto-generated by setup-node-red.sh if left empty

# Database
POSTGRES_USER=postgres
POSTGRES_PASSWORD=strong_password
POSTGRES_DB=maindb
PGADMIN_EMAIL=admin@example.com
PGADMIN_PASSWORD=strong_password

Get your PUID/PGID:

id $USER

Beszel agent required values:

  • BESZEL_AGENT_KEY, BESZEL_AGENT_TOKEN, and BESZEL_AGENT_HUB_URL come from Beszel Hub when adding a system.
  • Keep BESZEL_AGENT_LISTEN=45876 unless you intentionally changed the agent listen port in Beszel.
  • Keep BESZEL_SOCKET_PROXY_BIND_IP=127.0.0.1 so the Docker API is not exposed on your LAN.

2. Recyclarr Configuration

Copy and configure Recyclarr for quality profile management:

# Generate config from template
./substitute_env.sh docker-compose-files/arr-stack/config/recyclarr_template.yml /opt/arr-stack/appdata/recyclarr/recyclarr.yml

# Edit with your API keys
nano /opt/arr-stack/appdata/recyclarr/recyclarr.yml

Get API Keys:

  • Sonarr: Settings → General → Security → API Key
  • Radarr: Settings → General → Security → API Key

Important: Use service names (http://sonarr:8989), NOT localhost! This repository's template uses Recyclarr v8 guide-backed profiles and is pinned to ghcr.io/recyclarr/recyclarr:8.6.0.

3. Directory Structure

For the complete UNAS NFS, Proxmox bind mount, ownership, and verification procedure, follow docs/DEPLOYMENT.md. After the storage chain is verified, create the arr-stack directories inside the LXC:

# Config directories
sudo mkdir -p /opt/arr-stack/appdata/{gluetun,qbittorrent,sabnzbd,prowlarr,radarr,sonarr,bazarr,emby,seerr,recyclarr,unpackerr}

# Shared NFS-backed storage directories
sudo -u '#1000' mkdir -p /mnt/unas/arr-data/torrents/{movies,tv}
sudo -u '#1000' mkdir -p /mnt/unas/arr-data/usenet/{incomplete,complete/{movies,tv}}
sudo -u '#1000' mkdir -p /mnt/unas/arr-data/media/{movies,tv}
sudo -u '#1000' touch /mnt/unas/arr-data/.arr-stack-storage

# Configs stay on local LXC disk
sudo chown -R 1000:1000 /opt/arr-stack

🚀 Deployment

Option 1: Deploy All Stacks

./substitute_env.sh docker-compose-files/arr-stack/template.yaml        docker-compose.arr-stack.yml
./substitute_env.sh docker-compose-files/postgres/template.yaml         docker-compose.postgres.yml
./substitute_env.sh docker-compose-files/vault/template.yaml            docker-compose.vault.yml
./substitute_env.sh docker-compose-files/socket-proxy/template.yaml     docker-compose.beszel-proxy.yml
./substitute_env.sh docker-compose-files/beszel-agent/template.yaml     docker-compose.beszel-agent.yml
./substitute_env.sh docker-compose-files/homeassistant/template.yaml    docker-compose.homeassistant.yml
./substitute_env.sh caddy/Caddyfile_template caddy/Caddyfile .env
./setup-node-red.sh

docker-compose -f docker-compose.arr-stack.yml up -d
docker-compose -f docker-compose.postgres.yml up -d
docker-compose -f docker-compose.vault.yml up -d
docker-compose -f docker-compose.beszel-proxy.yml up -d
docker-compose -f docker-compose.beszel-agent.yml up -d
docker-compose -f docker-compose.homeassistant.yml up -d
docker-compose -f docker-compose.node-red.yml up -d --build

Option 2: Deploy Specific Stack

# Media stack only
./substitute_env.sh docker-compose-files/arr-stack/template.yaml docker-compose.arr-stack.yml
docker-compose -f docker-compose.arr-stack.yml up -d

# Home Assistant stack only
./substitute_env.sh docker-compose-files/homeassistant/template.yaml docker-compose.homeassistant.yml
docker-compose -f docker-compose.homeassistant.yml up -d

# Node-RED only (requires HA stack to be running first)
./setup-node-red.sh
docker-compose -f docker-compose.node-red.yml up -d --build

# Verify deployment
docker ps
docker logs -f gluetun

Option 3: Deploy Individual Services

# Start only specific services
docker-compose -f docker-compose.arr-stack.yml up -d gluetun qbittorrent prowlarr radarr sonarr

🔍 Verification

Check Service Health

# View all containers
docker ps

# Check specific service logs
docker logs gluetun

# Check health status
docker inspect gluetun | grep -A 10 Health

Test VPN Connection

# Check VPN is connected
docker logs gluetun | grep "connected"

# Test qBittorrent is using VPN
docker exec qbittorrent curl ifconfig.me
# Should show VPN IP, not your real IP

📊 Resource Allocation

Total resources if all services running:

Resource Reserved Limit Notes
Memory 4.00 GB 15.00 GB CPU limits are intentionally omitted

Per-service limits configured to prevent resource exhaustion


🔧 Common Tasks

Update Containers

# Manual update
docker-compose -f docker-compose.arr-stack.yml pull
docker-compose -f docker-compose.arr-stack.yml up -d

View Logs

# Follow logs
docker logs -f radarr

# Last 100 lines
docker logs --tail 100 sonarr

# All services
docker-compose -f docker-compose.arr-stack.yml logs -f

Restart Service

# Single service
docker-compose -f docker-compose.arr-stack.yml restart radarr

# All services
docker-compose -f docker-compose.arr-stack.yml restart

Stop/Start Stack

# Stop everything
docker-compose -f docker-compose.arr-stack.yml down

# Start everything
docker-compose -f docker-compose.arr-stack.yml up -d

# Stop but keep data
docker-compose -f docker-compose.arr-stack.yml stop

📚 Documentation

Detailed Guides

External Resources


🛠️ Troubleshooting

Quick Diagnostics

# Check container status
docker ps -a

# Check resource usage
docker stats

# Check networks
docker network ls
docker network inspect vpn_network

# Validate compose file
docker-compose -f docker-compose.arr-stack.yml config

Common Issues

Recyclarr: "base_url must start with http"

Fix: Use service names in recyclarr.yml:

base_url: http://sonarr:8989  # ✅ Correct
base_url: http://localhost:8989  # ❌ Wrong

Recyclarr: "Unable to find include template with name ..."

Cause: Old pre-v8 recyclarr.yml still using include: - template:. Fix: Regenerate from docker-compose-files/arr-stack/config/recyclarr_template.yml and recreate Recyclarr. See docs/RECYCLARR.md.

qBittorrent: No connection

Fix: Check Gluetun VPN is connected:

docker logs gluetun | grep -i "connected"

Full troubleshooting guide: docs/TROUBLESHOOTING.md


🎯 Best Practices

Security

  • Use strong passwords in .env
  • Never commit .env to git
  • Regularly update containers

Performance

  • Adjust resource limits based on your host specs
  • Monitor with docker stats
  • Use SSD for Docker volumes if possible
  • Enable hardware transcoding in Emby/Jellyfin

Maintenance

  • Regular backups of /opt/arr-stack/appdata
  • Monitor logs for errors
  • Test updates in staging before production
  • Document any custom configurations

🤝 Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Test your changes thoroughly
  4. Submit a pull request

📝 License

This project is provided as-is for personal and educational use.


🙏 Credits


📧 Support


Last Updated: 2026-02-14 Version: 2.1 Compatibility: Docker Compose 1.27.0+