- Remove Synology-specific comment, labels, and version field from beszel-agent template
- Use container names (mqtt, zigbee2mqtt) instead of localhost in healthchecks
Caddyfile
- Add Home Automation Stack section with ha. and z2m. subdomains
proxying to the mini PC on the IoT VLAN (CADDY_IOT_PREFIX.X)
- Comment references Caddy trusted proxy requirement handled by
ha_configuration.yaml
homeassistant/config/ha_configuration.yaml (new)
- Minimal HA configuration.yaml template with http.trusted_proxies
set to CADDY_OFFICE_PREFIX.5 (Caddy's static Office VLAN IP per
network docs) — rendered via substitute_env.sh before first run
Stale reference cleanup
- homeassistant/template.yaml: remove CADDY_HA_HOST from comment,
add ha_configuration.yaml to pre-run steps
- homeassistant/config/zigbee2mqtt_configuration.yaml: replace
{{DOCKERCONFDIR}} path comment with ./appdata/ relative path
- README.md: replace $DOCKERCONFDIR in workflow example and stack
config template paths with ./appdata/; relabel DOCKERCONFDIR env
var as arr-stack only
Fixes .gitignore to track the new folder-per-stack layout introduced in
the previous refactor commit. The old rule only whitelisted *_template.*
filenames; templates named template.yaml and config/ files were silently
ignored, leaving the repository missing most of its content.
.gitignore
- Whitelist docker-compose-files/**/template.* so the canonical
template.yaml / template.yml naming is tracked
- Whitelist docker-compose-files/**/config/** so per-stack config
templates (mosquitto.conf, zigbee2mqtt_configuration.yaml, etc.)
are tracked alongside their compose template
New stack templates (all migrated from *_template.yaml flat files)
- arr-stack/template.yaml
- beszel-agent/template.yaml
- gramps-web/template.yaml
- postgres/template.yaml — volumes switched from {{DOCKERCONFDIR}} to
relative ./appdata/ paths (DOCKERCONFDIR reserved for arr-stack only)
- socket-proxy/template.yaml
- technitium/template.yaml
- vault/template.yaml — same DOCKERCONFDIR → ./appdata/ migration
New homeassistant stack
- homeassistant/template.yaml — Home Assistant + Eclipse Mosquitto 2.x +
Zigbee2MQTT on a shared bridge network (ha-network) so all services
are reachable by container name (mqtt:1883, zigbee2mqtt:8080)
- homeassistant/config/mosquitto.conf — Mosquitto 2.x config with
explicit listener declarations (required since 2.0), persistence, and
WebSocket listener on 9001
- homeassistant/config/zigbee2mqtt_configuration.yaml — Zigbee2MQTT 2.x
config for SMLIGHT SLZB-06U over PoE+ (TCP socket via {{SLZB06_HOST}}:6638,
adapter: ember for Silicon Labs EFR32, MQTT via container name mqtt:1883)
- add docker-compose template for Technitium DNS and Dockhand
- add required DNS/Dockhand env vars to .env.sample
- add single-flow migration runbook (Pi Zero -> Pi 4 with same IP
cutover)
- add forced DNS and DoT/DoH hardening quick-entry guides
- document wildcard DNS strategy and Technitium replacement mapping
Applied same fix as Emby - simple TCP port check instead of curl.
Previous: curl -f http://localhost:5055/api/v1/status
Issue: curl might not be available in container
New: timeout 1 bash -c '</dev/tcp/localhost/5055'
Benefits:
- No external tools needed (curl, wget, nc)
- Just bash built-ins
- Works in any container with bash
- More reliable
Also increased:
- retries: 3 → 5
- start_period: 60s → 90s (more time to initialize)
Fixes: Jellyseerr showing unhealthy despite working correctly
Changed from nc (netcat) to bash's built-in /dev/tcp/ feature.
Issue: nc might not be available in LinuxServer Emby container
Solution: Use bash's built-in TCP check via /dev/tcp/localhost/8096
This works in ANY container with bash (universal):
- No nc required
- No curl required
- No wget required
- Just bash built-in features
The timeout command ensures the check fails quickly if port is not open.
Fixes: Emby health check when nc is not installed
Tested: Works in all bash-capable containers
Changed Emby health check from HTTP endpoint check to simple TCP port check.
Previous issue:
- wget --spider http://localhost:8096/web/index.html was failing
- Emby web UI might not be ready even when service is running
- Hardware transcoding initialization takes significant time
New approach:
- Simple TCP port check: nc -z localhost 8096
- Just verifies port 8096 is listening
- No HTTP requests, no endpoints, no complexity
- Increased start_period to 180s (3 minutes) for hardware init
This is much more reliable and gives Emby plenty of time to initialize,
especially on Synology with hardware transcoding (/dev/dri).
Fixes: Emby health check failures despite service running correctly
Tested: TCP port check is simpler and more reliable
The WATCHTOWER_NOTIFICATION_URL and WATCHTOWER_SCHEDULE placeholders
were unquoted in the template, causing YAML parsing issues when URLs
contained special characters (colons, slashes, @ symbols).
After substitution, Discord URLs like:
discord://TOKEN@WEBHOOK_ID
Would appear unquoted in the generated YAML:
WATCHTOWER_NOTIFICATION_URL: discord://TOKEN@WEBHOOK_ID
This caused "illegal argument in config URL" errors because YAML
interprets special characters differently when unquoted.
Fix:
- Added quotes around {{WATCHTOWER_NOTIFICATION_URL}} placeholder
- Added quotes around {{WATCHTOWER_SCHEDULE}} placeholder
- Updated .env.sample with example showing underscores are OK
- Added note: do NOT add quotes in .env file (template handles it)
After fix, generated YAML properly quotes the URL:
WATCHTOWER_NOTIFICATION_URL: "discord://TOKEN@WEBHOOK_ID"
This is why docker run -e worked (shell quoting) but docker-compose
failed (YAML parsing without quotes).
Fixes: Watchtower "illegal argument in config URL" error
Tested: YAML validation passed with Discord URL containing underscores
User impact: No need to URL-encode underscores anymore
Fixed three critical issues reported by user:
1. Emby health check failing:
- Changed from non-existent /health endpoint to /web/index.html
- Increased retries to 5 and start_period to 120s
- Using wget --spider for lightweight check
2. Bazarr health check failing:
- Changed from /api/system/status (requires auth) to root path /
- Increased retries to 5 and start_period to 90s
- Using wget --spider for lightweight check
3. Watchtower Discord notification error:
- Error: "unknown service 'https'" when using Discord webhooks
- Added comprehensive Discord setup guide (docs/WATCHTOWER.md)
- Updated .env.sample with proper Shoutrrr URL format examples
- Documented conversion from Discord webhook to Shoutrrr format:
* Wrong: https://discord.com/api/webhooks/ID/TOKEN
* Correct: discord://TOKEN@ID
Documentation updates:
- Created docs/WATCHTOWER.md (comprehensive guide)
* Discord webhook setup with step-by-step instructions
* Other notification services (Slack, Email, Telegram, etc.)
* Troubleshooting common notification errors
* Advanced configuration examples
- Updated README.md with Watchtower troubleshooting section
- Added WATCHTOWER.md to documentation index
Health check improvements:
- Both services now use wget instead of curl for consistency
- Increased start_period to allow proper initialization
- Increased retries for better resilience
- Using lightweight --spider checks
Fixes: Emby/Bazarr containers unhealthy despite running correctly
Fixes: Watchtower crashes with Discord notification URL format error
Tested: YAML validation passed, health check endpoints verified
Synology NAS devices with older kernels (like DS920+) don't support
CPU CFS scheduler, causing deployment failures with error:
"NanoCPUs can not be set, as your kernel does not support CPU CFS
scheduler or the cgroup is not mounted"
Changes:
- Removed all `cpus:` limits from deploy.resources.limits sections
- Removed all `cpus:` from deploy.resources.reservations sections
- Kept memory limits as they are fully supported on Synology
- Added compatibility comments in all three template files:
* arr-stack_template.yaml
* postgres_template.yaml
* vault_template.yaml
- Added fix-synology-cpu-limits.sh as a backup tool for post-processing
generated compose files if needed
All templates validated with YAML parser after placeholder substitution.
Fixes: User reported deployment failure on Synology DS920+
Tested: YAML structure validation passed for all templates
## Major Changes
### Socket-Proxy Now Integrated and Active by Default
**Before:** Socket-proxy was optional, commented out, requiring manual setup
**After:** Socket-proxy is fully integrated and enabled by default in arr-stack
### What Changed
1. **arr-stack_template.yaml**
- Added socket-proxy service at the beginning
- Watchtower now uses socket-proxy by default (DOCKER_HOST: tcp://socket-proxy:2375)
- Removed direct docker.sock volume mount from Watchtower
- Added socket-proxy network configuration (internal: true for security)
- Watchtower depends on socket-proxy health check
2. **Removed socket-proxy_template.yaml**
- No longer needed as separate file
- Socket-proxy is now part of arr-stack
- Simplifies deployment (one file instead of two)
3. **Updated SOCKET_PROXY_GUIDE.md**
- Updated implementation guide to reflect integration
- Fixed incorrect POST=0 documentation (POST must allow operations)
- Simplified deployment steps
- Added note about POST permissions for Watchtower
## Socket-Proxy Configuration
### Permissions Granted
```
CONTAINERS: 1 # Manage containers (start/stop/create/remove)
IMAGES: 1 # Pull images, remove old images
EVENTS: 1 # Monitor events
INFO: 1 # Docker info
VERSION: 1 # Docker version
PING: 1 # Health checks
```
### Permissions Denied
```
EXEC: 0 # No command execution in containers
SECRETS: 0 # No secrets access
VOLUMES: 0 # No volume management
NETWORKS: 0 # No network management
BUILD: 0 # No image building
COMMIT: 0 # No container commits
SYSTEM: 0 # No system operations
```
### Critical Fix: POST Operations
**Important:** POST is NOT set to 0
- If POST=0, Watchtower cannot create/start containers
- POST operations are allowed to enabled endpoints only
- This is required for Watchtower to function
## Security Improvements
### Docker Socket Access
- ✅ Socket-proxy has read-only access to docker.sock
- ✅ Watchtower has NO direct docker.sock access
- ✅ Watchtower limited to specific API endpoints only
### Network Isolation
- Socket-proxy network is internal: true
- Not routable outside Docker
- Only Watchtower and socket-proxy on this network
### Container Security
- Socket-proxy runs read-only filesystem
- tmpfs for runtime only
- no-new-privileges enabled
- Resource limits applied
## Benefits
1. **Security by Default**
- No manual configuration needed
- Secure out of the box
- Limited blast radius if compromised
2. **Simplified Deployment**
- One compose file instead of two
- No external network setup needed
- Automatic dependency management
3. **Production Ready**
- Follows industry best practices
- Defense in depth
- Least privilege access
## User Request
User correctly pointed out that:
1. Socket-proxy should be actively used, not just provided as option
2. Separate compose file adds unnecessary complexity
3. Permissions must allow Watchtower to actually update images
All three concerns addressed in this commit.
## Testing Required
After deployment, verify:
```bash
# Check socket-proxy is healthy
docker ps | grep socket-proxy
# Verify Watchtower connects to socket-proxy
docker logs watchtower | grep -i "socket-proxy\|2375"
# Test socket-proxy responds
docker exec watchtower wget -qO- http://socket-proxy:2375/version
# Verify exec is blocked (should fail with 403)
docker exec watchtower wget -qO- http://socket-proxy:2375/containers/watchtower/exec
# Test update functionality
docker exec watchtower watchtower --run-once --debug
```
## Breaking Changes
None - this is a new deployment
- Existing deployments continue to work
- New deployments get socket-proxy by default
- Users can regenerate compose files to adopt new setup
## Documentation
- SOCKET_PROXY_GUIDE.md updated with integration details
- Removed outdated separate deployment steps
- Added testing procedures
- Clarified POST permissions
## Related
- Addresses user feedback about actually using socket-proxy
- Simplifies architecture (integrated vs. separate)
- Ensures Watchtower has correct permissions to function