From b3af0c2109ced8ff1f2040e0859f9bd05e4e3904 Mon Sep 17 00:00:00 2001 From: menzelj Date: Mon, 31 Aug 2026 09:12:34 +0200 Subject: [PATCH] Grow the bundled template library to 83 homelab apps (0.43.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The library shipped five templates. This adds 78 more, covering what homelab lists and the self-hosted community actually run: media servers and the *arr automation chain, DNS ad-blocking, reverse proxies, VPN, SSO, monitoring and dashboards, files/backup, notes and wikis, home automation, dev tooling, databases, local AI, finance and notifications. Every template follows the existing shape — compose.yaml, .env.example, template.json — with PUID/PGID/TZ/DATA_PATH/HTTP_PORT knobs and no literal secrets: anything that must be set uses ${VAR:?...} so deploy fails loudly instead of coming up with a default password. Six ship the extra config file their app needs (prometheus.yml, Caddyfile, mosquitto.conf, frigate config.yml, Authelia's two files, zigbee2mqtt configuration.yaml), which the folder-copy pull already carries into the new stack. All 95 referenced images were verified pullable against their registries. Default host ports were deconflicted so several templates can be pulled side by side; the only remaining overlaps are between services you would never run together anyway (two DNS blockers on 53, three reverse proxies on 80/443). The Templates page would have been an unusable 83-card grid, so it now has a search box and tag filter chips, with the tags on each card clickable to filter by. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dk43rmEeRfYi5wsLDbmfyG --- README.md | 14 ++- backend/templates/actual-budget/.env.example | 3 + backend/templates/actual-budget/compose.yaml | 11 +++ backend/templates/actual-budget/template.json | 9 ++ backend/templates/adguard-home/.env.example | 4 + backend/templates/adguard-home/compose.yaml | 14 +++ backend/templates/adguard-home/template.json | 10 ++ backend/templates/audiobookshelf/.env.example | 5 + backend/templates/audiobookshelf/compose.yaml | 14 +++ .../templates/audiobookshelf/template.json | 10 ++ backend/templates/authelia/.env.example | 3 + backend/templates/authelia/compose.yaml | 13 +++ backend/templates/authelia/configuration.yml | 34 +++++++ backend/templates/authelia/template.json | 10 ++ backend/templates/authelia/users_database.yml | 10 ++ backend/templates/authentik/.env.example | 7 ++ backend/templates/authentik/compose.yaml | 61 ++++++++++++ backend/templates/authentik/template.json | 10 ++ backend/templates/bazarr/.env.example | 6 ++ backend/templates/bazarr/compose.yaml | 14 +++ backend/templates/bazarr/template.json | 10 ++ backend/templates/beszel/.env.example | 4 + backend/templates/beszel/compose.yaml | 22 +++++ backend/templates/beszel/template.json | 10 ++ backend/templates/bookstack/.env.example | 10 ++ backend/templates/bookstack/compose.yaml | 38 ++++++++ backend/templates/bookstack/template.json | 10 ++ backend/templates/caddy/.env.example | 3 + backend/templates/caddy/Caddyfile | 11 +++ backend/templates/caddy/compose.yaml | 13 +++ backend/templates/caddy/template.json | 9 ++ backend/templates/calibre-web/.env.example | 6 ++ backend/templates/calibre-web/compose.yaml | 16 ++++ backend/templates/calibre-web/template.json | 9 ++ .../templates/changedetection-io/.env.example | 3 + .../templates/changedetection-io/compose.yaml | 25 +++++ .../changedetection-io/template.json | 9 ++ backend/templates/code-server/.env.example | 9 ++ backend/templates/code-server/compose.yaml | 17 ++++ backend/templates/code-server/template.json | 8 ++ backend/templates/dashy/.env.example | 2 + backend/templates/dashy/compose.yaml | 12 +++ backend/templates/dashy/template.json | 8 ++ .../templates/docker-registry/.env.example | 4 + .../templates/docker-registry/compose.yaml | 29 ++++++ .../templates/docker-registry/template.json | 9 ++ backend/templates/dockge/.env.example | 3 + backend/templates/dockge/compose.yaml | 14 +++ backend/templates/dockge/template.json | 9 ++ backend/templates/dozzle/.env.example | 1 + backend/templates/dozzle/compose.yaml | 12 +++ backend/templates/dozzle/template.json | 10 ++ backend/templates/duplicati/.env.example | 10 ++ backend/templates/duplicati/compose.yaml | 17 ++++ backend/templates/duplicati/template.json | 9 ++ backend/templates/emby/.env.example | 6 ++ backend/templates/emby/compose.yaml | 17 ++++ backend/templates/emby/template.json | 9 ++ backend/templates/esphome/.env.example | 6 ++ backend/templates/esphome/compose.yaml | 15 +++ backend/templates/esphome/template.json | 10 ++ backend/templates/filebrowser/.env.example | 5 + backend/templates/filebrowser/compose.yaml | 16 ++++ backend/templates/filebrowser/template.json | 8 ++ backend/templates/firefly-iii/.env.example | 7 ++ backend/templates/firefly-iii/compose.yaml | 37 ++++++++ backend/templates/firefly-iii/template.json | 9 ++ backend/templates/forgejo/.env.example | 7 ++ backend/templates/forgejo/compose.yaml | 17 ++++ backend/templates/forgejo/template.json | 9 ++ backend/templates/freshrss/.env.example | 4 + backend/templates/freshrss/compose.yaml | 13 +++ backend/templates/freshrss/template.json | 9 ++ backend/templates/frigate/.env.example | 8 ++ backend/templates/frigate/compose.yaml | 32 +++++++ backend/templates/frigate/config.yml | 38 ++++++++ backend/templates/frigate/template.json | 10 ++ backend/templates/gotify/.env.example | 6 ++ backend/templates/gotify/compose.yaml | 13 +++ backend/templates/gotify/template.json | 9 ++ backend/templates/grafana/.env.example | 8 ++ backend/templates/grafana/compose.yaml | 15 +++ backend/templates/grafana/template.json | 10 ++ backend/templates/grocy/.env.example | 5 + backend/templates/grocy/compose.yaml | 13 +++ backend/templates/grocy/template.json | 9 ++ backend/templates/heimdall/.env.example | 6 ++ backend/templates/heimdall/compose.yaml | 14 +++ backend/templates/heimdall/template.json | 8 ++ backend/templates/homarr/.env.example | 4 + backend/templates/homarr/compose.yaml | 14 +++ backend/templates/homarr/template.json | 8 ++ backend/templates/home-assistant/.env.example | 3 + backend/templates/home-assistant/compose.yaml | 17 ++++ .../templates/home-assistant/template.json | 9 ++ backend/templates/homepage/.env.example | 5 + backend/templates/homepage/compose.yaml | 15 +++ backend/templates/homepage/template.json | 8 ++ backend/templates/immich/.env.example | 11 +++ backend/templates/immich/compose.yaml | 51 ++++++++++ backend/templates/immich/template.json | 10 ++ backend/templates/it-tools/.env.example | 1 + backend/templates/it-tools/compose.yaml | 7 ++ backend/templates/it-tools/template.json | 9 ++ backend/templates/jellyseerr/.env.example | 3 + backend/templates/jellyseerr/compose.yaml | 12 +++ backend/templates/jellyseerr/template.json | 10 ++ backend/templates/kavita/.env.example | 4 + backend/templates/kavita/compose.yaml | 12 +++ backend/templates/kavita/template.json | 9 ++ backend/templates/komga/.env.example | 6 ++ backend/templates/komga/compose.yaml | 13 +++ backend/templates/komga/template.json | 9 ++ backend/templates/lidarr/.env.example | 7 ++ backend/templates/lidarr/compose.yaml | 15 +++ backend/templates/lidarr/template.json | 11 +++ backend/templates/linkwarden/.env.example | 7 ++ backend/templates/linkwarden/compose.yaml | 38 ++++++++ backend/templates/linkwarden/template.json | 9 ++ backend/templates/mariadb/.env.example | 9 ++ backend/templates/mariadb/compose.yaml | 28 ++++++ backend/templates/mariadb/template.json | 9 ++ backend/templates/mealie/.env.example | 6 ++ backend/templates/mealie/compose.yaml | 19 ++++ backend/templates/mealie/template.json | 9 ++ backend/templates/memos/.env.example | 3 + backend/templates/memos/compose.yaml | 11 +++ backend/templates/memos/template.json | 9 ++ .../templates/minecraft-server/.env.example | 11 +++ .../templates/minecraft-server/compose.yaml | 20 ++++ .../templates/minecraft-server/template.json | 8 ++ backend/templates/miniflux/.env.example | 6 ++ backend/templates/miniflux/compose.yaml | 32 +++++++ backend/templates/miniflux/template.json | 9 ++ backend/templates/minio/.env.example | 7 ++ backend/templates/minio/compose.yaml | 15 +++ backend/templates/minio/template.json | 10 ++ backend/templates/mosquitto/.env.example | 3 + backend/templates/mosquitto/compose.yaml | 12 +++ backend/templates/mosquitto/mosquitto.conf | 13 +++ backend/templates/mosquitto/template.json | 10 ++ backend/templates/n8n/.env.example | 8 ++ backend/templates/n8n/compose.yaml | 19 ++++ backend/templates/n8n/template.json | 10 ++ backend/templates/navidrome/.env.example | 5 + backend/templates/navidrome/compose.yaml | 15 +++ backend/templates/navidrome/template.json | 10 ++ backend/templates/netdata/.env.example | 3 + backend/templates/netdata/compose.yaml | 26 +++++ backend/templates/netdata/template.json | 9 ++ backend/templates/nextcloud/.env.example | 9 ++ backend/templates/nextcloud/compose.yaml | 42 +++++++++ backend/templates/nextcloud/template.json | 10 ++ .../nginx-proxy-manager/.env.example | 4 + .../nginx-proxy-manager/compose.yaml | 12 +++ .../nginx-proxy-manager/template.json | 9 ++ backend/templates/node-red/.env.example | 5 + backend/templates/node-red/compose.yaml | 12 +++ backend/templates/node-red/template.json | 10 ++ backend/templates/ntfy/.env.example | 6 ++ backend/templates/ntfy/compose.yaml | 21 +++++ backend/templates/ntfy/template.json | 9 ++ .../templates/ollama-open-webui/.env.example | 5 + .../templates/ollama-open-webui/compose.yaml | 31 ++++++ .../templates/ollama-open-webui/template.json | 9 ++ backend/templates/photoprism/.env.example | 10 ++ backend/templates/photoprism/compose.yaml | 41 ++++++++ backend/templates/photoprism/template.json | 9 ++ backend/templates/pihole/.env.example | 5 + backend/templates/pihole/compose.yaml | 20 ++++ backend/templates/pihole/template.json | 10 ++ backend/templates/plex/.env.example | 7 ++ backend/templates/plex/compose.yaml | 20 ++++ backend/templates/plex/template.json | 9 ++ backend/templates/portainer/.env.example | 3 + backend/templates/portainer/compose.yaml | 11 +++ backend/templates/portainer/template.json | 9 ++ backend/templates/postgres/.env.example | 8 ++ backend/templates/postgres/compose.yaml | 30 ++++++ backend/templates/postgres/template.json | 9 ++ backend/templates/prometheus/.env.example | 5 + backend/templates/prometheus/compose.yaml | 39 ++++++++ backend/templates/prometheus/prometheus.yml | 16 ++++ backend/templates/prometheus/template.json | 9 ++ backend/templates/prowlarr/.env.example | 5 + backend/templates/prowlarr/compose.yaml | 13 +++ backend/templates/prowlarr/template.json | 10 ++ backend/templates/qbittorrent/.env.example | 7 ++ backend/templates/qbittorrent/compose.yaml | 18 ++++ backend/templates/qbittorrent/template.json | 9 ++ backend/templates/radarr/.env.example | 7 ++ backend/templates/radarr/compose.yaml | 15 +++ backend/templates/radarr/template.json | 10 ++ backend/templates/redis/.env.example | 4 + backend/templates/redis/compose.yaml | 20 ++++ backend/templates/redis/template.json | 9 ++ backend/templates/sabnzbd/.env.example | 7 ++ backend/templates/sabnzbd/compose.yaml | 15 +++ backend/templates/sabnzbd/template.json | 9 ++ backend/templates/scrutiny/.env.example | 2 + backend/templates/scrutiny/compose.yaml | 19 ++++ backend/templates/scrutiny/template.json | 9 ++ backend/templates/searxng/.env.example | 5 + backend/templates/searxng/compose.yaml | 20 ++++ backend/templates/searxng/template.json | 9 ++ backend/templates/sonarr/.env.example | 7 ++ backend/templates/sonarr/compose.yaml | 15 +++ backend/templates/sonarr/template.json | 10 ++ .../templates/speedtest-tracker/.env.example | 10 ++ .../templates/speedtest-tracker/compose.yaml | 20 ++++ .../templates/speedtest-tracker/template.json | 9 ++ backend/templates/stirling-pdf/.env.example | 3 + backend/templates/stirling-pdf/compose.yaml | 13 +++ backend/templates/stirling-pdf/template.json | 9 ++ backend/templates/syncthing/.env.example | 9 ++ backend/templates/syncthing/compose.yaml | 18 ++++ backend/templates/syncthing/template.json | 10 ++ backend/templates/tailscale/.env.example | 5 + backend/templates/tailscale/compose.yaml | 20 ++++ backend/templates/tailscale/template.json | 10 ++ backend/templates/traefik/.env.example | 6 ++ backend/templates/traefik/compose.yaml | 29 ++++++ backend/templates/traefik/template.json | 9 ++ backend/templates/trilium/.env.example | 3 + backend/templates/trilium/compose.yaml | 11 +++ backend/templates/trilium/template.json | 9 ++ backend/templates/vikunja/.env.example | 6 ++ backend/templates/vikunja/compose.yaml | 36 +++++++ backend/templates/vikunja/template.json | 9 ++ backend/templates/watchtower/.env.example | 5 + backend/templates/watchtower/compose.yaml | 16 ++++ backend/templates/watchtower/template.json | 9 ++ backend/templates/wg-easy/.env.example | 3 + backend/templates/wg-easy/compose.yaml | 17 ++++ backend/templates/wg-easy/template.json | 10 ++ backend/templates/wiki-js/.env.example | 3 + backend/templates/wiki-js/compose.yaml | 27 ++++++ backend/templates/wiki-js/template.json | 10 ++ backend/templates/zigbee2mqtt/.env.example | 4 + backend/templates/zigbee2mqtt/compose.yaml | 18 ++++ .../templates/zigbee2mqtt/configuration.yaml | 13 +++ backend/templates/zigbee2mqtt/template.json | 10 ++ backend/version.py | 2 +- frontend/package.json | 2 +- frontend/src/pages/Templates.tsx | 94 +++++++++++++++++-- 245 files changed, 2963 insertions(+), 14 deletions(-) create mode 100644 backend/templates/actual-budget/.env.example create mode 100644 backend/templates/actual-budget/compose.yaml create mode 100644 backend/templates/actual-budget/template.json create mode 100644 backend/templates/adguard-home/.env.example create mode 100644 backend/templates/adguard-home/compose.yaml create mode 100644 backend/templates/adguard-home/template.json create mode 100644 backend/templates/audiobookshelf/.env.example create mode 100644 backend/templates/audiobookshelf/compose.yaml create mode 100644 backend/templates/audiobookshelf/template.json create mode 100644 backend/templates/authelia/.env.example create mode 100644 backend/templates/authelia/compose.yaml create mode 100644 backend/templates/authelia/configuration.yml create mode 100644 backend/templates/authelia/template.json create mode 100644 backend/templates/authelia/users_database.yml create mode 100644 backend/templates/authentik/.env.example create mode 100644 backend/templates/authentik/compose.yaml create mode 100644 backend/templates/authentik/template.json create mode 100644 backend/templates/bazarr/.env.example create mode 100644 backend/templates/bazarr/compose.yaml create mode 100644 backend/templates/bazarr/template.json create mode 100644 backend/templates/beszel/.env.example create mode 100644 backend/templates/beszel/compose.yaml create mode 100644 backend/templates/beszel/template.json create mode 100644 backend/templates/bookstack/.env.example create mode 100644 backend/templates/bookstack/compose.yaml create mode 100644 backend/templates/bookstack/template.json create mode 100644 backend/templates/caddy/.env.example create mode 100644 backend/templates/caddy/Caddyfile create mode 100644 backend/templates/caddy/compose.yaml create mode 100644 backend/templates/caddy/template.json create mode 100644 backend/templates/calibre-web/.env.example create mode 100644 backend/templates/calibre-web/compose.yaml create mode 100644 backend/templates/calibre-web/template.json create mode 100644 backend/templates/changedetection-io/.env.example create mode 100644 backend/templates/changedetection-io/compose.yaml create mode 100644 backend/templates/changedetection-io/template.json create mode 100644 backend/templates/code-server/.env.example create mode 100644 backend/templates/code-server/compose.yaml create mode 100644 backend/templates/code-server/template.json create mode 100644 backend/templates/dashy/.env.example create mode 100644 backend/templates/dashy/compose.yaml create mode 100644 backend/templates/dashy/template.json create mode 100644 backend/templates/docker-registry/.env.example create mode 100644 backend/templates/docker-registry/compose.yaml create mode 100644 backend/templates/docker-registry/template.json create mode 100644 backend/templates/dockge/.env.example create mode 100644 backend/templates/dockge/compose.yaml create mode 100644 backend/templates/dockge/template.json create mode 100644 backend/templates/dozzle/.env.example create mode 100644 backend/templates/dozzle/compose.yaml create mode 100644 backend/templates/dozzle/template.json create mode 100644 backend/templates/duplicati/.env.example create mode 100644 backend/templates/duplicati/compose.yaml create mode 100644 backend/templates/duplicati/template.json create mode 100644 backend/templates/emby/.env.example create mode 100644 backend/templates/emby/compose.yaml create mode 100644 backend/templates/emby/template.json create mode 100644 backend/templates/esphome/.env.example create mode 100644 backend/templates/esphome/compose.yaml create mode 100644 backend/templates/esphome/template.json create mode 100644 backend/templates/filebrowser/.env.example create mode 100644 backend/templates/filebrowser/compose.yaml create mode 100644 backend/templates/filebrowser/template.json create mode 100644 backend/templates/firefly-iii/.env.example create mode 100644 backend/templates/firefly-iii/compose.yaml create mode 100644 backend/templates/firefly-iii/template.json create mode 100644 backend/templates/forgejo/.env.example create mode 100644 backend/templates/forgejo/compose.yaml create mode 100644 backend/templates/forgejo/template.json create mode 100644 backend/templates/freshrss/.env.example create mode 100644 backend/templates/freshrss/compose.yaml create mode 100644 backend/templates/freshrss/template.json create mode 100644 backend/templates/frigate/.env.example create mode 100644 backend/templates/frigate/compose.yaml create mode 100644 backend/templates/frigate/config.yml create mode 100644 backend/templates/frigate/template.json create mode 100644 backend/templates/gotify/.env.example create mode 100644 backend/templates/gotify/compose.yaml create mode 100644 backend/templates/gotify/template.json create mode 100644 backend/templates/grafana/.env.example create mode 100644 backend/templates/grafana/compose.yaml create mode 100644 backend/templates/grafana/template.json create mode 100644 backend/templates/grocy/.env.example create mode 100644 backend/templates/grocy/compose.yaml create mode 100644 backend/templates/grocy/template.json create mode 100644 backend/templates/heimdall/.env.example create mode 100644 backend/templates/heimdall/compose.yaml create mode 100644 backend/templates/heimdall/template.json create mode 100644 backend/templates/homarr/.env.example create mode 100644 backend/templates/homarr/compose.yaml create mode 100644 backend/templates/homarr/template.json create mode 100644 backend/templates/home-assistant/.env.example create mode 100644 backend/templates/home-assistant/compose.yaml create mode 100644 backend/templates/home-assistant/template.json create mode 100644 backend/templates/homepage/.env.example create mode 100644 backend/templates/homepage/compose.yaml create mode 100644 backend/templates/homepage/template.json create mode 100644 backend/templates/immich/.env.example create mode 100644 backend/templates/immich/compose.yaml create mode 100644 backend/templates/immich/template.json create mode 100644 backend/templates/it-tools/.env.example create mode 100644 backend/templates/it-tools/compose.yaml create mode 100644 backend/templates/it-tools/template.json create mode 100644 backend/templates/jellyseerr/.env.example create mode 100644 backend/templates/jellyseerr/compose.yaml create mode 100644 backend/templates/jellyseerr/template.json create mode 100644 backend/templates/kavita/.env.example create mode 100644 backend/templates/kavita/compose.yaml create mode 100644 backend/templates/kavita/template.json create mode 100644 backend/templates/komga/.env.example create mode 100644 backend/templates/komga/compose.yaml create mode 100644 backend/templates/komga/template.json create mode 100644 backend/templates/lidarr/.env.example create mode 100644 backend/templates/lidarr/compose.yaml create mode 100644 backend/templates/lidarr/template.json create mode 100644 backend/templates/linkwarden/.env.example create mode 100644 backend/templates/linkwarden/compose.yaml create mode 100644 backend/templates/linkwarden/template.json create mode 100644 backend/templates/mariadb/.env.example create mode 100644 backend/templates/mariadb/compose.yaml create mode 100644 backend/templates/mariadb/template.json create mode 100644 backend/templates/mealie/.env.example create mode 100644 backend/templates/mealie/compose.yaml create mode 100644 backend/templates/mealie/template.json create mode 100644 backend/templates/memos/.env.example create mode 100644 backend/templates/memos/compose.yaml create mode 100644 backend/templates/memos/template.json create mode 100644 backend/templates/minecraft-server/.env.example create mode 100644 backend/templates/minecraft-server/compose.yaml create mode 100644 backend/templates/minecraft-server/template.json create mode 100644 backend/templates/miniflux/.env.example create mode 100644 backend/templates/miniflux/compose.yaml create mode 100644 backend/templates/miniflux/template.json create mode 100644 backend/templates/minio/.env.example create mode 100644 backend/templates/minio/compose.yaml create mode 100644 backend/templates/minio/template.json create mode 100644 backend/templates/mosquitto/.env.example create mode 100644 backend/templates/mosquitto/compose.yaml create mode 100644 backend/templates/mosquitto/mosquitto.conf create mode 100644 backend/templates/mosquitto/template.json create mode 100644 backend/templates/n8n/.env.example create mode 100644 backend/templates/n8n/compose.yaml create mode 100644 backend/templates/n8n/template.json create mode 100644 backend/templates/navidrome/.env.example create mode 100644 backend/templates/navidrome/compose.yaml create mode 100644 backend/templates/navidrome/template.json create mode 100644 backend/templates/netdata/.env.example create mode 100644 backend/templates/netdata/compose.yaml create mode 100644 backend/templates/netdata/template.json create mode 100644 backend/templates/nextcloud/.env.example create mode 100644 backend/templates/nextcloud/compose.yaml create mode 100644 backend/templates/nextcloud/template.json create mode 100644 backend/templates/nginx-proxy-manager/.env.example create mode 100644 backend/templates/nginx-proxy-manager/compose.yaml create mode 100644 backend/templates/nginx-proxy-manager/template.json create mode 100644 backend/templates/node-red/.env.example create mode 100644 backend/templates/node-red/compose.yaml create mode 100644 backend/templates/node-red/template.json create mode 100644 backend/templates/ntfy/.env.example create mode 100644 backend/templates/ntfy/compose.yaml create mode 100644 backend/templates/ntfy/template.json create mode 100644 backend/templates/ollama-open-webui/.env.example create mode 100644 backend/templates/ollama-open-webui/compose.yaml create mode 100644 backend/templates/ollama-open-webui/template.json create mode 100644 backend/templates/photoprism/.env.example create mode 100644 backend/templates/photoprism/compose.yaml create mode 100644 backend/templates/photoprism/template.json create mode 100644 backend/templates/pihole/.env.example create mode 100644 backend/templates/pihole/compose.yaml create mode 100644 backend/templates/pihole/template.json create mode 100644 backend/templates/plex/.env.example create mode 100644 backend/templates/plex/compose.yaml create mode 100644 backend/templates/plex/template.json create mode 100644 backend/templates/portainer/.env.example create mode 100644 backend/templates/portainer/compose.yaml create mode 100644 backend/templates/portainer/template.json create mode 100644 backend/templates/postgres/.env.example create mode 100644 backend/templates/postgres/compose.yaml create mode 100644 backend/templates/postgres/template.json create mode 100644 backend/templates/prometheus/.env.example create mode 100644 backend/templates/prometheus/compose.yaml create mode 100644 backend/templates/prometheus/prometheus.yml create mode 100644 backend/templates/prometheus/template.json create mode 100644 backend/templates/prowlarr/.env.example create mode 100644 backend/templates/prowlarr/compose.yaml create mode 100644 backend/templates/prowlarr/template.json create mode 100644 backend/templates/qbittorrent/.env.example create mode 100644 backend/templates/qbittorrent/compose.yaml create mode 100644 backend/templates/qbittorrent/template.json create mode 100644 backend/templates/radarr/.env.example create mode 100644 backend/templates/radarr/compose.yaml create mode 100644 backend/templates/radarr/template.json create mode 100644 backend/templates/redis/.env.example create mode 100644 backend/templates/redis/compose.yaml create mode 100644 backend/templates/redis/template.json create mode 100644 backend/templates/sabnzbd/.env.example create mode 100644 backend/templates/sabnzbd/compose.yaml create mode 100644 backend/templates/sabnzbd/template.json create mode 100644 backend/templates/scrutiny/.env.example create mode 100644 backend/templates/scrutiny/compose.yaml create mode 100644 backend/templates/scrutiny/template.json create mode 100644 backend/templates/searxng/.env.example create mode 100644 backend/templates/searxng/compose.yaml create mode 100644 backend/templates/searxng/template.json create mode 100644 backend/templates/sonarr/.env.example create mode 100644 backend/templates/sonarr/compose.yaml create mode 100644 backend/templates/sonarr/template.json create mode 100644 backend/templates/speedtest-tracker/.env.example create mode 100644 backend/templates/speedtest-tracker/compose.yaml create mode 100644 backend/templates/speedtest-tracker/template.json create mode 100644 backend/templates/stirling-pdf/.env.example create mode 100644 backend/templates/stirling-pdf/compose.yaml create mode 100644 backend/templates/stirling-pdf/template.json create mode 100644 backend/templates/syncthing/.env.example create mode 100644 backend/templates/syncthing/compose.yaml create mode 100644 backend/templates/syncthing/template.json create mode 100644 backend/templates/tailscale/.env.example create mode 100644 backend/templates/tailscale/compose.yaml create mode 100644 backend/templates/tailscale/template.json create mode 100644 backend/templates/traefik/.env.example create mode 100644 backend/templates/traefik/compose.yaml create mode 100644 backend/templates/traefik/template.json create mode 100644 backend/templates/trilium/.env.example create mode 100644 backend/templates/trilium/compose.yaml create mode 100644 backend/templates/trilium/template.json create mode 100644 backend/templates/vikunja/.env.example create mode 100644 backend/templates/vikunja/compose.yaml create mode 100644 backend/templates/vikunja/template.json create mode 100644 backend/templates/watchtower/.env.example create mode 100644 backend/templates/watchtower/compose.yaml create mode 100644 backend/templates/watchtower/template.json create mode 100644 backend/templates/wg-easy/.env.example create mode 100644 backend/templates/wg-easy/compose.yaml create mode 100644 backend/templates/wg-easy/template.json create mode 100644 backend/templates/wiki-js/.env.example create mode 100644 backend/templates/wiki-js/compose.yaml create mode 100644 backend/templates/wiki-js/template.json create mode 100644 backend/templates/zigbee2mqtt/.env.example create mode 100644 backend/templates/zigbee2mqtt/compose.yaml create mode 100644 backend/templates/zigbee2mqtt/configuration.yaml create mode 100644 backend/templates/zigbee2mqtt/template.json diff --git a/README.md b/README.md index b48897e..54c237c 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,15 @@ as intuitive as Dockge, as capable as Portainer for Compose workflows. (`/proc/net/tcp[6]`) and running container bindings, with a confirm dialog. - **Resource limits**: CPU/memory sliders in the editor → `deploy.resources.limits`. - **Template library**: each template is a ready-to-run **stack folder** in git - (`backend/templates//` — `compose.yaml` + `.env.example` + `template.json`). - Bundled set: Jellyfin, Vaultwarden, Uptime-Kuma, Paperless-NGX, Gitea. "Pull" - copies the whole folder into a new stack (`.env.example` → `.env`) which you then - edit and deploy. Save any stack back as a custom template (stored under - `${DATA_DIR}/templates/`). Add your own by dropping a folder into the templates dir. + (`backend/templates//` — `compose.yaml` + `.env.example` + `template.json`, + plus any extra config the app needs, e.g. `prometheus.yml` or a `Caddyfile`). + 83 bundled homelab apps across media, *arr automation, networking, reverse + proxies, VPN, SSO, monitoring, dashboards, files/backup, notes and wikis, home + automation, dev tooling, databases, local AI and more — searchable and + filterable by tag on the Templates page. "Pull" copies the whole folder into a + new stack (`.env.example` → `.env`) which you then edit and deploy. Save any + stack back as a custom template (stored under `${DATA_DIR}/templates/`). Add + your own by dropping a folder into the templates dir. - **Healthcheck status** surfaced per container in the stack overview. ### Phase 4 — Operations diff --git a/backend/templates/actual-budget/.env.example b/backend/templates/actual-budget/.env.example new file mode 100644 index 0000000..09a0c6d --- /dev/null +++ b/backend/templates/actual-budget/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/actual +HTTP_PORT=5006 diff --git a/backend/templates/actual-budget/compose.yaml b/backend/templates/actual-budget/compose.yaml new file mode 100644 index 0000000..49ae6b3 --- /dev/null +++ b/backend/templates/actual-budget/compose.yaml @@ -0,0 +1,11 @@ +services: + actual: + image: ghcr.io/actualbudget/actual-server:latest + container_name: actual-budget + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5006}:5006" + volumes: + - ${DATA_PATH:-/srv/actual}:/data diff --git a/backend/templates/actual-budget/template.json b/backend/templates/actual-budget/template.json new file mode 100644 index 0000000..2bfd8f4 --- /dev/null +++ b/backend/templates/actual-budget/template.json @@ -0,0 +1,9 @@ +{ + "name": "Actual Budget", + "description": "Local-first envelope budgeting with end-to-end encrypted sync across devices.", + "tags": [ + "finance", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/adguard-home/.env.example b/backend/templates/adguard-home/.env.example new file mode 100644 index 0000000..adc5bad --- /dev/null +++ b/backend/templates/adguard-home/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/adguardhome +DNS_PORT=53 +SETUP_PORT=3300 +HTTP_PORT=8082 diff --git a/backend/templates/adguard-home/compose.yaml b/backend/templates/adguard-home/compose.yaml new file mode 100644 index 0000000..5be2531 --- /dev/null +++ b/backend/templates/adguard-home/compose.yaml @@ -0,0 +1,14 @@ +services: + adguardhome: + image: adguard/adguardhome:latest + container_name: adguardhome + restart: unless-stopped + ports: + - "${DNS_PORT:-53}:53/tcp" + - "${DNS_PORT:-53}:53/udp" + # Setup wizard on first run; the UI moves to HTTP_PORT afterwards. + - "${SETUP_PORT:-3300}:3000/tcp" + - "${HTTP_PORT:-8082}:80/tcp" + volumes: + - ${DATA_PATH:-/srv/adguardhome}/work:/opt/adguardhome/work + - ${DATA_PATH:-/srv/adguardhome}/conf:/opt/adguardhome/conf diff --git a/backend/templates/adguard-home/template.json b/backend/templates/adguard-home/template.json new file mode 100644 index 0000000..21da9ec --- /dev/null +++ b/backend/templates/adguard-home/template.json @@ -0,0 +1,10 @@ +{ + "name": "AdGuard Home", + "description": "DNS server with ad and tracker blocking, DoH/DoT and per-client rules.", + "tags": [ + "network", + "dns", + "ad-blocking" + ], + "gpu": null +} diff --git a/backend/templates/audiobookshelf/.env.example b/backend/templates/audiobookshelf/.env.example new file mode 100644 index 0000000..97e504e --- /dev/null +++ b/backend/templates/audiobookshelf/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/audiobookshelf +AUDIOBOOKS_PATH=/srv/media/audiobooks +PODCASTS_PATH=/srv/media/podcasts +HTTP_PORT=13378 diff --git a/backend/templates/audiobookshelf/compose.yaml b/backend/templates/audiobookshelf/compose.yaml new file mode 100644 index 0000000..3c99de1 --- /dev/null +++ b/backend/templates/audiobookshelf/compose.yaml @@ -0,0 +1,14 @@ +services: + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-13378}:80" + volumes: + - ${DATA_PATH:-/srv/audiobookshelf}/config:/config + - ${DATA_PATH:-/srv/audiobookshelf}/metadata:/metadata + - ${AUDIOBOOKS_PATH:-/srv/media/audiobooks}:/audiobooks + - ${PODCASTS_PATH:-/srv/media/podcasts}:/podcasts diff --git a/backend/templates/audiobookshelf/template.json b/backend/templates/audiobookshelf/template.json new file mode 100644 index 0000000..435ffc1 --- /dev/null +++ b/backend/templates/audiobookshelf/template.json @@ -0,0 +1,10 @@ +{ + "name": "Audiobookshelf", + "description": "Audiobook and podcast server that keeps progress in sync across devices.", + "tags": [ + "media", + "books", + "streaming" + ], + "gpu": null +} diff --git a/backend/templates/authelia/.env.example b/backend/templates/authelia/.env.example new file mode 100644 index 0000000..ce9fcc4 --- /dev/null +++ b/backend/templates/authelia/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/authelia +HTTP_PORT=9091 diff --git a/backend/templates/authelia/compose.yaml b/backend/templates/authelia/compose.yaml new file mode 100644 index 0000000..7e270b7 --- /dev/null +++ b/backend/templates/authelia/compose.yaml @@ -0,0 +1,13 @@ +services: + authelia: + image: authelia/authelia:latest + container_name: authelia + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9091}:9091" + volumes: + - ./configuration.yml:/config/configuration.yml:ro + - ./users_database.yml:/config/users_database.yml + - ${DATA_PATH:-/srv/authelia}:/config/db diff --git a/backend/templates/authelia/configuration.yml b/backend/templates/authelia/configuration.yml new file mode 100644 index 0000000..072ab2e --- /dev/null +++ b/backend/templates/authelia/configuration.yml @@ -0,0 +1,34 @@ +# Minimal Authelia config. Replace every "change-me" and the example domains. +theme: dark + +identity_validation: + reset_password: + jwt_secret: change-me-jwt-secret + +server: + address: tcp://0.0.0.0:9091 + +authentication_backend: + file: + path: /config/users_database.yml + +access_control: + default_policy: deny + rules: + - domain: "*.example.com" + policy: two_factor + +session: + secret: change-me-session-secret + cookies: + - domain: example.com + authelia_url: https://auth.example.com + +storage: + encryption_key: change-me-encryption-key-at-least-20-chars + local: + path: /config/db/db.sqlite3 + +notifier: + filesystem: + filename: /config/db/notification.txt diff --git a/backend/templates/authelia/template.json b/backend/templates/authelia/template.json new file mode 100644 index 0000000..dd0ef12 --- /dev/null +++ b/backend/templates/authelia/template.json @@ -0,0 +1,10 @@ +{ + "name": "Authelia", + "description": "Lightweight authentication and 2FA portal for reverse-proxy forward auth.", + "tags": [ + "security", + "identity", + "sso" + ], + "gpu": null +} diff --git a/backend/templates/authelia/users_database.yml b/backend/templates/authelia/users_database.yml new file mode 100644 index 0000000..1febf05 --- /dev/null +++ b/backend/templates/authelia/users_database.yml @@ -0,0 +1,10 @@ +# Generate a password hash with: +# docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' +users: + admin: + disabled: false + displayname: "Admin" + password: "$argon2id$v=19$m=65536,t=3,p=4$REPLACE_ME" + email: admin@example.com + groups: + - admins diff --git a/backend/templates/authentik/.env.example b/backend/templates/authentik/.env.example new file mode 100644 index 0000000..72c4742 --- /dev/null +++ b/backend/templates/authentik/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/authentik +AUTHENTIK_TAG=2026.8.0 +HTTP_PORT=9200 +HTTPS_PORT=9243 +# Both required. Generate with: openssl rand -base64 36 +PG_PASS=change-me +AUTHENTIK_SECRET_KEY=change-me diff --git a/backend/templates/authentik/compose.yaml b/backend/templates/authentik/compose.yaml new file mode 100644 index 0000000..5a4909e --- /dev/null +++ b/backend/templates/authentik/compose.yaml @@ -0,0 +1,61 @@ +services: + postgresql: + image: postgres:16-alpine + container_name: authentik-db + restart: unless-stopped + environment: + - POSTGRES_DB=authentik + - POSTGRES_USER=authentik + - POSTGRES_PASSWORD=${PG_PASS:?database password required} + healthcheck: + test: ["CMD-SHELL", "pg_isready -d authentik -U authentik"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 20s + volumes: + - ${DATA_PATH:-/srv/authentik}/database:/var/lib/postgresql/data + + server: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.8.0} + container_name: authentik-server + command: server + restart: unless-stopped + shm_size: 512mb + depends_on: + postgresql: + condition: service_healthy + environment: + - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY:?secret key required} + - AUTHENTIK_POSTGRESQL__HOST=postgresql + - AUTHENTIK_POSTGRESQL__NAME=authentik + - AUTHENTIK_POSTGRESQL__USER=authentik + - AUTHENTIK_POSTGRESQL__PASSWORD=${PG_PASS} + ports: + - "${HTTP_PORT:-9200}:9000" + - "${HTTPS_PORT:-9243}:9443" + volumes: + - ${DATA_PATH:-/srv/authentik}/data:/data + - ${DATA_PATH:-/srv/authentik}/custom-templates:/templates + + worker: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.8.0} + container_name: authentik-worker + command: worker + restart: unless-stopped + shm_size: 512mb + user: root + depends_on: + postgresql: + condition: service_healthy + environment: + - AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY} + - AUTHENTIK_POSTGRESQL__HOST=postgresql + - AUTHENTIK_POSTGRESQL__NAME=authentik + - AUTHENTIK_POSTGRESQL__USER=authentik + - AUTHENTIK_POSTGRESQL__PASSWORD=${PG_PASS} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/authentik}/data:/data + - ${DATA_PATH:-/srv/authentik}/certs:/certs + - ${DATA_PATH:-/srv/authentik}/custom-templates:/templates diff --git a/backend/templates/authentik/template.json b/backend/templates/authentik/template.json new file mode 100644 index 0000000..a814da2 --- /dev/null +++ b/backend/templates/authentik/template.json @@ -0,0 +1,10 @@ +{ + "name": "authentik", + "description": "Identity provider and SSO gateway (OAuth2, SAML, LDAP, forward auth). Finish setup at /if/flow/initial-setup/.", + "tags": [ + "security", + "identity", + "sso" + ], + "gpu": null +} diff --git a/backend/templates/bazarr/.env.example b/backend/templates/bazarr/.env.example new file mode 100644 index 0000000..6a82641 --- /dev/null +++ b/backend/templates/bazarr/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/bazarr +MEDIA_PATH=/srv/media +HTTP_PORT=6767 diff --git a/backend/templates/bazarr/compose.yaml b/backend/templates/bazarr/compose.yaml new file mode 100644 index 0000000..46fd88d --- /dev/null +++ b/backend/templates/bazarr/compose.yaml @@ -0,0 +1,14 @@ +services: + bazarr: + image: lscr.io/linuxserver/bazarr:latest + container_name: bazarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-6767}:6767" + volumes: + - ${DATA_PATH:-/srv/bazarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media diff --git a/backend/templates/bazarr/template.json b/backend/templates/bazarr/template.json new file mode 100644 index 0000000..4094c23 --- /dev/null +++ b/backend/templates/bazarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Bazarr", + "description": "Companion to Sonarr and Radarr that downloads matching subtitles.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/beszel/.env.example b/backend/templates/beszel/.env.example new file mode 100644 index 0000000..54eff02 --- /dev/null +++ b/backend/templates/beszel/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/beszel +HTTP_PORT=8090 +AGENT_PORT=45876 +AGENT_KEY= diff --git a/backend/templates/beszel/compose.yaml b/backend/templates/beszel/compose.yaml new file mode 100644 index 0000000..0ea37f1 --- /dev/null +++ b/backend/templates/beszel/compose.yaml @@ -0,0 +1,22 @@ +services: + beszel: + image: henrygd/beszel:latest + container_name: beszel + restart: unless-stopped + ports: + - "${HTTP_PORT:-8090}:8090" + volumes: + - ${DATA_PATH:-/srv/beszel}/data:/beszel_data + + beszel-agent: + image: henrygd/beszel-agent:latest + container_name: beszel-agent + restart: unless-stopped + network_mode: host + environment: + - LISTEN=${AGENT_PORT:-45876} + # Copy this from the "add system" dialog in the Beszel UI. + - KEY=${AGENT_KEY:-} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ${DATA_PATH:-/srv/beszel}/agent:/var/lib/beszel-agent diff --git a/backend/templates/beszel/template.json b/backend/templates/beszel/template.json new file mode 100644 index 0000000..762baa5 --- /dev/null +++ b/backend/templates/beszel/template.json @@ -0,0 +1,10 @@ +{ + "name": "Beszel", + "description": "Lightweight server monitoring with historical charts, alerts and Docker stats.", + "tags": [ + "monitoring", + "metrics", + "docker" + ], + "gpu": null +} diff --git a/backend/templates/bookstack/.env.example b/backend/templates/bookstack/.env.example new file mode 100644 index 0000000..4ae0beb --- /dev/null +++ b/backend/templates/bookstack/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/bookstack +HTTP_PORT=6875 +APP_URL=http://localhost:6875 +# Required, must be "base64:..." — generate with: echo "base64:$(openssl rand -base64 32)" +APP_KEY= +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/bookstack/compose.yaml b/backend/templates/bookstack/compose.yaml new file mode 100644 index 0000000..b8ec2e5 --- /dev/null +++ b/backend/templates/bookstack/compose.yaml @@ -0,0 +1,38 @@ +services: + bookstack: + image: lscr.io/linuxserver/bookstack:latest + container_name: bookstack + restart: unless-stopped + depends_on: + - bookstack-db + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - APP_URL=${APP_URL:-http://localhost:6875} + # Required. Generate with: openssl rand -base64 32 + - APP_KEY=${APP_KEY:?app key required} + - DB_HOST=bookstack-db + - DB_PORT=3306 + - DB_DATABASE=bookstackapp + - DB_USERNAME=bookstack + - DB_PASSWORD=${DB_PASSWORD:-bookstack} + ports: + - "${HTTP_PORT:-6875}:80" + volumes: + - ${DATA_PATH:-/srv/bookstack}/config:/config + + bookstack-db: + image: lscr.io/linuxserver/mariadb:latest + container_name: bookstack-db + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-bookstack} + - MYSQL_DATABASE=bookstackapp + - MYSQL_USER=bookstack + - MYSQL_PASSWORD=${DB_PASSWORD:-bookstack} + volumes: + - ${DATA_PATH:-/srv/bookstack}/db:/config diff --git a/backend/templates/bookstack/template.json b/backend/templates/bookstack/template.json new file mode 100644 index 0000000..28bf788 --- /dev/null +++ b/backend/templates/bookstack/template.json @@ -0,0 +1,10 @@ +{ + "name": "BookStack", + "description": "Documentation platform organised into shelves, books, chapters and pages. First login: admin@admin.com / password.", + "tags": [ + "documents", + "wiki", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/caddy/.env.example b/backend/templates/caddy/.env.example new file mode 100644 index 0000000..857334f --- /dev/null +++ b/backend/templates/caddy/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/caddy +HTTP_PORT=80 +HTTPS_PORT=443 diff --git a/backend/templates/caddy/Caddyfile b/backend/templates/caddy/Caddyfile new file mode 100644 index 0000000..b0d29a6 --- /dev/null +++ b/backend/templates/caddy/Caddyfile @@ -0,0 +1,11 @@ +# Caddy issues and renews TLS certificates automatically for any real hostname. +# Replace the examples below with your own, then restart the stack. + +app.example.com { + reverse_proxy host.docker.internal:8080 +} + +# Local-only site on plain HTTP (no certificate needed): +# http://nas.lan { +# reverse_proxy 192.168.1.10:5000 +# } diff --git a/backend/templates/caddy/compose.yaml b/backend/templates/caddy/compose.yaml new file mode 100644 index 0000000..16a7088 --- /dev/null +++ b/backend/templates/caddy/compose.yaml @@ -0,0 +1,13 @@ +services: + caddy: + image: caddy:2-alpine + container_name: caddy + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${HTTPS_PORT:-443}:443/udp" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ${DATA_PATH:-/srv/caddy}/data:/data + - ${DATA_PATH:-/srv/caddy}/config:/config diff --git a/backend/templates/caddy/template.json b/backend/templates/caddy/template.json new file mode 100644 index 0000000..bd39158 --- /dev/null +++ b/backend/templates/caddy/template.json @@ -0,0 +1,9 @@ +{ + "name": "Caddy", + "description": "Reverse proxy with automatic HTTPS, configured through a short Caddyfile.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/calibre-web/.env.example b/backend/templates/calibre-web/.env.example new file mode 100644 index 0000000..76f671b --- /dev/null +++ b/backend/templates/calibre-web/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/calibre-web +LIBRARY_PATH=/srv/media/books +HTTP_PORT=8083 diff --git a/backend/templates/calibre-web/compose.yaml b/backend/templates/calibre-web/compose.yaml new file mode 100644 index 0000000..de48c33 --- /dev/null +++ b/backend/templates/calibre-web/compose.yaml @@ -0,0 +1,16 @@ +services: + calibre-web: + image: lscr.io/linuxserver/calibre-web:latest + container_name: calibre-web + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + # Adds ebook format conversion (downloads Calibre on first start) + - DOCKER_MODS=linuxserver/mods:universal-calibre + ports: + - "${HTTP_PORT:-8083}:8083" + volumes: + - ${DATA_PATH:-/srv/calibre-web}:/config + - ${LIBRARY_PATH:-/srv/media/books}:/books diff --git a/backend/templates/calibre-web/template.json b/backend/templates/calibre-web/template.json new file mode 100644 index 0000000..0ed99c3 --- /dev/null +++ b/backend/templates/calibre-web/template.json @@ -0,0 +1,9 @@ +{ + "name": "Calibre-Web", + "description": "Web front-end for a Calibre ebook library, with OPDS and send-to-Kindle.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/changedetection-io/.env.example b/backend/templates/changedetection-io/.env.example new file mode 100644 index 0000000..78c675a --- /dev/null +++ b/backend/templates/changedetection-io/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/changedetection +HTTP_PORT=5000 +BASE_URL=http://localhost:5000 diff --git a/backend/templates/changedetection-io/compose.yaml b/backend/templates/changedetection-io/compose.yaml new file mode 100644 index 0000000..3ea38d3 --- /dev/null +++ b/backend/templates/changedetection-io/compose.yaml @@ -0,0 +1,25 @@ +services: + changedetection: + image: ghcr.io/dgtlmoon/changedetection.io:latest + container_name: changedetection + restart: unless-stopped + environment: + - BASE_URL=${BASE_URL:-http://localhost:5000} + - PLAYWRIGHT_DRIVER_URL=ws://browser:3000 + ports: + - "${HTTP_PORT:-5000}:5000" + volumes: + - ${DATA_PATH:-/srv/changedetection}:/datastore + depends_on: + - browser + + browser: + image: dgtlmoon/sockpuppetbrowser:latest + container_name: changedetection-browser + restart: unless-stopped + cap_add: + - SYS_ADMIN + environment: + - SCREEN_WIDTH=1920 + - SCREEN_HEIGHT=1024 + - MAX_CONCURRENT_CHROME_PROCESSES=10 diff --git a/backend/templates/changedetection-io/template.json b/backend/templates/changedetection-io/template.json new file mode 100644 index 0000000..af296fe --- /dev/null +++ b/backend/templates/changedetection-io/template.json @@ -0,0 +1,9 @@ +{ + "name": "changedetection.io", + "description": "Watches web pages for changes and notifies you — with a headless browser for JS-heavy sites.", + "tags": [ + "monitoring", + "automation" + ], + "gpu": null +} diff --git a/backend/templates/code-server/.env.example b/backend/templates/code-server/.env.example new file mode 100644 index 0000000..870f282 --- /dev/null +++ b/backend/templates/code-server/.env.example @@ -0,0 +1,9 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/code-server +PROJECTS_PATH=/srv/projects +HTTP_PORT=8443 +# Required — this is the web login password. +PASSWORD= +SUDO_PASSWORD= diff --git a/backend/templates/code-server/compose.yaml b/backend/templates/code-server/compose.yaml new file mode 100644 index 0000000..a6b6dfd --- /dev/null +++ b/backend/templates/code-server/compose.yaml @@ -0,0 +1,17 @@ +services: + code-server: + image: lscr.io/linuxserver/code-server:latest + container_name: code-server + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - PASSWORD=${PASSWORD:?password required} + - SUDO_PASSWORD=${SUDO_PASSWORD:-} + - DEFAULT_WORKSPACE=/config/workspace + ports: + - "${HTTP_PORT:-8443}:8443" + volumes: + - ${DATA_PATH:-/srv/code-server}:/config + - ${PROJECTS_PATH:-/srv/projects}:/config/workspace diff --git a/backend/templates/code-server/template.json b/backend/templates/code-server/template.json new file mode 100644 index 0000000..dcc15fe --- /dev/null +++ b/backend/templates/code-server/template.json @@ -0,0 +1,8 @@ +{ + "name": "code-server", + "description": "VS Code in the browser, running on the server where your files already are.", + "tags": [ + "dev" + ], + "gpu": null +} diff --git a/backend/templates/dashy/.env.example b/backend/templates/dashy/.env.example new file mode 100644 index 0000000..630755f --- /dev/null +++ b/backend/templates/dashy/.env.example @@ -0,0 +1,2 @@ +DATA_PATH=/srv/dashy +HTTP_PORT=4000 diff --git a/backend/templates/dashy/compose.yaml b/backend/templates/dashy/compose.yaml new file mode 100644 index 0000000..ad87352 --- /dev/null +++ b/backend/templates/dashy/compose.yaml @@ -0,0 +1,12 @@ +services: + dashy: + image: lissy93/dashy:latest + container_name: dashy + restart: unless-stopped + environment: + - NODE_ENV=production + ports: + - "${HTTP_PORT:-4000}:8080" + volumes: + # Dashy writes conf.yml back into this directory when you edit in the UI. + - ${DATA_PATH:-/srv/dashy}:/app/user-data diff --git a/backend/templates/dashy/template.json b/backend/templates/dashy/template.json new file mode 100644 index 0000000..8ababb7 --- /dev/null +++ b/backend/templates/dashy/template.json @@ -0,0 +1,8 @@ +{ + "name": "Dashy", + "description": "Highly themeable dashboard with status checks, editable from the browser.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/docker-registry/.env.example b/backend/templates/docker-registry/.env.example new file mode 100644 index 0000000..32ed92d --- /dev/null +++ b/backend/templates/docker-registry/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/registry +HTTP_PORT=5050 +UI_PORT=5080 +REGISTRY_TITLE=Homelab Registry diff --git a/backend/templates/docker-registry/compose.yaml b/backend/templates/docker-registry/compose.yaml new file mode 100644 index 0000000..98e93d3 --- /dev/null +++ b/backend/templates/docker-registry/compose.yaml @@ -0,0 +1,29 @@ +services: + registry: + image: registry:2 + container_name: registry + restart: unless-stopped + environment: + - REGISTRY_STORAGE_DELETE_ENABLED=true + ports: + - "${HTTP_PORT:-5050}:5000" + volumes: + - ${DATA_PATH:-/srv/registry}:/var/lib/registry + # Basic auth. Create it with: + # docker run --rm httpd:alpine htpasswd -Bbn user pass > htpasswd + # then uncomment the mount and the three REGISTRY_AUTH_* vars above. + # - ./htpasswd:/auth/htpasswd:ro + + registry-ui: + image: joxit/docker-registry-ui:latest + container_name: registry-ui + restart: unless-stopped + depends_on: + - registry + environment: + - SINGLE_REGISTRY=true + - REGISTRY_TITLE=${REGISTRY_TITLE:-Homelab Registry} + - NGINX_PROXY_PASS_URL=http://registry:5000 + - DELETE_IMAGES=true + ports: + - "${UI_PORT:-5080}:80" diff --git a/backend/templates/docker-registry/template.json b/backend/templates/docker-registry/template.json new file mode 100644 index 0000000..02e0c71 --- /dev/null +++ b/backend/templates/docker-registry/template.json @@ -0,0 +1,9 @@ +{ + "name": "Docker Registry", + "description": "Private container registry with a browsable web UI. Add auth and TLS before exposing it.", + "tags": [ + "docker", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/dockge/.env.example b/backend/templates/dockge/.env.example new file mode 100644 index 0000000..55a0ad4 --- /dev/null +++ b/backend/templates/dockge/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/dockge +STACKS_DIR=/opt/stacks +HTTP_PORT=5001 diff --git a/backend/templates/dockge/compose.yaml b/backend/templates/dockge/compose.yaml new file mode 100644 index 0000000..979b0b4 --- /dev/null +++ b/backend/templates/dockge/compose.yaml @@ -0,0 +1,14 @@ +services: + dockge: + image: louislam/dockge:1 + container_name: dockge + restart: unless-stopped + environment: + - DOCKGE_STACKS_DIR=${STACKS_DIR:-/opt/stacks} + ports: + - "${HTTP_PORT:-5001}:5001" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/dockge}:/app/data + # Host path and container path must match, or compose lookups break. + - ${STACKS_DIR:-/opt/stacks}:${STACKS_DIR:-/opt/stacks} diff --git a/backend/templates/dockge/template.json b/backend/templates/dockge/template.json new file mode 100644 index 0000000..8a755b3 --- /dev/null +++ b/backend/templates/dockge/template.json @@ -0,0 +1,9 @@ +{ + "name": "Dockge", + "description": "Compose-stack manager with a live editor and terminal output.", + "tags": [ + "docker", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/dozzle/.env.example b/backend/templates/dozzle/.env.example new file mode 100644 index 0000000..46bb51b --- /dev/null +++ b/backend/templates/dozzle/.env.example @@ -0,0 +1 @@ +HTTP_PORT=8888 diff --git a/backend/templates/dozzle/compose.yaml b/backend/templates/dozzle/compose.yaml new file mode 100644 index 0000000..b1b716f --- /dev/null +++ b/backend/templates/dozzle/compose.yaml @@ -0,0 +1,12 @@ +services: + dozzle: + image: amir20/dozzle:latest + container_name: dozzle + restart: unless-stopped + environment: + - DOZZLE_LEVEL=info + - DOZZLE_NO_ANALYTICS=true + ports: + - "${HTTP_PORT:-8888}:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/dozzle/template.json b/backend/templates/dozzle/template.json new file mode 100644 index 0000000..e5dd542 --- /dev/null +++ b/backend/templates/dozzle/template.json @@ -0,0 +1,10 @@ +{ + "name": "Dozzle", + "description": "Live container log viewer in the browser — no log shipping, no storage.", + "tags": [ + "monitoring", + "docker", + "logs" + ], + "gpu": null +} diff --git a/backend/templates/duplicati/.env.example b/backend/templates/duplicati/.env.example new file mode 100644 index 0000000..9ef1670 --- /dev/null +++ b/backend/templates/duplicati/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/duplicati +BACKUPS_PATH=/srv/backups +SOURCE_PATH=/srv +HTTP_PORT=8201 +WEB_PASSWORD=change-me +# Required. Generate with: openssl rand -hex 32 +SETTINGS_ENCRYPTION_KEY= diff --git a/backend/templates/duplicati/compose.yaml b/backend/templates/duplicati/compose.yaml new file mode 100644 index 0000000..451d004 --- /dev/null +++ b/backend/templates/duplicati/compose.yaml @@ -0,0 +1,17 @@ +services: + duplicati: + image: lscr.io/linuxserver/duplicati:latest + container_name: duplicati + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - SETTINGS_ENCRYPTION_KEY=${SETTINGS_ENCRYPTION_KEY:?encryption key required} + - DUPLICATI__WEBSERVICE_PASSWORD=${WEB_PASSWORD:-change-me} + ports: + - "${HTTP_PORT:-8201}:8200" + volumes: + - ${DATA_PATH:-/srv/duplicati}:/config + - ${BACKUPS_PATH:-/srv/backups}:/backups + - ${SOURCE_PATH:-/srv}:/source:ro diff --git a/backend/templates/duplicati/template.json b/backend/templates/duplicati/template.json new file mode 100644 index 0000000..cf299cd --- /dev/null +++ b/backend/templates/duplicati/template.json @@ -0,0 +1,9 @@ +{ + "name": "Duplicati", + "description": "Scheduled, encrypted, deduplicated backups to local disks or any cloud target.", + "tags": [ + "backup", + "files" + ], + "gpu": null +} diff --git a/backend/templates/emby/.env.example b/backend/templates/emby/.env.example new file mode 100644 index 0000000..52e600c --- /dev/null +++ b/backend/templates/emby/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +CONFIG_PATH=/srv/emby/config +MEDIA_PATH=/srv/media +HTTP_PORT=8105 diff --git a/backend/templates/emby/compose.yaml b/backend/templates/emby/compose.yaml new file mode 100644 index 0000000..176789a --- /dev/null +++ b/backend/templates/emby/compose.yaml @@ -0,0 +1,17 @@ +services: + emby: + image: lscr.io/linuxserver/emby:latest + container_name: emby + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8105}:8096" + volumes: + - ${CONFIG_PATH:-/srv/emby/config}:/config + - ${MEDIA_PATH:-/srv/media}:/media + # Uncomment for Intel/AMD hardware transcoding (Emby Premiere required): + # devices: + # - /dev/dri:/dev/dri diff --git a/backend/templates/emby/template.json b/backend/templates/emby/template.json new file mode 100644 index 0000000..635f3f4 --- /dev/null +++ b/backend/templates/emby/template.json @@ -0,0 +1,9 @@ +{ + "name": "Emby", + "description": "Media server for movies, TV and music, with live TV and DVR support.", + "tags": [ + "media", + "streaming" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/esphome/.env.example b/backend/templates/esphome/.env.example new file mode 100644 index 0000000..3632e96 --- /dev/null +++ b/backend/templates/esphome/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/esphome +# Optional basic auth for the dashboard. +USERNAME= +PASSWORD= +# Runs on host networking — the UI is on port 6052 of the host itself. diff --git a/backend/templates/esphome/compose.yaml b/backend/templates/esphome/compose.yaml new file mode 100644 index 0000000..e22b288 --- /dev/null +++ b/backend/templates/esphome/compose.yaml @@ -0,0 +1,15 @@ +services: + esphome: + image: ghcr.io/esphome/esphome:latest + container_name: esphome + restart: unless-stopped + # Host networking is needed for OTA discovery of your devices. + network_mode: host + privileged: true + environment: + - TZ=${TZ:-Europe/Berlin} + - USERNAME=${USERNAME:-} + - PASSWORD=${PASSWORD:-} + volumes: + - ${DATA_PATH:-/srv/esphome}:/config + - /etc/localtime:/etc/localtime:ro diff --git a/backend/templates/esphome/template.json b/backend/templates/esphome/template.json new file mode 100644 index 0000000..efd72c9 --- /dev/null +++ b/backend/templates/esphome/template.json @@ -0,0 +1,10 @@ +{ + "name": "ESPHome", + "description": "Build and flash firmware for ESP32/ESP8266 sensors straight from the browser.", + "tags": [ + "home-automation", + "iot", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/filebrowser/.env.example b/backend/templates/filebrowser/.env.example new file mode 100644 index 0000000..15864b1 --- /dev/null +++ b/backend/templates/filebrowser/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/filebrowser +FILES_PATH=/srv +HTTP_PORT=8088 diff --git a/backend/templates/filebrowser/compose.yaml b/backend/templates/filebrowser/compose.yaml new file mode 100644 index 0000000..9528b35 --- /dev/null +++ b/backend/templates/filebrowser/compose.yaml @@ -0,0 +1,16 @@ +services: + filebrowser: + # Pinned to the v2 line — v3 changes the on-disk layout. + image: filebrowser/filebrowser:v2 + container_name: filebrowser + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - FB_ROOT=/srv + - FB_DATABASE=/database/filebrowser.db + - FB_PORT=80 + ports: + - "${HTTP_PORT:-8088}:80" + volumes: + - ${FILES_PATH:-/srv}:/srv + - ${DATA_PATH:-/srv/filebrowser}/database:/database diff --git a/backend/templates/filebrowser/template.json b/backend/templates/filebrowser/template.json new file mode 100644 index 0000000..e2597e6 --- /dev/null +++ b/backend/templates/filebrowser/template.json @@ -0,0 +1,8 @@ +{ + "name": "File Browser", + "description": "Web file manager for a directory tree. Default login admin/admin — change it immediately.", + "tags": [ + "files" + ], + "gpu": null +} diff --git a/backend/templates/firefly-iii/.env.example b/backend/templates/firefly-iii/.env.example new file mode 100644 index 0000000..676bd87 --- /dev/null +++ b/backend/templates/firefly-iii/.env.example @@ -0,0 +1,7 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/firefly-iii +HTTP_PORT=8104 +APP_URL=http://localhost:8104 +# Required, exactly 32 characters. Generate with: openssl rand -hex 16 +APP_KEY= +DB_PASSWORD=change-me diff --git a/backend/templates/firefly-iii/compose.yaml b/backend/templates/firefly-iii/compose.yaml new file mode 100644 index 0000000..8877fc9 --- /dev/null +++ b/backend/templates/firefly-iii/compose.yaml @@ -0,0 +1,37 @@ +services: + app: + image: fireflyiii/core:latest + container_name: firefly-iii + restart: unless-stopped + depends_on: + - db + environment: + - TZ=${TZ:-Europe/Berlin} + - APP_ENV=production + - APP_URL=${APP_URL:-http://localhost:8095} + # Required, exactly 32 characters. Generate with: openssl rand -hex 16 + - APP_KEY=${APP_KEY:?app key required, exactly 32 characters} + - TRUSTED_PROXIES=** + - DB_CONNECTION=mysql + - DB_HOST=db + - DB_PORT=3306 + - DB_DATABASE=firefly + - DB_USERNAME=firefly + - DB_PASSWORD=${DB_PASSWORD:-firefly} + ports: + - "${HTTP_PORT:-8104}:8080" + volumes: + - ${DATA_PATH:-/srv/firefly-iii}/upload:/var/www/html/storage/upload + + db: + image: mariadb:11 + container_name: firefly-iii-db + restart: unless-stopped + environment: + - MARIADB_AUTO_UPGRADE=1 + - MYSQL_DATABASE=firefly + - MYSQL_USER=firefly + - MYSQL_PASSWORD=${DB_PASSWORD:-firefly} + - MYSQL_RANDOM_ROOT_PASSWORD=yes + volumes: + - ${DATA_PATH:-/srv/firefly-iii}/db:/var/lib/mysql diff --git a/backend/templates/firefly-iii/template.json b/backend/templates/firefly-iii/template.json new file mode 100644 index 0000000..4068ca5 --- /dev/null +++ b/backend/templates/firefly-iii/template.json @@ -0,0 +1,9 @@ +{ + "name": "Firefly III", + "description": "Double-entry personal finance manager with budgets, rules and reports.", + "tags": [ + "finance", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/forgejo/.env.example b/backend/templates/forgejo/.env.example new file mode 100644 index 0000000..bffbaac --- /dev/null +++ b/backend/templates/forgejo/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/forgejo +HTTP_PORT=3301 +SSH_PORT=2223 +ROOT_URL=http://localhost:3000/ +DOMAIN=localhost diff --git a/backend/templates/forgejo/compose.yaml b/backend/templates/forgejo/compose.yaml new file mode 100644 index 0000000..095d176 --- /dev/null +++ b/backend/templates/forgejo/compose.yaml @@ -0,0 +1,17 @@ +services: + forgejo: + image: codeberg.org/forgejo/forgejo:16 + container_name: forgejo + restart: unless-stopped + environment: + - USER_UID=${PUID:-1000} + - USER_GID=${PGID:-1000} + - FORGEJO__server__ROOT_URL=${ROOT_URL:-http://localhost:3000/} + - FORGEJO__server__DOMAIN=${DOMAIN:-localhost} + ports: + - "${HTTP_PORT:-3301}:3000" + - "${SSH_PORT:-2223}:22" + volumes: + - ${DATA_PATH:-/srv/forgejo}:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro diff --git a/backend/templates/forgejo/template.json b/backend/templates/forgejo/template.json new file mode 100644 index 0000000..d381d66 --- /dev/null +++ b/backend/templates/forgejo/template.json @@ -0,0 +1,9 @@ +{ + "name": "Forgejo", + "description": "Community-run Git forge with CI (Forgejo Actions) — the Gitea fork.", + "tags": [ + "git", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/freshrss/.env.example b/backend/templates/freshrss/.env.example new file mode 100644 index 0000000..adffcd0 --- /dev/null +++ b/backend/templates/freshrss/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/freshrss +HTTP_PORT=8093 +CRON_MIN=*/20 diff --git a/backend/templates/freshrss/compose.yaml b/backend/templates/freshrss/compose.yaml new file mode 100644 index 0000000..8e81018 --- /dev/null +++ b/backend/templates/freshrss/compose.yaml @@ -0,0 +1,13 @@ +services: + freshrss: + image: freshrss/freshrss:latest + container_name: freshrss + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - CRON_MIN=${CRON_MIN:-*/20} + ports: + - "${HTTP_PORT:-8093}:80" + volumes: + - ${DATA_PATH:-/srv/freshrss}/data:/var/www/FreshRSS/data + - ${DATA_PATH:-/srv/freshrss}/extensions:/var/www/FreshRSS/extensions diff --git a/backend/templates/freshrss/template.json b/backend/templates/freshrss/template.json new file mode 100644 index 0000000..d027205 --- /dev/null +++ b/backend/templates/freshrss/template.json @@ -0,0 +1,9 @@ +{ + "name": "FreshRSS", + "description": "Self-hosted RSS aggregator with an API for mobile readers, SQLite by default.", + "tags": [ + "rss", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/frigate/.env.example b/backend/templates/frigate/.env.example new file mode 100644 index 0000000..53e38a5 --- /dev/null +++ b/backend/templates/frigate/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/frigate +MEDIA_PATH=/srv/frigate/media +HTTP_PORT=8971 +RTSP_PORT=8554 +WEBRTC_PORT=8555 +SHM_SIZE=256mb +RTSP_PASSWORD=change-me diff --git a/backend/templates/frigate/compose.yaml b/backend/templates/frigate/compose.yaml new file mode 100644 index 0000000..6d88267 --- /dev/null +++ b/backend/templates/frigate/compose.yaml @@ -0,0 +1,32 @@ +services: + frigate: + image: ghcr.io/blakeblackshear/frigate:stable + container_name: frigate + restart: unless-stopped + privileged: true + shm_size: ${SHM_SIZE:-256mb} + environment: + - TZ=${TZ:-Europe/Berlin} + - FRIGATE_RTSP_PASSWORD=${RTSP_PASSWORD:-change-me} + ports: + - "${HTTP_PORT:-8971}:8971" + - "${RTSP_PORT:-8554}:8554" + - "${WEBRTC_PORT:-8555}:8555/tcp" + - "${WEBRTC_PORT:-8555}:8555/udp" + volumes: + # The config lives with the stack so you can edit it here; the directory + # underneath holds Frigate's database and model cache. + - ${DATA_PATH:-/srv/frigate}/config:/config + - ./config.yml:/config/config.yml + - ${MEDIA_PATH:-/srv/frigate/media}:/media/frigate + - /etc/localtime:/etc/localtime:ro + - type: tmpfs + target: /tmp/cache + tmpfs: + size: 1000000000 + # Uncomment what your host actually has: + # devices: + # # Google Coral USB accelerator + # - /dev/bus/usb:/dev/bus/usb + # # Intel iGPU for hardware decoding + # - /dev/dri/renderD128:/dev/dri/renderD128 diff --git a/backend/templates/frigate/config.yml b/backend/templates/frigate/config.yml new file mode 100644 index 0000000..0619e38 --- /dev/null +++ b/backend/templates/frigate/config.yml @@ -0,0 +1,38 @@ +mqtt: + enabled: false + +detectors: + cpu1: + type: cpu + # Swap for a Coral TPU once you have one: + # coral: + # type: edgetpu + # device: usb + +cameras: + front_door: + ffmpeg: + inputs: + - path: rtsp://user:{FRIGATE_RTSP_PASSWORD}@192.168.1.50:554/stream1 + roles: + - detect + - record + detect: + width: 1280 + height: 720 + fps: 5 + objects: + track: + - person + - car + +record: + enabled: true + retain: + days: 7 + mode: motion + +snapshots: + enabled: true + retain: + default: 14 diff --git a/backend/templates/frigate/template.json b/backend/templates/frigate/template.json new file mode 100644 index 0000000..cdff524 --- /dev/null +++ b/backend/templates/frigate/template.json @@ -0,0 +1,10 @@ +{ + "name": "Frigate NVR", + "description": "Network video recorder with real-time object detection. Edit config.yml with your cameras before deploying.", + "tags": [ + "home-automation", + "video", + "monitoring" + ], + "gpu": "Coral TPU / Intel / NVIDIA optional" +} diff --git a/backend/templates/gotify/.env.example b/backend/templates/gotify/.env.example new file mode 100644 index 0000000..19ddcc6 --- /dev/null +++ b/backend/templates/gotify/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/gotify +HTTP_PORT=8098 +ADMIN_USER=admin +# Required. +ADMIN_PASSWORD= diff --git a/backend/templates/gotify/compose.yaml b/backend/templates/gotify/compose.yaml new file mode 100644 index 0000000..51189cb --- /dev/null +++ b/backend/templates/gotify/compose.yaml @@ -0,0 +1,13 @@ +services: + gotify: + image: gotify/server:latest + container_name: gotify + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - GOTIFY_DEFAULTUSER_NAME=${ADMIN_USER:-admin} + - GOTIFY_DEFAULTUSER_PASS=${ADMIN_PASSWORD:?admin password required} + ports: + - "${HTTP_PORT:-8098}:80" + volumes: + - ${DATA_PATH:-/srv/gotify}:/app/data diff --git a/backend/templates/gotify/template.json b/backend/templates/gotify/template.json new file mode 100644 index 0000000..ec68ccb --- /dev/null +++ b/backend/templates/gotify/template.json @@ -0,0 +1,9 @@ +{ + "name": "Gotify", + "description": "Simple push notification server with per-application tokens and an Android app.", + "tags": [ + "notifications", + "monitoring" + ], + "gpu": null +} diff --git a/backend/templates/grafana/.env.example b/backend/templates/grafana/.env.example new file mode 100644 index 0000000..8e6b570 --- /dev/null +++ b/backend/templates/grafana/.env.example @@ -0,0 +1,8 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/grafana +HTTP_PORT=3030 +ROOT_URL=http://localhost:3000 +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +INSTALL_PLUGINS= diff --git a/backend/templates/grafana/compose.yaml b/backend/templates/grafana/compose.yaml new file mode 100644 index 0000000..f782cfd --- /dev/null +++ b/backend/templates/grafana/compose.yaml @@ -0,0 +1,15 @@ +services: + grafana: + image: grafana/grafana:latest + container_name: grafana + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - GF_SERVER_ROOT_URL=${ROOT_URL:-http://localhost:3000} + - GF_INSTALL_PLUGINS=${INSTALL_PLUGINS:-} + ports: + - "${HTTP_PORT:-3030}:3000" + volumes: + - ${DATA_PATH:-/srv/grafana}:/var/lib/grafana diff --git a/backend/templates/grafana/template.json b/backend/templates/grafana/template.json new file mode 100644 index 0000000..c330a4a --- /dev/null +++ b/backend/templates/grafana/template.json @@ -0,0 +1,10 @@ +{ + "name": "Grafana", + "description": "Dashboards and alerting on top of Prometheus, InfluxDB, Loki and friends.", + "tags": [ + "monitoring", + "metrics", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/grocy/.env.example b/backend/templates/grocy/.env.example new file mode 100644 index 0000000..12f248d --- /dev/null +++ b/backend/templates/grocy/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/grocy +HTTP_PORT=9283 diff --git a/backend/templates/grocy/compose.yaml b/backend/templates/grocy/compose.yaml new file mode 100644 index 0000000..5d86f97 --- /dev/null +++ b/backend/templates/grocy/compose.yaml @@ -0,0 +1,13 @@ +services: + grocy: + image: lscr.io/linuxserver/grocy:latest + container_name: grocy + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9283}:80" + volumes: + - ${DATA_PATH:-/srv/grocy}:/config diff --git a/backend/templates/grocy/template.json b/backend/templates/grocy/template.json new file mode 100644 index 0000000..00a1e7e --- /dev/null +++ b/backend/templates/grocy/template.json @@ -0,0 +1,9 @@ +{ + "name": "Grocy", + "description": "Groceries, stock, chores and household ERP with barcode scanning. First login: admin / admin.", + "tags": [ + "productivity", + "recipes" + ], + "gpu": null +} diff --git a/backend/templates/heimdall/.env.example b/backend/templates/heimdall/.env.example new file mode 100644 index 0000000..331718f --- /dev/null +++ b/backend/templates/heimdall/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/heimdall +HTTP_PORT=8102 +HTTPS_PORT=8103 diff --git a/backend/templates/heimdall/compose.yaml b/backend/templates/heimdall/compose.yaml new file mode 100644 index 0000000..24cfbcc --- /dev/null +++ b/backend/templates/heimdall/compose.yaml @@ -0,0 +1,14 @@ +services: + heimdall: + image: lscr.io/linuxserver/heimdall:latest + container_name: heimdall + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8102}:80" + - "${HTTPS_PORT:-8103}:443" + volumes: + - ${DATA_PATH:-/srv/heimdall}:/config diff --git a/backend/templates/heimdall/template.json b/backend/templates/heimdall/template.json new file mode 100644 index 0000000..77635ed --- /dev/null +++ b/backend/templates/heimdall/template.json @@ -0,0 +1,8 @@ +{ + "name": "Heimdall", + "description": "Simple application dashboard with enhanced tiles for common services.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/homarr/.env.example b/backend/templates/homarr/.env.example new file mode 100644 index 0000000..4155c08 --- /dev/null +++ b/backend/templates/homarr/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/homarr +HTTP_PORT=7575 +# Required. Generate with: openssl rand -hex 32 +SECRET_ENCRYPTION_KEY= diff --git a/backend/templates/homarr/compose.yaml b/backend/templates/homarr/compose.yaml new file mode 100644 index 0000000..28b3e8c --- /dev/null +++ b/backend/templates/homarr/compose.yaml @@ -0,0 +1,14 @@ +services: + homarr: + image: ghcr.io/homarr-labs/homarr:latest + container_name: homarr + restart: unless-stopped + environment: + # Required. Generate with: openssl rand -hex 32 + - SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY:?encryption key required} + ports: + - "${HTTP_PORT:-7575}:7575" + volumes: + - ${DATA_PATH:-/srv/homarr}:/appdata + # Optional: lets Homarr show container status + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/homarr/template.json b/backend/templates/homarr/template.json new file mode 100644 index 0000000..2865324 --- /dev/null +++ b/backend/templates/homarr/template.json @@ -0,0 +1,8 @@ +{ + "name": "Homarr", + "description": "Drag-and-drop dashboard with integrations for the *arr apps, Docker and more.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/home-assistant/.env.example b/backend/templates/home-assistant/.env.example new file mode 100644 index 0000000..a621ab9 --- /dev/null +++ b/backend/templates/home-assistant/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/homeassistant +# Runs on host networking — the UI is on port 8123 of the host itself. diff --git a/backend/templates/home-assistant/compose.yaml b/backend/templates/home-assistant/compose.yaml new file mode 100644 index 0000000..95d10f9 --- /dev/null +++ b/backend/templates/home-assistant/compose.yaml @@ -0,0 +1,17 @@ +services: + homeassistant: + image: ghcr.io/home-assistant/home-assistant:stable + container_name: homeassistant + restart: unless-stopped + # Host networking lets HA discover devices via mDNS, SSDP and DHCP. + # Swap it for a ports mapping if you only use cloud/MQTT integrations. + network_mode: host + privileged: true + environment: + - TZ=${TZ:-Europe/Berlin} + volumes: + - ${DATA_PATH:-/srv/homeassistant}:/config + - /run/dbus:/run/dbus:ro + # Uncomment to pass through a Zigbee/Z-Wave USB stick: + # devices: + # - /dev/ttyUSB0:/dev/ttyUSB0 diff --git a/backend/templates/home-assistant/template.json b/backend/templates/home-assistant/template.json new file mode 100644 index 0000000..cec95d0 --- /dev/null +++ b/backend/templates/home-assistant/template.json @@ -0,0 +1,9 @@ +{ + "name": "Home Assistant", + "description": "Home automation hub with thousands of integrations. Runs on host networking for device discovery.", + "tags": [ + "home-automation", + "iot" + ], + "gpu": null +} diff --git a/backend/templates/homepage/.env.example b/backend/templates/homepage/.env.example new file mode 100644 index 0000000..afb3da6 --- /dev/null +++ b/backend/templates/homepage/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/homepage +HTTP_PORT=3005 +ALLOWED_HOSTS=* diff --git a/backend/templates/homepage/compose.yaml b/backend/templates/homepage/compose.yaml new file mode 100644 index 0000000..e9fb7e5 --- /dev/null +++ b/backend/templates/homepage/compose.yaml @@ -0,0 +1,15 @@ +services: + homepage: + image: ghcr.io/gethomepage/homepage:latest + container_name: homepage + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + # Comma-separated hostnames the UI may be reached under, or "*" to allow all. + - HOMEPAGE_ALLOWED_HOSTS=${ALLOWED_HOSTS:-*} + ports: + - "${HTTP_PORT:-3005}:3000" + volumes: + - ${DATA_PATH:-/srv/homepage}:/app/config + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/homepage/template.json b/backend/templates/homepage/template.json new file mode 100644 index 0000000..7023a9c --- /dev/null +++ b/backend/templates/homepage/template.json @@ -0,0 +1,8 @@ +{ + "name": "Homepage", + "description": "YAML-configured start page with live widgets for dozens of self-hosted services.", + "tags": [ + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/immich/.env.example b/backend/templates/immich/.env.example new file mode 100644 index 0000000..5e49a8f --- /dev/null +++ b/backend/templates/immich/.env.example @@ -0,0 +1,11 @@ +TZ=Europe/Berlin +IMMICH_VERSION=release +DATA_PATH=/srv/immich +# Where uploaded photos and videos live. Network shares are fine here. +UPLOAD_LOCATION=/srv/immich/library +# The database must sit on local storage — never on NFS/SMB. +DB_DATA_LOCATION=/srv/immich/postgres +DB_USERNAME=postgres +DB_PASSWORD=change-me +DB_DATABASE_NAME=immich +HTTP_PORT=2283 diff --git a/backend/templates/immich/compose.yaml b/backend/templates/immich/compose.yaml new file mode 100644 index 0000000..8f7c530 --- /dev/null +++ b/backend/templates/immich/compose.yaml @@ -0,0 +1,51 @@ +services: + immich-server: + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} + container_name: immich-server + restart: unless-stopped + depends_on: + - redis + - database + environment: + - TZ=${TZ:-Europe/Berlin} + - DB_HOSTNAME=database + - DB_USERNAME=${DB_USERNAME:-postgres} + - DB_PASSWORD=${DB_PASSWORD:-postgres} + - DB_DATABASE_NAME=${DB_DATABASE_NAME:-immich} + - REDIS_HOSTNAME=redis + ports: + - "${HTTP_PORT:-2283}:2283" + volumes: + - ${UPLOAD_LOCATION:-/srv/immich/library}:/data + - /etc/localtime:/etc/localtime:ro + # Uncomment for Intel/AMD hardware transcoding: + # devices: + # - /dev/dri:/dev/dri + + immich-machine-learning: + # Add -cuda / -openvino / -rocm to the tag for accelerated inference. + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} + container_name: immich-machine-learning + restart: unless-stopped + volumes: + - ${DATA_PATH:-/srv/immich}/model-cache:/cache + + redis: + image: valkey/valkey:8-alpine + container_name: immich-redis + restart: unless-stopped + healthcheck: + test: redis-cli ping | grep -q PONG || exit 1 + + database: + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 + container_name: immich-postgres + restart: unless-stopped + shm_size: 128mb + environment: + - POSTGRES_USER=${DB_USERNAME:-postgres} + - POSTGRES_PASSWORD=${DB_PASSWORD:-postgres} + - POSTGRES_DB=${DB_DATABASE_NAME:-immich} + - POSTGRES_INITDB_ARGS=--data-checksums + volumes: + - ${DB_DATA_LOCATION:-/srv/immich/postgres}:/var/lib/postgresql/data diff --git a/backend/templates/immich/template.json b/backend/templates/immich/template.json new file mode 100644 index 0000000..451ead7 --- /dev/null +++ b/backend/templates/immich/template.json @@ -0,0 +1,10 @@ +{ + "name": "Immich", + "description": "Self-hosted photo and video backup with mobile apps and AI search.", + "tags": [ + "media", + "photos", + "backup" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/it-tools/.env.example b/backend/templates/it-tools/.env.example new file mode 100644 index 0000000..fbe97ce --- /dev/null +++ b/backend/templates/it-tools/.env.example @@ -0,0 +1 @@ +HTTP_PORT=8100 diff --git a/backend/templates/it-tools/compose.yaml b/backend/templates/it-tools/compose.yaml new file mode 100644 index 0000000..2144b27 --- /dev/null +++ b/backend/templates/it-tools/compose.yaml @@ -0,0 +1,7 @@ +services: + it-tools: + image: corentinth/it-tools:latest + container_name: it-tools + restart: unless-stopped + ports: + - "${HTTP_PORT:-8100}:80" diff --git a/backend/templates/it-tools/template.json b/backend/templates/it-tools/template.json new file mode 100644 index 0000000..32da26d --- /dev/null +++ b/backend/templates/it-tools/template.json @@ -0,0 +1,9 @@ +{ + "name": "IT Tools", + "description": "Offline collection of developer utilities — hashes, JWTs, cron parsing, converters and more.", + "tags": [ + "utilities", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/jellyseerr/.env.example b/backend/templates/jellyseerr/.env.example new file mode 100644 index 0000000..5b099d4 --- /dev/null +++ b/backend/templates/jellyseerr/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/jellyseerr +HTTP_PORT=5055 diff --git a/backend/templates/jellyseerr/compose.yaml b/backend/templates/jellyseerr/compose.yaml new file mode 100644 index 0000000..51b681a --- /dev/null +++ b/backend/templates/jellyseerr/compose.yaml @@ -0,0 +1,12 @@ +services: + jellyseerr: + image: fallenbagel/jellyseerr:latest + container_name: jellyseerr + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - LOG_LEVEL=info + ports: + - "${HTTP_PORT:-5055}:5055" + volumes: + - ${DATA_PATH:-/srv/jellyseerr}:/app/config diff --git a/backend/templates/jellyseerr/template.json b/backend/templates/jellyseerr/template.json new file mode 100644 index 0000000..100d196 --- /dev/null +++ b/backend/templates/jellyseerr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Jellyseerr", + "description": "Request portal for Jellyfin, Emby and Plex that feeds Sonarr and Radarr.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/kavita/.env.example b/backend/templates/kavita/.env.example new file mode 100644 index 0000000..337ee76 --- /dev/null +++ b/backend/templates/kavita/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/kavita +LIBRARY_PATH=/srv/media/books +HTTP_PORT=5010 diff --git a/backend/templates/kavita/compose.yaml b/backend/templates/kavita/compose.yaml new file mode 100644 index 0000000..c53ba62 --- /dev/null +++ b/backend/templates/kavita/compose.yaml @@ -0,0 +1,12 @@ +services: + kavita: + image: jvmilazz0/kavita:latest + container_name: kavita + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5010}:5000" + volumes: + - ${DATA_PATH:-/srv/kavita}:/kavita/config + - ${LIBRARY_PATH:-/srv/media/books}:/books diff --git a/backend/templates/kavita/template.json b/backend/templates/kavita/template.json new file mode 100644 index 0000000..fa1a63b --- /dev/null +++ b/backend/templates/kavita/template.json @@ -0,0 +1,9 @@ +{ + "name": "Kavita", + "description": "Fast reading server for ebooks, manga and comics.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/komga/.env.example b/backend/templates/komga/.env.example new file mode 100644 index 0000000..ea2c2b4 --- /dev/null +++ b/backend/templates/komga/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/komga +COMICS_PATH=/srv/media/comics +HTTP_PORT=25600 diff --git a/backend/templates/komga/compose.yaml b/backend/templates/komga/compose.yaml new file mode 100644 index 0000000..2600598 --- /dev/null +++ b/backend/templates/komga/compose.yaml @@ -0,0 +1,13 @@ +services: + komga: + image: gotson/komga:latest + container_name: komga + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-25600}:25600" + volumes: + - ${DATA_PATH:-/srv/komga}:/config + - ${COMICS_PATH:-/srv/media/comics}:/data diff --git a/backend/templates/komga/template.json b/backend/templates/komga/template.json new file mode 100644 index 0000000..2925989 --- /dev/null +++ b/backend/templates/komga/template.json @@ -0,0 +1,9 @@ +{ + "name": "Komga", + "description": "Comic, manga and BD server with a web reader and OPDS feed.", + "tags": [ + "media", + "books" + ], + "gpu": null +} diff --git a/backend/templates/lidarr/.env.example b/backend/templates/lidarr/.env.example new file mode 100644 index 0000000..6b46ef2 --- /dev/null +++ b/backend/templates/lidarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/lidarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8686 diff --git a/backend/templates/lidarr/compose.yaml b/backend/templates/lidarr/compose.yaml new file mode 100644 index 0000000..8f4551a --- /dev/null +++ b/backend/templates/lidarr/compose.yaml @@ -0,0 +1,15 @@ +services: + lidarr: + image: lscr.io/linuxserver/lidarr:latest + container_name: lidarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8686}:8686" + volumes: + - ${DATA_PATH:-/srv/lidarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/lidarr/template.json b/backend/templates/lidarr/template.json new file mode 100644 index 0000000..7813a96 --- /dev/null +++ b/backend/templates/lidarr/template.json @@ -0,0 +1,11 @@ +{ + "name": "Lidarr", + "description": "Automates finding, downloading and organising music.", + "tags": [ + "media", + "music", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/linkwarden/.env.example b/backend/templates/linkwarden/.env.example new file mode 100644 index 0000000..8e13a49 --- /dev/null +++ b/backend/templates/linkwarden/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/linkwarden +HTTP_PORT=3010 +NEXTAUTH_URL=http://localhost:3010 +POSTGRES_PASSWORD=change-me +# Both required. Generate each with: openssl rand -hex 32 +NEXTAUTH_SECRET= +MEILI_MASTER_KEY= diff --git a/backend/templates/linkwarden/compose.yaml b/backend/templates/linkwarden/compose.yaml new file mode 100644 index 0000000..6adecf2 --- /dev/null +++ b/backend/templates/linkwarden/compose.yaml @@ -0,0 +1,38 @@ +services: + linkwarden: + image: ghcr.io/linkwarden/linkwarden:latest + container_name: linkwarden + restart: unless-stopped + depends_on: + - postgres + - meilisearch + environment: + - DATABASE_URL=postgresql://linkwarden:${POSTGRES_PASSWORD:-linkwarden}@postgres:5432/linkwarden + - NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3010}/api/v1/auth + - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?auth secret required} + - MEILI_HOST=http://meilisearch:7700 + - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:?meilisearch key required} + ports: + - "${HTTP_PORT:-3010}:3000" + volumes: + - ${DATA_PATH:-/srv/linkwarden}/data:/data/data + + postgres: + image: postgres:16-alpine + container_name: linkwarden-db + restart: unless-stopped + environment: + - POSTGRES_DB=linkwarden + - POSTGRES_USER=linkwarden + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-linkwarden} + volumes: + - ${DATA_PATH:-/srv/linkwarden}/pgdata:/var/lib/postgresql/data + + meilisearch: + image: getmeili/meilisearch:v1.13 + container_name: linkwarden-meili + restart: unless-stopped + environment: + - MEILI_MASTER_KEY=${MEILI_MASTER_KEY} + volumes: + - ${DATA_PATH:-/srv/linkwarden}/meili:/meili_data diff --git a/backend/templates/linkwarden/template.json b/backend/templates/linkwarden/template.json new file mode 100644 index 0000000..ac805ef --- /dev/null +++ b/backend/templates/linkwarden/template.json @@ -0,0 +1,9 @@ +{ + "name": "Linkwarden", + "description": "Bookmark manager that archives pages as screenshots and PDFs, with full-text search.", + "tags": [ + "productivity", + "bookmarks" + ], + "gpu": null +} diff --git a/backend/templates/mariadb/.env.example b/backend/templates/mariadb/.env.example new file mode 100644 index 0000000..9a31cae --- /dev/null +++ b/backend/templates/mariadb/.env.example @@ -0,0 +1,9 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/mariadb +DB_PORT=3306 +ADMINER_PORT=8091 +MYSQL_DATABASE=app +MYSQL_USER=app +# Both required. +MYSQL_PASSWORD= +MYSQL_ROOT_PASSWORD= diff --git a/backend/templates/mariadb/compose.yaml b/backend/templates/mariadb/compose.yaml new file mode 100644 index 0000000..09e890b --- /dev/null +++ b/backend/templates/mariadb/compose.yaml @@ -0,0 +1,28 @@ +services: + mariadb: + image: mariadb:11 + container_name: mariadb + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED + environment: + - MARIADB_AUTO_UPGRADE=1 + - MARIADB_DATABASE=${MYSQL_DATABASE:-app} + - MARIADB_USER=${MYSQL_USER:-app} + - MARIADB_PASSWORD=${MYSQL_PASSWORD:?database password required} + - MARIADB_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:?root password required} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${DB_PORT:-3306}:3306" + volumes: + - ${DATA_PATH:-/srv/mariadb}:/var/lib/mysql + + adminer: + image: adminer:latest + container_name: mariadb-adminer + restart: unless-stopped + depends_on: + - mariadb + environment: + - ADMINER_DEFAULT_SERVER=mariadb + ports: + - "${ADMINER_PORT:-8091}:8080" diff --git a/backend/templates/mariadb/template.json b/backend/templates/mariadb/template.json new file mode 100644 index 0000000..38fcef9 --- /dev/null +++ b/backend/templates/mariadb/template.json @@ -0,0 +1,9 @@ +{ + "name": "MariaDB + Adminer", + "description": "Standalone MySQL-compatible database for your own apps, with Adminer for browsing it.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/mealie/.env.example b/backend/templates/mealie/.env.example new file mode 100644 index 0000000..43054d6 --- /dev/null +++ b/backend/templates/mealie/.env.example @@ -0,0 +1,6 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/mealie +HTTP_PORT=9925 +BASE_URL=http://localhost:9925 diff --git a/backend/templates/mealie/compose.yaml b/backend/templates/mealie/compose.yaml new file mode 100644 index 0000000..ce908d3 --- /dev/null +++ b/backend/templates/mealie/compose.yaml @@ -0,0 +1,19 @@ +services: + mealie: + image: ghcr.io/mealie-recipes/mealie:latest + container_name: mealie + restart: unless-stopped + deploy: + resources: + limits: + memory: 1G + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - BASE_URL=${BASE_URL:-http://localhost:9925} + - ALLOW_SIGNUP=false + ports: + - "${HTTP_PORT:-9925}:9000" + volumes: + - ${DATA_PATH:-/srv/mealie}:/app/data diff --git a/backend/templates/mealie/template.json b/backend/templates/mealie/template.json new file mode 100644 index 0000000..fd4ea71 --- /dev/null +++ b/backend/templates/mealie/template.json @@ -0,0 +1,9 @@ +{ + "name": "Mealie", + "description": "Recipe manager and meal planner that imports recipes from any URL. First login: changeme@example.com / MyPassword.", + "tags": [ + "recipes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/memos/.env.example b/backend/templates/memos/.env.example new file mode 100644 index 0000000..80cb2db --- /dev/null +++ b/backend/templates/memos/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/memos +HTTP_PORT=5230 diff --git a/backend/templates/memos/compose.yaml b/backend/templates/memos/compose.yaml new file mode 100644 index 0000000..aaf8187 --- /dev/null +++ b/backend/templates/memos/compose.yaml @@ -0,0 +1,11 @@ +services: + memos: + image: neosmemo/memos:stable + container_name: memos + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-5230}:5230" + volumes: + - ${DATA_PATH:-/srv/memos}:/var/opt/memos diff --git a/backend/templates/memos/template.json b/backend/templates/memos/template.json new file mode 100644 index 0000000..e9c63bf --- /dev/null +++ b/backend/templates/memos/template.json @@ -0,0 +1,9 @@ +{ + "name": "Memos", + "description": "Lightweight, Markdown-first note taking — a self-hosted take on microblogging your thoughts.", + "tags": [ + "notes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/minecraft-server/.env.example b/backend/templates/minecraft-server/.env.example new file mode 100644 index 0000000..1319733 --- /dev/null +++ b/backend/templates/minecraft-server/.env.example @@ -0,0 +1,11 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/minecraft +GAME_PORT=25565 +# VANILLA, PAPER, FABRIC, FORGE, ... +TYPE=PAPER +VERSION=LATEST +MEMORY=4G +DIFFICULTY=normal +MOTD=Homelab Minecraft +# Comma-separated player names granted operator rights. +OPS= diff --git a/backend/templates/minecraft-server/compose.yaml b/backend/templates/minecraft-server/compose.yaml new file mode 100644 index 0000000..50217d4 --- /dev/null +++ b/backend/templates/minecraft-server/compose.yaml @@ -0,0 +1,20 @@ +services: + minecraft: + image: itzg/minecraft-server:latest + container_name: minecraft + restart: unless-stopped + stdin_open: true + tty: true + environment: + - EULA=TRUE + - TZ=${TZ:-Europe/Berlin} + - TYPE=${TYPE:-PAPER} + - VERSION=${VERSION:-LATEST} + - MEMORY=${MEMORY:-4G} + - DIFFICULTY=${DIFFICULTY:-normal} + - MOTD=${MOTD:-Homelab Minecraft} + - OPS=${OPS:-} + ports: + - "${GAME_PORT:-25565}:25565" + volumes: + - ${DATA_PATH:-/srv/minecraft}:/data diff --git a/backend/templates/minecraft-server/template.json b/backend/templates/minecraft-server/template.json new file mode 100644 index 0000000..45cfb4d --- /dev/null +++ b/backend/templates/minecraft-server/template.json @@ -0,0 +1,8 @@ +{ + "name": "Minecraft Server", + "description": "Java Edition server (Paper, Fabric, Forge or vanilla) with automatic version downloads.", + "tags": [ + "games" + ], + "gpu": null +} diff --git a/backend/templates/miniflux/.env.example b/backend/templates/miniflux/.env.example new file mode 100644 index 0000000..d96a52a --- /dev/null +++ b/backend/templates/miniflux/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/miniflux +HTTP_PORT=8094 +DB_PASSWORD=change-me +ADMIN_USER=admin +# Required, at least 6 characters. +ADMIN_PASSWORD= diff --git a/backend/templates/miniflux/compose.yaml b/backend/templates/miniflux/compose.yaml new file mode 100644 index 0000000..307b938 --- /dev/null +++ b/backend/templates/miniflux/compose.yaml @@ -0,0 +1,32 @@ +services: + miniflux: + image: miniflux/miniflux:latest + container_name: miniflux + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + - DATABASE_URL=postgres://miniflux:${DB_PASSWORD:-miniflux}@db/miniflux?sslmode=disable + - RUN_MIGRATIONS=1 + - CREATE_ADMIN=1 + - ADMIN_USERNAME=${ADMIN_USER:-admin} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:?admin password required, min 6 chars} + ports: + - "${HTTP_PORT:-8094}:8080" + + db: + image: postgres:16-alpine + container_name: miniflux-db + restart: unless-stopped + environment: + - POSTGRES_DB=miniflux + - POSTGRES_USER=miniflux + - POSTGRES_PASSWORD=${DB_PASSWORD:-miniflux} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U miniflux"] + interval: 10s + timeout: 5s + retries: 10 + volumes: + - ${DATA_PATH:-/srv/miniflux}/db:/var/lib/postgresql/data diff --git a/backend/templates/miniflux/template.json b/backend/templates/miniflux/template.json new file mode 100644 index 0000000..720bb99 --- /dev/null +++ b/backend/templates/miniflux/template.json @@ -0,0 +1,9 @@ +{ + "name": "Miniflux", + "description": "Minimalist, very fast RSS reader with a Google Reader compatible API.", + "tags": [ + "rss", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/minio/.env.example b/backend/templates/minio/.env.example new file mode 100644 index 0000000..3686b2f --- /dev/null +++ b/backend/templates/minio/.env.example @@ -0,0 +1,7 @@ +DATA_PATH=/srv/minio +API_PORT=9010 +CONSOLE_PORT=9011 +CONSOLE_URL=http://localhost:9011 +ROOT_USER=minioadmin +# Required, at least 8 characters. +ROOT_PASSWORD= diff --git a/backend/templates/minio/compose.yaml b/backend/templates/minio/compose.yaml new file mode 100644 index 0000000..0d4abae --- /dev/null +++ b/backend/templates/minio/compose.yaml @@ -0,0 +1,15 @@ +services: + minio: + image: minio/minio:latest + container_name: minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + - MINIO_ROOT_USER=${ROOT_USER:-minioadmin} + - MINIO_ROOT_PASSWORD=${ROOT_PASSWORD:?root password required} + - MINIO_BROWSER_REDIRECT_URL=${CONSOLE_URL:-http://localhost:9011} + ports: + - "${API_PORT:-9010}:9000" + - "${CONSOLE_PORT:-9011}:9001" + volumes: + - ${DATA_PATH:-/srv/minio}:/data diff --git a/backend/templates/minio/template.json b/backend/templates/minio/template.json new file mode 100644 index 0000000..8a1caf3 --- /dev/null +++ b/backend/templates/minio/template.json @@ -0,0 +1,10 @@ +{ + "name": "MinIO", + "description": "S3-compatible object storage — a handy backup target for Restic, Duplicati and friends.", + "tags": [ + "storage", + "backup", + "files" + ], + "gpu": null +} diff --git a/backend/templates/mosquitto/.env.example b/backend/templates/mosquitto/.env.example new file mode 100644 index 0000000..712db9f --- /dev/null +++ b/backend/templates/mosquitto/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/mosquitto +MQTT_PORT=1883 +WS_PORT=9001 diff --git a/backend/templates/mosquitto/compose.yaml b/backend/templates/mosquitto/compose.yaml new file mode 100644 index 0000000..c2b97d7 --- /dev/null +++ b/backend/templates/mosquitto/compose.yaml @@ -0,0 +1,12 @@ +services: + mosquitto: + image: eclipse-mosquitto:2 + container_name: mosquitto + restart: unless-stopped + ports: + - "${MQTT_PORT:-1883}:1883" + - "${WS_PORT:-9001}:9001" + volumes: + - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro + - ${DATA_PATH:-/srv/mosquitto}/data:/mosquitto/data + - ${DATA_PATH:-/srv/mosquitto}/log:/mosquitto/log diff --git a/backend/templates/mosquitto/mosquitto.conf b/backend/templates/mosquitto/mosquitto.conf new file mode 100644 index 0000000..ac66092 --- /dev/null +++ b/backend/templates/mosquitto/mosquitto.conf @@ -0,0 +1,13 @@ +persistence true +persistence_location /mosquitto/data/ +log_dest stdout + +listener 1883 +listener 9001 +protocol websockets + +# Anonymous access is fine on a trusted LAN. To require credentials: +# 1. set allow_anonymous false +# 2. add "password_file /mosquitto/config/passwd" +# 3. docker compose exec mosquitto mosquitto_passwd -c /mosquitto/config/passwd +allow_anonymous true diff --git a/backend/templates/mosquitto/template.json b/backend/templates/mosquitto/template.json new file mode 100644 index 0000000..0d11966 --- /dev/null +++ b/backend/templates/mosquitto/template.json @@ -0,0 +1,10 @@ +{ + "name": "Mosquitto", + "description": "MQTT broker — the message bus behind Zigbee2MQTT, ESPHome and Home Assistant.", + "tags": [ + "home-automation", + "iot", + "mqtt" + ], + "gpu": null +} diff --git a/backend/templates/n8n/.env.example b/backend/templates/n8n/.env.example new file mode 100644 index 0000000..6b3561f --- /dev/null +++ b/backend/templates/n8n/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/n8n +HTTP_PORT=5678 +N8N_HOST=localhost +N8N_PROTOCOL=http +WEBHOOK_URL=http://localhost:5678/ +# Required. Generate with: openssl rand -hex 32 +N8N_ENCRYPTION_KEY= diff --git a/backend/templates/n8n/compose.yaml b/backend/templates/n8n/compose.yaml new file mode 100644 index 0000000..bd2df6c --- /dev/null +++ b/backend/templates/n8n/compose.yaml @@ -0,0 +1,19 @@ +services: + n8n: + image: n8nio/n8n:latest + container_name: n8n + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - GENERIC_TIMEZONE=${TZ:-Europe/Berlin} + - N8N_HOST=${N8N_HOST:-localhost} + - N8N_PORT=5678 + - N8N_PROTOCOL=${N8N_PROTOCOL:-http} + - WEBHOOK_URL=${WEBHOOK_URL:-http://localhost:5678/} + # Required. Generate with: openssl rand -hex 32 + - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY:?encryption key required} + - N8N_RUNNERS_ENABLED=true + ports: + - "${HTTP_PORT:-5678}:5678" + volumes: + - ${DATA_PATH:-/srv/n8n}:/home/node/.n8n diff --git a/backend/templates/n8n/template.json b/backend/templates/n8n/template.json new file mode 100644 index 0000000..b382854 --- /dev/null +++ b/backend/templates/n8n/template.json @@ -0,0 +1,10 @@ +{ + "name": "n8n", + "description": "Workflow automation with 400+ integrations — the self-hosted answer to Zapier.", + "tags": [ + "automation", + "dev", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/navidrome/.env.example b/backend/templates/navidrome/.env.example new file mode 100644 index 0000000..1aee1dd --- /dev/null +++ b/backend/templates/navidrome/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/navidrome +MUSIC_PATH=/srv/media/music +HTTP_PORT=4533 diff --git a/backend/templates/navidrome/compose.yaml b/backend/templates/navidrome/compose.yaml new file mode 100644 index 0000000..39e8126 --- /dev/null +++ b/backend/templates/navidrome/compose.yaml @@ -0,0 +1,15 @@ +services: + navidrome: + image: deluan/navidrome:latest + container_name: navidrome + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - ND_LOGLEVEL=info + - ND_SCANSCHEDULE=1h + - ND_SESSIONTIMEOUT=24h + ports: + - "${HTTP_PORT:-4533}:4533" + volumes: + - ${DATA_PATH:-/srv/navidrome}:/data + - ${MUSIC_PATH:-/srv/media/music}:/music:ro diff --git a/backend/templates/navidrome/template.json b/backend/templates/navidrome/template.json new file mode 100644 index 0000000..e0b2a7d --- /dev/null +++ b/backend/templates/navidrome/template.json @@ -0,0 +1,10 @@ +{ + "name": "Navidrome", + "description": "Music streaming server compatible with any Subsonic client.", + "tags": [ + "media", + "music", + "streaming" + ], + "gpu": null +} diff --git a/backend/templates/netdata/.env.example b/backend/templates/netdata/.env.example new file mode 100644 index 0000000..165b3a6 --- /dev/null +++ b/backend/templates/netdata/.env.example @@ -0,0 +1,3 @@ +NETDATA_HOSTNAME=homelab +DATA_PATH=/srv/netdata +HTTP_PORT=19999 diff --git a/backend/templates/netdata/compose.yaml b/backend/templates/netdata/compose.yaml new file mode 100644 index 0000000..8c38f90 --- /dev/null +++ b/backend/templates/netdata/compose.yaml @@ -0,0 +1,26 @@ +services: + netdata: + image: netdata/netdata:latest + container_name: netdata + restart: unless-stopped + hostname: ${NETDATA_HOSTNAME:-homelab} + pid: host + cap_add: + - SYS_PTRACE + - SYS_ADMIN + security_opt: + - apparmor:unconfined + ports: + - "${HTTP_PORT:-19999}:19999" + volumes: + - ${DATA_PATH:-/srv/netdata}/config:/etc/netdata + - ${DATA_PATH:-/srv/netdata}/lib:/var/lib/netdata + - ${DATA_PATH:-/srv/netdata}/cache:/var/cache/netdata + - /etc/passwd:/host/etc/passwd:ro + - /etc/group:/host/etc/group:ro + - /etc/localtime:/etc/localtime:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + - /var/log:/host/var/log:ro + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/backend/templates/netdata/template.json b/backend/templates/netdata/template.json new file mode 100644 index 0000000..19d735b --- /dev/null +++ b/backend/templates/netdata/template.json @@ -0,0 +1,9 @@ +{ + "name": "Netdata", + "description": "Per-second metrics for the host and every container, with zero configuration.", + "tags": [ + "monitoring", + "metrics" + ], + "gpu": null +} diff --git a/backend/templates/nextcloud/.env.example b/backend/templates/nextcloud/.env.example new file mode 100644 index 0000000..380a012 --- /dev/null +++ b/backend/templates/nextcloud/.env.example @@ -0,0 +1,9 @@ +DATA_PATH=/srv/nextcloud +FILES_PATH=/srv/nextcloud/data +HTTP_PORT=8087 +TRUSTED_DOMAINS=localhost +UPLOAD_LIMIT=16G +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/nextcloud/compose.yaml b/backend/templates/nextcloud/compose.yaml new file mode 100644 index 0000000..a250b94 --- /dev/null +++ b/backend/templates/nextcloud/compose.yaml @@ -0,0 +1,42 @@ +services: + nextcloud: + image: nextcloud:latest + container_name: nextcloud + restart: unless-stopped + depends_on: + - db + - redis + environment: + - MYSQL_HOST=db + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${DB_PASSWORD:-nextcloud} + - REDIS_HOST=redis + - NEXTCLOUD_ADMIN_USER=${ADMIN_USER:-admin} + - NEXTCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - NEXTCLOUD_TRUSTED_DOMAINS=${TRUSTED_DOMAINS:-localhost} + - PHP_UPLOAD_LIMIT=${UPLOAD_LIMIT:-16G} + ports: + - "${HTTP_PORT:-8087}:80" + volumes: + - ${DATA_PATH:-/srv/nextcloud}/html:/var/www/html + - ${FILES_PATH:-/srv/nextcloud/data}:/var/www/html/data + + db: + image: mariadb:11 + container_name: nextcloud-db + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW + environment: + - MARIADB_AUTO_UPGRADE=1 + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${DB_PASSWORD:-nextcloud} + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-nextcloud} + volumes: + - ${DATA_PATH:-/srv/nextcloud}/db:/var/lib/mysql + + redis: + image: redis:7-alpine + container_name: nextcloud-redis + restart: unless-stopped diff --git a/backend/templates/nextcloud/template.json b/backend/templates/nextcloud/template.json new file mode 100644 index 0000000..f8726d2 --- /dev/null +++ b/backend/templates/nextcloud/template.json @@ -0,0 +1,10 @@ +{ + "name": "Nextcloud", + "description": "File sync and share with calendar, contacts and an app store, backed by MariaDB and Redis.", + "tags": [ + "files", + "sync", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/nginx-proxy-manager/.env.example b/backend/templates/nginx-proxy-manager/.env.example new file mode 100644 index 0000000..57cc37c --- /dev/null +++ b/backend/templates/nginx-proxy-manager/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/nginx-proxy-manager +HTTP_PORT=80 +HTTPS_PORT=443 +ADMIN_PORT=81 diff --git a/backend/templates/nginx-proxy-manager/compose.yaml b/backend/templates/nginx-proxy-manager/compose.yaml new file mode 100644 index 0000000..4618f4d --- /dev/null +++ b/backend/templates/nginx-proxy-manager/compose.yaml @@ -0,0 +1,12 @@ +services: + npm: + image: jc21/nginx-proxy-manager:latest + container_name: nginx-proxy-manager + restart: unless-stopped + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${ADMIN_PORT:-81}:81" + volumes: + - ${DATA_PATH:-/srv/nginx-proxy-manager}/data:/data + - ${DATA_PATH:-/srv/nginx-proxy-manager}/letsencrypt:/etc/letsencrypt diff --git a/backend/templates/nginx-proxy-manager/template.json b/backend/templates/nginx-proxy-manager/template.json new file mode 100644 index 0000000..2273659 --- /dev/null +++ b/backend/templates/nginx-proxy-manager/template.json @@ -0,0 +1,9 @@ +{ + "name": "Nginx Proxy Manager", + "description": "Reverse proxy with a click-through UI for hosts and Let's Encrypt certs. First login: admin@example.com / changeme.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/node-red/.env.example b/backend/templates/node-red/.env.example new file mode 100644 index 0000000..43f70e9 --- /dev/null +++ b/backend/templates/node-red/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/node-red +HTTP_PORT=1880 diff --git a/backend/templates/node-red/compose.yaml b/backend/templates/node-red/compose.yaml new file mode 100644 index 0000000..ebaf5ce --- /dev/null +++ b/backend/templates/node-red/compose.yaml @@ -0,0 +1,12 @@ +services: + node-red: + image: nodered/node-red:latest + container_name: node-red + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-1880}:1880" + volumes: + - ${DATA_PATH:-/srv/node-red}:/data diff --git a/backend/templates/node-red/template.json b/backend/templates/node-red/template.json new file mode 100644 index 0000000..316a6e8 --- /dev/null +++ b/backend/templates/node-red/template.json @@ -0,0 +1,10 @@ +{ + "name": "Node-RED", + "description": "Flow-based automation wiring together MQTT, HTTP, Home Assistant and custom logic.", + "tags": [ + "home-automation", + "automation", + "iot" + ], + "gpu": null +} diff --git a/backend/templates/ntfy/.env.example b/backend/templates/ntfy/.env.example new file mode 100644 index 0000000..e796863 --- /dev/null +++ b/backend/templates/ntfy/.env.example @@ -0,0 +1,6 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/ntfy +HTTP_PORT=8097 +BASE_URL=http://localhost:8097 +DEFAULT_ACCESS=deny-all +BEHIND_PROXY=false diff --git a/backend/templates/ntfy/compose.yaml b/backend/templates/ntfy/compose.yaml new file mode 100644 index 0000000..ae15b5d --- /dev/null +++ b/backend/templates/ntfy/compose.yaml @@ -0,0 +1,21 @@ +services: + ntfy: + image: binwiederhier/ntfy:latest + container_name: ntfy + restart: unless-stopped + command: serve + environment: + - TZ=${TZ:-Europe/Berlin} + - NTFY_BASE_URL=${BASE_URL:-http://localhost:8097} + # Set to true only when a reverse proxy sits in front (fixes client IPs). + - NTFY_BEHIND_PROXY=${BEHIND_PROXY:-false} + - NTFY_CACHE_FILE=/var/cache/ntfy/cache.db + - NTFY_AUTH_FILE=/var/lib/ntfy/user.db + # "read-write" is an open server; use "deny-all" plus `ntfy user add` for a private one. + - NTFY_AUTH_DEFAULT_ACCESS=${DEFAULT_ACCESS:-deny-all} + - NTFY_ENABLE_LOGIN=true + ports: + - "${HTTP_PORT:-8097}:80" + volumes: + - ${DATA_PATH:-/srv/ntfy}/cache:/var/cache/ntfy + - ${DATA_PATH:-/srv/ntfy}/lib:/var/lib/ntfy diff --git a/backend/templates/ntfy/template.json b/backend/templates/ntfy/template.json new file mode 100644 index 0000000..c89813f --- /dev/null +++ b/backend/templates/ntfy/template.json @@ -0,0 +1,9 @@ +{ + "name": "ntfy", + "description": "Push notifications to your phone from any script with a plain HTTP POST. Create users with: docker compose exec ntfy ntfy user add --role=admin ", + "tags": [ + "notifications", + "monitoring" + ], + "gpu": null +} diff --git a/backend/templates/ollama-open-webui/.env.example b/backend/templates/ollama-open-webui/.env.example new file mode 100644 index 0000000..5851360 --- /dev/null +++ b/backend/templates/ollama-open-webui/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/ollama +HTTP_PORT=3020 +OLLAMA_PORT=11434 +# Required. Generate with: openssl rand -hex 32 +WEBUI_SECRET_KEY= diff --git a/backend/templates/ollama-open-webui/compose.yaml b/backend/templates/ollama-open-webui/compose.yaml new file mode 100644 index 0000000..f14ff35 --- /dev/null +++ b/backend/templates/ollama-open-webui/compose.yaml @@ -0,0 +1,31 @@ +services: + ollama: + image: ollama/ollama:latest + container_name: ollama + restart: unless-stopped + ports: + - "${OLLAMA_PORT:-11434}:11434" + volumes: + - ${DATA_PATH:-/srv/ollama}/models:/root/.ollama + # Uncomment for NVIDIA GPUs (needs the NVIDIA Container Toolkit on the host): + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: all + # capabilities: [gpu] + + open-webui: + image: ghcr.io/open-webui/open-webui:main + container_name: open-webui + restart: unless-stopped + depends_on: + - ollama + environment: + - OLLAMA_BASE_URL=http://ollama:11434 + - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY:?secret key required} + ports: + - "${HTTP_PORT:-3020}:8080" + volumes: + - ${DATA_PATH:-/srv/ollama}/open-webui:/app/backend/data diff --git a/backend/templates/ollama-open-webui/template.json b/backend/templates/ollama-open-webui/template.json new file mode 100644 index 0000000..7f364fa --- /dev/null +++ b/backend/templates/ollama-open-webui/template.json @@ -0,0 +1,9 @@ +{ + "name": "Ollama + Open WebUI", + "description": "Local LLM runtime with a ChatGPT-style web front-end. Runs on CPU; far faster with a GPU.", + "tags": [ + "ai", + "llm" + ], + "gpu": "NVIDIA optional" +} diff --git a/backend/templates/photoprism/.env.example b/backend/templates/photoprism/.env.example new file mode 100644 index 0000000..8009645 --- /dev/null +++ b/backend/templates/photoprism/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/photoprism +ORIGINALS_PATH=/srv/media/photos +HTTP_PORT=2342 +SITE_URL=http://localhost:2342/ +ADMIN_USER=admin +ADMIN_PASSWORD=change-me +DB_PASSWORD=change-me +DB_ROOT_PASSWORD=change-me diff --git a/backend/templates/photoprism/compose.yaml b/backend/templates/photoprism/compose.yaml new file mode 100644 index 0000000..e841cb9 --- /dev/null +++ b/backend/templates/photoprism/compose.yaml @@ -0,0 +1,41 @@ +services: + photoprism: + image: photoprism/photoprism:latest + container_name: photoprism + restart: unless-stopped + depends_on: + - mariadb + security_opt: + - seccomp:unconfined + - apparmor:unconfined + environment: + - PHOTOPRISM_ADMIN_USER=${ADMIN_USER:-admin} + - PHOTOPRISM_ADMIN_PASSWORD=${ADMIN_PASSWORD:-change-me} + - PHOTOPRISM_SITE_URL=${SITE_URL:-http://localhost:2342/} + - PHOTOPRISM_ORIGINALS_LIMIT=5000 + - PHOTOPRISM_DATABASE_DRIVER=mysql + - PHOTOPRISM_DATABASE_SERVER=mariadb:3306 + - PHOTOPRISM_DATABASE_NAME=photoprism + - PHOTOPRISM_DATABASE_USER=photoprism + - PHOTOPRISM_DATABASE_PASSWORD=${DB_PASSWORD:-photoprism} + - PHOTOPRISM_UID=${PUID:-1000} + - PHOTOPRISM_GID=${PGID:-1000} + ports: + - "${HTTP_PORT:-2342}:2342" + volumes: + - ${ORIGINALS_PATH:-/srv/media/photos}:/photoprism/originals + - ${DATA_PATH:-/srv/photoprism}/storage:/photoprism/storage + + mariadb: + image: mariadb:11 + container_name: photoprism-db + restart: unless-stopped + command: --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED + environment: + - MARIADB_AUTO_UPGRADE=1 + - MARIADB_DATABASE=photoprism + - MARIADB_USER=photoprism + - MARIADB_PASSWORD=${DB_PASSWORD:-photoprism} + - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-photoprism} + volumes: + - ${DATA_PATH:-/srv/photoprism}/mariadb:/var/lib/mysql diff --git a/backend/templates/photoprism/template.json b/backend/templates/photoprism/template.json new file mode 100644 index 0000000..2b47048 --- /dev/null +++ b/backend/templates/photoprism/template.json @@ -0,0 +1,9 @@ +{ + "name": "PhotoPrism", + "description": "AI-powered photo library that tags and indexes your originals.", + "tags": [ + "media", + "photos" + ], + "gpu": null +} diff --git a/backend/templates/pihole/.env.example b/backend/templates/pihole/.env.example new file mode 100644 index 0000000..2b9c441 --- /dev/null +++ b/backend/templates/pihole/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/pihole +DNS_PORT=53 +HTTP_PORT=8081 +WEB_PASSWORD=change-me diff --git a/backend/templates/pihole/compose.yaml b/backend/templates/pihole/compose.yaml new file mode 100644 index 0000000..f983d76 --- /dev/null +++ b/backend/templates/pihole/compose.yaml @@ -0,0 +1,20 @@ +services: + pihole: + image: pihole/pihole:latest + container_name: pihole + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - FTLCONF_webserver_api_password=${WEB_PASSWORD:-change-me} + - FTLCONF_dns_listeningMode=all + ports: + - "${DNS_PORT:-53}:53/tcp" + - "${DNS_PORT:-53}:53/udp" + - "${HTTP_PORT:-8081}:80/tcp" + volumes: + - ${DATA_PATH:-/srv/pihole}/etc-pihole:/etc/pihole + cap_add: + # Only required when Pi-hole also serves DHCP + - NET_ADMIN + - SYS_TIME + - SYS_NICE diff --git a/backend/templates/pihole/template.json b/backend/templates/pihole/template.json new file mode 100644 index 0000000..747d2c5 --- /dev/null +++ b/backend/templates/pihole/template.json @@ -0,0 +1,10 @@ +{ + "name": "Pi-hole", + "description": "Network-wide DNS ad blocker. Port 53 must be free — disable systemd-resolved's stub listener first.", + "tags": [ + "network", + "dns", + "ad-blocking" + ], + "gpu": null +} diff --git a/backend/templates/plex/.env.example b/backend/templates/plex/.env.example new file mode 100644 index 0000000..b43a153 --- /dev/null +++ b/backend/templates/plex/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +CONFIG_PATH=/srv/plex/config +MEDIA_PATH=/srv/media +HTTP_PORT=32400 +PLEX_CLAIM= diff --git a/backend/templates/plex/compose.yaml b/backend/templates/plex/compose.yaml new file mode 100644 index 0000000..524ab4a --- /dev/null +++ b/backend/templates/plex/compose.yaml @@ -0,0 +1,20 @@ +services: + plex: + image: lscr.io/linuxserver/plex:latest + container_name: plex + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - VERSION=docker + # Grab a claim token at https://plex.tv/claim (valid 4 minutes) + - PLEX_CLAIM=${PLEX_CLAIM:-} + ports: + - "${HTTP_PORT:-32400}:32400" + volumes: + - ${CONFIG_PATH:-/srv/plex/config}:/config + - ${MEDIA_PATH:-/srv/media}:/media + # Uncomment for Intel/AMD hardware transcoding (Plex Pass required): + # devices: + # - /dev/dri:/dev/dri diff --git a/backend/templates/plex/template.json b/backend/templates/plex/template.json new file mode 100644 index 0000000..e19c72d --- /dev/null +++ b/backend/templates/plex/template.json @@ -0,0 +1,9 @@ +{ + "name": "Plex", + "description": "Media server for movies, TV and music with polished client apps.", + "tags": [ + "media", + "streaming" + ], + "gpu": "NVIDIA / Intel optional" +} diff --git a/backend/templates/portainer/.env.example b/backend/templates/portainer/.env.example new file mode 100644 index 0000000..a9e2d4f --- /dev/null +++ b/backend/templates/portainer/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/portainer +HTTP_PORT=9000 +HTTPS_PORT=9443 diff --git a/backend/templates/portainer/compose.yaml b/backend/templates/portainer/compose.yaml new file mode 100644 index 0000000..4f4eae9 --- /dev/null +++ b/backend/templates/portainer/compose.yaml @@ -0,0 +1,11 @@ +services: + portainer: + image: portainer/portainer-ce:latest + container_name: portainer + restart: unless-stopped + ports: + - "${HTTPS_PORT:-9443}:9443" + - "${HTTP_PORT:-9000}:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${DATA_PATH:-/srv/portainer}:/data diff --git a/backend/templates/portainer/template.json b/backend/templates/portainer/template.json new file mode 100644 index 0000000..9502e72 --- /dev/null +++ b/backend/templates/portainer/template.json @@ -0,0 +1,9 @@ +{ + "name": "Portainer CE", + "description": "Docker management UI. The admin account must be created within a few minutes of first start.", + "tags": [ + "docker", + "dashboard" + ], + "gpu": null +} diff --git a/backend/templates/postgres/.env.example b/backend/templates/postgres/.env.example new file mode 100644 index 0000000..9914925 --- /dev/null +++ b/backend/templates/postgres/.env.example @@ -0,0 +1,8 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/postgres +DB_PORT=5432 +ADMINER_PORT=8089 +POSTGRES_DB=app +POSTGRES_USER=app +# Required. +POSTGRES_PASSWORD= diff --git a/backend/templates/postgres/compose.yaml b/backend/templates/postgres/compose.yaml new file mode 100644 index 0000000..d45354a --- /dev/null +++ b/backend/templates/postgres/compose.yaml @@ -0,0 +1,30 @@ +services: + postgres: + image: postgres:16-alpine + container_name: postgres + restart: unless-stopped + environment: + - POSTGRES_DB=${POSTGRES_DB:-app} + - POSTGRES_USER=${POSTGRES_USER:-app} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?database password required} + - TZ=${TZ:-Europe/Berlin} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app}"] + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${DB_PORT:-5432}:5432" + volumes: + - ${DATA_PATH:-/srv/postgres}:/var/lib/postgresql/data + + adminer: + image: adminer:latest + container_name: postgres-adminer + restart: unless-stopped + depends_on: + - postgres + environment: + - ADMINER_DEFAULT_SERVER=postgres + ports: + - "${ADMINER_PORT:-8089}:8080" diff --git a/backend/templates/postgres/template.json b/backend/templates/postgres/template.json new file mode 100644 index 0000000..63f78b6 --- /dev/null +++ b/backend/templates/postgres/template.json @@ -0,0 +1,9 @@ +{ + "name": "PostgreSQL + Adminer", + "description": "Standalone Postgres for your own apps, with Adminer for browsing it.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/prometheus/.env.example b/backend/templates/prometheus/.env.example new file mode 100644 index 0000000..bf80799 --- /dev/null +++ b/backend/templates/prometheus/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +DATA_PATH=/srv/prometheus +HTTP_PORT=9090 +RETENTION=30d diff --git a/backend/templates/prometheus/compose.yaml b/backend/templates/prometheus/compose.yaml new file mode 100644 index 0000000..626a1ae --- /dev/null +++ b/backend/templates/prometheus/compose.yaml @@ -0,0 +1,39 @@ +services: + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: unless-stopped + user: "${PUID:-1000}:${PGID:-1000}" + command: + - --config.file=/etc/prometheus/prometheus.yml + - --storage.tsdb.path=/prometheus + - --storage.tsdb.retention.time=${RETENTION:-30d} + - --web.enable-lifecycle + ports: + - "${HTTP_PORT:-9090}:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - ${DATA_PATH:-/srv/prometheus}:/prometheus + + node-exporter: + image: prom/node-exporter:latest + container_name: node-exporter + restart: unless-stopped + pid: host + command: + - --path.rootfs=/host + volumes: + - /:/host:ro,rslave + + cadvisor: + image: gcr.io/cadvisor/cadvisor:latest + container_name: cadvisor + restart: unless-stopped + privileged: true + devices: + - /dev/kmsg + volumes: + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro diff --git a/backend/templates/prometheus/prometheus.yml b/backend/templates/prometheus/prometheus.yml new file mode 100644 index 0000000..415d585 --- /dev/null +++ b/backend/templates/prometheus/prometheus.yml @@ -0,0 +1,16 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ["localhost:9090"] + + - job_name: node + static_configs: + - targets: ["node-exporter:9100"] + + - job_name: cadvisor + static_configs: + - targets: ["cadvisor:8080"] diff --git a/backend/templates/prometheus/template.json b/backend/templates/prometheus/template.json new file mode 100644 index 0000000..0ba49a5 --- /dev/null +++ b/backend/templates/prometheus/template.json @@ -0,0 +1,9 @@ +{ + "name": "Prometheus", + "description": "Metrics database with node-exporter and cAdvisor already wired up for host and container stats.", + "tags": [ + "monitoring", + "metrics" + ], + "gpu": null +} diff --git a/backend/templates/prowlarr/.env.example b/backend/templates/prowlarr/.env.example new file mode 100644 index 0000000..68e6d16 --- /dev/null +++ b/backend/templates/prowlarr/.env.example @@ -0,0 +1,5 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/prowlarr +HTTP_PORT=9696 diff --git a/backend/templates/prowlarr/compose.yaml b/backend/templates/prowlarr/compose.yaml new file mode 100644 index 0000000..aa6772e --- /dev/null +++ b/backend/templates/prowlarr/compose.yaml @@ -0,0 +1,13 @@ +services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + container_name: prowlarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-9696}:9696" + volumes: + - ${DATA_PATH:-/srv/prowlarr}:/config diff --git a/backend/templates/prowlarr/template.json b/backend/templates/prowlarr/template.json new file mode 100644 index 0000000..9ad645f --- /dev/null +++ b/backend/templates/prowlarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Prowlarr", + "description": "Indexer manager that feeds trackers and usenet indexers to the *arr apps.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/qbittorrent/.env.example b/backend/templates/qbittorrent/.env.example new file mode 100644 index 0000000..0323a26 --- /dev/null +++ b/backend/templates/qbittorrent/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/qbittorrent +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8080 +TORRENT_PORT=6881 diff --git a/backend/templates/qbittorrent/compose.yaml b/backend/templates/qbittorrent/compose.yaml new file mode 100644 index 0000000..030c299 --- /dev/null +++ b/backend/templates/qbittorrent/compose.yaml @@ -0,0 +1,18 @@ +services: + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + - WEBUI_PORT=${HTTP_PORT:-8080} + - TORRENTING_PORT=${TORRENT_PORT:-6881} + ports: + - "${HTTP_PORT:-8080}:${HTTP_PORT:-8080}" + - "${TORRENT_PORT:-6881}:${TORRENT_PORT:-6881}" + - "${TORRENT_PORT:-6881}:${TORRENT_PORT:-6881}/udp" + volumes: + - ${DATA_PATH:-/srv/qbittorrent}:/config + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/qbittorrent/template.json b/backend/templates/qbittorrent/template.json new file mode 100644 index 0000000..8b52309 --- /dev/null +++ b/backend/templates/qbittorrent/template.json @@ -0,0 +1,9 @@ +{ + "name": "qBittorrent", + "description": "BitTorrent client with a web UI. The temporary admin password is printed to the container log on first start.", + "tags": [ + "downloads", + "media" + ], + "gpu": null +} diff --git a/backend/templates/radarr/.env.example b/backend/templates/radarr/.env.example new file mode 100644 index 0000000..0845023 --- /dev/null +++ b/backend/templates/radarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/radarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=7878 diff --git a/backend/templates/radarr/compose.yaml b/backend/templates/radarr/compose.yaml new file mode 100644 index 0000000..ad266a3 --- /dev/null +++ b/backend/templates/radarr/compose.yaml @@ -0,0 +1,15 @@ +services: + radarr: + image: lscr.io/linuxserver/radarr:latest + container_name: radarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-7878}:7878" + volumes: + - ${DATA_PATH:-/srv/radarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/radarr/template.json b/backend/templates/radarr/template.json new file mode 100644 index 0000000..bd52e4b --- /dev/null +++ b/backend/templates/radarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Radarr", + "description": "Automates finding, downloading and organising movies.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/redis/.env.example b/backend/templates/redis/.env.example new file mode 100644 index 0000000..e0a5254 --- /dev/null +++ b/backend/templates/redis/.env.example @@ -0,0 +1,4 @@ +DATA_PATH=/srv/redis +REDIS_PORT=6379 +# Required. +REDIS_PASSWORD= diff --git a/backend/templates/redis/compose.yaml b/backend/templates/redis/compose.yaml new file mode 100644 index 0000000..ac9aa99 --- /dev/null +++ b/backend/templates/redis/compose.yaml @@ -0,0 +1,20 @@ +services: + redis: + image: redis:7-alpine + container_name: redis + restart: unless-stopped + environment: + - REDIS_PASSWORD=${REDIS_PASSWORD:?redis password required} + command: > + redis-server + --appendonly yes + --requirepass ${REDIS_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "redis-cli --no-auth-warning -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"] + interval: 10s + timeout: 5s + retries: 5 + ports: + - "${REDIS_PORT:-6379}:6379" + volumes: + - ${DATA_PATH:-/srv/redis}:/data diff --git a/backend/templates/redis/template.json b/backend/templates/redis/template.json new file mode 100644 index 0000000..7620d11 --- /dev/null +++ b/backend/templates/redis/template.json @@ -0,0 +1,9 @@ +{ + "name": "Redis", + "description": "In-memory cache and queue with append-only persistence, for apps that need one.", + "tags": [ + "database", + "dev" + ], + "gpu": null +} diff --git a/backend/templates/sabnzbd/.env.example b/backend/templates/sabnzbd/.env.example new file mode 100644 index 0000000..e729be9 --- /dev/null +++ b/backend/templates/sabnzbd/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/sabnzbd +DOWNLOADS_PATH=/srv/downloads +INCOMPLETE_PATH=/srv/downloads/incomplete +HTTP_PORT=8880 diff --git a/backend/templates/sabnzbd/compose.yaml b/backend/templates/sabnzbd/compose.yaml new file mode 100644 index 0000000..bfd55fb --- /dev/null +++ b/backend/templates/sabnzbd/compose.yaml @@ -0,0 +1,15 @@ +services: + sabnzbd: + image: lscr.io/linuxserver/sabnzbd:latest + container_name: sabnzbd + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8880}:8080" + volumes: + - ${DATA_PATH:-/srv/sabnzbd}:/config + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads + - ${INCOMPLETE_PATH:-/srv/downloads/incomplete}:/incomplete-downloads diff --git a/backend/templates/sabnzbd/template.json b/backend/templates/sabnzbd/template.json new file mode 100644 index 0000000..33042a2 --- /dev/null +++ b/backend/templates/sabnzbd/template.json @@ -0,0 +1,9 @@ +{ + "name": "SABnzbd", + "description": "Usenet binary downloader that hands finished jobs to the *arr apps.", + "tags": [ + "downloads", + "media" + ], + "gpu": null +} diff --git a/backend/templates/scrutiny/.env.example b/backend/templates/scrutiny/.env.example new file mode 100644 index 0000000..76f4b1e --- /dev/null +++ b/backend/templates/scrutiny/.env.example @@ -0,0 +1,2 @@ +DATA_PATH=/srv/scrutiny +HTTP_PORT=8085 diff --git a/backend/templates/scrutiny/compose.yaml b/backend/templates/scrutiny/compose.yaml new file mode 100644 index 0000000..8243283 --- /dev/null +++ b/backend/templates/scrutiny/compose.yaml @@ -0,0 +1,19 @@ +services: + scrutiny: + image: ghcr.io/analogj/scrutiny:master-omnibus + container_name: scrutiny + restart: unless-stopped + cap_add: + - SYS_RAWIO + ports: + - "${HTTP_PORT:-8085}:8080" + volumes: + - /run/udev:/run/udev:ro + - ${DATA_PATH:-/srv/scrutiny}/config:/opt/scrutiny/config + - ${DATA_PATH:-/srv/scrutiny}/influxdb:/opt/scrutiny/influxdb + devices: + # List every disk you want monitored. + - /dev/sda:/dev/sda + # - /dev/sdb:/dev/sdb + # NVMe drives additionally need: cap_add: [SYS_ADMIN] + # - /dev/nvme0:/dev/nvme0 diff --git a/backend/templates/scrutiny/template.json b/backend/templates/scrutiny/template.json new file mode 100644 index 0000000..13f0e6a --- /dev/null +++ b/backend/templates/scrutiny/template.json @@ -0,0 +1,9 @@ +{ + "name": "Scrutiny", + "description": "S.M.A.R.T. disk health dashboard with failure-rate context. Edit the devices list before deploying.", + "tags": [ + "monitoring", + "storage" + ], + "gpu": null +} diff --git a/backend/templates/searxng/.env.example b/backend/templates/searxng/.env.example new file mode 100644 index 0000000..7b19185 --- /dev/null +++ b/backend/templates/searxng/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/searxng +HTTP_PORT=8099 +BASE_URL=http://localhost:8099 +# Required. Generate with: openssl rand -hex 32 +SEARXNG_SECRET= diff --git a/backend/templates/searxng/compose.yaml b/backend/templates/searxng/compose.yaml new file mode 100644 index 0000000..9109db5 --- /dev/null +++ b/backend/templates/searxng/compose.yaml @@ -0,0 +1,20 @@ +services: + searxng: + image: searxng/searxng:latest + container_name: searxng + restart: unless-stopped + environment: + - SEARXNG_BASE_URL=${BASE_URL:-http://localhost:8099}/ + - SEARXNG_SECRET=${SEARXNG_SECRET:?secret required} + - UWSGI_WORKERS=4 + - UWSGI_THREADS=4 + ports: + - "${HTTP_PORT:-8099}:8080" + volumes: + - ${DATA_PATH:-/srv/searxng}:/etc/searxng + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID diff --git a/backend/templates/searxng/template.json b/backend/templates/searxng/template.json new file mode 100644 index 0000000..20d3f26 --- /dev/null +++ b/backend/templates/searxng/template.json @@ -0,0 +1,9 @@ +{ + "name": "SearXNG", + "description": "Private meta search engine that queries dozens of engines without tracking you.", + "tags": [ + "search", + "privacy" + ], + "gpu": null +} diff --git a/backend/templates/sonarr/.env.example b/backend/templates/sonarr/.env.example new file mode 100644 index 0000000..1641fc7 --- /dev/null +++ b/backend/templates/sonarr/.env.example @@ -0,0 +1,7 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/sonarr +MEDIA_PATH=/srv/media +DOWNLOADS_PATH=/srv/downloads +HTTP_PORT=8989 diff --git a/backend/templates/sonarr/compose.yaml b/backend/templates/sonarr/compose.yaml new file mode 100644 index 0000000..2d2c9e6 --- /dev/null +++ b/backend/templates/sonarr/compose.yaml @@ -0,0 +1,15 @@ +services: + sonarr: + image: lscr.io/linuxserver/sonarr:latest + container_name: sonarr + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8989}:8989" + volumes: + - ${DATA_PATH:-/srv/sonarr}:/config + - ${MEDIA_PATH:-/srv/media}:/media + - ${DOWNLOADS_PATH:-/srv/downloads}:/downloads diff --git a/backend/templates/sonarr/template.json b/backend/templates/sonarr/template.json new file mode 100644 index 0000000..7754695 --- /dev/null +++ b/backend/templates/sonarr/template.json @@ -0,0 +1,10 @@ +{ + "name": "Sonarr", + "description": "Automates finding, downloading and organising TV series.", + "tags": [ + "media", + "automation", + "arr" + ], + "gpu": null +} diff --git a/backend/templates/speedtest-tracker/.env.example b/backend/templates/speedtest-tracker/.env.example new file mode 100644 index 0000000..f53c1a7 --- /dev/null +++ b/backend/templates/speedtest-tracker/.env.example @@ -0,0 +1,10 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +DATA_PATH=/srv/speedtest-tracker +HTTP_PORT=8086 +APP_URL=http://localhost:8086 +# Required. Generate with: openssl rand -base64 32 +APP_KEY= +SPEEDTEST_SCHEDULE=0 */6 * * * +PRUNE_DAYS=90 diff --git a/backend/templates/speedtest-tracker/compose.yaml b/backend/templates/speedtest-tracker/compose.yaml new file mode 100644 index 0000000..af49460 --- /dev/null +++ b/backend/templates/speedtest-tracker/compose.yaml @@ -0,0 +1,20 @@ +services: + speedtest-tracker: + image: lscr.io/linuxserver/speedtest-tracker:latest + container_name: speedtest-tracker + restart: unless-stopped + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + # Generate with: openssl rand -base64 32 + - APP_KEY=${APP_KEY:?app key required} + - APP_URL=${APP_URL:-http://localhost:8086} + - DB_CONNECTION=sqlite + - SPEEDTEST_SCHEDULE=${SPEEDTEST_SCHEDULE:-0 */6 * * *} + - DISPLAY_TIMEZONE=${TZ:-Europe/Berlin} + - PRUNE_RESULTS_OLDER_THAN=${PRUNE_DAYS:-90} + ports: + - "${HTTP_PORT:-8086}:80" + volumes: + - ${DATA_PATH:-/srv/speedtest-tracker}:/config diff --git a/backend/templates/speedtest-tracker/template.json b/backend/templates/speedtest-tracker/template.json new file mode 100644 index 0000000..9caa551 --- /dev/null +++ b/backend/templates/speedtest-tracker/template.json @@ -0,0 +1,9 @@ +{ + "name": "Speedtest Tracker", + "description": "Runs scheduled internet speed tests and charts the history. First login: admin@example.com / password.", + "tags": [ + "monitoring", + "network" + ], + "gpu": null +} diff --git a/backend/templates/stirling-pdf/.env.example b/backend/templates/stirling-pdf/.env.example new file mode 100644 index 0000000..920a06e --- /dev/null +++ b/backend/templates/stirling-pdf/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/stirling-pdf +HTTP_PORT=8092 +LANGS=en_GB,de_DE diff --git a/backend/templates/stirling-pdf/compose.yaml b/backend/templates/stirling-pdf/compose.yaml new file mode 100644 index 0000000..b90f442 --- /dev/null +++ b/backend/templates/stirling-pdf/compose.yaml @@ -0,0 +1,13 @@ +services: + stirling-pdf: + image: ghcr.io/stirling-tools/stirling-pdf:latest + container_name: stirling-pdf + restart: unless-stopped + environment: + - DOCKER_ENABLE_SECURITY=false + - LANGS=${LANGS:-en_GB,de_DE} + ports: + - "${HTTP_PORT:-8092}:8080" + volumes: + - ${DATA_PATH:-/srv/stirling-pdf}/configs:/configs + - ${DATA_PATH:-/srv/stirling-pdf}/tessdata:/usr/share/tessdata diff --git a/backend/templates/stirling-pdf/template.json b/backend/templates/stirling-pdf/template.json new file mode 100644 index 0000000..b3fcca2 --- /dev/null +++ b/backend/templates/stirling-pdf/template.json @@ -0,0 +1,9 @@ +{ + "name": "Stirling PDF", + "description": "Split, merge, OCR, sign and convert PDFs entirely in your own browser session.", + "tags": [ + "documents", + "utilities" + ], + "gpu": null +} diff --git a/backend/templates/syncthing/.env.example b/backend/templates/syncthing/.env.example new file mode 100644 index 0000000..0272e3a --- /dev/null +++ b/backend/templates/syncthing/.env.example @@ -0,0 +1,9 @@ +PUID=1000 +PGID=1000 +TZ=Europe/Berlin +SYNC_HOSTNAME=homelab +DATA_PATH=/srv/syncthing +SYNC_PATH=/srv/sync +HTTP_PORT=8384 +SYNC_PORT=22000 +DISCOVERY_PORT=21027 diff --git a/backend/templates/syncthing/compose.yaml b/backend/templates/syncthing/compose.yaml new file mode 100644 index 0000000..806aff0 --- /dev/null +++ b/backend/templates/syncthing/compose.yaml @@ -0,0 +1,18 @@ +services: + syncthing: + image: lscr.io/linuxserver/syncthing:latest + container_name: syncthing + restart: unless-stopped + hostname: ${SYNC_HOSTNAME:-homelab} + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8384}:8384" + - "${SYNC_PORT:-22000}:22000/tcp" + - "${SYNC_PORT:-22000}:22000/udp" + - "${DISCOVERY_PORT:-21027}:21027/udp" + volumes: + - ${DATA_PATH:-/srv/syncthing}:/config + - ${SYNC_PATH:-/srv/sync}:/data diff --git a/backend/templates/syncthing/template.json b/backend/templates/syncthing/template.json new file mode 100644 index 0000000..0a979e5 --- /dev/null +++ b/backend/templates/syncthing/template.json @@ -0,0 +1,10 @@ +{ + "name": "Syncthing", + "description": "Continuous peer-to-peer file sync between your own devices — no server in the middle.", + "tags": [ + "files", + "sync", + "backup" + ], + "gpu": null +} diff --git a/backend/templates/tailscale/.env.example b/backend/templates/tailscale/.env.example new file mode 100644 index 0000000..f1873c2 --- /dev/null +++ b/backend/templates/tailscale/.env.example @@ -0,0 +1,5 @@ +DATA_PATH=/srv/tailscale +TS_HOSTNAME=homelab +TS_AUTHKEY= +TS_ROUTES= +TS_EXTRA_ARGS=--accept-dns=false diff --git a/backend/templates/tailscale/compose.yaml b/backend/templates/tailscale/compose.yaml new file mode 100644 index 0000000..f1bfc18 --- /dev/null +++ b/backend/templates/tailscale/compose.yaml @@ -0,0 +1,20 @@ +services: + tailscale: + image: tailscale/tailscale:latest + container_name: tailscale + restart: unless-stopped + hostname: ${TS_HOSTNAME:-homelab} + network_mode: host + environment: + # Generate at https://login.tailscale.com/admin/settings/keys + - TS_AUTHKEY=${TS_AUTHKEY:-} + - TS_STATE_DIR=/var/lib/tailscale + # Advertise your LAN so tailnet clients can reach it, e.g. 192.168.1.0/24 + - TS_ROUTES=${TS_ROUTES:-} + - TS_EXTRA_ARGS=${TS_EXTRA_ARGS:---accept-dns=false} + volumes: + - ${DATA_PATH:-/srv/tailscale}:/var/lib/tailscale + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + - NET_RAW diff --git a/backend/templates/tailscale/template.json b/backend/templates/tailscale/template.json new file mode 100644 index 0000000..ea186b5 --- /dev/null +++ b/backend/templates/tailscale/template.json @@ -0,0 +1,10 @@ +{ + "name": "Tailscale", + "description": "Mesh VPN node, optionally advertising your LAN as a subnet router.", + "tags": [ + "network", + "vpn", + "security" + ], + "gpu": null +} diff --git a/backend/templates/traefik/.env.example b/backend/templates/traefik/.env.example new file mode 100644 index 0000000..25cacfd --- /dev/null +++ b/backend/templates/traefik/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/traefik +HTTP_PORT=80 +HTTPS_PORT=443 +DASHBOARD_PORT=8180 +DASHBOARD_HOST=traefik.example.com +ACME_EMAIL=admin@example.com diff --git a/backend/templates/traefik/compose.yaml b/backend/templates/traefik/compose.yaml new file mode 100644 index 0000000..2c8f8cb --- /dev/null +++ b/backend/templates/traefik/compose.yaml @@ -0,0 +1,29 @@ +services: + traefik: + image: traefik:v3 + container_name: traefik + restart: unless-stopped + command: + - --api.dashboard=true + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --entrypoints.websecure.address=:443 + - --certificatesresolvers.le.acme.email=${ACME_EMAIL:-admin@example.com} + - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.le.acme.tlschallenge=true + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + - "${DASHBOARD_PORT:-8180}:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ${DATA_PATH:-/srv/traefik}/letsencrypt:/letsencrypt + labels: + - traefik.enable=true + - traefik.http.routers.dashboard.rule=Host(`${DASHBOARD_HOST:-traefik.example.com}`) + - traefik.http.routers.dashboard.service=api@internal + - traefik.http.routers.dashboard.entrypoints=websecure + - traefik.http.routers.dashboard.tls.certresolver=le diff --git a/backend/templates/traefik/template.json b/backend/templates/traefik/template.json new file mode 100644 index 0000000..354d0d5 --- /dev/null +++ b/backend/templates/traefik/template.json @@ -0,0 +1,9 @@ +{ + "name": "Traefik", + "description": "Reverse proxy that discovers containers via Docker labels and issues Let's Encrypt certificates.", + "tags": [ + "network", + "proxy" + ], + "gpu": null +} diff --git a/backend/templates/trilium/.env.example b/backend/templates/trilium/.env.example new file mode 100644 index 0000000..7a9e05d --- /dev/null +++ b/backend/templates/trilium/.env.example @@ -0,0 +1,3 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/trilium +HTTP_PORT=8101 diff --git a/backend/templates/trilium/compose.yaml b/backend/templates/trilium/compose.yaml new file mode 100644 index 0000000..cdbfa77 --- /dev/null +++ b/backend/templates/trilium/compose.yaml @@ -0,0 +1,11 @@ +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8101}:8080" + volumes: + - ${DATA_PATH:-/srv/trilium}:/home/node/trilium-data diff --git a/backend/templates/trilium/template.json b/backend/templates/trilium/template.json new file mode 100644 index 0000000..d1d98ff --- /dev/null +++ b/backend/templates/trilium/template.json @@ -0,0 +1,9 @@ +{ + "name": "Trilium Notes", + "description": "Hierarchical note taking for large personal knowledge bases, with sync and scripting.", + "tags": [ + "notes", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/vikunja/.env.example b/backend/templates/vikunja/.env.example new file mode 100644 index 0000000..710eb87 --- /dev/null +++ b/backend/templates/vikunja/.env.example @@ -0,0 +1,6 @@ +DATA_PATH=/srv/vikunja +HTTP_PORT=3456 +PUBLIC_URL=http://localhost:3456 +DB_PASSWORD=change-me +# Required. Generate with: openssl rand -hex 32 +SERVICE_SECRET= diff --git a/backend/templates/vikunja/compose.yaml b/backend/templates/vikunja/compose.yaml new file mode 100644 index 0000000..26dda03 --- /dev/null +++ b/backend/templates/vikunja/compose.yaml @@ -0,0 +1,36 @@ +services: + vikunja: + image: vikunja/vikunja:latest + container_name: vikunja + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + - VIKUNJA_SERVICE_PUBLICURL=${PUBLIC_URL:-http://localhost:3456} + - VIKUNJA_SERVICE_SECRET=${SERVICE_SECRET:?service secret required} + - VIKUNJA_DATABASE_TYPE=postgres + - VIKUNJA_DATABASE_HOST=db + - VIKUNJA_DATABASE_DATABASE=vikunja + - VIKUNJA_DATABASE_USER=vikunja + - VIKUNJA_DATABASE_PASSWORD=${DB_PASSWORD:-vikunja} + ports: + - "${HTTP_PORT:-3456}:3456" + volumes: + - ${DATA_PATH:-/srv/vikunja}/files:/app/vikunja/files + + db: + image: postgres:16-alpine + container_name: vikunja-db + restart: unless-stopped + environment: + - POSTGRES_DB=vikunja + - POSTGRES_USER=vikunja + - POSTGRES_PASSWORD=${DB_PASSWORD:-vikunja} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U vikunja"] + interval: 5s + timeout: 5s + retries: 10 + volumes: + - ${DATA_PATH:-/srv/vikunja}/db:/var/lib/postgresql/data diff --git a/backend/templates/vikunja/template.json b/backend/templates/vikunja/template.json new file mode 100644 index 0000000..68f1ecf --- /dev/null +++ b/backend/templates/vikunja/template.json @@ -0,0 +1,9 @@ +{ + "name": "Vikunja", + "description": "To-do and project management with list, gantt, table and kanban views.", + "tags": [ + "productivity", + "tasks" + ], + "gpu": null +} diff --git a/backend/templates/watchtower/.env.example b/backend/templates/watchtower/.env.example new file mode 100644 index 0000000..569ab1d --- /dev/null +++ b/backend/templates/watchtower/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +# Six-field cron (seconds first). Default: every day at 04:00. +SCHEDULE=0 0 4 * * * +MONITOR_ONLY=false +NOTIFICATION_URL= diff --git a/backend/templates/watchtower/compose.yaml b/backend/templates/watchtower/compose.yaml new file mode 100644 index 0000000..228793b --- /dev/null +++ b/backend/templates/watchtower/compose.yaml @@ -0,0 +1,16 @@ +services: + watchtower: + image: containrrr/watchtower:latest + container_name: watchtower + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_INCLUDE_RESTARTING=true + - WATCHTOWER_SCHEDULE=${SCHEDULE:-0 0 4 * * *} + # Report only, never pull: set to true for a dry run. + - WATCHTOWER_MONITOR_ONLY=${MONITOR_ONLY:-false} + # e.g. shoutrrr URL for ntfy/Gotify/Discord notifications + - WATCHTOWER_NOTIFICATION_URL=${NOTIFICATION_URL:-} + volumes: + - /var/run/docker.sock:/var/run/docker.sock diff --git a/backend/templates/watchtower/template.json b/backend/templates/watchtower/template.json new file mode 100644 index 0000000..9d60e33 --- /dev/null +++ b/backend/templates/watchtower/template.json @@ -0,0 +1,9 @@ +{ + "name": "Watchtower", + "description": "Watches running containers and pulls new images on a schedule.", + "tags": [ + "docker", + "automation" + ], + "gpu": null +} diff --git a/backend/templates/wg-easy/.env.example b/backend/templates/wg-easy/.env.example new file mode 100644 index 0000000..9a4e9e7 --- /dev/null +++ b/backend/templates/wg-easy/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/wg-easy +WG_PORT=51820 +HTTP_PORT=51821 diff --git a/backend/templates/wg-easy/compose.yaml b/backend/templates/wg-easy/compose.yaml new file mode 100644 index 0000000..dd81308 --- /dev/null +++ b/backend/templates/wg-easy/compose.yaml @@ -0,0 +1,17 @@ +services: + wg-easy: + image: ghcr.io/wg-easy/wg-easy:15 + container_name: wg-easy + restart: unless-stopped + ports: + - "${WG_PORT:-51820}:51820/udp" + - "${HTTP_PORT:-51821}:51821/tcp" + volumes: + - ${DATA_PATH:-/srv/wg-easy}:/etc/wireguard + - /lib/modules:/lib/modules:ro + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv4.conf.all.src_valid_mark=1 diff --git a/backend/templates/wg-easy/template.json b/backend/templates/wg-easy/template.json new file mode 100644 index 0000000..f9201dd --- /dev/null +++ b/backend/templates/wg-easy/template.json @@ -0,0 +1,10 @@ +{ + "name": "WG-Easy", + "description": "WireGuard VPN server with a web UI for clients and QR codes. Admin account is created on first visit.", + "tags": [ + "network", + "vpn", + "security" + ], + "gpu": null +} diff --git a/backend/templates/wiki-js/.env.example b/backend/templates/wiki-js/.env.example new file mode 100644 index 0000000..f784b68 --- /dev/null +++ b/backend/templates/wiki-js/.env.example @@ -0,0 +1,3 @@ +DATA_PATH=/srv/wiki-js +HTTP_PORT=3080 +DB_PASSWORD=change-me diff --git a/backend/templates/wiki-js/compose.yaml b/backend/templates/wiki-js/compose.yaml new file mode 100644 index 0000000..4179a75 --- /dev/null +++ b/backend/templates/wiki-js/compose.yaml @@ -0,0 +1,27 @@ +services: + wiki: + image: ghcr.io/requarks/wiki:2 + container_name: wiki-js + restart: unless-stopped + depends_on: + - db + environment: + - DB_TYPE=postgres + - DB_HOST=db + - DB_PORT=5432 + - DB_NAME=wiki + - DB_USER=wiki + - DB_PASS=${DB_PASSWORD:-wiki} + ports: + - "${HTTP_PORT:-3080}:3000" + + db: + image: postgres:16-alpine + container_name: wiki-js-db + restart: unless-stopped + environment: + - POSTGRES_DB=wiki + - POSTGRES_USER=wiki + - POSTGRES_PASSWORD=${DB_PASSWORD:-wiki} + volumes: + - ${DATA_PATH:-/srv/wiki-js}/db:/var/lib/postgresql/data diff --git a/backend/templates/wiki-js/template.json b/backend/templates/wiki-js/template.json new file mode 100644 index 0000000..bd53d3b --- /dev/null +++ b/backend/templates/wiki-js/template.json @@ -0,0 +1,10 @@ +{ + "name": "Wiki.js", + "description": "Modern wiki with Markdown and WYSIWYG editors, git sync and fine-grained permissions.", + "tags": [ + "documents", + "wiki", + "productivity" + ], + "gpu": null +} diff --git a/backend/templates/zigbee2mqtt/.env.example b/backend/templates/zigbee2mqtt/.env.example new file mode 100644 index 0000000..5f2ae53 --- /dev/null +++ b/backend/templates/zigbee2mqtt/.env.example @@ -0,0 +1,4 @@ +TZ=Europe/Berlin +DATA_PATH=/srv/zigbee2mqtt +HTTP_PORT=8095 +ZIGBEE_DEVICE=/dev/ttyUSB0 diff --git a/backend/templates/zigbee2mqtt/compose.yaml b/backend/templates/zigbee2mqtt/compose.yaml new file mode 100644 index 0000000..48a2e6a --- /dev/null +++ b/backend/templates/zigbee2mqtt/compose.yaml @@ -0,0 +1,18 @@ +services: + zigbee2mqtt: + image: koenkk/zigbee2mqtt:latest + container_name: zigbee2mqtt + restart: unless-stopped + environment: + - TZ=${TZ:-Europe/Berlin} + ports: + - "${HTTP_PORT:-8095}:8080" + volumes: + # The config lives with the stack so you can edit it here; the directory + # underneath holds the device database Zigbee2MQTT builds as you pair. + - ${DATA_PATH:-/srv/zigbee2mqtt}:/app/data + - ./configuration.yaml:/app/data/configuration.yaml + - /run/udev:/run/udev:ro + devices: + # Your Zigbee coordinator. Find it with: ls -l /dev/serial/by-id/ + - ${ZIGBEE_DEVICE:-/dev/ttyUSB0}:/dev/ttyACM0 diff --git a/backend/templates/zigbee2mqtt/configuration.yaml b/backend/templates/zigbee2mqtt/configuration.yaml new file mode 100644 index 0000000..73d5d4e --- /dev/null +++ b/backend/templates/zigbee2mqtt/configuration.yaml @@ -0,0 +1,13 @@ +# Point `server` at your MQTT broker. Zigbee2MQTT also writes back to this file +# as you pair devices, so keep it in the stack folder. +homeassistant: + enabled: true +frontend: + port: 8080 +mqtt: + base_topic: zigbee2mqtt + server: mqtt://mosquitto:1883 +serial: + port: /dev/ttyACM0 +advanced: + log_level: info diff --git a/backend/templates/zigbee2mqtt/template.json b/backend/templates/zigbee2mqtt/template.json new file mode 100644 index 0000000..23eadfe --- /dev/null +++ b/backend/templates/zigbee2mqtt/template.json @@ -0,0 +1,10 @@ +{ + "name": "Zigbee2MQTT", + "description": "Bridges Zigbee devices to MQTT without a vendor hub. Point it at your broker and coordinator stick.", + "tags": [ + "home-automation", + "iot", + "mqtt" + ], + "gpu": null +} diff --git a/backend/version.py b/backend/version.py index 62ebe57..6a55ffd 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,3 +1,3 @@ """Single source of truth for the StackPilot release version.""" -APP_VERSION = "0.42.2" +APP_VERSION = "0.43.0" diff --git a/frontend/package.json b/frontend/package.json index 0c862f9..04068a4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "stackpilot-frontend", "private": true, - "version": "0.42.2", + "version": "0.43.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/pages/Templates.tsx b/frontend/src/pages/Templates.tsx index 76ea29d..0394772 100644 --- a/frontend/src/pages/Templates.tsx +++ b/frontend/src/pages/Templates.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { LayoutTemplate, Cpu, Package, Trash2, FileCode } from "lucide-react"; +import { LayoutTemplate, Cpu, Package, Trash2, FileCode, Search } from "lucide-react"; import { Badge, Button, Card, Input, Spinner } from "@/components/ui"; import { ConfirmDialog } from "@/components/ui/ConfirmDialog"; import { templatesApi, type TemplateDetail, type TemplateSummary } from "@/api/templates"; @@ -18,8 +18,31 @@ export function Templates() { const queryClient = useQueryClient(); const [selected, setSelected] = useState(null); const [toDelete, setToDelete] = useState(null); + const [search, setSearch] = useState(""); + const [tag, setTag] = useState(null); const { data, isLoading } = useQuery({ queryKey: ["templates"], queryFn: templatesApi.list }); + // Tags sorted by how many templates carry them — the useful ones surface first. + const tags = useMemo(() => { + const counts = new Map(); + for (const t of data ?? []) { + for (const tg of t.tags) counts.set(tg, (counts.get(tg) ?? 0) + 1); + } + return [...counts.entries()] + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .map(([name, count]) => ({ name, count })); + }, [data]); + + const shown = useMemo(() => { + const q = search.trim().toLowerCase(); + return (data ?? []).filter((t) => { + if (tag && !t.tags.includes(tag)) return false; + if (!q) return true; + const haystack = `${t.name} ${t.description ?? ""} ${t.tags.join(" ")}`.toLowerCase(); + return haystack.includes(q); + }); + }, [data, search, tag]); + const open = async (t: TemplateSummary) => { try { setSelected(await templatesApi.get(t.id)); @@ -50,8 +73,36 @@ export function Templates() { Templates are stored as ready-to-run stack folders. Pull one to copy it into a new stack, then edit it like any other.

+ +
+
+ + setSearch(e.target.value)} + /> +
+
+ setTag(null)} /> + {tags.map((tg) => ( + setTag(tag === tg.name ? null : tg.name)} + /> + ))} +
+
+ + {shown.length === 0 && ( +

No template matches that filter.

+ )} +
- {data?.map((t) => ( + {shown.map((t) => (
{t.source === "custom" ? ( @@ -64,10 +115,15 @@ export function Templates() {
{t.description &&

{t.description}

}
- {t.tags.map((tag) => ( - - {tag} - + {t.tags.map((tg) => ( + ))} {t.gpu && ( @@ -109,6 +165,30 @@ export function Templates() { ); } +function TagChip({ + label, + active, + onClick, +}: { + label: string; + active: boolean; + onClick: () => void; +}) { + return ( + + ); +} + function UseTemplateDialog({ template, onClose,