|
|
||
|---|---|---|
| bookstack | ||
| dashy | ||
| dispatcharr | ||
| freshrss | ||
| frigate | ||
| homeassistant | ||
| komodo | ||
| mosquitto | ||
| pinchflat | ||
| renovate | ||
| tandoor | ||
| wyze-bridge | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .sops.yaml | ||
| .yamllint.yaml | ||
| README.md | ||
| renovate.json | ||
homelab-docker
Source of truth for all Docker stacks running on Nexus. Compose files live here with pinned image tags. Renovate opens PRs when newer images are available. Merging a PR to main triggers Komodo to redeploy the affected stacks automatically. SOPS-encrypted .env.enc files serve as disaster-recovery backups for secrets that can't go into git in plaintext.
Pipeline Overview
Atlas (dev machine) ──push PR──▶ Forgejo (192.168.1.240:3000)
│
Renovate opens PRs
(cron, top of every hour)
│
you review and merge
│
webhook fires to Komodo
(192.168.1.226:9120)
│
┌────────▼────────┐
│ Stage 1 │
│ git pull repo │
└────────┬─────────┘
│
┌────────▼────────────────────────┐
│ Stage 2 (parallel) │
│ Deploy Stack: dashy │
│ Deploy Stack: bookstack │
│ Deploy Stack: tandoor │
│ Deploy Stack: homeassistant │
│ Deploy Stack: pinchflat │
└──────────────────────────────────┘
│
docker compose up -d
(only restarts containers
whose config actually changed)
Managed Services
| Service | Image | Port | Notes |
|---|---|---|---|
| dashy | lissy93/dashy:4.0.0 | 4000 | Dashboard — conf.yml tracked in this repo |
| bookstack | linuxserver/bookstack:v22.03.1-ls11 | 6875 | Wiki — .env gitignored, .env.enc in git |
| bookstack_db | linuxserver/mariadb:10.5.15-r0-ls54 | — | Shared .env with bookstack |
| tandoor | vabene1111/recipes:2.6.9 | 8900 | Recipes — .env gitignored, no .env.enc yet (#40) |
| homeassistant | home-assistant:2023.12.4 | 8123* | *network_mode: host — no port mapping in compose |
| pinchflat | pinchflat:v2025.6.6 | 8945 | YouTube downloader |
| renovate | renovate/renovate:43.140.0 | — | Cron-only; config is server-side, not in this repo |
| komodo | komodo-core:2.1.2 | 9120 | Orchestrator — update via SSH only, never via itself |
| dispatcharr | ghcr.io/dispatcharr/dispatcharr:latest | 9191 | IPTV/EPG manager — runs on PVE LXC 113 (192.168.1.254), NOT Nexus. No semver tags yet — manual update checks required |
The Happy Path — How a Normal Update Works
- Renovate scans — at the top of every hour, Renovate runs as a one-shot container (
docker compose run --rm renovate), scans all compose files, and opens a PR in Forgejo if a newer image tag is available. Log:/mnt/server/containers/renovate/renovate.log - Review the PR — check the image changelog. PRs with major version bumps automatically get a
needs-manual-reviewlabel. No PRs auto-merge — all require manual approval. - Merge to
main— Forgejo fires a webhook to Komodo on port 9120 - Komodo Stage 1 —
git pullfetches the latest compose files from Forgejo - Komodo Stage 2 — five
Deploy Stackactions run in parallel, each callingdocker compose up -dfor its stack - Docker only restarts what changed — if only
pinchflat's image tag changed, only that container restarts. Everything else is untouched. - Verify — Komodo UI → Procedures → run history. The procedure should complete in several seconds. A run that completes in ~1ms means something is wrong (see Troubleshooting).
Images do not auto-update. Komodo does not pull new images on its own. A container only gets a new image when the tag in its compose file changes and a deployment runs. This keeps versions pinned and auditable via git history.
Secrets & SOPS Encryption
During normal operation, SOPS is not involved. Runtime .env files live only on Nexus at /home/matt/repos/homelab-docker/<service>/.env, are gitignored (**/.env), and are read directly by docker compose up -d. Komodo deploys without touching SOPS.
.env.enc files are disaster recovery only. If Nexus needs to be rebuilt from scratch, the plaintext .env files would be gone. .env.enc files are SOPS-encrypted versions committed to git so secrets can be recovered. Currently only bookstack/.env.enc exists — tandoor still needs this (issue #40).
Two age key holders — either can encrypt or decrypt:
- Nexus:
~/.config/sops/age/keys.txt - Atlas:
%APPDATA%\sops\age\keys.txt
Public keys are in .sops.yaml. The regex \.env(\.enc)?$ covers both .env and .env.enc files automatically.
Decrypt a backup:
sops -d --input-type dotenv --output-type dotenv bookstack/.env.enc > bookstack/.env
Never commit a plaintext
.env. The.gitignoreblocks**/.envandkomodo/compose.env.
What Komodo Does and Doesn't Manage
| Component | Managed? | Why |
|---|---|---|
| dashy, bookstack, tandoor, homeassistant, pinchflat | ✅ Yes | Deployed on every push to main |
| renovate | ❌ No | Cron-only; including it would trigger an unwanted scan on every push |
| komodo itself | ❌ Never | It would kill itself mid-deploy — update via SSH instead (see Known Gotchas) |
| dispatcharr | ✅ Yes | Deployed via Periphery agent on LXC 113 (192.168.1.254) |
Renovate Configuration
Renovate's actual config lives server-side only at /mnt/server/containers/renovate/config.js — it contains an unencrypted Forgejo API token and is intentionally not in git. The renovate.json in this repo root is a schema reference pointer only; it contains no actual settings.
Key behaviors:
- All PRs require manual approval —
automerge: falsefor all update types - Major version bumps — automatically labeled
needs-manual-review, lower queue priority - Max 5 open PRs at once (
prConcurrentLimit: 5)
Treat these PRs with extra caution before merging:
- Any PR labeled
needs-manual-review - Bookstack, Tandoor, or Home Assistant (stateful data — back up before merging)
- Postgres or MariaDB major versions (require manual migration steps)
- Home Assistant (breaking changes in nearly every major release)
Troubleshooting
Komodo procedure completes in ~1ms and deployed nothing
The procedure is misconfigured. Stage 2 must have 5 explicit Deploy Stack actions. If it instead has "Batch Deploy Stack If Changed," that action will always skip — stacks have no linked repo hash to compare against. Fix: Komodo UI → Procedures → edit Stage 2 → replace with 5 individual Deploy Stack actions (dashy, bookstack, tandoor, homeassistant, pinchflat).
Pull Repo stage fails — "fatal: not a git repository (or any parent up to mount point /home/matt/repos)"
Git is silently skipping .git because the repo is owned by matt but Periphery runs as root. Root is subject to git's safe.directory check; if the configured path doesn't match exactly, git traverses upward, hits the Docker bind-mount boundary at /home/matt/repos, and reports the misleading "not a git repository" error.
Check the gitconfig mounted into Periphery (komodo/.gitconfig, which becomes /root/.gitconfig in the container). It must contain at minimum:
[safe]
directory = /home/matt/repos/homelab-docker
directory = *
The directory = * wildcard is the safe option: Periphery runs as root and owns the host system anyway, so this doesn't weaken security. Without it, a git version mismatch or path normalization difference can silently break pulls even when the specific path entry is present.
After editing the file, the change is live immediately (the file is bind-mounted read-write into the container — no restart needed). Trigger a test pull to confirm: Komodo UI → Repos → homelab-docker → Pull.
Webhook fires but Pull Repo succeeds while stacks don't update
Check that git pull actually fetched the expected commit. In the Komodo run history for the procedure, expand Stage 1 (Pull Repo) and look at the "Latest Commit" stage output. If the commit hash doesn't match the HEAD on Forgejo, the pull ran but something (network, authentication) prevented it from reaching the remote.
Also verify that the Stack run_directory settings point to /home/matt/repos/homelab-docker/<service>. If they point elsewhere, the compose file Komodo uses won't reflect the git pull.
Komodo API — correct format for scripted operations
The Komodo REST API is served directly at the root (no /api prefix). Authentication requires both x-api-key and x-api-secret headers (not just the key). Example:
# Trigger Pull Repo manually
curl -s -X POST "http://192.168.1.226:9120/execute/PullRepo" \
-H "x-api-key: <key>" \
-H "x-api-secret: <secret>" \
-H "Content-Type: application/json" \
-d '{"repo":"homelab-docker"}'
# Trigger the full deploy procedure
curl -s -X POST "http://192.168.1.226:9120/execute/RunProcedure" \
-H "x-api-key: <key>" \
-H "x-api-secret: <secret>" \
-H "Content-Type: application/json" \
-d '{"procedure":"<procedure-id>"}'
# API docs (full OpenAPI spec)
curl http://192.168.1.226:9120/docs
API keys are created in the Komodo UI under your user profile → API Keys. Each key generates both a key (starts with K_) and a secret (starts with S_) — save both immediately, the secret is not shown again.
Webhook didn't fire after a merge
- Check Forgejo webhook delivery log: repo → Settings → Webhooks → recent deliveries
- Confirm Komodo is reachable:
curl http://192.168.1.226:9120 - Manual deploy:
ssh matt@192.168.1.226 'cd /home/matt/repos/homelab-docker/<service> && docker compose up -d'
Container running the wrong image tag
# Check what's actually running
ssh matt@192.168.1.226 'docker ps --format "table {{.Image}}\t{{.Names}}"'
# Force redeploy a specific stack
ssh matt@192.168.1.226 'cd /home/matt/repos/homelab-docker/<service> && docker compose up -d'
Service broken after image update — rollback
# Option 1: edit the compose file on Nexus to restore the old tag, redeploy
ssh matt@192.168.1.226
vi /home/matt/repos/homelab-docker/<service>/docker-compose.yml
docker compose -f /home/matt/repos/homelab-docker/<service>/docker-compose.yml up -d
# Option 2: revert the merge commit in Forgejo — Komodo redeploys on next push to main
.env file missing or corrupted on Nexus
ssh matt@192.168.1.226
cd /home/matt/repos/homelab-docker
# Bookstack has an encrypted backup:
sops -d --input-type dotenv --output-type dotenv bookstack/.env.enc > bookstack/.env
docker compose -f bookstack/docker-compose.yml up -d
# Tandoor does not have an encrypted backup yet — restore from PBS or recreate manually
Renovate not opening PRs
# Check the log
ssh matt@192.168.1.226 'tail -50 /mnt/server/containers/renovate/renovate.log'
# Trigger a manual run
ssh matt@192.168.1.226 'cd /home/matt/repos/homelab-docker/renovate && docker compose run --rm renovate'
Nexus rebuilt from scratch — full restore
- Install Docker via snap:
sudo snap install docker - Clone repo:
git clone http://192.168.1.240:3000/matt/homelab-docker.git /home/matt/repos/homelab-docker - Restore SOPS age key to
~/.config/sops/age/keys.txt - Restore container data volumes from PBS into
/mnt/server/containers/ - Restore
.envfiles from.env.encbackups:sops -d --input-type dotenv --output-type dotenv <service>/.env.enc > <service>/.env - Restore Renovate config from backup to
/mnt/server/containers/renovate/config.js - Restore Komodo
.envfrom backup (no.env.encexists yet for Komodo) - Start stacks one at a time:
cd /home/matt/repos/homelab-docker/<service> && docker compose up -d - Start Komodo last
Backup Strategy
| What | Where | Method |
|---|---|---|
| Container data (DBs, uploads, config) | /mnt/server/containers/ on Nexus |
Proxmox Backup Server (192.168.1.223) — automated |
| Secrets | bookstack/.env.enc in this repo |
SOPS-encrypted; tandoor (#40) and komodo still need this |
| Compose files & dashboard config | This repo on Forgejo | Every change tracked via PRs |
| Forgejo itself | PBS | Forgejo LXC on PVE is backed up by PBS |
Before upgrading any stateful service, take a manual snapshot first:
ssh matt@192.168.1.226
tar czf /mnt/server/containers/backups/<service>-pre-upgrade-$(date +%Y%m%d).tar.gz /mnt/server/containers/<service>
Adding a New Service
- Create the compose file:
<service>/docker-compose.ymlwith a pinned image tag - Handle secrets: if the service needs a
.env, create it on Nexus at/home/matt/repos/homelab-docker/<service>/.env(gitignore already covers it via**/.env), then create<service>/.env.encas a backup (see Secrets & SOPS) - Add to Komodo: Komodo UI → Stacks → New Stack → set
run_directoryto/home/matt/repos/homelab-docker/<service> - Add to the Deploy procedure: Komodo UI → Procedures → edit Stage 2 → add a Deploy Stack action for the new stack
- Add to Dashy: edit
dashy/conf.ymland add an entry in the appropriate section - Open a PR with the compose file and Dashy changes — merging triggers the first automated deploy
Known Gotchas
docker composenotdocker-compose— Docker on Nexus is installed via snap. The hyphenated form doesn't exist; always use the two-word form.- Home Assistant has no port mapping — it uses
network_mode: hostand binds directly to Nexus's network. Port 8123 is HA's internal default; it's not declared in the compose file. - Home Assistant is
privileged: true— required for hardware device access. Don't remove it. - ConBee II must stay in the compose —
devices: - /dev/ttyACM0:/dev/ttyACM0in the HA compose is the Zigbee USB dongle. Remove it and all Zigbee devices go offline. - Never deploy Komodo via Komodo — it would stop itself mid-deploy. To update Komodo, SSH to Nexus and run
docker compose up -din the komodo directory directly. - Comment-only changes don't restart containers —
docker compose up -dcompares the parsed config, not the raw file text. Adding a YAML comment will not trigger a restart. This is correct behavior. pull_on_deployis off — Komodo does not pull new images when deploying a stack. New images only arrive when the image tag in the compose file changes. Keep this setting off.renovate.jsonis a placeholder — the file in the repo root is just a schema reference. Real Renovate config (Forgejo endpoint, token, PR rules) is at/mnt/server/containers/renovate/config.json Nexus and is not in git.
Quick Reference
| Thing | Value |
|---|---|
| Forgejo | http://192.168.1.240:3000 |
| Komodo UI | http://192.168.1.226:9120 |
| Nexus SSH | matt@192.168.1.226 |
| Repo path on Nexus | /home/matt/repos/homelab-docker |
| Container data | /mnt/server/containers/ |
| Renovate config (server-side) | /mnt/server/containers/renovate/config.js |
| Renovate log | /mnt/server/containers/renovate/renovate.log |
| SOPS age key (Nexus) | ~/.config/sops/age/keys.txt |
| SOPS age key (Atlas) | %APPDATA%\sops\age\keys.txt |