fix: remove dockhand and related services

This commit is contained in:
Brian Pooe 2026-03-20 18:13:20 +02:00
parent f9c0261383
commit fede39358e
10 changed files with 107 additions and 172 deletions

View file

@ -25,16 +25,6 @@ WIREGUARD_ADDRESSES=
FIREWALL_VPN_INPUT_PORTS=
SERVER_COUNTRIES=
# Hawser agent (for Dockhand remote Synology host management)
HAWSER_BIND_IP=0.0.0.0
HAWSER_PORT=2376
# Shared secret between Dockhand and Hawser agent.
# Generate with: openssl rand -base64 32
# Must match the token configured when adding this host in Dockhand.
HAWSER_TOKEN=
HAWSER_AGENT_NAME=synology-host
HAWSER_LOG_LEVEL=info
# Database
POSTGRES_USER=
POSTGRES_PASSWORD=
@ -91,7 +81,7 @@ CADDY_LAN_PREFIX=10.1.0
CADDY_DNS_PREFIX=10.60.0
CADDY_OFFICE_PREFIX=10.10.0
# DNS stack (Technitium + Dockhand)
# DNS stack (Technitium)
# Bind IP for DNS service (use your DNS host IP, e.g. 10.60.0.5)
DNS_BIND_IP=10.60.0.5
TECHNITIUM_WEB_BIND_IP=0.0.0.0
@ -111,15 +101,11 @@ TECHNITIUM_MEM_LIMIT=512m
# Local persistent data path for Technitium on Pi (bind mount)
TECHNITIUM_CONFIG_DIR=./appdata/technitium
# Dockhand
DOCKHAND_IMAGE_TAG=v1.0.20
DOCKHAND_ENCRYPTION_KEY=
DOCKHAND_PORT=3000
DOCKHAND_BIND_IP=0.0.0.0
DOCKHAND_MEM_LIMIT=256m
# Local persistent data path for Dockhand on Pi (bind mount)
DOCKHAND_DATA_DIR=./appdata/dockhand
# Host path containing compose stacks that Dockhand should manage
DOCKHAND_STACKS_DIR=./stacks
# Find with: stat -c '%g' /var/run/docker.sock
DOCKER_GID=999
# Beszel Agent
# Required values come from Beszel Hub when adding a system.
BESZEL_AGENT_KEY=
BESZEL_AGENT_TOKEN=
BESZEL_AGENT_HUB_URL=
BESZEL_AGENT_LISTEN=45876
BESZEL_AGENT_IMAGE_TAG=latest
BESZEL_AGENT_MEM_LIMIT=128m

View file

@ -17,12 +17,14 @@ nano .env # Fill in your configuration
./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/beszel-agent_template.yaml docker-compose.beszel-agent.yml
./substitute_env.sh caddy/Caddyfile_template caddy/Caddyfile .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-agent.yml up -d
```
---
@ -33,7 +35,6 @@ docker-compose -f docker-compose.vault.yml up -d
Complete automated media management with VPN protection and security hardening.
**Services:**
- **Hawser Agent** - Remote host connector for Dockhand
- **Gluetun** - VPN container (WireGuard/OpenVPN)
- **qBittorrent** - Torrent client (through VPN)
- **SABnzbd** - Usenet client (through VPN)
@ -52,6 +53,18 @@ Complete automated media management with VPN protection and security hardening.
- ✅ Resource limits to prevent system exhaustion
- ✅ Security hardening (no-new-privileges, internal networks)
### 📈 Monitoring Stack (`beszel-agent_template.yaml`)
Host-level telemetry agent for Beszel Hub.
**Services:**
- **Beszel Agent** - Host metrics and Docker telemetry collector
**Features:**
- ✅ Official Beszel agent container setup
- ✅ Host networking for interface metrics visibility
- ✅ Docker socket read-only mount
- ✅ Persistent state volume and constrained memory limit
### 🗄️ Database Stack (`postgres_template.yaml`)
PostgreSQL with pgAdmin web interface.
@ -131,12 +144,13 @@ WIREGUARD_ADDRESSES=10.x.x.x/32
FIREWALL_VPN_INPUT_PORTS=12345
SERVER_COUNTRIES=Netherlands
# Hawser Agent (for Dockhand remote host management)
HAWSER_BIND_IP=0.0.0.0
HAWSER_PORT=2376
HAWSER_TOKEN=strong_shared_token
HAWSER_AGENT_NAME=synology-host
HAWSER_LOG_LEVEL=info
# 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
@ -159,14 +173,9 @@ ssh admin@synology-nas
id $USER
```
**Hawser token details (Dockhand remote host):**
- `HAWSER_TOKEN` is a shared secret used by Dockhand to authenticate to the Hawser agent.
- Generate a token:
```bash
openssl rand -base64 32
```
- Set that value in `.env` as `HAWSER_TOKEN=...`.
- Use the exact same token in Dockhand when adding the Synology host endpoint.
**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.
### 2. Recyclarr Configuration
@ -193,7 +202,7 @@ Create required directories:
```bash
# Config directories
sudo mkdir -p /volume1/docker/appdata/{gluetun,qbittorrent,sabnzbd,prowlarr,radarr,sonarr,bazarr,emby,seerr,recyclarr,postgres,pgadmin,vault}
sudo mkdir -p /volume1/docker/appdata/{gluetun,qbittorrent,sabnzbd,prowlarr,radarr,sonarr,bazarr,emby,seerr,recyclarr,postgres,pgadmin,vault,beszel-agent}
# Storage directories
sudo mkdir -p /volume1/data/{torrents,usenet,media}/{movies,tv,music}
@ -216,11 +225,13 @@ sudo chown -R 5050:5050 /volume1/docker/appdata/pgadmin
./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/beszel-agent_template.yaml docker-compose.beszel-agent.yml
./substitute_env.sh caddy/Caddyfile_template caddy/Caddyfile .env
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-agent.yml up -d
```
### Option 2: Deploy Specific Stack

View file

@ -77,11 +77,6 @@ dns01.{{CADDY_BASE_DOMAIN:-home.example.com}} {
reverse_proxy {{CADDY_DNS_PREFIX:-10.60.0}}.5:5380
}
dockhand.{{CADDY_BASE_DOMAIN:-home.example.com}} {
import common
reverse_proxy {{CADDY_DNS_PREFIX:-10.60.0}}.5:3000
}
## 3) Office services
nas.{{CADDY_BASE_DOMAIN:-home.example.com}} {
import common

View file

@ -1,4 +1,4 @@
# End-to-End Migration Runbook (Pi-hole on Pi Zero 2 W -> Technitium + Dockhand on Pi 4)
# End-to-End Migration Runbook (Pi-hole on Pi Zero 2 W -> Technitium on Pi 4)
Use this as the single flow document. Each step links to the deeper guide where needed.
@ -28,7 +28,7 @@ docker compose version
```
Expected:
- `uname -m` should be `aarch64` (64-bit) for best compatibility with Dockhand.
- `uname -m` should be `aarch64` (64-bit) for best compatibility with current Docker images.
- OS can be Raspberry Pi OS Lite based on Debian Trixie.
- Docker Engine + Compose plugin installed and working.
- 2GB RAM Pi 4 is fine for this stack with current memory caps.
@ -37,23 +37,19 @@ Expected:
1. Fill required values in `.env` (or environment file you use):
- `DNS_BIND_IP=10.60.0.6` (temporary during staging)
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
- `DOCKHAND_ENCRYPTION_KEY=<base64 key from openssl rand -base64 32>`
- `TECHNITIUM_CONFIG_DIR=./appdata/technitium`
- `DOCKHAND_DATA_DIR=./appdata/dockhand`
- `DOCKER_GID=<docker.sock group id>`
- `DOCKHAND_STACKS_DIR=./stacks` (or local path you want Dockhand to manage)
2. Render the compose from template:
```bash
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
./substitute_env.sh docker-compose-files/technitium_template.yaml docker-compose-files/technitium.yaml .env
```
3. Start the stack:
```bash
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
docker compose -f docker-compose-files/technitium.yaml up -d
```
Reference:
- [technitium-dockhand-deployment.md](./technitium-dockhand-deployment.md)
- [technitium-dockhand_template.yaml](../docker-compose-files/technitium-dockhand_template.yaml)
- [technitium-deployment.md](./technitium-deployment.md)
- [technitium_template.yaml](../docker-compose-files/technitium_template.yaml)
## 4) Configure Technitium to mirror current behavior
1. Open Technitium UI on Pi 4 temporary IP:
@ -117,8 +113,8 @@ Optional hardening for encrypted DNS bypass:
- set `DNS_BIND_IP=10.60.0.5`
8. Re-render and restart stack:
```bash
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
./substitute_env.sh docker-compose-files/technitium_template.yaml docker-compose-files/technitium.yaml .env
docker compose -f docker-compose-files/technitium.yaml up -d
```
## 8) Post-cutover validation (production IP)

View file

@ -38,8 +38,8 @@ A wildcard record closes this drift gap permanently.
## Cutover plan
### Docker stack files in this repo
- Compose template: [technitium-dockhand_template.yaml](../docker-compose-files/technitium-dockhand_template.yaml)
- Deployment guide: [technitium-dockhand-deployment.md](./technitium-dockhand-deployment.md)
- Compose template: [technitium_template.yaml](../docker-compose-files/technitium_template.yaml)
- Deployment guide: [technitium-deployment.md](./technitium-deployment.md)
### 1. Keep network references stable
- Keep Technitium service IP as `10.60.0.5`.

View file

@ -1,52 +1,41 @@
# Technitium + Dockhand Docker Deployment Guide
# Technitium Docker Deployment Guide
This guide deploys Technitium DNS and Dockhand using:
- [technitium-dockhand_template.yaml](../docker-compose-files/technitium-dockhand_template.yaml)
This guide deploys Technitium DNS using:
- [technitium_template.yaml](../docker-compose-files/technitium_template.yaml)
Compatibility note:
- Dockhand image in this stack is `fnsys/dockhand:latest` and should be run on 64-bit Linux (`arm64` on Pi 4).
- 2GB RAM Pi 4 is supported with the included memory caps in the compose template.
- This template hardcodes `PUID=1000`, `PGID=1000`, `max-file=3`, and `max-size=10m` for Pi-local use.
- 2GB RAM Pi 4 is supported with the included memory cap in the compose template.
- This template hardcodes `max-file=3` and `max-size=10m` for Pi-local use.
## 1) Prepare env values
Update your `.env` with these required values:
- `DNS_BIND_IP=10.60.0.5`
- `TECHNITIUM_ADMIN_PASSWORD=<strong-password>`
- `DOCKHAND_ENCRYPTION_KEY=<base64 key that decodes to exactly 32 bytes>`
- `TECHNITIUM_CONFIG_DIR=./appdata/technitium`
- `DOCKHAND_DATA_DIR=./appdata/dockhand`
- `DOCKHAND_STACKS_DIR=./stacks` (or any local host path containing compose files)
- `DOCKER_GID=<docker.sock group id>`
For 2GB Pi 4 (recommended defaults already in `.env.sample`):
Optional but recommended values:
- `TECHNITIUM_MEM_LIMIT=512m`
- `DOCKHAND_MEM_LIMIT=256m`
- `TECHNITIUM_DNS_DOMAIN=home.arpa`
- `TECHNITIUM_BLOCKLIST_URLS=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts`
Tip:
- `DOCKER_GID` command: `stat -c '%g' /var/run/docker.sock`
- Relative bind paths (like `./appdata/...`) are resolved from the folder containing the rendered compose file (`docker-compose-files/`).
Generate a valid Dockhand encryption key:
Create local bind-mount folder:
```bash
openssl rand -base64 32
```
Paste the output into `.env` as `DOCKHAND_ENCRYPTION_KEY=...`.
Create local bind-mount folders:
```bash
mkdir -p docker-compose-files/appdata/technitium docker-compose-files/appdata/dockhand docker-compose-files/stacks
mkdir -p docker-compose-files/appdata/technitium
```
## 2) Render compose from template
Run:
```bash
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
./substitute_env.sh docker-compose-files/technitium_template.yaml docker-compose-files/technitium.yaml .env
```
## 3) Start stack
Run:
```bash
docker compose -f docker-compose-files/technitium-dockhand.yaml up -d
docker compose -f docker-compose-files/technitium.yaml up -d
```
## 4) First-time Technitium setup (required)
@ -85,16 +74,14 @@ nslookup switchlite8poe.home.example.com 10.60.0.5
## 7) Common gotchas
- If Technitium fails to bind port 53, check for other DNS services on the host (for example `systemd-resolved`, `dnsmasq`, old Pi-hole container).
- If Dockhand cannot manage containers, verify `DOCKER_GID` and docker socket mount.
- If Dockhand shows `Invalid ENCRYPTION_KEY`, regenerate with `openssl rand -base64 32`, re-render the compose file, and restart Dockhand.
- If Caddy ACME DNS-01 fails with `expected 1 zone, got 0 for home.example.com`, your forced-DNS NAT is likely intercepting Caddy's resolver lookups.
- Add `CADDY_HOST` alias and exclude it in the forced DNS NAT rule on Caddy's interface (source `!CADDY_HOST`, source port `any`).
- Keep destination invert `!LOCAL_DNS`, destination port `53`, redirect to `LOCAL_DNS:53`.
- See: [pfsense-forced-dns-all-vlans.md](./pfsense-forced-dns-all-vlans.md)
- If `http://<DNS_BIND_IP>:5380` is refused, run:
```bash
docker compose -f docker-compose-files/technitium-dockhand.yaml ps technitium-dns
docker compose -f docker-compose-files/technitium-dockhand.yaml port technitium-dns 5380
docker compose -f docker-compose-files/technitium.yaml ps technitium-dns
docker compose -f docker-compose-files/technitium.yaml port technitium-dns 5380
ss -ltnp | grep 5380 || true
```
- Confirm `TECHNITIUM_WEB_BIND_IP` in `.env` is not `127.0.0.1`.

View file

@ -5,39 +5,6 @@ version: '3.8'
# Memory limits are supported and configured below
services:
hawser:
image: ghcr.io/finsys/hawser:latest
container_name: hawser
restart: unless-stopped
labels:
- "com.synology.stack=media"
- "com.synology.service=hawser-agent"
networks:
- hawser-network
ports:
- "{{HAWSER_BIND_IP:-0.0.0.0}}:{{HAWSER_PORT:-2376}}:2376"
environment:
PORT: "2376"
DOCKER_SOCKET: /var/run/docker.sock
TOKEN: "{{HAWSER_TOKEN:-}}"
AGENT_NAME: "{{HAWSER_AGENT_NAME:-synology-host}}"
LOG_LEVEL: "{{HAWSER_LOG_LEVEL:-info}}"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
resources:
limits:
memory: 128M
reservations:
memory: 32M
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
max-file: "{{DOCKERLOGGING_MAXFILE}}"
max-size: "{{DOCKERLOGGING_MAXSIZE}}"
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
@ -90,8 +57,7 @@ services:
qbittorrent:
image: ghcr.io/hotio/qbittorrent:latest
container_name: qbittorrent
# Use direct container network mode for better compatibility with single-service recreate flows
# (for example Dockhand updates on Synology).
# Use direct container network mode for better compatibility with single-service recreate flows.
network_mode: "container:gluetun"
restart: unless-stopped
labels:
@ -619,9 +585,3 @@ networks:
name: vpn_network
labels:
- "com.synology.network=media"
hawser-network:
driver: bridge
name: hawser_network
labels:
- "com.synology.network=hawser"

View file

@ -0,0 +1,41 @@
version: '3.8'
# Synology DS920+ Compatible Configuration
# Note: CPU limits removed due to kernel lacking CPU CFS scheduler support
# Memory limits are supported and configured below
services:
beszel-agent:
image: henrygd/beszel-agent:{{BESZEL_AGENT_IMAGE_TAG:-latest}}
container_name: beszel-agent
restart: unless-stopped
labels:
- "com.synology.stack=monitoring"
- "com.synology.service=beszel-agent"
# Beszel recommends host mode for host network interface stats.
network_mode: host
environment:
TZ: {{TZ}}
LISTEN: "{{BESZEL_AGENT_LISTEN:-45876}}"
KEY: "{{BESZEL_AGENT_KEY}}"
HUB_URL: "{{BESZEL_AGENT_HUB_URL}}"
TOKEN: "{{BESZEL_AGENT_TOKEN}}"
volumes:
- /etc/localtime:/etc/localtime:ro
- {{DOCKERCONFDIR}}/beszel-agent:/var/lib/beszel-agent
- /var/run/docker.sock:/var/run/docker.sock:ro
# Monitor additional disks by mounting them into /extra-filesystems.
# - /volume1/data:/extra-filesystems/data:ro
deploy:
resources:
limits:
memory: "{{BESZEL_AGENT_MEM_LIMIT:-128m}}"
reservations:
memory: 32M
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
max-file: "{{DOCKERLOGGING_MAXFILE}}"
max-size: "{{DOCKERLOGGING_MAXSIZE}}"

View file

@ -47,39 +47,3 @@ services:
options:
max-file: "3"
max-size: "10m"
dockhand:
# Use a pinned tag to avoid regressions during stack updates.
# v1.0.18+ includes a fix for shared network mode container updates.
image: fnsys/dockhand:{{DOCKHAND_IMAGE_TAG:-v1.0.20}}
container_name: dockhand
restart: unless-stopped
labels:
- "com.synology.stack=management"
- "com.synology.service=dockhand"
environment:
TZ: "{{TZ}}"
# Required: set a strong random value in .env
ENCRYPTION_KEY: "{{DOCKHAND_ENCRYPTION_KEY}}"
PORT: "3000"
DATA_DIR: "/opt/dockhand"
PUID: "1000"
PGID: "1000"
group_add:
- "{{DOCKER_GID:-999}}"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Local persistent config path for Raspberry Pi deployments
- {{DOCKHAND_DATA_DIR:-./appdata/dockhand}}:/opt/dockhand
# Host folder containing compose stacks Dockhand should manage
- {{DOCKHAND_STACKS_DIR:-./stacks}}:/opt/stacks
ports:
- "{{DOCKHAND_BIND_IP:-0.0.0.0}}:{{DOCKHAND_PORT:-3000}}:3000/tcp"
mem_limit: "{{DOCKHAND_MEM_LIMIT:-256m}}"
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
max-file: "3"
max-size: "10m"

View file

@ -156,29 +156,24 @@ docker-compose -f docker-compose.arr-stack.yml ps gluetun qbittorrent
docker-compose -f docker-compose.arr-stack.yml logs --tail 100 qbittorrent
```
### 5c. qBittorrent HTTPS `502` after Dockhand dashboard upgrades
### 5c. qBittorrent HTTPS `502` after stack updates
Symptoms:
- qBittorrent opens via HTTPS reverse proxy URL and returns `502` after a Dockhand-triggered stack update.
- qBittorrent opens via HTTPS reverse proxy URL and returns `502` after stack updates/recreates.
Common causes:
- Older Dockhand releases had a known container update issue for shared network modes (`container:`, `host`, `none`), which affects `qbittorrent` when it uses `network_mode: "container:gluetun"`.
- `gluetun` and `qbittorrent` are out of sync after recreate/update and need a paired restart.
- qBittorrent WebUI can be left in HTTPS mode while your reverse proxy still targets HTTP upstream.
Fix:
```bash
# 1) Ensure Dockhand is on a fixed version that includes the network-mode update fix
./substitute_env.sh docker-compose-files/technitium-dockhand_template.yaml docker-compose-files/technitium-dockhand.yaml .env
docker-compose -f docker-compose-files/technitium-dockhand.yaml pull dockhand
docker-compose -f docker-compose-files/technitium-dockhand.yaml up -d dockhand
# 2) Recreate the VPN + torrent pair together
# 1) Recreate the VPN + torrent pair together
docker-compose -f docker-compose.arr-stack.yml up -d --force-recreate gluetun qbittorrent
# 3) If 502 persists, force qBittorrent WebUI back to HTTP
# 2) If 502 persists, force qBittorrent WebUI back to HTTP
CONF="$(find /volume1/docker/appdata/qbittorrent -type f -name 'qBittorrent.conf' | head -n1)"
echo "Using config: $CONF"
sudo sed -i 's#^WebUI\\HTTPS\\Enabled=.*#WebUI\\HTTPS\\Enabled=false#' "$CONF"