claude.symlink/skills/vm-docker/SKILL.md
Use when deploying Docker services on the local VM (hostname: vm, Pop!_OS) with Traefik reverse proxy and Homepage dashboard. Covers crane image workflow, Traefik file-provider registration, Homepage services.yaml entries, and compose templates on the traefik-proxy network.
npx skillsauth add htlin222/dotfiles vm-dockerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Deploy Docker services on the VM (Pop!_OS). Web services go behind Traefik (port 80) and register on Homepage (dashboard at /).
vm.local (mDNS) — use for app CMD_DOMAIN/BASE_URL, NOT vmtraefik-proxy (external bridge) — all web containers must join~/go/bin/crane (NOT on PATH)docker compose (plugin at ~/.docker/cli-plugins/). Must cd into service dir — -f flag brokenPort 80 → Traefik ──┬── / → Homepage (dashboard)
├── /hedgedoc → HedgeDoc (:3000) [subpath-aware]
├── /freshrss → FreshRSS (:80)
├── /traefik → Traefik dashboard (api@internal)
└── /<service> → Your new service
Direct ports ───────── :6060 → BookLore [no subpath support]
Config: ~/traefik/traefik.yml — static config
~/traefik/dynamic/<service>.yml — per-service routing
~/homepage/config/services.yaml — dashboard entries
mkdir -p ~/docker/<service>/{data,config}~/go/bin/crane pull <image>:<tag> /tmp/<name>.tardocker load -i /tmp/<name>.tar~/docker/<service>/docker-compose.yml → cd ~/docker/<service> && docker compose up -d~/traefik/dynamic/<service>.yml (auto-reloads)~/homepage/config/services.yaml + group in settings.yamlcurl -sI --noproxy '*' http://localhost/<service>Always curl-test links before hardcoding them. Do not assume a route works — prove it.
# 1. Test container responds directly (find internal IP or use exposed port)
curl -sI --noproxy '*' http://127.0.0.1:<host-port>/
# 2. Test Traefik route works (after writing dynamic config, before Homepage)
curl -sI --noproxy '*' http://127.0.0.1/<service>/
# 3. Test subpath: check HTML AND assets (not just the first response)
curl -s --noproxy '*' http://127.0.0.1/<service>/ | grep -E '<base href|<script src|<link.*href'
# If asset paths are absolute (e.g. /styles.css not ./<service>/styles.css) → subpath won't work
# 4. Test Homepage href URL resolves (the exact URL that will go in services.yaml)
curl -sI --noproxy '*' <href-value>
# 5. Test widget URLs (e.g. Traefik widget)
curl -s --noproxy '*' http://traefik:8080/api/overview
Rule: If curl returns 404, 502, or broken assets → fix FIRST, then write the config.
| Gotcha | Detail |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Container name | Use http://<container>:<port> in Traefik config, NOT localhost |
| Sub-path + stripPrefix | Need BOTH: stripPrefix in Traefik AND app's BASE_URL/CMD_URL_PATH. Test CSS/JS assets, not just HTML. BUT some SPAs (e.g. Booklore) don't support subpath at all — use direct port instead |
| Subpath compatibility | Before deploying behind subpath, check if the app supports it. SPAs with hardcoded <base href="/"> and no BASE_PATH config will break. Use direct host port + Homepage href: http://172.16.252.7:<port> as fallback |
| CMD_DOMAIN must match access IP | For apps like HedgeDoc with CMD_DOMAIN, set it to the IP/hostname users actually use (e.g. 172.16.252.7), NOT vm.local if users access via IP |
| No host ports | Don't expose ports if Traefik handles routing. Exception: apps without subpath support MUST expose host ports |
| Traefik dashboard needs 2 routers | Dashboard at /traefik uses stripPrefix + api@internal. But dashboard JS calls /api/... hardcoded — need a second router PathPrefix(/api) → api@internal (no strip). Without it, API calls fall through to Homepage and dashboard shows empty data. Do NOT use api.basePath — it breaks the insecure port API and the Homepage widget |
| Priority 10 | Set on all routers so Homepage / catch-all still works |
| Auth | auth@file for BasicAuth — omit for public services |
| Volume paths | Always absolute: /home/htlin222/docker/<service>/... |
| Topic | File |
| ------------------------------------------------ | ------------------- |
| Templates (compose, traefik, homepage, makefile) | @templates.md |
| Troubleshooting | @troubleshooting.md |
| Reference stacks (ready-to-deploy) | references/*.yml |
| Group | Current Services |
| -------------- | --------------------------------------------------------- |
| Media | Booklore (direct port :6060, no subpath support) |
| Productivity | HedgeDoc (subpath via Traefik, CMD_DOMAIN=172.16.252.7) |
| Infrastructure | Traefik |
testing
Converts narrative medical text into Pocket Medicine bullet-style notes with proper abbreviations, then modularizes sections exceeding 20 lines into linked standalone files.
development
Use when reviewing a data visualization or figure for clarity, checking if a graph communicates its message without additional context, or iterating on R/Python plot scripts until a naive reader can fully understand the figure.
development
Runs Vale prose linter on markdown/text files and auto-fixes issues. Use when the user asks to lint, proofread, or improve writing quality of markdown or text files.
development
Update dev docs before session compaction to capture progress. Use before compacting conversation, ending sessions, or switching tasks.