fix-dollar-double-interpolation
Root cause of the LITELLM_MASTER_KEY/LITELLM_SALT_KEY truncation incident (2026-08-26): stack-deploy.sh's single-file deploy path is `envsubst "$VARS" < stack.yaml | docker stack deploy -c - stack`. envsubst embeds the raw secret value into the compose YAML text. If that value contains a literal '$' followed by word chars, the resulting YAML now contains what looks like a second variable reference. `docker stack deploy -c -` runs Compose's own interpolation pass on that text before creating the service, finds no such env var, and silently substitutes empty string -- truncating the secret in the running container with no error. Confirmed: an 87-char LITELLM_MASTER_KEY arrived in the ai_litellm container as 73 chars, silently, on a real deploy. This is not specific to ai -- it affects every Pattern B stack (host .env + envsubst, not native Docker secrets): maintenance, media, unifi, guacamole, security, auth, traefik, meshcentral, ddm. Any of them could have a '$'-containing value truncating right now without detection, since the failure produces no warning. Fix: escape every literal '$' as '$$' in export/export_merged (which feed the `eval` that sets envsubst's actual source values), before envsubst ever sees them. envsubst does not interpret '$' in replacement text, so the doubled dollar survives envsubst intact; Compose's own interpolation pass then consumes exactly one level of escaping, landing on the correct single '$' with no leftover false variable reference. vars/vars_merged (envsubst's allowlist string, unrelated to values) are untouched. NOT deployed/merged yet -- pending review. The currently-running ai_litellm service still has the truncated keys and needs a fresh `stack-deploy.sh ai` run after this merges to pick up the corrected values.
Homelab Infrastructure Repository - Docker Swarm Compose Files
This Gitea repository contains Docker Swarm compose files for all services running in the homelab.
For operational scripts (backup hooks, prune watchdog, network configuration), see the separate homelab-scripts repository.
📁 Directory Structure
├── traefik.yaml # Reverse proxy & load balancer
├── auth.yaml # Authentik authentication
├── postgresql.yaml # PostgreSQL database
├── maintenance.yaml # Cronicle scheduler, Uptime Kuma
├── ... (other service stacks)
└── README.md # This file
🚀 Quick Start
Deploy a Stack
# SSH into a Docker LXC (docker-1, docker-2, or docker-3)
ssh root@docker-1
# Clone this repo locally
cd /volume1/docker
git clone https://git.bryanmail.net/admin/compose-files.git repo
cd repo
# Deploy a stack
docker stack deploy -c traefik.yaml traefik
docker stack deploy -c auth.yaml auth
docker stack deploy -c postgresql.yaml postgresql
Update a Stack
# Pull latest changes
git pull origin main
# Re-deploy (applies changes)
docker stack deploy -c traefik.yaml traefik
# View status
docker stack ps traefik
docker service ls
Remove a Stack
docker stack rm traefik
📋 Available Stacks
| Stack | File | Purpose |
|---|---|---|
| Traefik | traefik.yaml | Reverse proxy, load balancer, TLS termination |
| Authentik | auth.yaml | Authentication & authorization |
| PostgreSQL | postgresql.yaml | Database backend |
| Maintenance | maintenance.yaml | Cronicle jobs, Uptime Kuma monitoring |
| ... | ... | (Add more as you create them) |
🛠️ Common Tasks
Deploy a New Service
- Create compose file in this repo:
myservice.yaml - Test locally (on single host):
docker-compose -f myservice.yaml up -d - Convert to Swarm format (remove
container_name, useservices:for Swarm) - Deploy to Swarm:
docker stack deploy -c myservice.yaml myservice - Commit & push:
git add myservice.yaml git commit -m "Add myservice stack" git push origin main
Check Service Status
# List all services
docker service ls
# Get details about a service
docker service inspect traefik_reverse-proxy
# View service logs
docker service logs -f traefik_reverse-proxy
# Check tasks (containers)
docker service ps traefik_reverse-proxy
Monitor Disk Space
df -h /volume1/docker-root
# Docker prune watchdog handles auto-cleanup (see homelab-scripts repo)
🔐 Secrets Management
DO NOT commit secrets, passwords, or API keys to this repo.
Use one of these approaches:
Option 1: Docker Secrets (Recommended for Swarm)
services:
myapp:
secrets:
- db_password
secrets:
db_password:
external: true
Create the secret:
echo "mysecretpassword" | docker secret create db_password -
Option 2: Environment Files (Not tracked by git)
# Create .env (add to .gitignore)
echo "DB_PASSWORD=mysecretpassword" > .env
# Use in compose
env_file: .env
📚 Architecture
Swarm Cluster
nuck7-1 (Hypervisor) nuck7-2 (Hypervisor) nuck7-3 (Hypervisor)
├─ docker-1 (LXC 4031) ├─ docker-2 (LXC 4032) ├─ docker-3 (LXC 4033)
│ └─ Swarm Manager │ └─ Swarm Leader │ └─ Swarm Manager
└─ ... └─ ... └─ ...
Storage
- CephFS mounted at
/volume1/docker/(shared across all nodes) - Compose files:
/volume1/docker/compose-files/ - Service data: Named volumes or
/volume1/docker/mounts
Networking
- VIP: 192.168.4.30 (Keepalived)
- Docker hosts: 192.168.4.31-33
- Traefik: Reverse proxy with Let's Encrypt TLS
- Domain: bryanmail.net
🔗 Related Repositories
- homelab-scripts - Operational scripts (backup hooks, monitoring, network config)
- Proxmox MCP Setup - Documented in notes
- Architecture Decision Records (ADRs) - Documented in notes
🐛 Troubleshooting
Stack won't deploy
# Check syntax
docker-compose config -f myservice.yaml
# Check node availability
docker node ls
# Check disk space
df -h /volume1/docker-root
Service keeps crashing
# View logs
docker service logs -f myservice_name
# Inspect container
docker ps -a | grep myservice
Network issues
# List networks
docker network ls --filter driver=overlay
# Test connectivity
docker run --rm --network traefik_backend alpine ping traefik_reverse-proxy
📞 Contributing
When adding new services:
- Use Swarm-compatible YAML (no
container_name) - Document requirements in compose file comments
- Test on non-production first
- Add notes about volumes, secrets, networking
- Update this README with stack description
📝 Git Workflow
# Before starting work
git pull origin main
# Create feature branch for new service
git checkout -b feature/new-service
# Make changes and commit
git add .
git commit -m "Add new-service stack"
# Push
git push origin feature/new-service
Version Control Best Practices
- Keep compose files in sync with deployed state
- Pin image versions (avoid
latesttag) - Document breaking changes in commit messages
- Use meaningful commit messages for audit trail
Languages
Shell
77.6%
Python
22.4%