Add subdirectory routing mode for public domain deployments

Apps can now be served at domain.com/appname/ instead of requiring
subdomain DNS records (appname.domain.com). Supports three subpath
modes per template: native (URL base env var), strip (handle_path),
and none (incompatible warning). Tested on Linux with deploy/removal
lifecycle verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 03:03:17 -08:00
parent f61e85d9a7
commit 77030931b7
13 changed files with 407 additions and 41 deletions

View File

@@ -29,6 +29,7 @@ const APP_TEMPLATES = {
subdomain: "plex",
defaultPort: 32400,
healthCheck: "/web/index.html",
subpathSupport: 'none',
mediaMount: {
required: true,
containerPath: "/data",
@@ -75,6 +76,8 @@ const APP_TEMPLATES = {
subdomain: "jellyfin",
defaultPort: 8096,
healthCheck: "/health",
subpathSupport: 'native',
urlBaseEnv: 'JELLYFIN_BaseUrl',
mediaMount: {
required: true,
containerPath: "/media",
@@ -113,6 +116,7 @@ const APP_TEMPLATES = {
subdomain: "emby",
defaultPort: 8096,
healthCheck: "/emby/web/",
subpathSupport: 'none',
mediaMount: {
required: true,
containerPath: "/media",
@@ -152,6 +156,8 @@ const APP_TEMPLATES = {
subdomain: "sonarr",
defaultPort: 8989,
healthCheck: "/api/v3/system/status",
subpathSupport: 'native',
urlBaseEnv: 'URL_BASE',
setupInstructions: [
"Configure download clients (qBittorrent, etc.)",
"Add indexers for content discovery",
@@ -182,7 +188,9 @@ const APP_TEMPLATES = {
},
subdomain: "radarr",
defaultPort: 7878,
healthCheck: "/api/v3/system/status"
healthCheck: "/api/v3/system/status",
subpathSupport: 'native',
urlBaseEnv: 'URL_BASE'
},
"prowlarr": {
@@ -204,7 +212,9 @@ const APP_TEMPLATES = {
},
subdomain: "prowlarr",
defaultPort: 9696,
healthCheck: "/api/v1/system/status"
healthCheck: "/api/v1/system/status",
subpathSupport: 'native',
urlBaseEnv: 'URL_BASE'
},
"qbittorrent": {
@@ -231,6 +241,8 @@ const APP_TEMPLATES = {
subdomain: "torrent",
defaultPort: 8080,
healthCheck: "/",
subpathSupport: 'native',
urlBaseEnv: 'WEBUI_BASE_PATH',
setupInstructions: [
"Default login: admin/adminadmin",
"Change default password immediately",
@@ -262,6 +274,7 @@ const APP_TEMPLATES = {
subdomain: "cloud",
defaultPort: 8080,
healthCheck: "/status.php",
subpathSupport: 'none',
setupInstructions: [
"Change the default admin password",
"Configure trusted domains",
@@ -301,6 +314,7 @@ const APP_TEMPLATES = {
subdomain: "code",
defaultPort: 8443,
healthCheck: "/healthz",
subpathSupport: 'strip',
secrets: [
{
envVar: "VSCODE_PASSWORD",
@@ -332,7 +346,8 @@ const APP_TEMPLATES = {
},
subdomain: "portainer",
defaultPort: 9000,
healthCheck: "/api/status"
healthCheck: "/api/status",
subpathSupport: 'strip'
},
"grafana": {
@@ -353,6 +368,8 @@ const APP_TEMPLATES = {
subdomain: "grafana",
defaultPort: 3000,
healthCheck: "/api/health",
subpathSupport: 'native',
urlBaseEnv: 'GF_SERVER_ROOT_URL',
secrets: [
{
envVar: "GRAFANA_ADMIN_PASSWORD",
@@ -380,7 +397,8 @@ const APP_TEMPLATES = {
},
subdomain: "uptime",
defaultPort: 3002,
healthCheck: "/"
healthCheck: "/",
subpathSupport: 'strip'
},
// === NETWORKING & SECURITY ===
@@ -406,6 +424,7 @@ const APP_TEMPLATES = {
subdomain: "pihole",
defaultPort: 80,
healthCheck: "/admin/",
subpathSupport: 'strip',
secrets: [
{
envVar: "PIHOLE_WEB_PASSWORD",
@@ -443,6 +462,7 @@ const APP_TEMPLATES = {
subdomain: "vpn",
defaultPort: 51820,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure your external IP/domain",
"Set up port forwarding on router",
@@ -477,6 +497,7 @@ const APP_TEMPLATES = {
subdomain: "dns1",
defaultPort: 5380,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Access web interface at https://dns1.sami",
"Login with admin credentials",
@@ -529,6 +550,7 @@ const APP_TEMPLATES = {
subdomain: "dns2",
defaultPort: 953,
healthCheck: null,
subpathSupport: 'strip',
setupInstructions: [
"Configure zone files in /opt/bind9/config/",
"Create named.conf.local for your .sami zone",
@@ -570,6 +592,7 @@ const APP_TEMPLATES = {
subdomain: "dns3",
defaultPort: 8081,
healthCheck: "/api/v1/servers",
subpathSupport: 'strip',
setupInstructions: [
"Access API at https://dns3.sami:8081",
"Use API key for authentication",
@@ -625,6 +648,7 @@ const APP_TEMPLATES = {
subdomain: "dns4",
defaultPort: 53,
healthCheck: null,
subpathSupport: 'strip',
setupInstructions: [
"Create Corefile in /opt/coredns/config/",
"Define .sami zone with file plugin",
@@ -656,6 +680,7 @@ const APP_TEMPLATES = {
subdomain: "files",
defaultPort: 8085,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Default login: admin/admin",
"Change default password immediately",
@@ -686,6 +711,7 @@ const APP_TEMPLATES = {
subdomain: "sync",
defaultPort: 8384,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Add devices using their Device IDs",
"Configure shared folders",
@@ -721,6 +747,7 @@ const APP_TEMPLATES = {
subdomain: "mail",
defaultPort: 25,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure DNS records (MX, SPF, DKIM, DMARC)",
"Create email accounts using setup.sh",
@@ -750,6 +777,7 @@ const APP_TEMPLATES = {
subdomain: "webmail",
defaultPort: 8086,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure IMAP/SMTP server settings",
"Set up database connection",
@@ -776,6 +804,7 @@ const APP_TEMPLATES = {
subdomain: "matrix",
defaultPort: 8008,
healthCheck: "/_matrix/client/versions",
subpathSupport: 'none',
setupInstructions: [
"Generate initial config with --generate",
"Configure homeserver.yaml",
@@ -802,6 +831,7 @@ const APP_TEMPLATES = {
subdomain: "chat",
defaultPort: 3004,
healthCheck: "/api/info",
subpathSupport: 'strip',
setupInstructions: [
"Requires MongoDB - deploy mongo container first",
"Complete admin setup wizard",
@@ -831,6 +861,7 @@ const APP_TEMPLATES = {
subdomain: "home",
defaultPort: 8123,
healthCheck: "/api/",
subpathSupport: 'strip',
setupInstructions: [
"Complete onboarding wizard",
"Add integrations for your smart devices",
@@ -856,6 +887,7 @@ const APP_TEMPLATES = {
subdomain: "nodered",
defaultPort: 1880,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Install additional nodes from palette",
"Create flows for automation",
@@ -884,6 +916,7 @@ const APP_TEMPLATES = {
subdomain: "postgres",
defaultPort: 5432,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Change default password immediately",
"Create databases and users as needed",
@@ -918,6 +951,7 @@ const APP_TEMPLATES = {
subdomain: "redis",
defaultPort: 6379,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure redis.conf for persistence",
"Set up authentication if needed",
@@ -944,6 +978,7 @@ const APP_TEMPLATES = {
subdomain: "mongo",
defaultPort: 27017,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Change default admin password",
"Create application databases and users",
@@ -980,6 +1015,7 @@ const APP_TEMPLATES = {
subdomain: "adminer",
defaultPort: 8087,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Connect to your database servers",
"Supports MySQL, PostgreSQL, SQLite, etc."
@@ -1006,6 +1042,7 @@ const APP_TEMPLATES = {
subdomain: "vault",
defaultPort: 8088,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Change admin token immediately",
"Create your account",
@@ -1036,6 +1073,7 @@ const APP_TEMPLATES = {
subdomain: "ca",
defaultPort: null, // Static site, no port needed
healthCheck: null,
subpathSupport: 'strip',
features: [
"Automatic OS detection",
"One-click installation",
@@ -1065,6 +1103,7 @@ const APP_TEMPLATES = {
subdomain: null,
defaultPort: null,
healthCheck: null,
subpathSupport: 'strip',
features: [
"Current temperature and conditions",
"Wind speed and direction",
@@ -1091,6 +1130,7 @@ const APP_TEMPLATES = {
subdomain: null,
defaultPort: null,
healthCheck: null,
subpathSupport: 'strip',
features: [
"12-hour format with AM/PM",
"Live seconds display",
@@ -1130,6 +1170,8 @@ const APP_TEMPLATES = {
subdomain: "lidarr",
defaultPort: 8686,
healthCheck: "/api/v1/system/status",
subpathSupport: 'native',
urlBaseEnv: 'URL_BASE',
setupInstructions: [
"Configure download clients",
"Add indexers",
@@ -1161,6 +1203,8 @@ const APP_TEMPLATES = {
subdomain: "readarr",
defaultPort: 8787,
healthCheck: "/api/v1/system/status",
subpathSupport: 'native',
urlBaseEnv: 'URL_BASE',
setupInstructions: [
"Configure download clients",
"Add indexers for books",
@@ -1192,6 +1236,8 @@ const APP_TEMPLATES = {
subdomain: "bazarr",
defaultPort: 6767,
healthCheck: "/",
subpathSupport: 'native',
urlBaseEnv: 'BASE_URL',
setupInstructions: [
"Connect to Sonarr and Radarr",
"Configure subtitle providers",
@@ -1218,6 +1264,8 @@ const APP_TEMPLATES = {
subdomain: "requests",
defaultPort: 5055,
healthCheck: "/api/v1/status",
subpathSupport: 'native',
urlBaseEnv: 'BASE_PATH',
setupInstructions: [
"Connect to Plex, Jellyfin, or Emby server",
"Link Sonarr and Radarr",
@@ -1245,6 +1293,8 @@ const APP_TEMPLATES = {
subdomain: "tautulli",
defaultPort: 8181,
healthCheck: "/",
subpathSupport: 'native',
urlBaseEnv: 'TAUTULLI_HTTP_ROOT',
setupInstructions: [
"Connect to Plex server",
"Configure notifications",
@@ -1276,6 +1326,8 @@ const APP_TEMPLATES = {
subdomain: "git",
defaultPort: 3005,
healthCheck: "/",
subpathSupport: 'native',
urlBaseEnv: 'GITEA__server__ROOT_URL',
setupInstructions: [
"Complete initial setup wizard",
"Create admin account",
@@ -1299,6 +1351,7 @@ const APP_TEMPLATES = {
subdomain: "jenkins",
defaultPort: 8089,
healthCheck: "/login",
subpathSupport: 'strip',
setupInstructions: [
"Get initial admin password from logs",
"Install suggested plugins",
@@ -1327,6 +1380,7 @@ const APP_TEMPLATES = {
subdomain: "drone",
defaultPort: 8090,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure Git provider integration",
"Set up shared secret",
@@ -1370,6 +1424,7 @@ const APP_TEMPLATES = {
subdomain: "wiki",
defaultPort: 8091,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Requires MariaDB/MySQL database",
"Default login: admin@admin.com / password",
@@ -1408,6 +1463,7 @@ const APP_TEMPLATES = {
subdomain: "outline",
defaultPort: 3006,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Requires PostgreSQL and Redis",
"Configure OAuth provider",
@@ -1453,6 +1509,7 @@ const APP_TEMPLATES = {
subdomain: "notes",
defaultPort: 3007,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure environment variables",
"Set up database connection",
@@ -1486,6 +1543,7 @@ const APP_TEMPLATES = {
subdomain: "photos",
defaultPort: 2283,
healthCheck: "/api/server-info/ping",
subpathSupport: 'strip',
setupInstructions: [
"Requires PostgreSQL and Redis",
"Install mobile apps for backup",
@@ -1527,6 +1585,7 @@ const APP_TEMPLATES = {
subdomain: "gallery",
defaultPort: 2342,
healthCheck: "/api/v1/status",
subpathSupport: 'strip',
setupInstructions: [
"Change admin password",
"Import your photos",
@@ -1569,6 +1628,8 @@ const APP_TEMPLATES = {
subdomain: "sabnzbd",
defaultPort: 8092,
healthCheck: "/",
subpathSupport: 'native',
urlBaseEnv: 'SABNZBD_URL_BASE',
setupInstructions: [
"Configure Usenet server credentials",
"Set up download categories",
@@ -1599,6 +1660,7 @@ const APP_TEMPLATES = {
subdomain: "nzbget",
defaultPort: 6789,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Default login: nzbget/tegbzn6789",
"Configure news servers",
@@ -1629,6 +1691,8 @@ const APP_TEMPLATES = {
subdomain: "transmission",
defaultPort: 9092,
healthCheck: "/transmission/web/",
subpathSupport: 'native',
urlBaseEnv: 'TRANSMISSION_WEB_HOME',
setupInstructions: [
"Configure download paths",
"Set bandwidth limits",
@@ -1655,6 +1719,7 @@ const APP_TEMPLATES = {
subdomain: "jdownloader",
defaultPort: 5800,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Access web interface to configure",
"Link to MyJDownloader account",
@@ -1685,6 +1750,7 @@ const APP_TEMPLATES = {
subdomain: "music",
defaultPort: 4533,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Point to your music library",
"Create user accounts",
@@ -1716,6 +1782,7 @@ const APP_TEMPLATES = {
subdomain: "airsonic",
defaultPort: 4040,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Default login: admin/admin",
"Configure media folders",
@@ -1743,6 +1810,7 @@ const APP_TEMPLATES = {
subdomain: "dashboard",
defaultPort: 3008,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Edit config files to add services",
"Configure widgets",
@@ -1770,6 +1838,7 @@ const APP_TEMPLATES = {
subdomain: "homarr",
defaultPort: 7575,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Add your services via UI",
"Configure integrations",
@@ -1793,6 +1862,7 @@ const APP_TEMPLATES = {
subdomain: "watch",
defaultPort: 5001,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Add URLs to monitor",
"Configure check frequency",
@@ -1820,6 +1890,7 @@ const APP_TEMPLATES = {
subdomain: "speedtest",
defaultPort: 8093,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Configure test schedule",
"View historical data",
@@ -1843,6 +1914,7 @@ const APP_TEMPLATES = {
subdomain: "whoami",
defaultPort: 8094,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Useful for testing reverse proxy setup",
"Shows request headers and info"
@@ -1873,6 +1945,7 @@ const APP_TEMPLATES = {
subdomain: "pdf",
defaultPort: 8084,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Access the web interface to start manipulating PDFs",
"Supports merge, split, rotate, convert, compress, and more",
@@ -1899,6 +1972,7 @@ const APP_TEMPLATES = {
subdomain: "budget",
defaultPort: 5006,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Create your first budget in the web interface",
"Import transactions from your bank (OFX, QFX, CSV)",
@@ -1930,6 +2004,7 @@ const APP_TEMPLATES = {
subdomain: "mealie",
defaultPort: 9925,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Default login: changeme@example.com / MyPassword",
"Import recipes from URLs or add them manually",
@@ -1965,6 +2040,7 @@ const APP_TEMPLATES = {
subdomain: "paperless",
defaultPort: 8095,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Create admin account via: docker exec -it <container> python3 manage.py createsuperuser",
"Drop documents into the consume folder for automatic import",
@@ -1993,6 +2069,7 @@ const APP_TEMPLATES = {
subdomain: "audiobooks",
defaultPort: 13378,
healthCheck: "/",
subpathSupport: 'strip',
mediaMount: {
required: true,
containerPath: "/audiobooks",
@@ -2031,6 +2108,7 @@ const APP_TEMPLATES = {
subdomain: "books",
defaultPort: 8083,
healthCheck: "/",
subpathSupport: 'strip',
mediaMount: {
required: true,
containerPath: "/books",
@@ -2067,6 +2145,7 @@ const APP_TEMPLATES = {
subdomain: "komga",
defaultPort: 25600,
healthCheck: "/",
subpathSupport: 'strip',
mediaMount: {
required: true,
containerPath: "/data",
@@ -2101,6 +2180,7 @@ const APP_TEMPLATES = {
subdomain: "kavita",
defaultPort: 5004,
healthCheck: "/",
subpathSupport: 'strip',
mediaMount: {
required: true,
containerPath: "/data",
@@ -2134,6 +2214,7 @@ const APP_TEMPLATES = {
subdomain: "notes",
defaultPort: 8085,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Set your password on first access",
"Organize notes in a tree hierarchy",
@@ -2158,6 +2239,7 @@ const APP_TEMPLATES = {
subdomain: "draw",
defaultPort: 8086,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Start drawing immediately - no account needed",
"Share drawings via link for real-time collaboration",
@@ -2182,6 +2264,7 @@ const APP_TEMPLATES = {
subdomain: "tools",
defaultPort: 8087,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"Access the web interface for instant tools access",
"Includes: hash generators, UUID, JWT decoder, base64, regex tester, and 70+ more",
@@ -2208,6 +2291,7 @@ const APP_TEMPLATES = {
subdomain: "logs",
defaultPort: 8088,
healthCheck: "/",
subpathSupport: 'strip',
setupInstructions: [
"View real-time logs from all running containers",
"Filter and search across container logs",
@@ -2239,6 +2323,7 @@ const APP_TEMPLATES = {
subdomain: "watchtower",
defaultPort: 8089,
healthCheck: "/v1/update",
subpathSupport: 'strip',
setupInstructions: [
"Watchtower checks for image updates daily at 4 AM by default",
"Customize schedule via WATCHTOWER_SCHEDULE (cron format)",
@@ -2269,6 +2354,7 @@ const APP_TEMPLATES = {
subdomain: "auth",
defaultPort: 9010,
healthCheck: "/-/health/live/",
subpathSupport: 'strip',
setupInstructions: [
"Requires a PostgreSQL database and Redis instance",
"Consider deploying via the Dev Environment recipe for full stack",
@@ -2298,6 +2384,7 @@ const APP_TEMPLATES = {
subdomain: "crowdsec",
defaultPort: 8091,
healthCheck: "/health",
subpathSupport: 'strip',
setupInstructions: [
"Register at app.crowdsec.net for community threat intelligence",
"Install bouncers on your reverse proxy for active blocking",
@@ -2331,6 +2418,7 @@ const APP_TEMPLATES = {
subdomain: "mc",
defaultPort: 25565,
healthCheck: null,
subpathSupport: 'none',
setupInstructions: [
"Server accepts the Minecraft EULA automatically",
"Connect with your Minecraft client to the server IP:port",
@@ -2364,6 +2452,7 @@ const APP_TEMPLATES = {
subdomain: "valheim",
defaultPort: 2456,
healthCheck: null,
subpathSupport: 'none',
setupInstructions: [
"Connect via Steam: Add Server > IP:2456",
"Default server password is auto-generated (check environment variables)",