Skip to content

Community-Script LXCs

Configuration for applications installed straight into an LXC from the community-scripts.org helpers, rather than run as Docker Compose stacks. 64 of the 66 LXCs carry the community-script tag — this is how most of the fleet is installed. What lives here is the subset whose configuration is worth version-controlling.

A helper script installs the app natively — a systemd unit, files under /opt/<app>, no Docker — so the assumptions the rest of this repo runs on do not hold:

Compose stacksCommunity-script LXCs
Deployed bydocker compose up -d under bws runA one-shot installer, run once by hand
Config livesIn the compose file, in gitInside the guest, at whatever path the app wants
Routed viatraefik-web network + providerTraefik labels in the Proxmox guest description
SecretsInjected by bws run at startNot injected — whatever the app stores on disk

Nothing re-applies these files: a guest rebuilt from its script comes back with stock defaults. Keeping the file here is what makes the customisation recoverable.

One directory per application, mirroring the path inside the guest:

community-scripts-lxc/
└── dashy/
├── README.md # the guest, the service, how to update it
└── user-data/
└── conf.yml # ← /opt/dashy/user-data/conf.yml in CT 270
ApplicationGuestNotes
DashyCT 270The homelab homepage — 89 tiles across 11 sections

Every guest running an application natively — tagged community-script without also carrying docker or docker-template, since those are Docker hosts whose configuration belongs in docker-compose-apps/. That is 40 of the 66 tagged guests; only a couple have configuration tracked here so far, and the rest are listed so the gap is visible.

Routed reports whether the guest’s description carries Traefik labels. no is often fine — plenty of these are reached by IP or consumed by another service. One exception is worth knowing:

  • joplin-server is deliberately unrouted. Joplin validates the Host header against its own APP_BASE_URL and 404s anything else; pointing that at a toolsera.lan name works but then breaks http://192.168.1.20:22300. Reach it by IP.
GuestApplicationAddressRoutedTags
CT 258autobrr192.168.1.76yesarr
CT 208bookorbit192.168.1.197yesbooks, library, reading
CT 221cloudflare-tunnel192.168.1.105noos
CT 261dagu192.168.1.44yesos
CT 270dashy192.168.1.25yesdashboard
CT 232flaresolverr-byparr192.168.1.49nobyparr, flaresolverr
CT 231freshrss192.168.1.19yesrss
CT 212garage192.168.1.15noobject-storage
CT 265gatus192.168.1.64yesmonitoring
CT 252glance192.168.1.158yesdashboard
CT 222jackett192.168.1.22yesjackett
CT 226jellyfin192.168.1.62yesjellyfin, media
CT 215joplin-server192.168.1.20nonotes
CT 236komga192.168.1.166yescomic, ebook, media
CT 246linkwarden192.168.1.183yesbookmark
CT 203metube192.168.1.21yesmedia, youtube
CT 227n8n192.168.1.46yesautomation
CT 264nx-tempo-prod192.168.1.104noos
CT 253opengist192.168.1.189yesdevelopment
CT 132pihole192.168.1.184yesadblock
CT 204plex-us-24192.168.1.13yesmedia
VM 224prod-vm-us192.168.1.148no—
CT 267profilarr192.168.1.113yesarr, config, radarr, sonarr
CT 230prowlarr192.168.1.53yesarr
CT 244pulse192.168.1.50yesmonitoring, proxmox
CT 210pve-scripts-local192.168.1.110yespve-scripts-local
CT 209radarr192.168.1.97yesarr
CT 254radarr-anime192.168.1.36noarr
CT 213readeck192.168.1.144yesbookmark
CT 266seerr192.168.1.139yesmedia
CT 241semaphore192.168.1.137yesdev_ops, semaphore
CT 207sonarr192.168.1.120yesarr
CT 126sonarr-anime192.168.1.177yesarr, sonar-anime
CT 202suggestarr192.168.1.24yesarr, media
CT 247tautulli192.168.1.52yesmedia
CT 271tracearr192.168.1.165yesmedia
CT 242unbound192.168.1.95nodns, unbound
CT 240upsnap192.168.1.196yesnetwork, upsnap
CT 248uptimekuma192.168.1.187yesanalytics, monitoring
CT 225whois192.168.1.98yesarr

Run this on pve-dell (192.168.1.131) and replace the generated block with its output. It asks each guest for its address — lxc-info for LXCs, the QEMU guest agent for VMs — and only falls back to the address the installer recorded as a tag, which VMs never have. _stopped_ means exactly that.

Terminal window
cd /etc/pve
for f in lxc/*.conf qemu-server/*.conf; do
[ -e "$f" ] || continue
id=$(basename "$f" .conf)
case "$f" in lxc/*) kind="CT"; nk="hostname";; *) kind="VM"; nk="name";; esac
name=$(grep -m1 "^$nk:" "$f" | cut -d" " -f2)
tags=$(grep -m1 "^tags:" "$f" | cut -d" " -f2-)
echo "$tags" | tr ";" "\n" | grep -qx "community-script" || continue
echo "$tags" | tr ";" "\n" | grep -qxE "docker|docker-template" && continue
if [ "$kind" = CT ]; then
state=$(pct status "$id" 2>/dev/null | awk "{print \$2}")
ip=$(lxc-info -n "$id" -iH 2>/dev/null | grep -m1 -E "^192\.168\.")
else
state=$(qm status "$id" 2>/dev/null | awk "{print \$2}")
ip=$(qm agent "$id" network-get-interfaces 2>/dev/null \
| grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
fi
[ -n "$ip" ] || ip=$(echo "$tags" | tr ";" "\n" | grep -m1 -E "^192\.168\.")
[ -n "$ip" ] || { [ "$state" = running ] && ip="—" || ip="_stopped_"; }
# paste -d cycles its delimiter list, so join on a bare comma then space it out
tagl=$(echo "$tags" | tr ";" "\n" \
| grep -vE "^[0-9]+\.|^community-script$|^tailscale$" \
| paste -sd , - | sed "s/,/, /g")
grep -q "traefik.enable" "$f" && r=yes || r=no
echo "| $kind $id | $name | $ip | $r | ${tagl:-—} |"
done | sort -t"|" -k3

stirling-pdf (CT 113) and sftpgo (CT 214) carry the project’s former proxmox-helper-scripts tag and fall outside this filter. Retag them if they should appear.

This is the canonical copy; the guest holds a copy, and they drift unless someone pushes. Most LXCs refuse SSH, so pushing goes through the node:

Terminal window
pct push <vmid> <local-file> <path-inside-guest>
pct exec <vmid> -- systemctl restart <service>

See Host access notes for why direct SSH mostly fails.

Several of these apps can also edit their own configuration from their web UI, writing to the same file. Treat UI edits as a scratchpad and fold anything worth keeping back into the copy here.

The traefik-proxmox-provider plugin reads Traefik labels out of the guest’s description and points a service at its IP:port. Compose labels and container names do nothing here.

A guest with no labels gets no router — and because the toolsera.lan zone wildcards to Traefik, its hostname still resolves and answers 404, which reads as a broken app rather than a missing route. The Dashy README has the label block and the pct set command that applies it without destroying the installer’s description. The plugin polls every 30 seconds; no Traefik restart is involved.

  1. Install it from community-scripts.org onto the node.
  2. If it needs routing, append a Traefik label block to the guest’s description and confirm the route answers.
  3. Create community-scripts-lxc/<app>/ and copy in only the files worth tracking.
  4. Write an <app>/README.md recording the guest ID, systemd service, config path inside the guest, and port.
  5. Add both to SOURCES in docs/scripts/sync-content.mjs and to the sidebar in docs/astro.config.mjs — a listed source that goes missing fails the docs build rather than dropping the page silently.

Never copy in databases, caches, or anything holding a secret. Files here are plain git objects with no bws run around them; the gitleaks hook is the backstop, not the policy.