feat: public template catalog + Vercel git integration

Backend: service_templates.is_public lets owners publish user templates to
the anonymous catalog (GET /api/v1/templates), mirroring Railway's
marketplace publish flow. Migration, sqlc queries, visibility rules, and
openapi.yaml updated; types regenerated.

App: template editor gains a "List in the public catalog" checkbox;
Public badge on catalog cards and detail view.

Site: new templates.html catalog page - search, category filters, Official
vs Community badges, detail modal with variables and copyable import JSON.
Fetches the live catalog from a reachable instance (?api= or localStorage
override) and falls back to templates.json, generated from SeedTemplates
via cmd/dumptemplates. Shared styles extracted to style.css.

Vercel: project connected to GitHub repo with rootDirectory=site;
.vercelignore limits CLI uploads to site/ so `vercel deploy --prod` from
repo root matches git builds.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-23 16:40:58 +02:00
1 parent 2b38406a63
commit 117b469fae
18 files changed
+1589 -492

No files matched your search

+394
View File
@@ -0,0 +1,394 @@
{
"templates": [
{
"id": "tpl-nodejs",
"name": "Node.js Application",
"description": "Generic Node.js application with automatic dependency detection",
"category": "web",
"logo": "https://cdn.simpleicons.org/node.js",
"config": "{\"type\":\"web\",\"runtime\":\"node\",\"build_command\":\"npm install \u0026\u0026 npm run build\",\"start_command\":\"npm start\",\"port\":3000,\"health_check\":\"/health\"}",
"variables": "[{\"key\":\"NODE_ENV\",\"label\":\"Node Environment\",\"default\":\"production\",\"required\":false,\"secret\":false},{\"key\":\"NPM_TOKEN\",\"label\":\"NPM Token\",\"default\":\"\",\"required\":false,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-react",
"name": "React Application",
"description": "React single-page application with Vite",
"category": "frontend",
"logo": "https://cdn.simpleicons.org/react",
"config": "{\"type\":\"web\",\"runtime\":\"node\",\"build_command\":\"npm install \u0026\u0026 npm run build\",\"start_command\":\"npx serve -s dist\",\"port\":3000}",
"variables": "[{\"key\":\"VITE_API_URL\",\"label\":\"API URL\",\"default\":\"\",\"required\":true,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-python",
"name": "Python Application",
"description": "Python application with FastAPI/Flask support",
"category": "web",
"logo": "https://cdn.simpleicons.org/python",
"config": "{\"type\":\"web\",\"runtime\":\"python\",\"build_command\":\"pip install -r requirements.txt\",\"start_command\":\"python main.py\",\"port\":8000}",
"variables": "[{\"key\":\"PYTHON_VERSION\",\"label\":\"Python Version\",\"default\":\"3.11\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-go",
"name": "Go Application",
"description": "Go backend service",
"category": "web",
"logo": "https://cdn.simpleicons.org/go",
"config": "{\"type\":\"web\",\"runtime\":\"go\",\"build_command\":\"go build -o app .\",\"start_command\":\"./app\",\"port\":8080}",
"variables": "[{\"key\":\"GO_VERSION\",\"label\":\"Go Version\",\"default\":\"1.21\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-postgres",
"name": "PostgreSQL Database",
"description": "Managed PostgreSQL database",
"category": "database",
"logo": "https://cdn.simpleicons.org/postgresql",
"config": "{\"type\":\"database\",\"runtime\":\"postgres\",\"port\":5432}",
"variables": "[{\"key\":\"POSTGRES_USER\",\"label\":\"Username\",\"default\":\"postgres\",\"required\":true,\"secret\":false},{\"key\":\"POSTGRES_PASSWORD\",\"label\":\"Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"POSTGRES_DB\",\"label\":\"Database Name\",\"default\":\"app\",\"required\":true,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-redis",
"name": "Redis Cache",
"description": "In-memory data store",
"category": "database",
"logo": "https://cdn.simpleicons.org/redis",
"config": "{\"type\":\"database\",\"runtime\":\"redis\",\"port\":6379}",
"variables": "[{\"key\":\"REDIS_PASSWORD\",\"label\":\"Password\",\"default\":\"\",\"required\":false,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-mongodb",
"name": "MongoDB Database",
"description": "NoSQL document database",
"category": "database",
"logo": "https://cdn.simpleicons.org/mongodb",
"config": "{\"type\":\"database\",\"runtime\":\"mongodb\",\"port\":27017}",
"variables": "[{\"key\":\"MONGO_INITDB_ROOT_USERNAME\",\"label\":\"Root Username\",\"default\":\"admin\",\"required\":true,\"secret\":false},{\"key\":\"MONGO_INITDB_ROOT_PASSWORD\",\"label\":\"Root Password\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-mysql",
"name": "MySQL Database",
"description": "Managed MySQL database service",
"category": "database",
"logo": "https://cdn.simpleicons.org/mysql",
"config": "{\"type\":\"database\",\"runtime\":\"mysql\",\"port\":3306}",
"variables": "[{\"key\":\"MYSQL_DATABASE\",\"label\":\"Database Name\",\"default\":\"app\",\"required\":true,\"secret\":false},{\"key\":\"MYSQL_USER\",\"label\":\"Username\",\"default\":\"app\",\"required\":true,\"secret\":false},{\"key\":\"MYSQL_PASSWORD\",\"label\":\"User Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"MYSQL_ROOT_PASSWORD\",\"label\":\"Root Password\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-mariadb",
"name": "MariaDB Database",
"description": "Managed MariaDB database service",
"category": "database",
"logo": "https://cdn.simpleicons.org/mariadb",
"config": "{\"type\":\"database\",\"runtime\":\"mariadb\",\"port\":3306}",
"variables": "[{\"key\":\"MARIADB_DATABASE\",\"label\":\"Database Name\",\"default\":\"app\",\"required\":true,\"secret\":false},{\"key\":\"MARIADB_USER\",\"label\":\"Username\",\"default\":\"app\",\"required\":true,\"secret\":false},{\"key\":\"MARIADB_PASSWORD\",\"label\":\"User Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"MARIADB_ROOT_PASSWORD\",\"label\":\"Root Password\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-clickhouse",
"name": "ClickHouse Database",
"description": "Column-oriented analytics database",
"category": "database",
"logo": "https://cdn.simpleicons.org/clickhouse",
"config": "{\"type\":\"database\",\"runtime\":\"clickhouse\",\"port\":8123}",
"variables": "[{\"key\":\"CLICKHOUSE_DB\",\"label\":\"Database Name\",\"default\":\"app\",\"required\":false,\"secret\":false},{\"key\":\"CLICKHOUSE_USER\",\"label\":\"Username\",\"default\":\"default\",\"required\":false,\"secret\":false},{\"key\":\"CLICKHOUSE_PASSWORD\",\"label\":\"Password\",\"default\":\"\",\"required\":false,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-dragonfly",
"name": "Dragonfly Database",
"description": "Redis-compatible in-memory data store powered by Dragonfly",
"category": "database",
"logo": "https://cdn.simpleicons.org/redis",
"config": "{\"type\":\"database\",\"runtime\":\"dragonfly\",\"port\":6379}",
"variables": "[{\"key\":\"DRAGONFLY_PASSWORD\",\"label\":\"Password\",\"default\":\"\",\"required\":false,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-worker",
"name": "Background Worker",
"description": "Background job processing service",
"category": "worker",
"logo": "https://cdn.simpleicons.org/terminal",
"config": "{\"type\":\"worker\",\"runtime\":\"node\",\"build_command\":\"npm install\",\"start_command\":\"npm run worker\"}",
"variables": "[{\"key\":\"WORKER_CONCURRENCY\",\"label\":\"Concurrency\",\"default\":\"4\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-cron",
"name": "Cron Job",
"description": "Scheduled task runner",
"category": "cron",
"logo": "https://cdn.simpleicons.org/clock",
"config": "{\"type\":\"cron\",\"runtime\":\"node\",\"build_command\":\"npm install\",\"start_command\":\"npm run cron\"}",
"variables": "[{\"key\":\"CRON_SCHEDULE\",\"label\":\"Schedule\",\"default\":\"0 * * * *\",\"required\":true,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-docker",
"name": "Docker Image",
"description": "Deploy from any Docker image",
"category": "custom",
"logo": "https://cdn.simpleicons.org/docker",
"config": "{\"type\":\"web\",\"runtime\":\"docker\",\"port\":80}",
"variables": "[{\"key\":\"IMAGE\",\"label\":\"Docker Image\",\"default\":\"\",\"required\":true,\"secret\":false},{\"key\":\"TAG\",\"label\":\"Image Tag\",\"default\":\"latest\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-nextcloud",
"name": "Nextcloud",
"description": "Self-hosted file sync, share and collaboration platform",
"category": "app",
"logo": "https://cdn.simpleicons.org/nextcloud",
"config": "{\"type\":\"web\",\"runtime\":\"nextcloud:30-apache\",\"port\":80,\"health_check\":\"/status.php\"}",
"variables": "[{\"key\":\"NEXTCLOUD_ADMIN_USER\",\"label\":\"Admin User\",\"default\":\"admin\",\"required\":false,\"secret\":false},{\"key\":\"NEXTCLOUD_ADMIN_PASSWORD\",\"label\":\"Admin Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"NEXTCLOUD_TRUSTED_DOMAINS\",\"label\":\"Trusted Domains\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-gitea",
"name": "Gitea",
"description": "Lightweight self-hosted Git service",
"category": "app",
"logo": "https://cdn.simpleicons.org/gitea",
"config": "{\"type\":\"web\",\"runtime\":\"gitea/gitea:1\",\"port\":3000,\"health_check\":\"/api/healthz\"}",
"variables": "[{\"key\":\"GITEA__server__DOMAIN\",\"label\":\"Server Domain\",\"default\":\"localhost\",\"required\":false,\"secret\":false},{\"key\":\"GITEA__server__ROOT_URL\",\"label\":\"Root URL\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-ghost",
"name": "Ghost",
"description": "Independent publishing platform for blogs and newsletters",
"category": "app",
"logo": "https://cdn.simpleicons.org/ghost",
"config": "{\"type\":\"web\",\"runtime\":\"ghost:5-alpine\",\"port\":2368,\"health_check\":\"/ghost/api/admin/site/\"}",
"variables": "[{\"key\":\"url\",\"label\":\"Public URL\",\"default\":\"\",\"required\":true,\"secret\":false},{\"key\":\"database__client\",\"label\":\"Database Client\",\"default\":\"sqlite3\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-uptime-kuma",
"name": "Uptime Kuma",
"description": "Self-hosted monitoring tool for services and websites",
"category": "app",
"logo": "https://cdn.simpleicons.org/uptimerobot",
"config": "{\"type\":\"web\",\"runtime\":\"louislam/uptime-kuma:1\",\"port\":3001,\"health_check\":\"/\"}",
"variables": "[]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-vaultwarden",
"name": "Vaultwarden",
"description": "Lightweight Bitwarden-compatible password manager",
"category": "app",
"logo": "https://cdn.simpleicons.org/bitwarden",
"config": "{\"type\":\"web\",\"runtime\":\"vaultwarden/server:latest\",\"port\":80,\"health_check\":\"/alive\"}",
"variables": "[{\"key\":\"ADMIN_TOKEN\",\"label\":\"Admin Token\",\"default\":\"\",\"required\":false,\"secret\":true},{\"key\":\"SIGNUPS_ALLOWED\",\"label\":\"Allow Signups\",\"default\":\"false\",\"required\":false,\"secret\":false},{\"key\":\"DOMAIN\",\"label\":\"Domain URL\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-jellyfin",
"name": "Jellyfin",
"description": "Free software media system for movies, shows and music",
"category": "app",
"logo": "https://cdn.simpleicons.org/jellyfin",
"config": "{\"type\":\"web\",\"runtime\":\"jellyfin/jellyfin:latest\",\"port\":8096,\"health_check\":\"/health\"}",
"variables": "[]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-n8n",
"name": "n8n",
"description": "Workflow automation tool with hundreds of integrations",
"category": "app",
"logo": "https://cdn.simpleicons.org/n8n",
"config": "{\"type\":\"web\",\"runtime\":\"n8nio/n8n:latest\",\"port\":5678,\"health_check\":\"/healthz\"}",
"variables": "[{\"key\":\"N8N_HOST\",\"label\":\"Hostname\",\"default\":\"\",\"required\":false,\"secret\":false},{\"key\":\"N8N_ENCRYPTION_KEY\",\"label\":\"Encryption Key\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"WEBHOOK_URL\",\"label\":\"Webhook URL\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-grafana",
"name": "Grafana",
"description": "Observability dashboards for metrics, logs and traces",
"category": "app",
"logo": "https://cdn.simpleicons.org/grafana",
"config": "{\"type\":\"web\",\"runtime\":\"grafana/grafana:latest\",\"port\":3000,\"health_check\":\"/api/health\"}",
"variables": "[{\"key\":\"GF_SECURITY_ADMIN_PASSWORD\",\"label\":\"Admin Password\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-immich",
"name": "Immich",
"description": "Self-hosted photo and video backup",
"category": "app",
"logo": "https://cdn.simpleicons.org/immich",
"config": "{\"type\":\"web\",\"runtime\":\"ghcr.io/immich-app/immich-server:release\",\"port\":2283,\"health_check\":\"/api/server/ping\"}",
"variables": "[{\"key\":\"DB_PASSWORD\",\"label\":\"Database Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"DB_HOSTNAME\",\"label\":\"Database Host\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-meilisearch",
"name": "Meilisearch",
"description": "Fast, typo-tolerant search engine API",
"category": "app",
"logo": "https://cdn.simpleicons.org/meilisearch",
"config": "{\"type\":\"web\",\"runtime\":\"getmeili/meilisearch:v1\",\"port\":7700,\"health_check\":\"/health\"}",
"variables": "[{\"key\":\"MEILI_MASTER_KEY\",\"label\":\"Master Key\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"MEILI_ENV\",\"label\":\"Environment\",\"default\":\"production\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-plausible",
"name": "Plausible Analytics",
"description": "Lightweight, privacy-friendly web analytics",
"category": "app",
"logo": "https://cdn.simpleicons.org/plausibleanalytics",
"config": "{\"type\":\"web\",\"runtime\":\"ghcr.io/plausible/community-edition:v2\",\"port\":8000,\"health_check\":\"/api/health\"}",
"variables": "[{\"key\":\"BASE_URL\",\"label\":\"Base URL\",\"default\":\"\",\"required\":true,\"secret\":false},{\"key\":\"SECRET_KEY_BASE\",\"label\":\"Secret Key Base\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-minio",
"name": "MinIO",
"description": "S3-compatible object storage",
"category": "app",
"logo": "https://cdn.simpleicons.org/minio",
"config": "{\"type\":\"web\",\"runtime\":\"minio/minio:latest\",\"start_command\":\"server /data --console-address \\\":9001\\\"\",\"port\":9001,\"health_check\":\"/minio/health/live\"}",
"variables": "[{\"key\":\"MINIO_ROOT_USER\",\"label\":\"Root User\",\"default\":\"minioadmin\",\"required\":true,\"secret\":false},{\"key\":\"MINIO_ROOT_PASSWORD\",\"label\":\"Root Password\",\"default\":\"\",\"required\":true,\"secret\":true}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-appwrite",
"name": "Appwrite",
"description": "Backend-as-a-service for web and mobile apps",
"category": "app",
"logo": "https://cdn.simpleicons.org/appwrite",
"config": "{\"type\":\"web\",\"runtime\":\"appwrite/appwrite:1.6\",\"port\":80,\"health_check\":\"/health\"}",
"variables": "[]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-wordpress",
"name": "WordPress",
"description": "Classic CMS powering a large share of the web",
"category": "app",
"logo": "https://cdn.simpleicons.org/wordpress",
"config": "{\"type\":\"web\",\"runtime\":\"wordpress:6-apache\",\"port\":80,\"health_check\":\"/wp-login.php\"}",
"variables": "[{\"key\":\"WORDPRESS_DB_HOST\",\"label\":\"Database Host\",\"default\":\"\",\"required\":true,\"secret\":false},{\"key\":\"WORDPRESS_DB_USER\",\"label\":\"Database User\",\"default\":\"wordpress\",\"required\":true,\"secret\":false},{\"key\":\"WORDPRESS_DB_PASSWORD\",\"label\":\"Database Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"WORDPRESS_DB_NAME\",\"label\":\"Database Name\",\"default\":\"wordpress\",\"required\":true,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-paperless",
"name": "Paperless-ngx",
"description": "Document management system with OCR",
"category": "app",
"logo": "https://cdn.simpleicons.org/paperlessngx",
"config": "{\"type\":\"web\",\"runtime\":\"ghcr.io/paperless-ngx/paperless-ngx:latest\",\"port\":8000,\"health_check\":\"/api/\"}",
"variables": "[{\"key\":\"PAPERLESS_ADMIN_PASSWORD\",\"label\":\"Admin Password\",\"default\":\"\",\"required\":true,\"secret\":true},{\"key\":\"PAPERLESS_URL\",\"label\":\"Public URL\",\"default\":\"\",\"required\":false,\"secret\":false}]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
},
{
"id": "tpl-ntfy",
"name": "ntfy",
"description": "Push notification service over HTTP",
"category": "app",
"logo": "https://cdn.simpleicons.org/ntfy",
"config": "{\"type\":\"web\",\"runtime\":\"binwiederhier/ntfy:latest\",\"start_command\":\"serve\",\"port\":80,\"health_check\":\"/v1/health\"}",
"variables": "[]",
"is_official": true,
"is_public": false,
"created_at": "0001-01-01T00:00:00Z",
"updated_at": "0001-01-01T00:00:00Z"
}
]
}