diff --git a/app/index.html b/app/index.html index 84e5e3a72..25b4e974b 100644 --- a/app/index.html +++ b/app/index.html @@ -4,7 +4,16 @@ - Dokploy Blueprints + + + + + + + + + + Dokploy Templates: 500+ Open Source Apps
diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index efebcaea7..3908a5477 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -1703,8 +1703,8 @@ packages: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} style-mod@4.1.2: @@ -2786,7 +2786,7 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.2 magic-string: 0.30.21 - source-map-js: 1.2.1 + source-map-js: 1.2.2 tailwindcss: 4.1.17 '@tailwindcss/oxide-android-arm64@4.1.17': @@ -3217,7 +3217,7 @@ snapshots: dependencies: nanoid: 3.3.19 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 queue-microtask@1.2.3: {} @@ -3335,7 +3335,7 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - source-map-js@1.2.1: {} + source-map-js@1.2.2: {} style-mod@4.1.2: {} diff --git a/blueprints/3dokploy/3dokploy.svg b/blueprints/3dokploy/3dokploy.svg new file mode 100644 index 000000000..6cde9fb03 --- /dev/null +++ b/blueprints/3dokploy/3dokploy.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/blueprints/3dokploy/docker-compose.yml b/blueprints/3dokploy/docker-compose.yml new file mode 100644 index 000000000..e87fe5d70 --- /dev/null +++ b/blueprints/3dokploy/docker-compose.yml @@ -0,0 +1,10 @@ +services: + 3dokploy: + image: ghcr.io/suleyman/3dokploy:0.1.1 + restart: unless-stopped + environment: + - DOKPLOY_URL=${DOKPLOY_URL} + - DOKPLOY_API_KEY=${DOKPLOY_API_KEY} + - BASIC_AUTH=${BASIC_AUTH} + extra_hosts: + - host.docker.internal:host-gateway diff --git a/blueprints/3dokploy/meta.json b/blueprints/3dokploy/meta.json new file mode 100644 index 000000000..d9d98a449 --- /dev/null +++ b/blueprints/3dokploy/meta.json @@ -0,0 +1,13 @@ +{ + "id": "3dokploy", + "name": "3Dokploy", + "version": "0.1.1", + "description": "A live, isometric 3D view of your Dokploy: projects as buildings, services as racks, deploys rolling in on carts, network traffic as packets. Read-only.", + "logo": "3dokploy.svg", + "links": { + "github": "https://github.com/suleyman/3dokploy", + "website": "", + "docs": "https://github.com/suleyman/3dokploy#readme" + }, + "tags": ["monitoring", "dashboard", "devops"] +} diff --git a/blueprints/3dokploy/template.toml b/blueprints/3dokploy/template.toml new file mode 100644 index 000000000..86f652a2a --- /dev/null +++ b/blueprints/3dokploy/template.toml @@ -0,0 +1,19 @@ +[variables] +main_domain = "${domain}" +auth_password = "${password:24}" + +[config] +mounts = [] + +[[config.domains]] +serviceName = "3dokploy" +port = 3_000 +host = "${main_domain}" + +[config.env] +# The Dokploy on this same server. If its port 3000 is closed, use its public URL instead. +DOKPLOY_URL = "http://host.docker.internal:3000" +# Required: an API key from Settings → Profile → API/CLI. It stays on the server. +DOKPLOY_API_KEY = "" +# Site login (user admin). +BASIC_AUTH = "admin:${auth_password}" diff --git a/blueprints/adguardhome/meta.json b/blueprints/adguardhome/meta.json index def9542c9..0aebf42c6 100644 --- a/blueprints/adguardhome/meta.json +++ b/blueprints/adguardhome/meta.json @@ -2,7 +2,7 @@ "id": "adguardhome", "name": "AdGuard Home", "version": "latest", - "description": "AdGuard Home is a comprehensive solution designed to enhance your online browsing experience by eliminating all kinds of ads, from annoying banners and pop-ups to intrusive video ads. It provides privacy protection, browsing security, and parental control features while maintaining website functionality.", + "description": "AdGuard Home is a network-wide ad and tracker blocker. Self-host it on your own server with Dokploy, which deploys AdGuard Home as a Docker Compose service in a few clicks.", "logo": "logo.svg", "links": { "github": "https://github.com/AdguardTeam/AdGuardHome", @@ -14,5 +14,8 @@ "security", "dns", "ad-blocking" - ] + ], + "seoTitle": "AdGuard Home Docker Compose Setup", + "seoDescription": "Network-wide ad and tracker blocker. Deploy AdGuard Home with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/adminer/meta.json b/blueprints/adminer/meta.json index 2587251bf..e89710827 100644 --- a/blueprints/adminer/meta.json +++ b/blueprints/adminer/meta.json @@ -2,7 +2,7 @@ "id": "adminer", "name": "Adminer", "version": "4.8.1", - "description": "Adminer is a comprehensive database management tool that supports MySQL, MariaDB, PostgreSQL, SQLite, MS SQL, Oracle, Elasticsearch, MongoDB and others. It provides a clean interface for efficient database operations, with strong security features and extensive customization options.", + "description": "Adminer is a lightweight, web-based database management tool. Self-host it on your own server with Dokploy, which deploys Adminer as a Docker Compose service in a few clicks.", "logo": "logo.svg", "links": { "github": "https://github.com/vrana/adminer", @@ -14,5 +14,8 @@ "developer-tools", "mysql", "postgresql" - ] + ], + "seoTitle": "Adminer Docker Compose Setup", + "seoDescription": "Lightweight database management tool. Deploy Adminer with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/affinepro/meta.json b/blueprints/affinepro/meta.json index 66e670547..478e3f163 100644 --- a/blueprints/affinepro/meta.json +++ b/blueprints/affinepro/meta.json @@ -2,7 +2,7 @@ "id": "affinepro", "name": "Affine Pro", "version": "stable", - "description": "Affine Pro is a modern, self-hosted platform designed for collaborative content creation and project management. It offers an intuitive interface, seamless real-time collaboration, and powerful tools for organizing tasks, notes, and ideas.", + "description": "AFFiNE is a workspace for notes, docs, and whiteboards, and an alternative to Notion. Self-host it on your own server with Dokploy, which deploys AFFiNE as a Docker Compose service in a few clicks.", "logo": "logo.png", "links": { "github": "https://github.com/toeverything/Affine", @@ -14,5 +14,7 @@ "self-hosted", "productivity", "project-management" - ] + ], + "seoTitle": "Self-Host Affine in minutes", + "seoDescription": "Workspace for notes and docs, an alternative to Notion. Self-host Affine on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/anythingllm/meta.json b/blueprints/anythingllm/meta.json index 2a5258085..2fa320ec7 100644 --- a/blueprints/anythingllm/meta.json +++ b/blueprints/anythingllm/meta.json @@ -2,7 +2,7 @@ "id": "anythingllm", "name": "AnythingLLM", "version": "latest", - "description": "AnythingLLM is a private, self-hosted, and local document chatbot platform that allows you to chat with your documents using various LLM providers.", + "description": "AnythingLLM is a private AI chat app for working with your own documents. Self-host it on your own server with Dokploy, which deploys AnythingLLM as a Docker Compose service in a few clicks.", "logo": "logo.png", "links": { "github": "https://github.com/Mintplex-Labs/anything-llm", @@ -13,5 +13,7 @@ "ai", "llm", "chatbot" - ] + ], + "seoTitle": "Self-Host AnythingLLM in minutes", + "seoDescription": "Private AI chatbot for your documents. Self-host AnythingLLM on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/anytype/meta.json b/blueprints/anytype/meta.json index fdd252f20..9d3961122 100644 --- a/blueprints/anytype/meta.json +++ b/blueprints/anytype/meta.json @@ -2,7 +2,7 @@ "id": "anytype", "name": "Anytype", "version": "latest", - "description": "Anytype is a personal knowledge base—your digital brain—that lets you gather, connect and remix all kinds of information. Create pages, tasks, wikis, journals—even entire apps—and define your own data model while your data stays offline-first, private and encrypted across devices.\n\nAfter installation, you can view the Anytype client configuration by running `cat /data/client-config.yml` inside the service container.", + "description": "Anytype is a private, offline-first app for notes and personal knowledge. Self-host it on your own server with Dokploy, which deploys Anytype as a Docker Compose service in a few clicks.", "logo": "logo.png", "links": { "github": "https://github.com/grishy/any-sync-bundle", @@ -13,5 +13,7 @@ "note-taking", "local-first", "peer-to-peer" - ] + ], + "seoTitle": "Self-Host Anytype with Dokploy", + "seoDescription": "Private, offline-first personal knowledge base. Self-host Anytype on Dokploy – view your client config afterward with a quick command inside the container." } diff --git a/blueprints/appflowy/meta.json b/blueprints/appflowy/meta.json index db05c5566..a888f2f85 100644 --- a/blueprints/appflowy/meta.json +++ b/blueprints/appflowy/meta.json @@ -2,7 +2,7 @@ "id": "appflowy", "name": "App Flowy", "version": "0.9.3", - "description": "AppFlowy is an open-source alternative to Notion. You are in charge of your data and customizations.", + "description": "AppFlowy is a workspace app for notes, docs, and projects, and an open-source alternative to Notion. Self-host it on your own server with Dokploy, which deploys AppFlowy as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/AppFlowy-IO/AppFlowy", "website": "https://appflowy.io/", @@ -15,5 +15,7 @@ "notes", "knowledge-base", "notion-alternative" - ] + ], + "seoTitle": "Self-Host AppFlowy in minutes", + "seoDescription": "Workspace app, an alternative to Notion. Self-host AppFlowy on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/appwrite-2/appwrite.svg b/blueprints/appwrite-2/appwrite.svg deleted file mode 100644 index 2034a812a..000000000 --- a/blueprints/appwrite-2/appwrite.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/blueprints/appwrite-2/docker-compose.yml b/blueprints/appwrite-2/docker-compose.yml deleted file mode 100644 index c8b8e7632..000000000 --- a/blueprints/appwrite-2/docker-compose.yml +++ /dev/null @@ -1,1790 +0,0 @@ -x-logging: - logging: - driver: json-file - options: - max-file: "5" - max-size: "10m" -services: - appwrite: - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - restart: unless-stopped - healthcheck: - test: - - CMD-SHELL - - 'curl -fsS -o /dev/null -H "Host: $${_APP_DOMAIN:-localhost}" http://localhost/v1/health/version' - interval: 5s - timeout: 5s - retries: 12 - start_period: 120s - labels: - - traefik.enable=true - - 'traefik.http.routers.appwrite-sites-wildcard.rule=HostRegexp(`^.+\.${_APP_DOMAIN_SITES}$$`)' - - traefik.http.routers.appwrite-sites-wildcard.entrypoints=web - - traefik.http.routers.appwrite-sites-wildcard.service=appwrite-wildcard - - 'traefik.http.routers.appwrite-sites-wildcard-secure.rule=HostRegexp(`^.+\.${_APP_DOMAIN_SITES}$$`)' - - traefik.http.routers.appwrite-sites-wildcard-secure.entrypoints=websecure - - traefik.http.routers.appwrite-sites-wildcard-secure.tls=true - - traefik.http.routers.appwrite-sites-wildcard-secure.service=appwrite-wildcard - - 'traefik.http.routers.appwrite-functions-wildcard.rule=HostRegexp(`^.+\.${_APP_DOMAIN_FUNCTIONS}$$`)' - - traefik.http.routers.appwrite-functions-wildcard.entrypoints=web - - traefik.http.routers.appwrite-functions-wildcard.service=appwrite-wildcard - - 'traefik.http.routers.appwrite-functions-wildcard-secure.rule=HostRegexp(`^.+\.${_APP_DOMAIN_FUNCTIONS}$$`)' - - traefik.http.routers.appwrite-functions-wildcard-secure.entrypoints=websecure - - traefik.http.routers.appwrite-functions-wildcard-secure.tls=true - - traefik.http.routers.appwrite-functions-wildcard-secure.service=appwrite-wildcard - - traefik.http.services.appwrite-wildcard.loadbalancer.server.port=80 - volumes: - - appwrite-uploads:/storage/uploads:rw - - appwrite-imports:/storage/imports:rw - - appwrite-cache:/storage/cache:rw - - appwrite-config:/storage/config:rw - - appwrite-certificates:/storage/certificates:rw - - appwrite-functions:/storage/functions:rw - - appwrite-sites:/storage/sites:rw - - appwrite-builds:/storage/builds:rw - depends_on: - redis: - condition: service_healthy - postgresql: - condition: service_healthy - appwrite-geo: - condition: service_healthy - appwrite-autogravity: - condition: service_started - environment: - - _APP_ENV - - _APP_USAGE_STATS - - _APP_USAGE_PASS - - _APP_CONNECTIONS_DB_USAGE - - _APP_POOL_SIZE_USAGE - - _APP_CONNECTIONS_DB_EXECUTIONS - - _APP_POOL_SIZE_EXECUTIONS - - _APP_MAINTENANCE_RETENTION_USAGE_TTL - - _APP_STATS_USAGE_QUEUE_NAME - - _APP_STATS_RESOURCES_QUEUE_NAME - - _APP_STATS_RESOURCES_INTERVAL - - _APP_EDITION - - _APP_WORKER_PER_CORE - - _APP_LOCALE - - _APP_COMPRESSION_ENABLED - - _APP_COMPRESSION_MIN_SIZE_BYTES - - _APP_CONSOLE_WHITELIST_ROOT - - _APP_CONSOLE_WHITELIST_EMAILS - - _APP_CONSOLE_SESSION_ALERTS - - _APP_CONSOLE_WHITELIST_IPS - - _APP_CONSOLE_HOSTNAMES - - _APP_CONSOLE_SCHEMA - - _APP_CONSOLE_GITHUB_APP_ID - - _APP_CONSOLE_GITHUB_SECRET - - _APP_CONSOLE_GITLAB_APP_ID - - _APP_CONSOLE_GITLAB_SECRET - - _APP_CONSOLE_GITLAB_ENDPOINT - - _APP_CONSOLE_BITBUCKET_APP_ID - - _APP_CONSOLE_BITBUCKET_SECRET - - _APP_CONSOLE_GOOGLE_APP_ID - - _APP_CONSOLE_GOOGLE_SECRET - - _APP_SYSTEM_EMAIL_NAME - - _APP_SYSTEM_EMAIL_ADDRESS - - _APP_SYSTEM_TEAM_EMAIL - - _APP_EMAIL_SECURITY - - _APP_SYSTEM_RESPONSE_FORMAT - - _APP_OPTIONS_ABUSE - - _APP_OPTIONS_ABUSE_INCREASED_LIMIT_PROJECTS - - _APP_OPTIONS_ROUTER_PROTECTION - - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_ROUTER_FORCE_HTTPS - - _APP_ROUTER_AUTO_CERTIFICATES - - _APP_OPENSSL_KEY_V1 - - _APP_NOTIFICATIONS_TRACKING_SECRET - - _APP_DOMAIN - - _APP_CONSOLE_DOMAIN - - _APP_CONSOLE_URL - - _APP_CONSOLE_TRUSTED_PROJECTS - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_DOMAIN_FUNCTIONS - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_SMTP_HOST - - _APP_SMTP_PORT - - _APP_SMTP_SECURE - - _APP_SMTP_USERNAME - - _APP_SMTP_PASSWORD - - _APP_STORAGE_LIMIT - - _APP_STORAGE_PREVIEW_LIMIT - - _APP_AUTOGRAVITY_HOST - - _APP_STORAGE_ANTIVIRUS - - _APP_STORAGE_ANTIVIRUS_HOST - - _APP_STORAGE_ANTIVIRUS_PORT - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_COMPUTE_SIZE_LIMIT - - _APP_COMPUTE_BUILD_SIZE_LIMIT - - _APP_FUNCTIONS_TIMEOUT - - _APP_SITES_TIMEOUT - - _APP_COMPUTE_BUILD_TIMEOUT - - _APP_COMPUTE_CPUS - - _APP_COMPUTE_MEMORY - - _APP_FUNCTIONS_RUNTIMES - - _APP_SITES_RUNTIMES - - _APP_DOMAIN_SITES - - _APP_EXECUTOR_SECRET - - _APP_EXECUTOR_HOST - - _APP_BUILDS_VOLUME - - _APP_JOBS_HOST - - _APP_JOBS_SECRET - - _APP_JOBS_ENDPOINT - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_LOCKING_ENABLED - - _APP_MAINTENANCE_INTERVAL - - _APP_MAINTENANCE_RETENTION_EXECUTION - - _APP_MAINTENANCE_RETENTION_CACHE - - _APP_MAINTENANCE_RETENTION_ABUSE - - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - - _APP_MAINTENANCE_RETENTION_SCHEDULES - - _APP_SMS_PROVIDER - - _APP_SMS_FROM - - _APP_GRAPHQL_INTROSPECTION - - _APP_GRAPHQL_MAX_BATCH_SIZE - - _APP_GRAPHQL_MAX_COMPLEXITY - - _APP_GRAPHQL_MAX_DEPTH - - _APP_VCS_GITHUB_APP_NAME - - _APP_VCS_GITHUB_PRIVATE_KEY - - _APP_VCS_GITHUB_APP_ID - - _APP_VCS_GITHUB_WEBHOOK_SECRET - - _APP_VCS_GITHUB_CLIENT_SECRET - - _APP_VCS_GITHUB_CLIENT_ID - - _APP_VCS_GITEA_ENDPOINT - - _APP_VCS_GITEA_BROWSER_ENDPOINT - - _APP_VCS_GITEA_CLIENT_ID - - _APP_VCS_GITEA_CLIENT_SECRET - - _APP_VCS_GITEA_WEBHOOK_SECRET - - _APP_VCS_GITLAB_ENDPOINT - - _APP_VCS_GITLAB_CLIENT_ID - - _APP_VCS_GITLAB_CLIENT_SECRET - - _APP_VCS_GITLAB_WEBHOOK_SECRET - - _APP_VCS_BITBUCKET_CLIENT_ID - - _APP_VCS_BITBUCKET_CLIENT_SECRET - - _APP_VCS_BITBUCKET_WEBHOOK_SECRET - - _APP_VCS_ORIGIN_CLIENT_ID - - _APP_VCS_ORIGIN_PRIVATE_KEY - - _APP_VCS_WEBHOOK_URL - - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - - _APP_ASSISTANT_OPENAI_API_KEY - - _APP_CONSOLE_COUNTRIES_DENYLIST - - _APP_DATABASE_SHARED_TABLES - - _APP_DATABASE_SHARED_NAMESPACE - - _APP_FUNCTIONS_CREATION_ABUSE_LIMIT - - _APP_CUSTOM_DOMAIN_DENY_LIST - - _APP_TRUSTED_HEADERS - - _APP_MIGRATION_HOST - - _APP_GEO_SECRET - - _APP_GEO_ENDPOINT - - _APP_PWNED_PASSWORDS_DSN - - _APP_LIMIT_DATABASE_BATCH - - _APP_DOCUMENTSDB - - _APP_VECTORSDB - - _APP_EMBEDDING - appwrite-console: - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/new:1.1.159-self-hosted - restart: unless-stopped - environment: - - VITE_CONSOLE_PROFILE=self-hosted - - APPWRITE_ENDPOINT_SAME_ORIGIN=true - - VITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1 - appwrite-realtime: - entrypoint: realtime - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - restart: unless-stopped - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_USAGE_STATS - - _APP_STATS_USAGE_QUEUE_NAME - - _APP_WORKER_PER_CORE - - _APP_OPTIONS_ABUSE - - _APP_OPTIONS_ROUTER_PROTECTION - - _APP_CONSOLE_HOSTNAMES - - _APP_REALTIME_TAIL_RATE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - - _APP_LIMIT_DATABASE_BATCH - - _APP_DOCUMENTSDB - - _APP_VECTORSDB - - _APP_EMBEDDING - - appwrite-mqtt: - entrypoint: mqtt - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - healthcheck: - test: - - CMD - - php - - -r - - "exit(@fsockopen('127.0.0.1', 1883) ? 0 : 1);" - interval: 5s - timeout: 5s - retries: 20 - start_period: 20s - environment: - - _APP_ENV - - _APP_WORKER_PER_CORE - - _APP_OPTIONS_ABUSE - - _APP_OPTIONS_ROUTER_PROTECTION - - _APP_REALTIME_TAIL_RATE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - - _APP_MQTT_REPLAY_DEPTH - - _APP_MQTT_WS_PORT - - _APP_MQTT_MAX_PACKET_SIZE - - appwrite-worker: - entrypoint: worker - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - volumes: - - appwrite-uploads:/storage/uploads:rw - - appwrite-cache:/storage/cache:rw - - appwrite-functions:/storage/functions:rw - - appwrite-sites:/storage/sites:rw - - appwrite-builds:/storage/builds:rw - - appwrite-certificates:/storage/certificates:rw - - appwrite-config:/storage/config:rw - - appwrite-imports:/storage/imports:rw - extra_hosts: - - host.docker.internal:host-gateway - environment: - - _APP_ENV - - _APP_USAGE_STATS - - _APP_USAGE_PASS - - _APP_CONNECTIONS_DB_USAGE - - _APP_POOL_SIZE_USAGE - - _APP_CONNECTIONS_DB_EXECUTIONS - - _APP_POOL_SIZE_EXECUTIONS - - _APP_MAINTENANCE_RETENTION_USAGE_TTL - - _APP_STATS_USAGE_QUEUE_NAME - - _APP_STATS_RESOURCES_QUEUE_NAME - - _APP_STATS_RESOURCES_INTERVAL - - _APP_WORKERS_NUM=1 - # Pool sizing only (sum of per-queue caps). Each queue keeps its own maxCoroutines in PHP; databases stays at 1. - - _APP_WORKER_MAX_COROUTINES=78 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_EMAIL_SECURITY - - _APP_EMAIL_CERTIFICATES - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_QUEUE_NAME - - _APP_DATABASE_SHARED_TABLES - - _APP_CONSOLE_URL - - _APP_CONSOLE_DOMAIN - - _APP_CONSOLE_TRUSTED_PROJECTS - - _APP_DOMAIN - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_ROUTER_FORCE_HTTPS - - _APP_WEBHOOK_MAX_FAILED_ATTEMPTS - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_EXECUTOR_SECRET - - _APP_EXECUTOR_HOST - - _APP_DOCKER_HUB_USERNAME - - _APP_DOCKER_HUB_PASSWORD - - _APP_FUNCTIONS_TIMEOUT - - _APP_SITES_TIMEOUT - - _APP_COMPUTE_BUILD_TIMEOUT - - _APP_COMPUTE_CPUS - - _APP_COMPUTE_MEMORY - - _APP_OPEN_RUNTIMES_NFT - - _APP_COMPUTE_SIZE_LIMIT - - _APP_COMPUTE_BUILD_SIZE_LIMIT - - _APP_VCS_GITHUB_APP_NAME - - _APP_VCS_GITHUB_PRIVATE_KEY - - _APP_VCS_GITHUB_APP_ID - - _APP_VCS_GITEA_ENDPOINT - - _APP_VCS_GITEA_BROWSER_ENDPOINT - - _APP_VCS_GITEA_CLIENT_ID - - _APP_VCS_GITEA_CLIENT_SECRET - - _APP_VCS_GITEA_WEBHOOK_SECRET - - _APP_VCS_GITLAB_ENDPOINT - - _APP_VCS_GITLAB_CLIENT_ID - - _APP_VCS_GITLAB_CLIENT_SECRET - - _APP_VCS_GITLAB_WEBHOOK_SECRET - - _APP_VCS_BITBUCKET_CLIENT_ID - - _APP_VCS_BITBUCKET_CLIENT_SECRET - - _APP_VCS_BITBUCKET_WEBHOOK_SECRET - - _APP_VCS_ORIGIN_CLIENT_ID - - _APP_VCS_ORIGIN_PRIVATE_KEY - - _APP_VCS_WEBHOOK_URL - - _APP_BUILDS_VOLUME - - _APP_JOBS_HOST - - _APP_JOBS_SECRET - - _APP_JOBS_ENDPOINT - - _APP_BROWSER_HOST - - _APP_WORKER_SCREENSHOTS_ROUTER - - _APP_NOTIFICATIONS_TRACKING_SECRET - - _APP_SYSTEM_EMAIL_NAME - - _APP_SYSTEM_EMAIL_ADDRESS - - _APP_SMTP_HOST - - _APP_SMTP_PORT - - _APP_SMTP_SECURE - - _APP_SMTP_USERNAME - - _APP_SMTP_PASSWORD - - _APP_SMS_FROM - - _APP_SMS_PROVIDER - - _APP_SMS_PROJECTS_DENY_LIST - - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - - _APP_MIGRATION_HOST - - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - - _APP_LIMIT_DATABASE_BATCH - - _APP_DOCUMENTSDB - - _APP_VECTORSDB - - _APP_EMBEDDING - - appwrite-task-scheduler: - entrypoint: schedule - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_USAGE_STATS - - _APP_USAGE_PASS - - _APP_CONNECTIONS_DB_USAGE - - _APP_POOL_SIZE_USAGE - - _APP_CONNECTIONS_DB_EXECUTIONS - - _APP_POOL_SIZE_EXECUTIONS - - _APP_MAINTENANCE_RETENTION_USAGE_TTL - - _APP_STATS_USAGE_QUEUE_NAME - - _APP_STATS_RESOURCES_QUEUE_NAME - - _APP_STATS_RESOURCES_INTERVAL - - _APP_LOGGING_FORMAT - # Combined mode runs functions/executions/messages timers + enqueue coroutines - # against shared console/cache pools; floor must cover concurrent ticks. - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_OPTIONS_FORCE_HTTPS - - _APP_DOMAIN - - _APP_CONSOLE_DOMAIN - - _APP_CONSOLE_SCHEMA - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_MIGRATION_HOST - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - - _APP_FUNCTIONS_SCHEDULE_SPREAD - - appwrite-worker-stats-usage: - profiles: - - separate - extends: - service: appwrite-worker - entrypoint: worker-stats-usage - - appwrite-worker-stats-resources: - profiles: - - separate - extends: - service: appwrite-worker - entrypoint: worker-stats-resources - - appwrite-task-stats-resources: - profiles: - - separate - extends: - service: appwrite-task-scheduler - entrypoint: stats-resources - - appwrite-worker-webhooks: - profiles: - - separate - entrypoint: worker-webhooks - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_EMAIL_SECURITY - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_WEBHOOK_MAX_FAILED_ATTEMPTS - - _APP_DATABASE_SHARED_TABLES - - _APP_CONSOLE_URL - appwrite-worker-deletes: - profiles: - - separate - entrypoint: worker-deletes - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - volumes: - - appwrite-uploads:/storage/uploads:rw - - appwrite-cache:/storage/cache:rw - - appwrite-functions:/storage/functions:rw - - appwrite-sites:/storage/sites:rw - - appwrite-builds:/storage/builds:rw - - appwrite-certificates:/storage/certificates:rw - environment: - - _APP_ENV - - _APP_USAGE_PASS - - _APP_CONNECTIONS_DB_USAGE - - _APP_CONNECTIONS_DB_EXECUTIONS - - _APP_POOL_SIZE_EXECUTIONS - - _APP_MAINTENANCE_RETENTION_EXECUTION - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_EXECUTOR_SECRET - - _APP_EXECUTOR_HOST - - _APP_DATABASE_SHARED_TABLES - - _APP_EMAIL_CERTIFICATES - - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - appwrite-worker-databases: - profiles: - - separate - entrypoint: worker-databases - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=1 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_QUEUE_NAME - - _APP_DATABASE_SHARED_TABLES - - _APP_LIMIT_DATABASE_BATCH - - _APP_DOCUMENTSDB - - _APP_VECTORSDB - - _APP_EMBEDDING - appwrite-worker-builds: - profiles: - - separate - entrypoint: worker-builds - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - volumes: - - appwrite-functions:/storage/functions:rw - - appwrite-sites:/storage/sites:rw - - appwrite-builds:/storage/builds:rw - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_EXECUTOR_SECRET - - _APP_EXECUTOR_HOST - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_VCS_GITHUB_APP_NAME - - _APP_VCS_GITHUB_PRIVATE_KEY - - _APP_VCS_GITHUB_APP_ID - - _APP_VCS_GITEA_ENDPOINT - - _APP_VCS_GITEA_BROWSER_ENDPOINT - - _APP_VCS_GITEA_CLIENT_ID - - _APP_VCS_GITEA_CLIENT_SECRET - - _APP_VCS_GITEA_WEBHOOK_SECRET - - _APP_VCS_GITLAB_ENDPOINT - - _APP_VCS_GITLAB_CLIENT_ID - - _APP_VCS_GITLAB_CLIENT_SECRET - - _APP_VCS_GITLAB_WEBHOOK_SECRET - - _APP_VCS_BITBUCKET_CLIENT_ID - - _APP_VCS_BITBUCKET_CLIENT_SECRET - - _APP_VCS_BITBUCKET_WEBHOOK_SECRET - - _APP_VCS_ORIGIN_CLIENT_ID - - _APP_VCS_ORIGIN_PRIVATE_KEY - - _APP_VCS_WEBHOOK_URL - - _APP_FUNCTIONS_TIMEOUT - - _APP_SITES_TIMEOUT - - _APP_COMPUTE_BUILD_TIMEOUT - - _APP_COMPUTE_CPUS - - _APP_COMPUTE_MEMORY - - _APP_OPEN_RUNTIMES_NFT - - _APP_COMPUTE_SIZE_LIMIT - - _APP_COMPUTE_BUILD_SIZE_LIMIT - - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_ROUTER_FORCE_HTTPS - - _APP_DOMAIN - - _APP_CONSOLE_DOMAIN - - _APP_CONSOLE_URL - - _APP_CONSOLE_TRUSTED_PROJECTS - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_DATABASE_SHARED_TABLES - - _APP_DOMAIN_SITES - - _APP_BUILDS_VOLUME - - _APP_JOBS_HOST - - _APP_JOBS_SECRET - - _APP_JOBS_ENDPOINT - extra_hosts: - - host.docker.internal:host-gateway - - appwrite-worker-jobs: - profiles: - - separate - entrypoint: worker-jobs - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - volumes: - - appwrite-builds:/storage/builds:rw - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_JOBS_HOST - - _APP_JOBS_SECRET - - _APP_COMPUTE_BUILD_SIZE_LIMIT - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - extra_hosts: - - host.docker.internal:host-gateway - - appwrite-worker-screenshots: - profiles: - - separate - entrypoint: worker-screenshots - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - volumes: - - appwrite-uploads:/storage/uploads:rw - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_BROWSER_HOST - - _APP_SITES_TIMEOUT - - _APP_WORKER_SCREENSHOTS_ROUTER - - _APP_OPTIONS_FORCE_HTTPS - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - extra_hosts: - - host.docker.internal:host-gateway - appwrite-worker-certificates: - profiles: - - separate - entrypoint: worker-certificates - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - volumes: - - appwrite-config:/storage/config:rw - - appwrite-certificates:/storage/certificates:rw - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_DOMAIN - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_EMAIL_CERTIFICATES - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - - appwrite-worker-executions: - profiles: - - separate - entrypoint: worker-executions - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_USAGE_PASS - - _APP_CONNECTIONS_DB_USAGE - - _APP_CONNECTIONS_DB_EXECUTIONS - - _APP_POOL_SIZE_EXECUTIONS - - _APP_MAINTENANCE_RETENTION_EXECUTION - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - - appwrite-worker-functions: - profiles: - - separate - entrypoint: worker-functions - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=8 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_DOMAIN - - _APP_OPTIONS_FORCE_HTTPS - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_FUNCTIONS_TIMEOUT - - _APP_SITES_TIMEOUT - - _APP_COMPUTE_BUILD_TIMEOUT - - _APP_COMPUTE_CPUS - - _APP_COMPUTE_MEMORY - - _APP_EXECUTOR_SECRET - - _APP_EXECUTOR_HOST - - _APP_DOCKER_HUB_USERNAME - - _APP_DOCKER_HUB_PASSWORD - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DATABASE_SHARED_TABLES - appwrite-worker-mails: - profiles: - - separate - entrypoint: worker-mails - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=6 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_NOTIFICATIONS_TRACKING_SECRET - - _APP_SYSTEM_EMAIL_NAME - - _APP_SYSTEM_EMAIL_ADDRESS - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER - - _APP_SMTP_HOST - - _APP_SMTP_PORT - - _APP_SMTP_SECURE - - _APP_SMTP_USERNAME - - _APP_SMTP_PASSWORD - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DOMAIN - - _APP_OPTIONS_FORCE_HTTPS - - _APP_DATABASE_SHARED_TABLES - - appwrite-worker-notifications: - profiles: - - separate - entrypoint: worker-notifications - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - restart: unless-stopped - image: appwrite/appwrite:2.3.0 - depends_on: - - redis - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_NOTIFICATIONS_TRACKING_SECRET - - _APP_SYSTEM_EMAIL_NAME - - _APP_SYSTEM_EMAIL_ADDRESS - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER - - _APP_SMTP_HOST - - _APP_SMTP_PORT - - _APP_SMTP_SECURE - - _APP_SMTP_USERNAME - - _APP_SMTP_PASSWORD - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_DOMAIN - - _APP_OPTIONS_FORCE_HTTPS - - _APP_DATABASE_SHARED_TABLES - appwrite-worker-messaging: - profiles: - - separate - entrypoint: worker-messaging - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - restart: unless-stopped - image: appwrite/appwrite:2.3.0 - volumes: - - appwrite-uploads:/storage/uploads:rw - depends_on: - - redis - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=1 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_SMS_FROM - - _APP_SMS_PROVIDER - - _APP_SMS_PROJECTS_DENY_LIST - - _APP_STORAGE_DEVICE - - _APP_STORAGE_S3_ACCESS_KEY - - _APP_STORAGE_S3_SECRET - - _APP_STORAGE_S3_REGION - - _APP_STORAGE_S3_BUCKET - - _APP_STORAGE_S3_ENDPOINT - - _APP_STORAGE_DO_SPACES_ACCESS_KEY - - _APP_STORAGE_DO_SPACES_SECRET - - _APP_STORAGE_DO_SPACES_REGION - - _APP_STORAGE_DO_SPACES_BUCKET - - _APP_STORAGE_BACKBLAZE_ACCESS_KEY - - _APP_STORAGE_BACKBLAZE_SECRET - - _APP_STORAGE_BACKBLAZE_REGION - - _APP_STORAGE_BACKBLAZE_BUCKET - - _APP_STORAGE_LINODE_ACCESS_KEY - - _APP_STORAGE_LINODE_SECRET - - _APP_STORAGE_LINODE_REGION - - _APP_STORAGE_LINODE_BUCKET - - _APP_STORAGE_WASABI_ACCESS_KEY - - _APP_STORAGE_WASABI_SECRET - - _APP_STORAGE_WASABI_REGION - - _APP_STORAGE_WASABI_BUCKET - - _APP_DATABASE_SHARED_TABLES - appwrite-worker-migrations: - profiles: - - separate - entrypoint: worker-migrations - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - restart: unless-stopped - image: appwrite/appwrite:2.3.0 - volumes: - - appwrite-imports:/storage/imports:rw - - appwrite-uploads:/storage/uploads:rw - depends_on: - - ${_APP_DB_HOST:-postgresql} - environment: - - _APP_ENV - - _APP_WORKERS_NUM=1 - - _APP_WORKER_MAX_COROUTINES=1 - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_DOMAIN - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_EMAIL_SECURITY - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DB_ADAPTER_VECTORSDB - - _APP_DB_HOST_VECTORSDB - - _APP_DB_PORT_VECTORSDB - - _APP_DB_SCHEMA_VECTORSDB - - _APP_DB_USER_VECTORSDB - - _APP_DB_PASS_VECTORSDB - - _APP_DB_ADAPTER_DOCUMENTSDB - - _APP_DB_HOST_DOCUMENTSDB - - _APP_DB_PORT_DOCUMENTSDB - - _APP_DB_SCHEMA_DOCUMENTSDB - - _APP_DB_USER_DOCUMENTSDB - - _APP_DB_PASS_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_DOCUMENTSDB - - _APP_CONNECTIONS_DATABASE_VECTORSDB - - _APP_LOGGING_CONFIG - - _APP_LOGGING_FORMAT - - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - - _APP_DATABASE_SHARED_TABLES - - _APP_OPTIONS_FORCE_HTTPS - - _APP_MIGRATION_HOST - appwrite-task-maintenance: - entrypoint: maintenance - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_LOGGING_FORMAT - - _APP_WORKER_PER_CORE - - _APP_DOMAIN - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_DOMAIN_FUNCTIONS - - _APP_EDITION - - _APP_ROUTER_AUTO_CERTIFICATES - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_MAINTENANCE_INTERVAL - - _APP_MAINTENANCE_RETENTION_EXECUTION - - _APP_MAINTENANCE_RETENTION_CACHE - - _APP_MAINTENANCE_RETENTION_ABUSE - - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - - _APP_MAINTENANCE_RETENTION_SCHEDULES - - _APP_MAINTENANCE_START_TIME - - _APP_DATABASE_SHARED_TABLES - appwrite-task-interval: - entrypoint: interval - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_LOGGING_FORMAT - - _APP_WORKER_PER_CORE - - _APP_DOMAIN - - _APP_DOMAIN_TARGET_CNAME - - _APP_DOMAIN_TARGET_AAAA - - _APP_DOMAIN_TARGET_A - - _APP_DOMAIN_TARGET_CAA - - _APP_DNS - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - - _APP_INTERVAL_DOMAIN_VERIFICATION - - _APP_INTERVAL_CLEANUP_STALE_EXECUTIONS - - appwrite-task-scheduler-functions: - profiles: - - separate - entrypoint: schedule-functions - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_LOGGING_FORMAT - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_OPTIONS_FORCE_HTTPS - - _APP_DOMAIN - - _APP_CONSOLE_DOMAIN - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_MIGRATION_HOST - - _APP_CONSOLE_SCHEMA - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - - _APP_FUNCTIONS_SCHEDULE_SPREAD - appwrite-task-scheduler-executions: - profiles: - - separate - entrypoint: schedule-executions - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_LOGGING_FORMAT - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_OPTIONS_FORCE_HTTPS - - _APP_DOMAIN - - _APP_CONSOLE_DOMAIN - - _APP_DOMAIN_FUNCTIONS - - _APP_DOMAIN_SITES - - _APP_MIGRATION_HOST - - _APP_CONSOLE_SCHEMA - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - appwrite-task-scheduler-messages: - profiles: - - separate - entrypoint: schedule-messages - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/appwrite:2.3.0 - depends_on: - - ${_APP_DB_HOST:-postgresql} - - redis - environment: - - _APP_ENV - - _APP_LOGGING_FORMAT - - _APP_WORKER_PER_CORE - - _APP_OPENSSL_KEY_V1 - - _APP_REDIS_HOST - - _APP_REDIS_PORT - - _APP_REDIS_USER - - _APP_REDIS_PASS - - _APP_DB_ADAPTER - - _APP_DB_HOST - - _APP_DB_PORT - - _APP_DB_SCHEMA - - _APP_DB_USER - - _APP_DB_PASS - - _APP_DATABASE_SHARED_TABLES - appwrite-assistant: - image: appwrite/assistant:0.8.4 - restart: unless-stopped - environment: - - _APP_ASSISTANT_OPENAI_API_KEY - appwrite-browser: - image: appwrite/browser:0.3.4 - restart: unless-stopped - appwrite-autogravity: - image: ghcr.io/appwrite/autogravity:0.0.11 - restart: unless-stopped - environment: - - MODEL_BACKEND=focalnet - clickhouse: - image: clickhouse/clickhouse-server:26.4.3-alpine - restart: unless-stopped - environment: - - CLICKHOUSE_DB=appwrite - - CLICKHOUSE_USER=appwrite - - CLICKHOUSE_PASSWORD=${_APP_USAGE_PASS:-appwrite} - - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 - volumes: - - appwrite-clickhouse:/var/lib/clickhouse:rw - ulimits: - nofile: - soft: 262144 - hard: 262144 - healthcheck: - test: - - CMD-SHELL - - 'clickhouse-client --user "$$CLICKHOUSE_USER" --password "$$CLICKHOUSE_PASSWORD" --query "SELECT 1" >/dev/null' - interval: 5s - timeout: 5s - retries: 20 - start_period: 30s - - appwrite-geo: - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - image: appwrite/geo:0.3.1 - restart: unless-stopped - environment: - - GEO_SECRET=$_APP_GEO_SECRET - healthcheck: - test: 'php -r "exit(json_decode(file_get_contents(''http://localhost/v1/health''))->status === ''ok'' ? 0 : 1);"' - interval: 10s - timeout: 5s - retries: 3 - start_period: 5s - - openruntimes-executor: - # Must stay equal to the service name: the executor finds its own container - # with gethostname() against Docker's name filter, and the generated name is - # "-openruntimes-executor-1" (substring match). A hostname like - # "exc1" makes the lookup fail and the executor crash loops with - # "Own container not found" (appwrite/appwrite#13016). Keep it in sync with - # _APP_EXECUTOR_HOST=http://openruntimes-executor/v1. - hostname: openruntimes-executor - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - stop_signal: SIGINT - image: openruntimes/executor:0.29.1 - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - appwrite-builds:/storage/builds:rw - - appwrite-functions:/storage/functions:rw - - appwrite-sites:/storage/sites:rw - - /tmp:/tmp:rw - environment: - - OPR_EXECUTOR_IMAGES=$_APP_EXECUTOR_IMAGES - - OPR_EXECUTOR_INACTIVE_THRESHOLD=$_APP_COMPUTE_INACTIVE_THRESHOLD - - OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_COMPUTE_MAINTENANCE_INTERVAL - - OPR_EXECUTOR_NETWORK=$_APP_COMPUTE_RUNTIMES_NETWORK - - OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME - - OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD - - OPR_EXECUTOR_ENV=$_APP_ENV - - OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES,$_APP_SITES_RUNTIMES - - OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET - - OPR_EXECUTOR_RUNTIME_VERSIONS=v5 - - OPEN_RUNTIMES_NFT=$_APP_OPEN_RUNTIMES_NFT - - OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG - - OPR_EXECUTOR_CONNECTION_STORAGE=${_APP_EXECUTOR_CONNECTION_STORAGE:-local://localhost} - - OPR_EXECUTOR_CONNECTION_BUILD_CACHE_STORAGE= - healthcheck: - test: - - CMD-SHELL - - 'curl -fsS -H "Authorization: Bearer $$OPR_EXECUTOR_SECRET" http://localhost/v1/health - >/dev/null' - interval: 5s - timeout: 3s - retries: 20 - start_period: 5s - - orchestrator: - hostname: orchestrator - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - # One image carrying every orchestrator control plane. Appwrite drives the - # jobs plane today; the per-plane images exist for Kubernetes, where each - # scales, fails and gets RBAC on its own. - image: ghcr.io/open-runtimes/orchestrator/orchestrator:2.2.2 - restart: unless-stopped - user: root - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - - ORCHESTRATOR_BACKEND=docker - # The jobs plane and the serving planes read the network under different - # names; both name the network the workload containers join. - - ORCHESTRATOR_NETWORK=${_APP_COMPUTE_RUNTIMES_NETWORK:-dokploy-network} - - DOCKER_NETWORK=${_APP_COMPUTE_RUNTIMES_NETWORK:-dokploy-network} - - ARTIFACT_ENDPOINT=http://orchestrator:8080 - - S3_ENDPOINT=${_APP_STORAGE_S3_ENDPOINT:-} - - S3_REGION=${_APP_STORAGE_S3_REGION:-us-east-1} - - S3_ACCESS_KEY_ID=${_APP_STORAGE_S3_ACCESS_KEY:-} - - S3_SECRET_ACCESS_KEY=${_APP_STORAGE_S3_SECRET:-} - - mariadb: - image: mariadb:10.11 - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - restart: unless-stopped - volumes: - - appwrite-mariadb:/var/lib/mysql:rw - environment: - - MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS} - - MYSQL_DATABASE=${_APP_DB_SCHEMA} - - MYSQL_USER=${_APP_DB_USER} - - MYSQL_PASSWORD=${_APP_DB_PASS} - - MARIADB_AUTO_UPGRADE=1 - command: - - mysqld - - --innodb-flush-method=fsync - healthcheck: - test: - - CMD-SHELL - - mariadb --protocol=tcp -h127.0.0.1 -uroot -p"$$MYSQL_ROOT_PASSWORD" --silent -e "SELECT 1" - interval: 5s - timeout: 5s - retries: 12 - start_period: 10s - mongodb: - image: mongo:8.2.5 - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - restart: unless-stopped - volumes: - - appwrite-mongodb:/data/db - - appwrite-mongodb-keyfile:/data/keyfile - - ../files/mongo-init.js:/mongo-init.js:ro - - ../files/mongo-entrypoint.sh:/mongo-entrypoint.sh:ro - environment: - - MONGO_INITDB_ROOT_USERNAME=root - - MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS} - - MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA} - - MONGO_INITDB_USERNAME=${_APP_DB_USER} - - MONGO_INITDB_PASSWORD=${_APP_DB_PASS} - entrypoint: - - /bin/bash - - /mongo-entrypoint.sh - healthcheck: - test: | - bash -c " - mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval \" - try { - const hello = db.adminCommand({hello: 1}); - if (hello.isWritablePrimary && hello.setName === 'rs0') { - quit(0); - } - } catch (e) {} - - try { - rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongodb:27017'}]}); - } catch (e) {} - - quit(1); - \" 2>/dev/null - " - interval: 10s - timeout: 10s - retries: 10 - start_period: 60s - postgresql: - image: appwrite/postgres:0.1.0 - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - volumes: - - appwrite-postgresql:/var/lib/postgresql:rw - environment: - - POSTGRES_DB=${_APP_DB_SCHEMA} - - POSTGRES_USER=${_APP_DB_USER} - - POSTGRES_PASSWORD=${_APP_DB_PASS} - healthcheck: - test: - - CMD-SHELL - - pg_isready -U ${_APP_DB_USER} -d ${_APP_DB_SCHEMA} - interval: 5s - timeout: 5s - retries: 10 - start_period: 10s - command: - - postgres - appwrite-embedding: - image: appwrite/embedding:0.3.1 - profiles: - - embedding - environment: - EMBEDDING_MODELS: ${_APP_EMBEDDING_MODELS:-nomic-embed-text} - restart: unless-stopped - volumes: - - appwrite-models:/home/embedder/models - redis: - image: redis:7.4.7-alpine - restart: unless-stopped - logging: - driver: json-file - options: - max-file: "5" - max-size: 10m - command: - - redis-server - - --maxmemory - - 512mb - - --maxmemory-policy - - allkeys-lru - - --maxmemory-samples - - "5" - volumes: - - appwrite-redis:/data:rw - healthcheck: - test: - - CMD - - redis-cli - - ping - interval: 5s - timeout: 5s - retries: 12 -volumes: - appwrite-mariadb: null - appwrite-mongodb: null - appwrite-mongodb-keyfile: null - appwrite-postgresql: null - appwrite-redis: null - appwrite-cache: null - appwrite-clickhouse: null - appwrite-uploads: null - appwrite-imports: null - appwrite-certificates: null - appwrite-functions: null - appwrite-sites: null - # Pinned name (unprefixed) so the jobs-service can attach it to build workers - # by a stable name across environments (see _APP_BUILDS_VOLUME). - appwrite-builds: - name: appwrite-builds - appwrite-config: null - appwrite-models: null - openruntimes-node-modules-cache: null - diff --git a/blueprints/appwrite-2/meta.json b/blueprints/appwrite-2/meta.json deleted file mode 100644 index e2ab92e9d..000000000 --- a/blueprints/appwrite-2/meta.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "appwrite-2", - "name": "Appwrite 2", - "version": "2.3.0", - "description": "Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. This template deploys Appwrite 2.3 with PostgreSQL, MongoDB, Redis, ClickHouse, Open Runtimes, MQTT, and the Appwrite Console for self-hosted application development.", - "links": { - "github": "https://github.com/appwrite/appwrite", - "website": "https://appwrite.io/", - "docs": "https://appwrite.io/docs" - }, - "logo": "appwrite.svg", - "tags": [ - "database", - "firebase", - "postgresql", - "mongodb", - "hosting", - "self-hosted" - ] -} diff --git a/blueprints/appwrite-2/template.toml b/blueprints/appwrite-2/template.toml deleted file mode 100644 index 768cc6f0f..000000000 --- a/blueprints/appwrite-2/template.toml +++ /dev/null @@ -1,377 +0,0 @@ -# Appwrite 2 exposes the API, the Console, Sites and Functions on separate hosts. -# Defaults below keep a single main domain for API + Console while Sites and -# Functions live on their own subdomains. To separate the Console from the API -# (e.g. to protect it behind Cloudflare Access without exposing the public API), -# change `console_domain` to a dedicated host such as "console.example.com". -# API -> ${main_domain}/v1 (and /v1/realtime, /.well-known) -# Console -> ${console_domain}/ -# Sites -> ${sites_domain} -# Functions -> ${functions_domain} -[variables] -main_domain = "${domain}" -console_domain = "console.${main_domain}" -functions_domain = "functions.${main_domain}" -sites_domain = "sites.${main_domain}" -openssl_key = "${password:32}" -notifications_tracking_secret = "${password:32}" -db_root_pw = "${password:32}" -db_user_pw = "${password:32}" -executor_secret = "${password:32}" -jobs_secret = "${password:32}" -usage_pass = "${password:32}" -geo_secret = "${password:32}" - -[config] -env = [ - "_APP_ENV=production", - "_APP_EDITION=self-hosted", - "_APP_LOCALE=en", - "_APP_OPENSSL_KEY_V1=${openssl_key}", - "_APP_NOTIFICATIONS_TRACKING_SECRET=${notifications_tracking_secret}", - "_APP_RULES_FORMAT=md5", - "_APP_SYSTEM_RESPONSE_FORMAT=", - "_APP_TRUSTED_HEADERS=x-forwarded-for", - "_APP_WORKER_PER_CORE=6", - "_APP_WORKERS_NUM=1", - "_APP_WORKER_MAX_COROUTINES=1", - "_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite", - "_APP_REALTIME_TAIL_RATE=50", - "_APP_COMPRESSION_ENABLED=enabled", - "_APP_COMPRESSION_MIN_SIZE_BYTES=1024", - "_APP_DOMAIN=${main_domain}", - "_APP_DOMAIN_FUNCTIONS=${functions_domain}", - "_APP_DOMAIN_SITES=${sites_domain}", - "_APP_DOMAIN_TARGET_A=127.0.0.1", - "_APP_DOMAIN_TARGET_AAAA=::1", - "_APP_DOMAIN_TARGET_CAA=", - "_APP_DOMAIN_TARGET_CNAME=localhost", - "_APP_CUSTOM_DOMAIN_DENY_LIST=example.com,test.com,app.example.com", - "_APP_DNS=8.8.8.8", - "_APP_OPTIONS_FORCE_HTTPS=disabled", - "_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled", - "_APP_OPTIONS_ROUTER_PROTECTION=disabled", - "_APP_ROUTER_AUTO_CERTIFICATES=disabled", - "_APP_OPTIONS_ABUSE=enabled", - "_APP_OPTIONS_ABUSE_INCREASED_LIMIT_PROJECTS=increased-limit-project", - "_APP_CONSOLE_DOMAIN=${console_domain}", - "_APP_CONSOLE_URL=https://${console_domain}", - "_APP_CONSOLE_HOSTNAMES=${console_domain}", - "_APP_CONSOLE_SCHEMA=appwriteio", - "_APP_CONSOLE_SESSION_ALERTS=disabled", - "_APP_CONSOLE_TRUSTED_PROJECTS=", - "_APP_CONSOLE_COUNTRIES_DENYLIST=", - "_APP_CONSOLE_WHITELIST_EMAILS=", - "_APP_CONSOLE_WHITELIST_IPS=", - "_APP_CONSOLE_WHITELIST_ROOT=enabled", - "_APP_CONSOLE_GITHUB_APP_ID=", - "_APP_CONSOLE_GITHUB_SECRET=", - "_APP_CONSOLE_GITLAB_APP_ID=", - "_APP_CONSOLE_GITLAB_SECRET=", - "_APP_CONSOLE_GITLAB_ENDPOINT=", - "_APP_CONSOLE_BITBUCKET_APP_ID=", - "_APP_CONSOLE_BITBUCKET_SECRET=", - "_APP_CONSOLE_GOOGLE_APP_ID=", - "_APP_CONSOLE_GOOGLE_SECRET=", - "_APP_DB_ADAPTER=postgresql", - "_APP_DB_HOST=postgresql", - "_APP_DB_PORT=5432", - "_APP_DB_SCHEMA=appwrite", - "_APP_DB_USER=user", - "_APP_DB_PASS=${db_user_pw}", - "_APP_DB_ROOT_PASS=${db_root_pw}", - "_APP_DATABASE_SHARED_NAMESPACE=", - "_APP_DATABASE_SHARED_TABLES=", - "_APP_DB_ADAPTER_DOCUMENTSDB=mongodb", - "_APP_DB_HOST_DOCUMENTSDB=mongodb", - "_APP_DB_PORT_DOCUMENTSDB=27017", - "_APP_DB_ADAPTER_VECTORSDB=postgresql", - "_APP_DB_HOST_VECTORSDB=postgresql", - "_APP_DB_PORT_VECTORSDB=5432", - "_APP_REDIS_HOST=redis", - "_APP_REDIS_PORT=6379", - "_APP_REDIS_USER=", - "_APP_REDIS_PASS=", - "_APP_EMBEDDING=disabled", - "_APP_EMBEDDING_ENDPOINT='http://appwrite-embedding:3000/embed'", - "_APP_EMBEDDING_MODELS=nomic-embed-text", - "_APP_EMBEDDING_TIMEOUT=30000", - "_APP_PWNED_PASSWORDS_DSN=none://localhost", - "_APP_MQTT_REPLAY_DEPTH=5", - "_APP_MQTT_MAX_PACKET_SIZE=64000", - "_APP_STORAGE_DEVICE=Local", - "_APP_STORAGE_LIMIT=30000000", - "_APP_STORAGE_PREVIEW_LIMIT=20000000", - "_APP_AUTOGRAVITY_HOST=http://appwrite-autogravity:8080", - "_APP_STORAGE_ANTIVIRUS=disabled", - "_APP_STORAGE_ANTIVIRUS_HOST=clamav", - "_APP_STORAGE_ANTIVIRUS_PORT=3310", - "_APP_STORAGE_S3_ACCESS_KEY=", - "_APP_STORAGE_S3_SECRET=", - "_APP_STORAGE_S3_REGION=us-east-1", - "_APP_STORAGE_S3_BUCKET=", - "_APP_STORAGE_S3_ENDPOINT=", - "_APP_STORAGE_DO_SPACES_ACCESS_KEY=", - "_APP_STORAGE_DO_SPACES_SECRET=", - "_APP_STORAGE_DO_SPACES_REGION=us-east-1", - "_APP_STORAGE_DO_SPACES_BUCKET=", - "_APP_STORAGE_BACKBLAZE_ACCESS_KEY=", - "_APP_STORAGE_BACKBLAZE_SECRET=", - "_APP_STORAGE_BACKBLAZE_REGION=us-west-004", - "_APP_STORAGE_BACKBLAZE_BUCKET=", - "_APP_STORAGE_LINODE_ACCESS_KEY=", - "_APP_STORAGE_LINODE_SECRET=", - "_APP_STORAGE_LINODE_REGION=eu-central-1", - "_APP_STORAGE_LINODE_BUCKET=", - "_APP_STORAGE_WASABI_ACCESS_KEY=", - "_APP_STORAGE_WASABI_SECRET=", - "_APP_STORAGE_WASABI_REGION=eu-central-1", - "_APP_STORAGE_WASABI_BUCKET=", - "_APP_SYSTEM_EMAIL_NAME=Appwrite", - "_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io", - "_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io", - "_APP_EMAIL_SECURITY=security@appwrite.io", - "_APP_EMAIL_CERTIFICATES=certificates@appwrite.io", - "_APP_SMTP_HOST=", - "_APP_SMTP_PORT=", - "_APP_SMTP_SECURE=", - "_APP_SMTP_USERNAME=", - "_APP_SMTP_PASSWORD=", - "_APP_SMS_PROVIDER=", - "_APP_SMS_FROM=", - "_APP_SMS_PROJECTS_DENY_LIST=", - "_APP_MESSAGE_EMAIL_TEST_DSN=", - "_APP_MESSAGE_PUSH_TEST_DSN=", - "_APP_MESSAGE_SMS_TEST_DSN=", - "_APP_COMPUTE_SIZE_LIMIT=30000000", - "_APP_COMPUTE_BUILD_SIZE_LIMIT=2000000000", - "_APP_COMPUTE_BUILD_TIMEOUT=900", - "_APP_COMPUTE_CPUS=0", - "_APP_COMPUTE_MEMORY=0", - "_APP_COMPUTE_INACTIVE_THRESHOLD=600", - "_APP_COMPUTE_MAINTENANCE_INTERVAL=600", - "_APP_COMPUTE_RUNTIMES_NETWORK=dokploy-network", - "_APP_OPEN_RUNTIMES_NFT=enabled", - "_APP_FUNCTIONS_RUNTIMES=node-22,node-24,node-26,python-3.12,python-3.14,php-8.4,ruby-4.0,dart-3.5,go-1.23,go-1.26,rust-1.83,bun-1.3,deno-2.6,java-22,dotnet-10,kotlin-2.3,swift-6.2", - "_APP_FUNCTIONS_TIMEOUT=900", - "_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000", - "_APP_SITES_RUNTIMES=static-1,node-22,node-24,node-26,flutter-3.44", - "_APP_SITES_TIMEOUT=60", - "_APP_EXECUTOR_HOST=http://openruntimes-executor/v1", - "_APP_EXECUTOR_SECRET=${executor_secret}", - "_APP_EXECUTOR_IMAGES=openruntimes/node:v5-22,openruntimes/node:v5-24,openruntimes/node:v5-26,openruntimes/static:v5-1,openruntimes/python:v5-3.12,openruntimes/python:v5-3.14,openruntimes/php:v5-8.4,openruntimes/ruby:v5-4.0,openruntimes/dart:v5-3.5,openruntimes/go:v5-1.23,openruntimes/go:v5-1.26,openruntimes/rust:v5-1.83,openruntimes/bun:v5-1.3,openruntimes/deno:v5-2.6,openruntimes/java:v5-22,openruntimes/dotnet:v5-10,openruntimes/kotlin:v5-2.3,openruntimes/swift:v5-6.2,openruntimes/flutter:v5-3.44", - "_APP_EXECUTOR_CONNECTION_STORAGE=local://localhost", - "_APP_BUILDS_VOLUME=appwrite-builds", - "_APP_JOBS_HOST=http://orchestrator:8080", - "_APP_JOBS_SECRET=${jobs_secret}", - "_APP_JOBS_ENDPOINT=http://appwrite", - "_APP_BROWSER_HOST=http://appwrite-browser:3000/v1", - "_APP_DOCKER_HUB_USERNAME=", - "_APP_DOCKER_HUB_PASSWORD=", - "_APP_MAINTENANCE_INTERVAL=86400", - "_APP_MAINTENANCE_START_TIME=12:00", - "_APP_MAINTENANCE_RETENTION_ABUSE=86400", - "_APP_MAINTENANCE_RETENTION_AUDIT=1209600", - "_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800", - "_APP_MAINTENANCE_RETENTION_CACHE=2592000", - "_APP_MAINTENANCE_RETENTION_EXECUTION=1209600", - "_APP_MAINTENANCE_RETENTION_SCHEDULES=86400", - "_APP_USAGE_STATS=enabled", - "_APP_USAGE_PASS=${usage_pass}", - "_APP_CONNECTIONS_DB_USAGE=http://appwrite:${usage_pass}@clickhouse:8123/appwrite", - "_APP_POOL_SIZE_USAGE=2", - "_APP_CONNECTIONS_DB_EXECUTIONS=http://appwrite:${usage_pass}@clickhouse:8123/appwrite", - "_APP_POOL_SIZE_EXECUTIONS=2", - "_APP_MAINTENANCE_RETENTION_USAGE_TTL=180", - "_APP_STATS_USAGE_QUEUE_NAME=v1-stats-usage", - "_APP_STATS_RESOURCES_QUEUE_NAME=v1-stats-resources", - "_APP_STATS_RESOURCES_INTERVAL=3600", - "_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300", - "_APP_INTERVAL_DOMAIN_VERIFICATION=120", - "_APP_GRAPHQL_INTROSPECTION=enabled", - "_APP_GRAPHQL_MAX_BATCH_SIZE=10", - "_APP_GRAPHQL_MAX_COMPLEXITY=250", - "_APP_GRAPHQL_MAX_DEPTH=4", - "_APP_ASSISTANT_OPENAI_API_KEY=", - "_APP_LOGGING_CONFIG=", - "_APP_LOGGING_FORMAT=pretty", - "_APP_MIGRATION_HOST=appwrite", - "_APP_MIGRATIONS_FIREBASE_CLIENT_ID=", - "_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=", - "_APP_PROJECT_REGIONS=default", - "_APP_GEO_SECRET=${geo_secret}", - "_APP_GEO_ENDPOINT=http://appwrite-geo/v1", - "_APP_VCS_GITHUB_APP_ID=", - "_APP_VCS_GITHUB_APP_NAME=", - "_APP_VCS_GITHUB_CLIENT_ID=", - "_APP_VCS_GITHUB_CLIENT_SECRET=", - "_APP_VCS_GITHUB_PRIVATE_KEY=disabled", - "_APP_VCS_GITHUB_WEBHOOK_SECRET=", - "_APP_VCS_GITEA_ENDPOINT=", - "_APP_VCS_GITEA_BROWSER_ENDPOINT=", - "_APP_VCS_GITEA_CLIENT_ID=", - "_APP_VCS_GITEA_CLIENT_SECRET=", - "_APP_VCS_GITEA_WEBHOOK_SECRET=", - "_APP_VCS_GITLAB_ENDPOINT=", - "_APP_VCS_GITLAB_CLIENT_ID=", - "_APP_VCS_GITLAB_CLIENT_SECRET=", - "_APP_VCS_GITLAB_WEBHOOK_SECRET=", - "_APP_VCS_BITBUCKET_CLIENT_ID=", - "_APP_VCS_BITBUCKET_CLIENT_SECRET=", - "_APP_VCS_BITBUCKET_WEBHOOK_SECRET=", - "_APP_VCS_ORIGIN_CLIENT_ID=", - "_APP_VCS_ORIGIN_PRIVATE_KEY=", - "_APP_VCS_WEBHOOK_URL=https://${main_domain}/v1", - "_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10", -] - -[[config.mounts]] -filePath = "mongo-init.js" -content = """// mongo-init.js - -const adminDb = db.getSiblingDB('admin'); - -const username = process.env.MONGO_INITDB_USERNAME; -const password = process.env.MONGO_INITDB_PASSWORD; -const database = process.env.MONGO_INITDB_DATABASE; - -// Idempotent: the official entrypoint races the real server on port 27017 when -// initialising a fresh volume (appwrite/appwrite#13593), so this script may run -// more than once. Skip if the user already exists. -if (adminDb.getUser(username) === null) { - adminDb.createUser({ - user: username, - pwd: password, - roles: [ - { role: 'readWrite', db: database } - ] - }); -} -""" - -[[config.mounts]] -filePath = "mongo-entrypoint.sh" -content = """#!/bin/bash -set -e - -KEYFILE_PATH="/data/keyfile/mongo-keyfile" - -if [ ! -f "$KEYFILE_PATH" ]; then - echo "Generating random MongoDB keyfile..." - mkdir -p /data/keyfile - openssl rand -base64 756 > "$KEYFILE_PATH" -fi - -chmod 400 "$KEYFILE_PATH" -chown mongodb:mongodb "$KEYFILE_PATH" 2>/dev/null || chown 999:999 "$KEYFILE_PATH" - -# The standard entrypoint initialises an empty data directory with a second -# mongod on 27017, stopped with `mongod --shutdown`. That returns once the -# server clears /mongod.lock, several steps before it exits and -# releases the port, and the real server binds a millisecond after it starts -- -# so a slow shutdown tail leaves it exiting 48, "Address already in use", -# taking the container with it (appwrite/appwrite#13593). Initialise here -# instead, on a port the real server never binds, and hand over a directory -# the standard entrypoint has nothing left to do. -INIT_MARKER=/data/db/.appwrite-initialised -if [ ! -e "$INIT_MARKER" ]; then - INIT_PORT=27018 - echo "Initialising MongoDB data directory..." - find -L /data/db \\! -user mongodb -exec chown mongodb '{}' + - export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}" - gosu mongodb mongod --dbpath /data/db --bind_ip 127.0.0.1 --port "$INIT_PORT" & - INIT_SERVER=$! - READY= - for _ in $(seq 1 30); do - if mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet --eval 'quit(0)' > /dev/null 2>&1; then - READY=1 - break - fi - if ! kill -0 "$INIT_SERVER" 2>/dev/null; then - break - fi - sleep 1 - done - if [ -z "$READY" ]; then - echo "MongoDB did not accept connections for initialisation" >&2 - exit 1 - fi - mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet admin --eval ' - const username = process.env.MONGO_INITDB_ROOT_USERNAME; - if (db.getUser(username) === null) { - db.createUser({ - user: username, - pwd: process.env.MONGO_INITDB_ROOT_PASSWORD, - roles: [{ role: "root", db: "admin" }] - }); - } - ' - mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet "$MONGO_INITDB_DATABASE" /mongo-init.js - touch "$INIT_MARKER" - kill -TERM "$INIT_SERVER" - wait "$INIT_SERVER" - echo "MongoDB data directory initialised." -fi - -exec docker-entrypoint.sh mongod --replSet rs0 --bind_ip_all --auth --keyFile "$KEYFILE_PATH" -""" - -# --- Main domain: API + Console (kept for backwards compatibility) --- -[[config.domains]] -serviceName = "appwrite-console" -port = 3000 -host = "${main_domain}" -path = "/" - -[[config.domains]] -serviceName = "appwrite" -port = 80 -host = "${main_domain}" -path = "/v1" - -[[config.domains]] -serviceName = "appwrite" -port = 80 -host = "${main_domain}" -path = "/.well-known" - -[[config.domains]] -serviceName = "appwrite-realtime" -port = 80 -host = "${main_domain}" -path = "/v1/realtime" - -# --- Dedicated Console domain --- -# The Console SPA is served at the root of ${console_domain}. Requests to -# /v1 and /v1/realtime on that same host are proxied to the API and Realtime -# services so the Console reaches the API on its own origin. No wildcard is -# used here; Sites and Functions keep their own wildcard hosts below. -[[config.domains]] -serviceName = "appwrite-console" -port = 3000 -host = "${console_domain}" -path = "/" - -[[config.domains]] -serviceName = "appwrite" -port = 80 -host = "${console_domain}" -path = "/v1" - -[[config.domains]] -serviceName = "appwrite-realtime" -port = 80 -host = "${console_domain}" -path = "/v1/realtime" - -# --- Sites and Functions (wildcards handled by Traefik HostRegexp labels) --- -[[config.domains]] -serviceName = "appwrite" -port = 80 -host = "${sites_domain}" -path = "/" - -[[config.domains]] -serviceName = "appwrite" -port = 80 -host = "${functions_domain}" -path = "/" diff --git a/blueprints/appwrite/docker-compose.yml b/blueprints/appwrite/docker-compose.yml index 4c8e36cbb..eb4b9d739 100644 --- a/blueprints/appwrite/docker-compose.yml +++ b/blueprints/appwrite/docker-compose.yml @@ -6,7 +6,7 @@ x-logging: &x-logging max-size: "10m" services: appwrite: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 <<: *x-logging restart: unless-stopped labels: @@ -110,6 +110,8 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_DB_ROOT_PASS + - _APP_DB_USER_PASS - _APP_DATABASE_SHARED_TABLES - _APP_DATABASE_SHARED_NAMESPACE - _APP_LIMIT_DATABASE_BATCH @@ -231,12 +233,11 @@ services: - _APP_MIGRATION_HOST - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - - _APP_ASSISTANT_OPENAI_API_KEY - _APP_CONSOLE_COUNTRIES_DENYLIST - _APP_PWNED_PASSWORDS_DSN appwrite-console: - image: appwrite/new:1.1.159-self-hosted + image: appwrite/new:1.2.74-self-hosted <<: *x-logging restart: unless-stopped environment: @@ -245,7 +246,7 @@ services: - VITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1 appwrite-realtime: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: realtime <<: *x-logging restart: unless-stopped @@ -297,7 +298,7 @@ services: - _APP_LOGGING_FORMAT appwrite-mqtt: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: mqtt <<: *x-logging restart: unless-stopped @@ -347,7 +348,7 @@ services: - _APP_MQTT_MAX_PACKET_SIZE appwrite-worker: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: worker <<: *x-logging restart: unless-stopped @@ -508,7 +509,7 @@ services: - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE appwrite-task-scheduler: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: schedule <<: *x-logging restart: unless-stopped @@ -556,7 +557,7 @@ services: - _APP_LOGGING_FORMAT appwrite-task-maintenance: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: maintenance <<: *x-logging restart: unless-stopped @@ -600,7 +601,7 @@ services: - _APP_LOGGING_FORMAT appwrite-task-interval: - image: appwrite/appwrite:2.3.0 + image: appwrite/appwrite:2.5.0 entrypoint: interval <<: *x-logging restart: unless-stopped @@ -637,9 +638,16 @@ services: - _APP_LOGGING_FORMAT appwrite-browser: - image: appwrite/browser:0.3.4 + image: appwrite/browser:0.3.6 <<: *x-logging restart: unless-stopped + environment: + - ALLOWED_HOSTS=appwrite + # Public resolvers only, so the browser cannot resolve internal service + # names (e.g. redis, appwrite-mariadb) and render them via a user URL. + dns: + - 1.1.1.1 + - 8.8.8.8 appwrite-autogravity: image: ghcr.io/appwrite/autogravity:0.0.11 @@ -649,7 +657,7 @@ services: - MODEL_BACKEND=focalnet appwrite-geo: - image: appwrite/geo:0.3.1 + image: appwrite/geo:0.3.2 <<: *x-logging restart: unless-stopped environment: @@ -670,8 +678,11 @@ services: - CLICKHOUSE_USER=appwrite - CLICKHOUSE_PASSWORD=${_APP_USAGE_PASS:-appwrite} - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 + - CLICKHOUSE_ALWAYS_RUN_INITDB_SCRIPTS=1 volumes: - appwrite-clickhouse:/var/lib/clickhouse:rw + - ../files/clickhouse-config.xml:/etc/clickhouse-server/config.d/appwrite.xml:ro + - ../files/clickhouse-init.sh:/docker-entrypoint-initdb.d/clickhouse-init.sh:ro ulimits: nofile: soft: 262144 @@ -686,7 +697,7 @@ services: start_period: 30s openruntimes-executor: - image: openruntimes/executor:0.29.0 + image: openruntimes/executor:1.1.2 # The executor looks up its own container by hostname to join the runtimes network. hostname: openruntimes-executor <<: *x-logging @@ -787,13 +798,69 @@ services: timeout: 5s retries: 12 - # Appwrite Assistant (optional): set _APP_ASSISTANT_OPENAI_API_KEY and uncomment. - # appwrite-assistant: - # image: appwrite/assistant:0.8.4 - # <<: *x-logging - # restart: unless-stopped - # environment: - # - _APP_ASSISTANT_OPENAI_API_KEY + mariadb: + image: mariadb:10.11 + <<: *x-logging + restart: unless-stopped + volumes: + - appwrite-mariadb:/var/lib/mysql:rw + environment: + - MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS} + - MYSQL_DATABASE=${_APP_DB_SCHEMA} + - MYSQL_USER=${_APP_DB_USER} + - MYSQL_PASSWORD=${_APP_DB_PASS} + - MARIADB_AUTO_UPGRADE=1 + command: + - mysqld + - --innodb-flush-method=fsync + healthcheck: + test: + - CMD-SHELL + - mariadb --protocol=tcp -h127.0.0.1 -uroot -p"$$MYSQL_ROOT_PASSWORD" --silent -e "SELECT 1" + interval: 5s + timeout: 5s + retries: 12 + start_period: 10s + mongodb: + image: mongo:8.2.5 + <<: *x-logging + restart: unless-stopped + volumes: + - appwrite-mongodb:/data/db + - appwrite-mongodb-keyfile:/data/keyfile + - ../files/mongo-init.js:/mongo-init.js:ro + - ../files/mongo-entrypoint.sh:/mongo-entrypoint.sh:ro + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=${_APP_DB_ROOT_PASS} + - MONGO_INITDB_DATABASE=${_APP_DB_SCHEMA} + - MONGO_INITDB_USERNAME=${_APP_DB_USER} + - MONGO_INITDB_PASSWORD=${_APP_DB_PASS} + entrypoint: + - /bin/bash + - /mongo-entrypoint.sh + healthcheck: + test: | + bash -c " + mongosh -u root -p ${_APP_DB_ROOT_PASS} --authenticationDatabase admin --quiet --eval \" + try { + const hello = db.adminCommand({hello: 1}); + if (hello.isWritablePrimary && hello.setName === 'rs0') { + quit(0); + } + } catch (e) {} + + try { + rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'mongodb:27017'}]}); + } catch (e) {} + + quit(1); + \" 2>/dev/null + " + interval: 10s + timeout: 10s + retries: 10 + start_period: 60s # Embeddings API (optional, resource heavy): set _APP_EMBEDDING=enabled and uncomment. # appwrite-embedding: @@ -825,4 +892,7 @@ volumes: appwrite-builds: name: ${COMPOSE_PROJECT_NAME:-appwrite}_appwrite-builds appwrite-config: + appwrite-mariadb: + appwrite-mongodb: + appwrite-mongodb-keyfile: # appwrite-models: diff --git a/blueprints/appwrite/meta.json b/blueprints/appwrite/meta.json index bddfa7183..f04c7e159 100644 --- a/blueprints/appwrite/meta.json +++ b/blueprints/appwrite/meta.json @@ -1,7 +1,7 @@ { "id": "appwrite", "name": "Appwrite", - "version": "2.3.0", + "version": "2.5.0", "description": "Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. It includes both a backend server and a fully integrated hosting solution for deploying static and server-side rendered frontends. Appwrite abstracts the complexity and repetitiveness required to build modern apps from scratch and allows you to build secure, full-stack applications faster.\nUsing Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and more services.", "links": { "github": "https://github.com/appwrite/appwrite", @@ -13,6 +13,7 @@ "database", "firebase", "postgresql", + "mongodb", "hosting", "self-hosted" ] diff --git a/blueprints/appwrite/template.toml b/blueprints/appwrite/template.toml index 1a59e70cf..fcb930ad8 100644 --- a/blueprints/appwrite/template.toml +++ b/blueprints/appwrite/template.toml @@ -6,10 +6,13 @@ mqtt_domain = "mqtt.${main_domain}" openssl_key = "${password:32}" notifications_tracking_secret = "${password:32}" db_pw = "${password:32}" +db_root_pw = "${password:32}" +db_user_pw = "${password:32}" usage_pw = "${password:32}" executor_secret = "${password:32}" jobs_secret = "${password:32}" geo_secret = "${password:32}" +console_domain = "console.${main_domain}" [config] # Build jobs and runtimes join _default, which isolated deployments never create @@ -30,8 +33,9 @@ env = [ "_APP_OPENSSL_KEY_V1=${openssl_key}", "_APP_NOTIFICATIONS_TRACKING_SECRET=${notifications_tracking_secret}", "_APP_DOMAIN=${main_domain}", - "_APP_CONSOLE_DOMAIN=${main_domain}", - "_APP_CONSOLE_URL=", + "_APP_CONSOLE_DOMAIN=${console_domain}", + "_APP_CONSOLE_URL=https://${console_domain}", + "_APP_CONSOLE_HOSTNAMES=${console_domain}", "_APP_CONSOLE_TRUSTED_PROJECTS=", "_APP_CUSTOM_DOMAIN_DENY_LIST=example.com,test.com,app.example.com", "_APP_DOMAIN_FUNCTIONS=${functions_domain}", @@ -83,7 +87,7 @@ env = [ "_APP_DATABASE_SHARED_TABLES=", "_APP_DATABASE_SHARED_NAMESPACE=", "_APP_LIMIT_DATABASE_BATCH=100", - "_APP_DOCUMENTSDB=disabled", + "_APP_DOCUMENTSDB=enabled", "_APP_DB_ADAPTER_DOCUMENTSDB=mongodb", "_APP_DB_HOST_DOCUMENTSDB=mongodb", "_APP_DB_PORT_DOCUMENTSDB=27017", @@ -91,7 +95,7 @@ env = [ "_APP_DB_USER_DOCUMENTSDB=", "_APP_DB_PASS_DOCUMENTSDB=", "_APP_CONNECTIONS_DATABASE_DOCUMENTSDB=", - "_APP_VECTORSDB=disabled", + "_APP_VECTORSDB=enabled", "_APP_DB_ADAPTER_VECTORSDB=postgresql", "_APP_DB_HOST_VECTORSDB=postgresql", "_APP_DB_PORT_VECTORSDB=5432", @@ -160,7 +164,7 @@ env = [ "_APP_FUNCTIONS_TIMEOUT=900", "_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000", "_APP_FUNCTIONS_SCHEDULE_SPREAD=0", - "_APP_FUNCTIONS_RUNTIMES=node-22,python-3.12,php-8.3,ruby-3.3,dart-3.5,go-1.23,rust-1.83,bun-1.1,deno-1.46,java-21.0,dotnet-8.0", + "_APP_FUNCTIONS_RUNTIMES=node-22,node-24,node-26,python-3.12,php-8.3,ruby-3.3,dart-3.5,go-1.23,rust-1.83,bun-1.1,deno-1.46,java-21.0,dotnet-8.0", "_APP_SITES_TIMEOUT=900", "_APP_SITES_RUNTIMES=static-1,node-22", "_APP_EXECUTOR_SECRET=${executor_secret}", @@ -213,9 +217,181 @@ env = [ "_APP_MIGRATION_HOST=appwrite", "_APP_MIGRATIONS_FIREBASE_CLIENT_ID=", "_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=", - "_APP_ASSISTANT_OPENAI_API_KEY=", + "_APP_DB_ROOT_PASS=${db_root_pw}", + "_APP_DB_USER_PASS=${db_user_pw}", ] +[[config.mounts]] +filePath = "mongo-init.js" +content = """// mongo-init.js + +const adminDb = db.getSiblingDB('admin'); + +const username = process.env.MONGO_INITDB_USERNAME; +const password = process.env.MONGO_INITDB_PASSWORD; +const database = process.env.MONGO_INITDB_DATABASE; + +// Idempotent: the official entrypoint races the real server on port 27017 when +// initialising a fresh volume (appwrite/appwrite#13593), so this script may run +// more than once. Skip if the user already exists. +if (adminDb.getUser(username) === null) { + adminDb.createUser({ + user: username, + pwd: password, + roles: [ + { role: 'readWrite', db: database } + ] + }); +} +""" + +[[config.mounts]] +filePath = "mongo-entrypoint.sh" +content = """#!/bin/bash +set -e + +KEYFILE_PATH="/data/keyfile/mongo-keyfile" + +if [ ! -f "$KEYFILE_PATH" ]; then + echo "Generating random MongoDB keyfile..." + mkdir -p /data/keyfile + openssl rand -base64 756 > "$KEYFILE_PATH" +fi + +chmod 400 "$KEYFILE_PATH" +chown mongodb:mongodb "$KEYFILE_PATH" 2>/dev/null || chown 999:999 "$KEYFILE_PATH" + +# The standard entrypoint initialises an empty data directory with a second +# mongod on 27017, stopped with `mongod --shutdown`. That returns once the +# server clears /mongod.lock, several steps before it exits and +# releases the port, and the real server binds a millisecond after it starts -- +# so a slow shutdown tail leaves it exiting 48, "Address already in use", +# taking the container with it (appwrite/appwrite#13593). Initialise here +# instead, on a port the real server never binds, and hand over a directory +# the standard entrypoint has nothing left to do. +INIT_MARKER=/data/db/.appwrite-initialised +if [ ! -e "$INIT_MARKER" ]; then + INIT_PORT=27018 + echo "Initialising MongoDB data directory..." + find -L /data/db \\! -user mongodb -exec chown mongodb '{}' + + export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}" + gosu mongodb mongod --dbpath /data/db --bind_ip 127.0.0.1 --port "$INIT_PORT" & + INIT_SERVER=$! + READY= + for _ in $(seq 1 30); do + if mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet --eval 'quit(0)' > /dev/null 2>&1; then + READY=1 + break + fi + if ! kill -0 "$INIT_SERVER" 2>/dev/null; then + break + fi + sleep 1 + done + if [ -z "$READY" ]; then + echo "MongoDB did not accept connections for initialisation" >&2 + exit 1 + fi + mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet admin --eval ' + const username = process.env.MONGO_INITDB_ROOT_USERNAME; + if (db.getUser(username) === null) { + db.createUser({ + user: username, + pwd: process.env.MONGO_INITDB_ROOT_PASSWORD, + roles: [{ role: "root", db: "admin" }] + }); + } + ' + mongosh --host 127.0.0.1 --port "$INIT_PORT" --quiet "$MONGO_INITDB_DATABASE" /mongo-init.js + touch "$INIT_MARKER" + kill -TERM "$INIT_SERVER" + wait "$INIT_SERVER" + echo "MongoDB data directory initialised." +fi + +exec docker-entrypoint.sh mongod --replSet rs0 --bind_ip_all --auth --keyFile "$KEYFILE_PATH" +""" + +[[config.mounts]] +filePath = "clickhouse-config.xml" +content = """ + + + + + + + + + + + + + + event_date + INTERVAL 3 DAY DELETE + event_date + INTERVAL 7 DAY DELETE + event_date + INTERVAL 7 DAY DELETE + event_date + INTERVAL 7 DAY DELETE + event_date + INTERVAL 7 DAY DELETE + event_date + INTERVAL 7 DAY DELETE + + + event_date + INTERVAL 14 DAY DELETE + +""" + +[[config.mounts]] +filePath = "clickhouse-init.sh" +content = """#!/bin/bash +# The ClickHouse entrypoint sources this file, so a subshell keeps these options out of it. +( + set -euo pipefail + + client() { + clickhouse-client --host 127.0.0.1 --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" "$@" + } + + # Flushing replaces log tables whose definition changed, keeping each old table as _. + client --query 'SYSTEM FLUSH LOGS' + + # Tables left behind never expire on their own: take the TTL of the log that + # replaced this one, or a week when the log is no longer written at all. + # The named logs must match the ones clickhouse-config.xml removes: a log that + # is still written gets replaced on the next restart if its TTL is altered here. + client --format TSVRaw <<'SQL' | client --multiquery +SELECT concat('ALTER TABLE system.', stale.name, ' MODIFY TTL ', if(active.name = '', 'event_date + INTERVAL 7 DAY DELETE', splitByString(' SETTINGS ', splitByString(' TTL ', active.engine_full)[2])[1]), ';') +FROM ( + SELECT name, splitByChar('_', name)[-1] AS suffix, arrayStringConcat(arrayPopBack(splitByChar('_', name)), '_') AS log + FROM system.tables + WHERE database = 'system' + AND position(engine_full, ' TTL ') = 0 + AND endsWith(engine, 'MergeTree') + AND name IN (SELECT table FROM system.columns WHERE database = 'system' AND name = 'event_date' AND type = 'Date') +) AS stale +LEFT JOIN system.tables AS active + ON active.database = 'system' + AND active.name = stale.log + AND position(active.engine_full, ' TTL ') > 0 +WHERE (endsWith(stale.log, '_log') AND toUInt64OrNull(stale.suffix) IS NOT NULL) + OR stale.name IN ('asynchronous_metric_log', 'metric_log', 'histogram_metric_log', 'query_metric_log', 'processors_profile_log', 'trace_log', 'text_log') +SQL +) +""" + [[config.domains]] serviceName = "appwrite-console" port = 3000 @@ -240,6 +416,28 @@ port = 80 host = "${main_domain}" path = "/v1/realtime" +# --- Dedicated Console domain --- +# The Console SPA is served at the root of ${console_domain}, with /v1 and +# /v1/realtime proxied to the API and Realtime so the Console reaches the API +# on its own origin. +[[config.domains]] +serviceName = "appwrite-console" +port = 3000 +host = "${console_domain}" +path = "/" + +[[config.domains]] +serviceName = "appwrite" +port = 80 +host = "${console_domain}" +path = "/v1" + +[[config.domains]] +serviceName = "appwrite-realtime" +port = 80 +host = "${console_domain}" +path = "/v1/realtime" + [[config.domains]] serviceName = "appwrite" port = 80 diff --git a/blueprints/authelia/meta.json b/blueprints/authelia/meta.json index 9ef51bd96..03d747b23 100644 --- a/blueprints/authelia/meta.json +++ b/blueprints/authelia/meta.json @@ -2,7 +2,7 @@ "id": "authelia", "name": "Authelia", "version": "latest", - "description": "The Single Sign-On Multi-Factor portal for web apps. An open-source authentication and authorization server providing 2FA and SSO via web portal.", + "description": "Authelia is an open-source authentication portal for single sign-on (SSO) and two-factor authentication. Self-host it on your own server with Dokploy, which deploys Authelia as a Docker Compose service in a few clicks.", "logo": "authelia.png", "links": { "github": "https://github.com/authelia/authelia", @@ -17,5 +17,8 @@ "security", "reverse-proxy", "ldap" - ] + ], + "seoTitle": "Authelia Docker Compose Setup", + "seoDescription": "Single sign-on and two-factor auth portal. Deploy Authelia with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/authentik/meta.json b/blueprints/authentik/meta.json index a683c1820..a20da39ac 100644 --- a/blueprints/authentik/meta.json +++ b/blueprints/authentik/meta.json @@ -2,7 +2,7 @@ "id": "authentik", "name": "Authentik", "version": "2026.8.2", - "description": "Authentik is an open-source Identity Provider for authentication and authorization. It provides a comprehensive solution for managing user authentication, authorization, and identity federation with support for SAML, OAuth2, OIDC, and more.", + "description": "Authentik is an identity provider for single sign-on (SSO) authorization and authentication, with support for SAML, OAuth2, OIDC, and more. Self-host it on your own server with Dokploy, which deploys Authentik as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/goauthentik/authentik", "website": "https://goauthentik.io/", @@ -17,5 +17,8 @@ "saml", "oauth2", "self-hosted" - ] + ], + "seoTitle": "Authentik Docker Compose Setup", + "seoDescription": "Identity provider for SSO and authentication. Deploy Authentik with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/bookstack/meta.json b/blueprints/bookstack/meta.json index b13a364a8..b64f2c49c 100644 --- a/blueprints/bookstack/meta.json +++ b/blueprints/bookstack/meta.json @@ -2,7 +2,7 @@ "id": "bookstack", "name": "BookStack", "version": "24.12.1", - "description": "BookStack is a self-hosted platform for creating beautiful, feature-rich documentation sites.", + "description": "BookStack is a wiki platform for organizing documentation. Self-host it on your own server with Dokploy, which deploys BookStack as a Docker Compose service in a few clicks.", "logo": "logo.svg", "links": { "github": "https://github.com/BookStackApp/BookStack", @@ -12,5 +12,8 @@ "tags": [ "documentation", "self-hosted" - ] + ], + "seoTitle": "BookStack Docker Compose Setup", + "seoDescription": "Platform for building documentation wikis. Deploy BookStack with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/cachkey/cachkey.svg b/blueprints/cachkey/cachkey.svg new file mode 100644 index 000000000..1c11fe239 --- /dev/null +++ b/blueprints/cachkey/cachkey.svg @@ -0,0 +1,8 @@ + +Cachkey + + + + + + \ No newline at end of file diff --git a/blueprints/cachkey/docker-compose.yml b/blueprints/cachkey/docker-compose.yml new file mode 100644 index 000000000..c2ff7ddce --- /dev/null +++ b/blueprints/cachkey/docker-compose.yml @@ -0,0 +1,56 @@ +services: + # Web app (public): named after the template, as Dokploy requires. + cachkey: + image: ghcr.io/joyboy-dy/cachkey-web:0.1.0 + restart: unless-stopped + expose: + - 3000 + environment: + APP_URL: ${APP_URL} + # Unique name: Dokploy also attaches this service to its shared network, where a + # plain `api` could resolve to another application's container. + API_INTERNAL_URL: http://cachkey-api:3001 + depends_on: + cachkey-api: + condition: service_healthy + + # Applies pending database migrations at startup, then serves /api. + cachkey-api: + image: ghcr.io/joyboy-dy/cachkey-api:0.1.0 + restart: unless-stopped + expose: + - 3001 + environment: + APP_URL: ${APP_URL} + DATABASE_URL: postgresql://cachkey:${POSTGRES_PASSWORD}@postgres:5432/cachkey + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + EMAIL_PROVIDER: ${EMAIL_PROVIDER} + EMAIL_FROM: ${EMAIL_FROM} + SMTP_HOST: ${SMTP_HOST} + SMTP_PORT: ${SMTP_PORT} + SMTP_SECURE: ${SMTP_SECURE} + SMTP_USER: ${SMTP_USER} + SMTP_PASSWORD: ${SMTP_PASSWORD} + RESEND_API_KEY: ${RESEND_API_KEY} + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:17-alpine + restart: unless-stopped + environment: + POSTGRES_USER: cachkey + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: cachkey + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U cachkey -d cachkey"] + interval: 5s + timeout: 5s + retries: 20 + +volumes: + postgres-data: {} diff --git a/blueprints/cachkey/meta.json b/blueprints/cachkey/meta.json new file mode 100644 index 000000000..95c84c347 --- /dev/null +++ b/blueprints/cachkey/meta.json @@ -0,0 +1,14 @@ +{ + "id": "cachkey", + "name": "Cachkey", + "version": "0.1.0", + "description": "Cachkey is an open-source, self-hosted manager for secrets and environment variables: encrypted values, per-environment access for your team, a CLI and a read-only API for servers and CI.", + "logo": "cachkey.svg", + "links": { + "github": "https://github.com/Joyboy-dy/cachkey", + "website": "https://github.com/Joyboy-dy/cachkey", + "docs": "https://github.com/Joyboy-dy/cachkey/tree/main/docs" + }, + "tags": ["security", "secrets", "self-hosted"], + "license": "AGPL-3.0" +} diff --git a/blueprints/cachkey/template.toml b/blueprints/cachkey/template.toml new file mode 100644 index 000000000..59598cfd1 --- /dev/null +++ b/blueprints/cachkey/template.toml @@ -0,0 +1,34 @@ +[variables] +main_domain = "${domain}" +# Hexadecimal: safe inside the database URL (no @, #, / or $). +postgres_password = "${hash:32}" +better_auth_secret = "${base64:48}" +# 32 random bytes, base64-encoded: the master key encrypting every stored value. +# Back it up outside the server: without it, stored values cannot be read. +encryption_key = "${base64:32}" + +[config] +mounts = [] +env = [ + # Exact public address. With your own domain or HTTPS, update it (https://…) and + # redeploy: sign-in is refused when the address does not match. + "APP_URL=http://${main_domain}", + "POSTGRES_PASSWORD=${postgres_password}", + "BETTER_AUTH_SECRET=${better_auth_secret}", + "ENCRYPTION_KEY=${encryption_key}", + # Required: Cachkey confirms every address by email before the first sign-in. + # Fill in SMTP_* (EMAIL_PROVIDER=smtp) or RESEND_API_KEY (EMAIL_PROVIDER=resend). + "EMAIL_PROVIDER=smtp", + "EMAIL_FROM=Cachkey ", + "SMTP_HOST=", + "SMTP_PORT=587", + "SMTP_SECURE=false", + "SMTP_USER=", + "SMTP_PASSWORD=", + "RESEND_API_KEY=", +] + +[[config.domains]] +serviceName = "cachkey" +port = 3000 +host = "${main_domain}" diff --git a/blueprints/calcom/meta.json b/blueprints/calcom/meta.json index 497e416bf..d5bf1be82 100644 --- a/blueprints/calcom/meta.json +++ b/blueprints/calcom/meta.json @@ -2,7 +2,7 @@ "id": "calcom", "name": "Calcom", "version": "v6.2.0", - "description": "Calcom is a open source alternative to Calendly that allows to create scheduling and booking services.", + "description": "Cal.com is a scheduling and booking tool and an alternative to Calendly. Self-host it on your own server with Dokploy, which deploys Cal.com as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/calcom/cal.com", "website": "https://cal.com/", @@ -12,5 +12,7 @@ "tags": [ "scheduling", "booking" - ] + ], + "seoTitle": "Self-Host Cal.com in minutes", + "seoDescription": "Scheduling and booking tool, an alternative to Calendly. Self-host Cal.com on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/calibre-web/meta.json b/blueprints/calibre-web/meta.json index e4ebdff6a..73b11841a 100644 --- a/blueprints/calibre-web/meta.json +++ b/blueprints/calibre-web/meta.json @@ -2,7 +2,7 @@ "id": "calibre-web", "name": "Calibre-Web", "version": "latest", - "description": "Calibre-Web is a web app providing a clean interface for browsing, reading, and managing your eBooks library using an existing Calibre database.", + "description": "Calibre-Web is a web interface for browsing, reading, and downloading ebooks from a Calibre library. Self-host it on your own server with Dokploy, which deploys Calibre-Web as a Docker Compose service in a few clicks.", "logo": "image.png", "links": { "github": "https://github.com/janeczku/calibre-web", @@ -14,5 +14,8 @@ "media", "library", "self-hosted" - ] + ], + "seoTitle": "Calibre-Web Docker Compose Setup", + "seoDescription": "Web interface for browsing your ebook library. Deploy Calibre-Web with Docker Compose on Dokploy – requires an existing Calibre database.", + "newConfig": true } diff --git a/blueprints/chatwoot/meta.json b/blueprints/chatwoot/meta.json index 0bd26806a..32939e590 100644 --- a/blueprints/chatwoot/meta.json +++ b/blueprints/chatwoot/meta.json @@ -2,7 +2,7 @@ "id": "chatwoot", "name": "Chatwoot", "version": "v4.12.1", - "description": "Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.", + "description": "Chatwoot is an open-source customer support and engagement platform with a shared inbox and live chat. Self-host it on your own server with Dokploy, which deploys Chatwoot as a Docker Compose service in a few clicks.", "logo": "chatwoot.svg", "links": { "github": "https://github.com/chatwoot/chatwoot", @@ -13,5 +13,7 @@ "support", "chat", "customer-service" - ] + ], + "seoTitle": "Self-Host Chatwoot in minutes", + "seoDescription": "Shared inbox and live chat for customer support. Self-host Chatwoot on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/clickhouse/meta.json b/blueprints/clickhouse/meta.json index b184e2f11..8a30f593c 100644 --- a/blueprints/clickhouse/meta.json +++ b/blueprints/clickhouse/meta.json @@ -2,7 +2,7 @@ "id": "clickhouse", "name": "ClickHouse", "version": "latest", - "description": "ClickHouse is an open-source column-oriented DBMS (columnar database management system) for online analytical processing (OLAP) that allows users to generate analytical reports using SQL queries in real-time. ClickHouse works 100-1000x faster than traditional database management systems, and processes hundreds of millions to over a billion rows and tens of gigabytes of data per server per second.", + "description": "ClickHouse is a fast, open-source columnar database built for real-time analytics. Self-host it on your own server with Dokploy, which deploys ClickHouse as a Docker Compose service in a few clicks.", "logo": "clickhouse_logo.png", "links": { "github": "https://github.com/ClickHouse/ClickHouse", @@ -15,5 +15,8 @@ "database", "olap", "analytics" - ] + ], + "seoTitle": "ClickHouse Docker Compose Setup", + "seoDescription": "Fast columnar database for analytics. Deploy ClickHouse with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/code-server/meta.json b/blueprints/code-server/meta.json index fd427a9a1..369e4ef87 100644 --- a/blueprints/code-server/meta.json +++ b/blueprints/code-server/meta.json @@ -2,7 +2,7 @@ "id": "code-server", "name": "code-server", "version": "4.121.0", - "description": "code-server runs VS Code in the browser, enabling a self-hosted development environment from any machine.", + "description": "code-server is a version of VS Code that runs in your browser. Self-host it on your own server with Dokploy, which deploys code-server as a Docker Compose service in a few clicks.", "logo": "code-server.png", "links": { "github": "https://github.com/coder/code-server", @@ -14,5 +14,8 @@ "development", "vscode", "self-hosted" - ] + ], + "seoTitle": "code-server Docker Compose Setup", + "seoDescription": "Run VS Code in your browser. Deploy code-server with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/convex/docker-compose.yml b/blueprints/convex/docker-compose.yml index 624271e30..088f3acfe 100644 --- a/blueprints/convex/docker-compose.yml +++ b/blueprints/convex/docker-compose.yml @@ -1,6 +1,6 @@ services: backend: - image: ghcr.io/get-convex/convex-backend:5cdea511cd6527a95dd24152ea0d3c3bb2ab379f + image: ghcr.io/convex/convex-backend:72ce13c379fe6dbdfc305bc8ad699c5663c68c2a expose: - "3210" - "3211" @@ -24,7 +24,7 @@ services: start_period: 5s dashboard: - image: ghcr.io/get-convex/convex-dashboard:5cdea511cd6527a95dd24152ea0d3c3bb2ab379f + image: ghcr.io/convex/convex-dashboard:72ce13c379fe6dbdfc305bc8ad699c5663c68c2a expose: - "6791" environment: diff --git a/blueprints/convex/meta.json b/blueprints/convex/meta.json index 51074f31b..9e81cf057 100644 --- a/blueprints/convex/meta.json +++ b/blueprints/convex/meta.json @@ -1,17 +1,13 @@ { "id": "convex", "name": "Convex", - "version": "latest", + "version": "72ce13c", "description": "Convex is an open-source reactive database designed to make life easy for web app developers.", "logo": "convex.svg", "links": { - "github": "https://github.com/get-convex/convex-backend", + "github": "https://github.com/convex/convex-backend", "website": "https://www.convex.dev/", "docs": "https://www.convex.dev/docs" }, - "tags": [ - "backend", - "database", - "api" - ] + "tags": ["backend", "database", "api"] } diff --git a/blueprints/cypht/cypht.svg b/blueprints/cypht/cypht.svg new file mode 100644 index 000000000..8e0fe23fd --- /dev/null +++ b/blueprints/cypht/cypht.svg @@ -0,0 +1 @@ +cypht_logo_white \ No newline at end of file diff --git a/blueprints/cypht/docker-compose.yml b/blueprints/cypht/docker-compose.yml new file mode 100644 index 000000000..815b865b1 --- /dev/null +++ b/blueprints/cypht/docker-compose.yml @@ -0,0 +1,80 @@ +services: + cypht: + image: cypht/cypht:2.13.0 + restart: unless-stopped + depends_on: + cypht-db: + condition: service_healthy + cypht-redis: + condition: service_healthy + expose: + - 80 + environment: + # Initial account (created on first start) and admin + AUTH_USERNAME: ${ADMIN_USERNAME} + AUTH_PASSWORD: ${ADMIN_PASSWORD} + ADMIN_USERS: ${ADMIN_USERNAME} + + # Accounts and user settings stored in MariaDB + AUTH_TYPE: DB + USER_CONFIG_TYPE: DB + DB_CONNECTION_TYPE: host + DB_DRIVER: mysql + DB_HOST: cypht-db + DB_PORT: 3306 + DB_NAME: cypht + DB_USER: cypht + DB_PASS: ${DB_PASSWORD} + + # Sessions and cache in Redis + SESSION_TYPE: REDIS + ENABLE_REDIS: "true" + REDIS_SERVER: cypht-redis + REDIS_PORT: 6379 + REDIS_PASS: ${REDIS_PASSWORD} + # The image's baked-in .env sets a unix socket path, which takes precedence over host/port + REDIS_SOCKET: "" + ENABLE_MEMCACHED: "false" + + ENCRYPT_LOCAL_STORAGE: "true" + volumes: + - cypht-attachments:/var/lib/hm3/attachments + + cypht-db: + image: mariadb:10.11 + restart: unless-stopped + environment: + MARIADB_DATABASE: cypht + MARIADB_USER: cypht + MARIADB_PASSWORD: ${DB_PASSWORD} + MARIADB_RANDOM_ROOT_PASSWORD: "yes" + volumes: + - cypht-db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + + cypht-redis: + image: redis:7-alpine + restart: unless-stopped + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD} + # volatile-lru: sessions and cache keys both carry a TTL, so only they can be evicted + command: + - sh + - -c + - exec redis-server --requirepass "$$REDIS_PASSWORD" --appendonly yes --maxmemory 256mb --maxmemory-policy volatile-lru + volumes: + - cypht-redis:/data + healthcheck: + test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" --no-auth-warning ping | grep -q PONG"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + cypht-attachments: {} + cypht-db: {} + cypht-redis: {} diff --git a/blueprints/cypht/meta.json b/blueprints/cypht/meta.json new file mode 100644 index 000000000..5e0e93179 --- /dev/null +++ b/blueprints/cypht/meta.json @@ -0,0 +1,17 @@ +{ + "id": "cypht", + "name": "Cypht", + "version": "2.13.0", + "description": "Lightweight open source webmail aggregator. Read and send mail from multiple IMAP, SMTP, JMAP and EWS accounts in one place.", + "logo": "cypht.svg", + "links": { + "github": "https://github.com/cypht-org/cypht", + "website": "https://cypht.org/", + "docs": "https://cypht.org/install.html" + }, + "tags": [ + "self-hosted", + "email", + "webmail" + ] +} diff --git a/blueprints/cypht/template.toml b/blueprints/cypht/template.toml new file mode 100644 index 000000000..7a3138cc6 --- /dev/null +++ b/blueprints/cypht/template.toml @@ -0,0 +1,20 @@ +[variables] +main_domain = "${domain}" +admin_username = "admin" +admin_password = "${password:24}" +db_password = "${password:32}" +redis_password = "${password:32}" + +[config] +env = [ + "ADMIN_USERNAME=${admin_username}", + "ADMIN_PASSWORD=${admin_password}", + "DB_PASSWORD=${db_password}", + "REDIS_PASSWORD=${redis_password}", +] +mounts = [] + +[[config.domains]] +serviceName = "cypht" +port = 80 +host = "${main_domain}" diff --git a/blueprints/dashy/meta.json b/blueprints/dashy/meta.json index 042e10aa0..f65c8c9e3 100644 --- a/blueprints/dashy/meta.json +++ b/blueprints/dashy/meta.json @@ -2,7 +2,7 @@ "id": "dashy", "name": "Dashy", "version": "latest", - "description": "A self-hostable personal dashboard built for you. Includes status-checking, widgets, themes, icon packs, a UI editor and tons more!", + "description": "Dashy is a customizable dashboard for organizing your apps and services. Self-host it on your own server with Dokploy, which deploys Dashy as a Docker Compose service in a few clicks.", "logo": "dashy.png", "links": { "github": "https://github.com/Lissy93/dashy", @@ -14,5 +14,8 @@ "monitoring", "self-hosted", "homelab" - ] + ], + "seoTitle": "Dashy Docker Compose Setup", + "seoDescription": "Customizable personal dashboard. Deploy Dashy with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/directus/meta.json b/blueprints/directus/meta.json index f7757a478..a51a552d6 100644 --- a/blueprints/directus/meta.json +++ b/blueprints/directus/meta.json @@ -2,7 +2,7 @@ "id": "directus", "name": "Directus", "version": "12.1.1", - "description": "Directus is an open source headless CMS that provides an API-first solution for building custom backends.", + "description": "Directus is an open-source headless CMS that adds an API and admin app to your SQL database. Self-host it on your own server with Dokploy, which deploys Directus as a Docker Compose service in a few clicks.", "logo": "directus.jpg", "links": { "github": "https://github.com/directus/directus", @@ -11,5 +11,7 @@ }, "tags": [ "cms" - ] + ], + "seoTitle": "Self-Host Directus in minutes", + "seoDescription": "Headless CMS for building custom backends. Self-host Directus on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/discourse/meta.json b/blueprints/discourse/meta.json index 0cdc7b572..8e1f5603f 100644 --- a/blueprints/discourse/meta.json +++ b/blueprints/discourse/meta.json @@ -2,7 +2,7 @@ "id": "discourse", "name": "Discourse", "version": "2026.1.4", - "description": "Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.", + "description": "Discourse is forum software for running online communities. Self-host it on your own server with Dokploy, which deploys Discourse as a Docker Compose service in a few clicks.", "logo": "discourse.svg", "links": { "github": "https://github.com/discourse/discourse", @@ -13,5 +13,7 @@ "forum", "community", "discussion" - ] + ], + "seoTitle": "Self-Host Discourse in minutes", + "seoDescription": "Modern forum software for online communities. Self-host Discourse on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/dockge/meta.json b/blueprints/dockge/meta.json index 2d11a89bb..0b1991a87 100644 --- a/blueprints/dockge/meta.json +++ b/blueprints/dockge/meta.json @@ -2,7 +2,7 @@ "id": "dockge", "name": "Dockge", "version": "1", - "description": "Dockge is a reactive, self-hosted Docker Compose stack manager from the creator of Uptime Kuma.", + "description": "Dockge is a web interface for managing Docker Compose stacks. Self-host it on your own server with Dokploy, which deploys Dockge as a Docker Compose service in a few clicks.", "logo": "dockge.svg", "links": { "github": "https://github.com/louislam/dockge", @@ -13,5 +13,8 @@ "docker", "compose", "management" - ] + ], + "seoTitle": "Dockge Docker Compose Setup", + "seoDescription": "Web UI for managing Docker Compose stacks. Deploy Dockge with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/docmost/meta.json b/blueprints/docmost/meta.json index 4f4ac7640..d6d5b9d4d 100644 --- a/blueprints/docmost/meta.json +++ b/blueprints/docmost/meta.json @@ -2,7 +2,7 @@ "id": "docmost", "name": "Docmost", "version": "0.4.1", - "description": "Docmost, is an open-source collaborative wiki and documentation software.", + "description": "Docmost is a collaborative wiki and documentation tool. Self-host it on your own server with Dokploy, which deploys Docmost as a Docker Compose service in a few clicks.", "logo": "docmost.png", "links": { "github": "https://github.com/docmost/docmost", @@ -13,5 +13,7 @@ "self-hosted", "open-source", "manager" - ] + ], + "seoTitle": "Self-Host Docmost in minutes", + "seoDescription": "Collaborative wiki and documentation tool. Self-host Docmost on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/dokploy-actions-runner/docker-compose.yml b/blueprints/dokploy-actions-runner/docker-compose.yml new file mode 100644 index 000000000..ca0a83b5f --- /dev/null +++ b/blueprints/dokploy-actions-runner/docker-compose.yml @@ -0,0 +1,70 @@ +services: + dokploy-actions-runner: + # Maintained upstream runner, Ubuntu 24.04; multi-architecture index digest. + image: ghcr.io/myoung34/docker-github-actions-runner:2.338.0-ubuntu-noble@sha256:2112bde2ee92b7480babfbe3043d0461e322b97ca01e6ac94698895f3dc1f528 + restart: unless-stopped + user: runner + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + stop_grace_period: 2m + # Dokploy's .env supplies interpolation, not automatic container injection. + # Allowlist inputs explicitly. Never replace this with env_file: .env. + environment: + ACCESS_TOKEN: ${ACCESS_TOKEN:?Set ACCESS_TOKEN to a GitHub PAT in Dokploy Environment} + RUNNER_SCOPE: ${RUNNER_SCOPE:?Set RUNNER_SCOPE to repo or org} + REPO_URL: ${REPO_URL:-} + ORG_NAME: ${ORG_NAME:-} + RUNNER_NAME: ${RUNNER_NAME:?Set a unique RUNNER_NAME for each deployment} + LABELS: ${LABELS:?Set comma-separated runner LABELS} + RUNNER_GROUP: ${RUNNER_GROUP:-Default} + RUN_AS_ROOT: "false" + START_DOCKER_SERVICE: "false" + DISABLE_AUTOMATIC_DEREGISTRATION: "false" + RUNNER_WORKDIR: /_work + # Keep the PAT in the entrypoint shell for shutdown deregistration. + # Upstream un-exports it before starting the listener. See the linked project security guide. + UNSET_CONFIG_VARS: "false" + # Only validates configuration, then execs the upstream lifecycle handler. + # Inline so Dokploy imports need no custom image or repository file mounts. + # Double dollars defer expansion to the container, not Compose/Dokploy. + entrypoint: + - /bin/bash + - -c + - | + set -euo pipefail + fail() { printf 'Configuration error: %s\n' "$$1" >&2; exit 64; } + [[ "$${ACCESS_TOKEN:-}" =~ ^[A-Za-z0-9_]+$$ ]] || fail 'ACCESS_TOKEN must be a PAT without whitespace, quotes or variable placeholders' + case "$${ACCESS_TOKEN}" in + changeme|CHANGEME|replace_me|your_token) fail 'Replace the ACCESS_TOKEN placeholder with a GitHub PAT' ;; + esac + [[ "$${RUNNER_NAME:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$$ ]] || fail 'RUNNER_NAME must be 1-64 letters, digits, dots, underscores or hyphens; start with a letter or digit' + [[ "$${LABELS:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*(,[A-Za-z0-9][A-Za-z0-9._-]*)*$$ ]] || fail 'LABELS must be comma-separated labels without spaces (letters, digits, dots, underscores or hyphens)' + [[ "$${RUNNER_GROUP:-}" =~ ^[A-Za-z0-9][A-Za-z0-9\ ._-]{0,99}$$ ]] || fail 'RUNNER_GROUP must be 1-100 letters, digits, spaces, dots, underscores or hyphens; start with a letter or digit' + case "$${RUNNER_SCOPE:-}" in + repo) + [[ "$${REPO_URL:-}" =~ ^https://github\.com/[A-Za-z0-9][A-Za-z0-9-]*/[A-Za-z0-9._-]+$$ ]] || fail 'repo scope requires REPO_URL=https://github.com/OWNER/REPO (no trailing slash or .git suffix)' + [[ "$${REPO_URL}" != *.git ]] || fail 'REPO_URL must not have a .git suffix' + [[ -z "$${ORG_NAME:-}" ]] || fail 'Clear ORG_NAME when RUNNER_SCOPE=repo' + [[ "$${RUNNER_GROUP}" == Default ]] || fail 'Custom RUNNER_GROUP is only supported with RUNNER_SCOPE=org' + ;; + org) + [[ "$${ORG_NAME:-}" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}$$ ]] || fail 'org scope requires an ORG_NAME slug (not a URL)' + [[ -z "$${REPO_URL:-}" ]] || fail 'Clear REPO_URL when RUNNER_SCOPE=org' + ;; + *) fail 'RUNNER_SCOPE must be exactly repo or org' ;; + esac + exec /entrypoint.sh ./bin/Runner.Listener run --startuptype service + # Local listener liveness only; does not prove GitHub connectivity/readiness. + healthcheck: + test: ["CMD-SHELL", "pgrep -x 'Runner[.]Listener' > /dev/null"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 2m + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" diff --git a/blueprints/dokploy-actions-runner/logo.svg b/blueprints/dokploy-actions-runner/logo.svg new file mode 100644 index 000000000..e697580a5 --- /dev/null +++ b/blueprints/dokploy-actions-runner/logo.svg @@ -0,0 +1,9 @@ + + Actions runner + + + + + + + diff --git a/blueprints/dokploy-actions-runner/meta.json b/blueprints/dokploy-actions-runner/meta.json new file mode 100644 index 000000000..80c60955c --- /dev/null +++ b/blueprints/dokploy-actions-runner/meta.json @@ -0,0 +1,13 @@ +{ + "id": "dokploy-actions-runner", + "name": "GitHub Actions Runner", + "version": "2.338.0-ubuntu-noble", + "description": "Self-hosted GitHub Actions runner for repositories or organizations, with independent names, labels and runner groups. Runs without privileged mode or Docker socket access.", + "logo": "logo.svg", + "links": { + "github": "https://github.com/Simplarix/dokploy-actions-runner", + "website": "https://docs.github.com/en/actions/concepts/runners/self-hosted-runners", + "docs": "https://github.com/Simplarix/dokploy-actions-runner#readme" + }, + "tags": ["development", "automation", "ci"] +} diff --git a/blueprints/dokploy-actions-runner/template.toml b/blueprints/dokploy-actions-runner/template.toml new file mode 100644 index 000000000..ba0afddac --- /dev/null +++ b/blueprints/dokploy-actions-runner/template.toml @@ -0,0 +1,16 @@ +[variables] +runner_name = "dokploy-${uuid}" + +[config] +domains = [] +mounts = [] +# Set a GitHub PAT and repository/organization target before deploying. +env = [ + "ACCESS_TOKEN=", + "RUNNER_SCOPE=repo", + "REPO_URL=", + "ORG_NAME=", + "RUNNER_NAME=${runner_name}", + "LABELS=dokploy", + "RUNNER_GROUP=Default", +] diff --git a/blueprints/dossier/docker-compose.yml b/blueprints/dossier/docker-compose.yml new file mode 100644 index 000000000..56fad07fc --- /dev/null +++ b/blueprints/dossier/docker-compose.yml @@ -0,0 +1,78 @@ +services: + dossier: + image: ghcr.io/aientech/dossier:v1.5.0 + restart: unless-stopped + env_file: + - path: .env + required: false + environment: + DATABASE_URL: "postgresql://dossier:${POSTGRES_PASSWORD:?Keep the generated database password}@dossier-postgres:5432/dossier" + # Optional: an argon2id hash from `hash-password`. When empty, it's derived at + # startup from DOSSIER_ADMIN_PASSWORD (generated by the template). + DOSSIER_PASSWORD: "${DOSSIER_PASSWORD:-}" + DOSSIER_ADMIN_PASSWORD: "${DOSSIER_ADMIN_PASSWORD:-}" + DOSSIER_SECRET: "${DOSSIER_SECRET:?Keep the generated DOSSIER_SECRET}" + DOSSIER_DATA_DIR: /data + DOSSIER_TELEMETRY: "${DOSSIER_TELEMETRY:-off}" + DOSSIER_BACKUP_RETENTION_DAYS: "${DOSSIER_BACKUP_RETENTION_DAYS:-14}" + TZ: "${TZ:-UTC}" + command: + - sh + - -c + - | + if [ -z "$$DOSSIER_PASSWORD" ]; then + [ -n "$$DOSSIER_ADMIN_PASSWORD" ] || { echo "Set DOSSIER_ADMIN_PASSWORD or DOSSIER_PASSWORD" >&2; exit 1; } + DOSSIER_PASSWORD="$$(printf '%s\n' "$$DOSSIER_ADMIN_PASSWORD" | hash-password)" + export DOSSIER_PASSWORD + fi + unset DOSSIER_ADMIN_PASSWORD + exec ./start.sh + expose: + - "3000" + volumes: + - dossier-data:/data + depends_on: + dossier-postgres: + condition: service_healthy + healthcheck: + test: + - CMD + - node + - -e + - "fetch('http://127.0.0.1:3000/api/health').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))" + start_period: 60s + interval: 30s + timeout: 10s + retries: 3 + # The bundled Chromium prints PDFs in this process. + mem_limit: 1g + stop_grace_period: 30s + extra_hosts: + - "host.docker.internal:host-gateway" + logging: &logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + + dossier-postgres: + image: postgres:18.6-alpine + restart: unless-stopped + environment: + POSTGRES_USER: dossier + POSTGRES_DB: dossier + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?Keep the generated database password}" + volumes: + - dossier-postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dossier -d dossier"] + start_period: 20s + interval: 5s + timeout: 5s + retries: 10 + stop_grace_period: 30s + logging: *logging + +volumes: + dossier-data: + dossier-postgres: diff --git a/blueprints/dossier/dossier.svg b/blueprints/dossier/dossier.svg new file mode 100644 index 000000000..d0ddcd447 --- /dev/null +++ b/blueprints/dossier/dossier.svg @@ -0,0 +1 @@ + diff --git a/blueprints/dossier/environment.md b/blueprints/dossier/environment.md new file mode 100644 index 000000000..5f579d376 --- /dev/null +++ b/blueprints/dossier/environment.md @@ -0,0 +1,55 @@ +# Environment variables + +These settings were checked against Dossier **v1.5.0**: [environment validation](https://github.com/AienTech/dossier/blob/v1.5.0/src/lib/env.ts), [example environment](https://github.com/AienTech/dossier/blob/v1.5.0/.env.example), and [production Compose](https://github.com/AienTech/dossier/blob/v1.5.0/compose.production.yml). + +Add optional values to Dokploy's Environment tab and redeploy. The app loads that environment through an optional `.env` file. Blank optional values count as unset. Required credentials are checked explicitly by Compose; the app validates their format on startup. App settings saved in the database override AI and job search environment defaults. + +## Required and generated values + +| Variable | Template value | Purpose | +| --- | --- | --- | +| `DOSSIER_PASSWORD` | Empty; you must set it | Login password **hash** from `docker run --rm -it ghcr.io/aientech/dossier:v1.5.0 hash-password`. Paste the complete `argon2id:...` output, never plaintext or a standard PHC hash. | +| `DOSSIER_SECRET` | Generated random hexadecimal string | At least 32 characters. Signs sessions and encrypts saved provider keys. Preserve across redeployments and backups. | +| `POSTGRES_PASSWORD` | Generated random hexadecimal string | Required by both services. Used in the connection URL; a replacement must be URL-safe. | +| `DATABASE_URL` | Constructed by Compose | `postgresql://dossier:@dossier-postgres:5432/dossier`. No manual value is needed. | + +`POSTGRES_USER=dossier` and `POSTGRES_DB=dossier` are fixed in Compose and match the app's connection URL. Change both service configurations together if customizing them. `POSTGRES_PORT` from upstream's development example is unused: PostgreSQL is private and has no published port. + +## Optional application settings + +| Variable | Template / upstream default | Purpose | +| --- | --- | --- | +| `DOSSIER_TELEMETRY` | Template `off`; upstream `on` | `on` or `off`. Controls the daily usage ping, independently of update checks. | +| `DOSSIER_BACKUP_RETENTION_DAYS` | `14` | Whole number, at least 1. Retention of nightly backups. | +| `TZ` | `UTC` | Time zone for the app and its 03:00 backup schedule. See the v1.5.0 dashboard caveat in [instructions.md](instructions.md) before changing it. | +| `DOSSIER_DATA_DIR` | `/data`, fixed by Compose | Mounted persistent directory. If customizing, change the volume target and this value together in Compose. | + +## Optional AI defaults + +No AI variables are required for startup, login, the profile, or base resume PDFs. AI features require a reachable provider and a suitable model. You can configure the same settings in the app instead. + +| Variable | Default when unset | Purpose | +| --- | --- | --- | +| `DOSSIER_AI_PROVIDER` | No provider configured | `local`, `openai` (also compatible APIs), or `anthropic`. | +| `DOSSIER_AI_URL` | Provider-specific | HTTP(S) endpoint. Local: `http://localhost:11434`; OpenAI: `https://api.openai.com/v1`; Anthropic: `https://api.anthropic.com`. For host Ollama, use `http://host.docker.internal:11434`. | +| `DOSSIER_AI_MODEL` | No default for local/OpenAI; `claude-sonnet-5` for Anthropic | Provider model identifier. | +| `DOSSIER_AI_KEY` | Unset | Provider credential, if needed. Keep private. | +| `DOSSIER_AI_TIMEOUT_SECONDS` | `300` local; `120` hosted | Whole number of seconds, at least 5, per call. | + +## Optional job search defaults + +| Variable | Default when unset | Purpose | +| --- | --- | --- | +| `DOSSIER_JOBS_SOURCE` | `off` | `jsearch`, `adzuna`, `arbeitsagentur`, or `off`. | +| `DOSSIER_JSEARCH_KEY` | Unset | Required only for JSearch. | +| `DOSSIER_JSEARCH_HOST` | `openwebninja` | `openwebninja` or `rapidapi`, matching the key issuer. | +| `DOSSIER_ADZUNA_APP_ID` | Unset | Required only for Adzuna. | +| `DOSSIER_ADZUNA_KEY` | Unset | Required only for Adzuna. | + +Arbeitsagentur needs no credential. Provider availability, quotas, and billing are managed by the provider. + +## Image and test controls + +The published image already sets `NODE_ENV=production`, `NEXT_TELEMETRY_DISABLED=1`, `PORT=3000`, and `HOSTNAME=0.0.0.0`. Leave them unchanged so routing and the health check agree. This template uses the prebuilt production image; frontend build inputs and development database port settings are not additional installation requirements. + +`DOSSIER_JOBS_TEST_BASE_URL` and `DOSSIER_UPDATES_TEST_BASE_URL` are upstream end-to-end test hooks that redirect job search and release requests to mock endpoints. Keep them unset in normal deployments. They are intentionally absent from the generated Environment tab. diff --git a/blueprints/dossier/instructions.md b/blueprints/dossier/instructions.md new file mode 100644 index 000000000..7e56efbad --- /dev/null +++ b/blueprints/dossier/instructions.md @@ -0,0 +1,45 @@ +# Dossier + +Dossier is a single-user job search workspace for your profile, resumes, applications, and cover letters. This template runs Dossier v1.5.0 with PostgreSQL 18 and persistent storage. AI and job search providers are optional. + +## Before the first deployment + +1. Import the template in Dokploy. Keep the generated `POSTGRES_PASSWORD` and `DOSSIER_SECRET` values. +2. Generate a login password hash on a machine with Docker: + + ```sh + docker run --rm -it ghcr.io/aientech/dossier:v1.5.0 hash-password + ``` + + Enter your chosen password twice. Copy the printed `argon2id:...` string into `DOSSIER_PASSWORD` in Dokploy's Environment tab. Use Dossier's command: it produces a special dollar-free format, rather than a standard `$argon2id$...` hash. Never put your plaintext password in this variable. The empty template value deliberately prevents deployment until you set the hash. +3. Set your domain in Dokploy and enable HTTPS with a certificate. The domain targets service `dossier`, port `3000`. Dokploy manages routing and networks; neither the app nor PostgreSQL needs a published host port. +4. Deploy, wait for both services to become healthy, and open the domain. Sign in with the password you entered in the hashing command. + +Allow at least 1 GB of memory for Dossier, plus memory for PostgreSQL and Dokploy. The app's 1 GB container limit accommodates its bundled Chromium PDF renderer. Larger documents or heavier use may need a higher limit. Run exactly one Dossier instance against this database: backups, the application queue, and the browser process run inside the app. + +## Optional integrations + +You can save your profile and print a base resume without an AI provider. Configure AI later in **Settings → AI provider**, or add the `DOSSIER_AI_*` defaults listed in [environment.md](environment.md). Settings saved in the app take precedence over environment defaults. Hosted services require their own credentials; no provider key is generated or required by this template. + +An Ollama service is not installed automatically. To use Ollama already running on the Docker host, set the local provider URL to `http://host.docker.internal:11434` and choose an installed model. The host gateway mapping is included, but Ollama must listen on an address reachable from the container and the host firewall must permit that connection. `localhost` inside Dossier refers to the Dossier container. Alternatively, add your own Ollama service and persistent model volume, then use its service name as the URL. Model downloads and CPU/GPU memory are additional requirements only for that optional service. + +Job search is off until you choose a source in **Settings → Job search**. JSearch needs a key; Adzuna needs an app ID and key; Arbeitsagentur needs no key. None is required to deploy or use the application tracker. + +The template sets `DOSSIER_TELEMETRY=off`. Upstream defaults to `on`; enabling it sends an install ID and version once per day the app is used. The release update check is separate and can be disabled in Settings → About. + +## Persistence, backups, and upgrades + +- `dossier-postgres` stores PostgreSQL data at `/var/lib/postgresql`, the parent directory required by PostgreSQL 18 images. +- `dossier-data` stores `/data`, including nightly backups and downloadable PDF/ZIP artifacts. Backups run at 03:00 in `TZ` and are retained for `DOSSIER_BACKUP_RETENTION_DAYS` days, default 14. +- Back up the database, `/data`, and your deployment secrets to another location. A volume on the same server is not an off-server backup. Dossier's Settings → Your data also provides export/import. +- Preserve `DOSSIER_SECRET`: it signs sessions and encrypts saved provider keys. Replacing it signs everyone out and makes previously encrypted keys unreadable. Changing `DOSSIER_PASSWORD` also invalidates existing sessions. +- Keep the generated database password URL-safe if you replace it: it is interpolated into `DATABASE_URL`. Changing it in Environment alone does not change the password in an initialized database. +- The official image applies database migrations before starting the server. Back up first, review upstream release notes, then update the image and metadata together. Keep PostgreSQL on major version 18 unless you perform a planned database migration. + +GET `/api/health` is public and checks database connectivity without returning private data. Behind Dokploy's Traefik proxy, Dossier uses `X-Forwarded-Proto` for secure cookies and `X-Forwarded-For` for login rate limiting. Keep the application reachable through the trusted proxy and use HTTPS for regular access. + +### Upstream time zone caveat + +In v1.5.0, testing `TZ=Europe/Paris` with a UTC browser produced a React hydration warning on the dashboard's weekly activity chart. That chart formats dates in both the server and browser time zones. The template's default `TZ=UTC` passed the tested UTC browser flow. Review the dashboard after changing `TZ`, especially when your browser uses a different zone; this template does not modify upstream application code. + +See [environment.md](environment.md) for required and optional variables and [testing.md](testing.md) for a repeatable verification checklist. diff --git a/blueprints/dossier/meta.json b/blueprints/dossier/meta.json new file mode 100644 index 000000000..ad0d9f43d --- /dev/null +++ b/blueprints/dossier/meta.json @@ -0,0 +1,13 @@ +{ + "id": "dossier", + "name": "Dossier", + "version": "v1.5.0", + "description": "Dossier is a personal job search app with a career profile, resume and cover letter drafting, PDF export, and an application tracking board. It supports local and hosted AI providers.", + "logo": "dossier.svg", + "links": { + "github": "https://github.com/AienTech/dossier", + "website": "https://github.com/AienTech/dossier", + "docs": "https://github.com/AienTech/dossier#self-hosting" + }, + "tags": ["productivity", "job-search", "ai"] +} diff --git a/blueprints/dossier/template.toml b/blueprints/dossier/template.toml new file mode 100644 index 000000000..7a2325c37 --- /dev/null +++ b/blueprints/dossier/template.toml @@ -0,0 +1,21 @@ +[variables] +main_domain = "${domain}" +postgres_password = "${hash:48}" +dossier_secret = "${hash:64}" +admin_password = "${password:24}" + +[config] +env = [ + "POSTGRES_PASSWORD=${postgres_password}", + "DOSSIER_SECRET=${dossier_secret}", + # Sign-in password. Its argon2id hash is computed at startup. + "DOSSIER_ADMIN_PASSWORD=${admin_password}", + "DOSSIER_TELEMETRY=off", + "DOSSIER_BACKUP_RETENTION_DAYS=14", + "TZ=UTC" +] + +[[config.domains]] +serviceName = "dossier" +port = 3000 +host = "${main_domain}" diff --git a/blueprints/dossier/testing.md b/blueprints/dossier/testing.md new file mode 100644 index 000000000..580aad318 --- /dev/null +++ b/blueprints/dossier/testing.md @@ -0,0 +1,16 @@ +# Testing Dossier + +Use disposable data and a fresh Dokploy Compose service. Do not run upstream's database-resetting end-to-end suite against an existing installation. + +1. Validate metadata, Compose, and TOML with the repository's build scripts, then build the template catalog. +2. Import the Base64 configuration from the PR preview into Dokploy. Generate the password hash using the pinned image's `hash-password` command. Keep both generated secrets, set the hash, and deploy without AI or job search credentials. +3. Confirm both services become healthy and GET `/api/health` returns HTTP 200 with `status: ok`, `db: ok`, and the pinned version. A blank password hash must fail Compose interpolation; a plaintext value must be rejected by the app without logging the value. +4. Open the generated domain. Check that private pages require login, a wrong password is rejected, and the chosen password works. Save a synthetic profile, reload it, and download a base resume PDF. Verify that the downloaded bytes begin with `%PDF-` and contain the expected profile text. +5. For an optional AI integration test, use a local OpenAI-compatible mock with synthetic responses. Configure it in Settings, create an application from a pasted posting, and check the generated application documents. This verifies request handling without paid API calls; it does not validate a live provider's credentials or model quality. +6. Export data, then exercise import/restore only on the disposable installation. Confirm backups appear under `/data`. Recreate the app container and redeploy through Dokploy; profile, applications, database credentials, and backup files must survive. +7. Enable HTTPS on the Dokploy domain, redeploy, and check login, secure cookies, and PDF generation through Traefik. A local test certificate is sufficient for proxy behavior; public certificate issuance requires a real domain. +8. In the disposable fixture, stop PostgreSQL temporarily: `/api/health` should return 503, then recover after PostgreSQL restarts. Inspect app and database logs for migration, permission, connection, and browser errors. + +Record the image versions, Dokploy version, checks, and integration limits in the PR. Capture screenshots using synthetic data only. Remove the test service, its volumes, and test images after collecting evidence. + +When testing a non-default server time zone, also inspect the populated dashboard for hydration warnings. Dossier v1.5.0 showed a weekly-chart date mismatch with `TZ=Europe/Paris` and a UTC browser; see [instructions.md](instructions.md). diff --git a/blueprints/dozzle/meta.json b/blueprints/dozzle/meta.json index b276d9f18..f7d61ed64 100644 --- a/blueprints/dozzle/meta.json +++ b/blueprints/dozzle/meta.json @@ -2,7 +2,7 @@ "id": "dozzle", "name": "Dozzle", "version": "latest", - "description": "Dozzle is a lightweight, real-time log viewer for Docker containers.", + "description": "Dozzle is a real-time log viewer for Docker containers. Self-host it on your own server with Dokploy, which deploys Dozzle as a Docker Compose service in a few clicks.", "logo": "image.png", "links": { "github": "https://github.com/amir20/dozzle", @@ -13,5 +13,8 @@ "monitoring", "logs", "docker" - ] + ], + "seoTitle": "Dozzle Docker Compose Setup", + "seoDescription": "Real-time log viewer for Docker containers. Deploy Dozzle with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/emaki-video-starter/docker-compose.yml b/blueprints/emaki-video-starter/docker-compose.yml new file mode 100644 index 000000000..203809476 --- /dev/null +++ b/blueprints/emaki-video-starter/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.8" +services: + emaki-video-starter: + image: san8888/emaki-video-starter:1.0.0 + restart: unless-stopped + expose: + - 3000 + environment: + - PORT=3000 + - EMAKI_API_KEY=${EMAKI_API_KEY} + - ACCESS_PASSWORD=${ACCESS_PASSWORD} + - EMAKI_MODEL=${EMAKI_MODEL} + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"] + interval: 30s + timeout: 10s + retries: 3 diff --git a/blueprints/emaki-video-starter/emaki-video-starter.png b/blueprints/emaki-video-starter/emaki-video-starter.png new file mode 100644 index 000000000..61846cb27 Binary files /dev/null and b/blueprints/emaki-video-starter/emaki-video-starter.png differ diff --git a/blueprints/emaki-video-starter/meta.json b/blueprints/emaki-video-starter/meta.json new file mode 100644 index 000000000..943b9e24c --- /dev/null +++ b/blueprints/emaki-video-starter/meta.json @@ -0,0 +1,17 @@ +{ + "id": "emaki-video-starter", + "name": "Seedance 2.5 Video Generator", + "version": "1.0.0", + "description": "One-page text-to-video and image-to-video web app on the Seedance 2.5 API from emaki. Set EMAKI_API_KEY; an access password is generated so only people you share it with can spend your credits.", + "logo": "emaki-video-starter.png", + "links": { + "github": "https://github.com/hanshs474/emaki-video-starter", + "website": "https://emaki.ai/seedance-2-5", + "docs": "https://emaki.readthedocs.io/" + }, + "tags": [ + "ai", + "video-generation", + "text-to-video" + ] +} diff --git a/blueprints/emaki-video-starter/template.toml b/blueprints/emaki-video-starter/template.toml new file mode 100644 index 000000000..34f06e562 --- /dev/null +++ b/blueprints/emaki-video-starter/template.toml @@ -0,0 +1,16 @@ +[variables] +main_domain = "${domain}" +access_password = "${password:16}" + +[config] +env = [ + "EMAKI_API_KEY=", + "ACCESS_PASSWORD=${access_password}", + "EMAKI_MODEL=seedance_2_5" +] +mounts = [] + +[[config.domains]] +serviceName = "emaki-video-starter" +port = 3000 +host = "${main_domain}" diff --git a/blueprints/erpnext/meta.json b/blueprints/erpnext/meta.json index d6c87d586..01806c6ff 100644 --- a/blueprints/erpnext/meta.json +++ b/blueprints/erpnext/meta.json @@ -2,7 +2,7 @@ "id": "erpnext", "name": "ERPNext", "version": "version-15", - "description": "100% Open Source and highly customizable ERP software.", + "description": "ERPNext is an ERP system for managing accounting, inventory, sales, and more. Self-host it on your own server with Dokploy, which deploys ERPNext as a Docker Compose service in a few clicks.", "logo": "erpnext.svg", "links": { "github": "https://github.com/frappe/erpnext", @@ -17,5 +17,7 @@ "sales", "pos", "hrms" - ] + ], + "seoTitle": "Self-Host ERPNext in minutes", + "seoDescription": "Open source ERP for business management. Self-host ERPNext on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/excalidraw/meta.json b/blueprints/excalidraw/meta.json index d052e111f..45db5da1c 100644 --- a/blueprints/excalidraw/meta.json +++ b/blueprints/excalidraw/meta.json @@ -2,7 +2,7 @@ "id": "excalidraw", "name": "Excalidraw", "version": "latest", - "description": "Excalidraw is a free and open source online diagramming tool that lets you easily create and share beautiful diagrams.", + "description": "Excalidraw is an open-source whiteboard tool for sketching hand-drawn-style diagrams. Self-host it on your own server with Dokploy, which deploys Excalidraw as a Docker Compose service in a few clicks.", "logo": "excalidraw.jpg", "links": { "github": "https://github.com/excalidraw/excalidraw", @@ -11,5 +11,7 @@ }, "tags": [ "drawing" - ] + ], + "seoTitle": "Self-Host Excalidraw in minutes", + "seoDescription": "Online tool for sketching diagrams. Self-host Excalidraw on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/falco/docker-compose.yml b/blueprints/falco/docker-compose.yml new file mode 100644 index 000000000..728c2d720 --- /dev/null +++ b/blueprints/falco/docker-compose.yml @@ -0,0 +1,148 @@ +x-logging: &default-logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + +services: + falco: + image: falcosecurity/falco:0.45.0 + hostname: "${MONITORED_HOST:-dokploy}" + restart: unless-stopped + cap_drop: + - ALL + cap_add: + - SYS_ADMIN + - SYS_RESOURCE + - SYS_PTRACE + security_opt: + - apparmor:unconfined + environment: + HOST_ROOT: /host + command: + - /usr/bin/falco + - -o + - engine.kind=modern_ebpf + - -o + - json_output=true + - -o + - json_include_output_property=true + - -o + - stdout_output.enabled=true + - -o + - http_output.enabled=true + - -o + - http_output.url=http://falcosidekick:2801/ + - -o + - priority=notice + # Falco needs the real host paths to inspect host and container activity. + volumes: + - type: bind + source: /var/run/docker.sock + target: /host/var/run/docker.sock + read_only: true + bind: + create_host_path: false + - type: bind + source: /proc + target: /host/proc + read_only: true + bind: + create_host_path: false + - type: bind + source: /etc + target: /host/etc + read_only: true + bind: + create_host_path: false + - type: bind + source: /sys/kernel/tracing + target: /sys/kernel/tracing + read_only: true + bind: + create_host_path: false + depends_on: + falcosidekick: + condition: service_started + mem_limit: 1g + stop_grace_period: 30s + logging: *default-logging + + falcosidekick: + image: falcosecurity/falcosidekick:2.35.0 + restart: unless-stopped + environment: + TELEGRAM_TOKEN: "${TELEGRAM_BOT_TOKEN:-}" + TELEGRAM_CHATID: "${TELEGRAM_CHAT_ID:-}" + TELEGRAM_MINIMUMPRIORITY: "${TELEGRAM_MINIMUM_PRIORITY:-notice}" + WEBUI_URL: http://falco-ui:2802 + depends_on: + falco-ui: + condition: service_started + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + mem_limit: 256m + stop_grace_period: 30s + logging: *default-logging + + falco-ui: + image: falcosecurity/falcosidekick-ui:2.3.1 + restart: unless-stopped + environment: + FALCOSIDEKICK_UI_USER: "${FALCO_UI_USER:-admin}:${FALCO_UI_PASSWORD:?Set FALCO_UI_PASSWORD}" + FALCOSIDEKICK_UI_REDIS_URL: falco-redis:6379 + FALCOSIDEKICK_UI_REDIS_PASSWORD: "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}" + FALCOSIDEKICK_UI_TTL: "168h" + expose: + - "2802" + depends_on: + falco-redis: + condition: service_healthy + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + mem_limit: 256m + stop_grace_period: 30s + logging: *default-logging + + falco-redis: + image: redis/redis-stack-server:7.4.0-v8 + restart: unless-stopped + # Falcosidekick UI needs RediSearch and RedisJSON, not RedisGears or RedisAI. + command: + - /opt/redis-stack/bin/redis-server + - --dir + - /data + - --loadmodule + - /opt/redis-stack/lib/redisearch.so + - --loadmodule + - /opt/redis-stack/lib/rejson.so + - --requirepass + - "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}" + - --appendonly + - "yes" + - --appendfsync + - everysec + - --maxmemory + - 512mb + - --maxmemory-policy + - noeviction + environment: + REDISCLI_AUTH: "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}" + volumes: + - falco-events:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + mem_limit: 1g + stop_grace_period: 30s + logging: *default-logging + +volumes: + falco-events: diff --git a/blueprints/falco/falco.svg b/blueprints/falco/falco.svg new file mode 100644 index 000000000..8562b6000 --- /dev/null +++ b/blueprints/falco/falco.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/blueprints/falco/instructions.md b/blueprints/falco/instructions.md new file mode 100644 index 000000000..113ad49ae --- /dev/null +++ b/blueprints/falco/instructions.md @@ -0,0 +1,51 @@ +# Falco + +This template runs Falco with the modern eBPF engine and stores events in Redis Stack for Falcosidekick UI. Telegram alerts through Falcosidekick are optional. + +## Host requirements + +- Use Docker Compose on a Linux server with a kernel that supports modern eBPF (normally Linux 5.8 or later) and kernel BTF at `/sys/kernel/btf/vmlinux`. +- The Docker socket, `/proc`, `/etc`, and `/sys/kernel/tracing` must exist on the deployment server. Falco mounts these host paths read-only to inspect activity. Unlike application data, these paths must refer to the real host directories. +- If tracefs is mounted at `/sys/kernel/debug/tracing`, change only the tracing bind mount's `source` to that path and keep its `target` as `/sys/kernel/tracing`. +- Falco needs `SYS_ADMIN`, `SYS_RESOURCE`, and `SYS_PTRACE`, with an unconfined AppArmor profile. Rootless Docker and hosts that prohibit these capabilities are not supported by this template. +- The service memory limits total 2.5 GiB. Leave additional memory for Dokploy, the operating system, and other applications. + +See [Falco's container deployment guide](https://falco.org/docs/setup/container/) for the supported host configuration. + +## Before deploying + +Set these values in Dokploy's **Environment** tab: + +| Variable | Value | +| --- | --- | +| `MONITORED_HOST` | A hostname label for the monitored server, such as `production-01`. This appears in alerts and is not the UI domain. | +| `TELEGRAM_BOT_TOKEN` | Optional. Leave empty to disable Telegram alerts, or set it together with `TELEGRAM_CHAT_ID` to enable them. | +| `TELEGRAM_CHAT_ID` | Optional. The target chat ID, including its leading minus sign for a group chat. Set it together with `TELEGRAM_BOT_TOKEN`. | +| `TELEGRAM_MINIMUM_PRIORITY` | Defaults to `notice`. Filters Telegram alerts when Telegram is configured. | +| `FALCO_UI_USER` | Defaults to `admin`. | +| `FALCO_UI_PASSWORD` | Generated automatically when you import the template. | +| `FALCO_REDIS_PASSWORD` | Generated automatically when you import the template. | + +For deployment without Telegram, leave `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` empty. Falco still detects events and sends them to the dashboard. Only the monitored hostname and the UI and Redis passwords are required. + +To enable Telegram alerts, provide both credentials, start a conversation with the bot or add it to the target group, and redeploy. + +The generated passwords are independent 64-character hexadecimal strings. To replace one, generate a new value with `openssl rand -hex 32`. Keep the Redis password unchanged across redeployments unless you also update all services that use it. + +Falco collects events at `notice` or higher. Changing `TELEGRAM_MINIMUM_PRIORITY` only filters Telegram delivery; it does not change Falco's collection threshold or the events sent to the UI. + +## Open the dashboard + +Deploy with Dokploy's isolated deployments enabled. Open the domain assigned to `falco-ui` on port `2802` and log in with `FALCO_UI_USER` and `FALCO_UI_PASSWORD`. Enable HTTPS for that domain in Dokploy. + +Dokploy manages the stack's network and domain routing. No host ports are published. Redis loads the RediSearch and RedisJSON modules required by the UI, requires authentication, and stores data in the `falco-events` volume, with append-only persistence and a seven-day UI event lifetime. + +## Verify alerts + +On the monitored server, start an interactive shell in a disposable container: + +```bash +docker run --rm -it alpine:3.21 sh +``` + +Exit the shell after it starts. Check the Falco logs and dashboard for the **Terminal shell in container** event. Its hostname should match `MONITORED_HOST`. If Telegram is configured, check the Telegram chat as well. diff --git a/blueprints/falco/meta.json b/blueprints/falco/meta.json new file mode 100644 index 000000000..609580996 --- /dev/null +++ b/blueprints/falco/meta.json @@ -0,0 +1,13 @@ +{ + "id": "falco", + "name": "Falco", + "version": "0.45.0", + "description": "Monitor Linux host and container activity with Falco's modern eBPF engine. Includes Falcosidekick UI, persistent event storage, and optional Telegram alerts.", + "logo": "falco.svg", + "links": { + "github": "https://github.com/falcosecurity/falco", + "website": "https://falco.org/", + "docs": "https://falco.org/docs/" + }, + "tags": ["security", "monitoring"] +} diff --git a/blueprints/falco/template.toml b/blueprints/falco/template.toml new file mode 100644 index 000000000..f23109fa4 --- /dev/null +++ b/blueprints/falco/template.toml @@ -0,0 +1,23 @@ +[variables] +main_domain = "${domain}" +ui_password = "${hash:64}" +redis_password = "${hash:64}" + +[config] +env = [ + # Name shown for this host in alerts. + "MONITORED_HOST=dokploy", + # Optional Telegram notifications; leave both credentials empty to disable. + "TELEGRAM_BOT_TOKEN=", + "TELEGRAM_CHAT_ID=", + "TELEGRAM_MINIMUM_PRIORITY=notice", + "FALCO_UI_USER=admin", + "FALCO_UI_PASSWORD=${ui_password}", + "FALCO_REDIS_PASSWORD=${redis_password}" +] +mounts = [] + +[[config.domains]] +serviceName = "falco-ui" +port = 2802 +host = "${main_domain}" diff --git a/blueprints/falco/testing.md b/blueprints/falco/testing.md new file mode 100644 index 000000000..350c6f438 --- /dev/null +++ b/blueprints/falco/testing.md @@ -0,0 +1,49 @@ +# Falco template validation + +Tested on 2026-10-18 with Dokploy `v0.30.8`, Traefik `v3.6.7`, Linux `7.0.0-34-generic`, Docker Engine `29.9.1`, and Docker Compose `5.6.0`, using the exact image tags in this template. Dokploy and the deployment Docker engine ran in a disposable local test environment. + +## Checks performed + +- Passed `node build-scripts/generate-meta.js --check`, the repository's Compose and TOML validators, and `pnpm build` in `app/`. +- Downloaded the Compose and TOML files from the PR preview and imported their Base64 payload through Dokploy's `compose.import` API. Repeated the import with the corrected template configuration. +- Confirmed that Dokploy generated distinct 64-character hexadecimal UI and Redis passwords and a domain mapped to `falco-ui:2802`. The template uses `${hash:64}` because Dokploy's helper takes a character count; the template repository's local helper has different length semantics. +- Enabled isolated deployments and deployed through Dokploy's `compose.deploy` API. The deployment finished successfully. +- Passed `docker compose config --quiet` with Telegram credentials absent or populated. Empty values for the required monitored hostname and UI and Redis passwords failed with the expected error. +- Started all four services and confirmed Falco opened the syscall source with the modern BPF probe. +- Requested the generated domain through Traefik and received HTTP 200 from the dashboard and health endpoint. The events API returned HTTP 401 without credentials or with an incorrect password, and HTTP 200 with the generated credentials. +- Logged in through Traefik using Chrome and the generated UI credentials, opened the Events tab, and observed a newly triggered container-shell event. No JavaScript errors occurred after authentication. +- Confirmed Redis returned `NOAUTH` without a password and `PONG` with its configured password. Its module list contained exactly RediSearch and RedisJSON; startup no longer logged errors for the unused RedisGears/RedisAI integration. +- Sent synthetic Notice and Informational events to Falcosidekick. Both appeared in the UI; only Notice reached a local mock Telegram API at the default `notice` threshold. +- Confirmed stored events had a seven-day TTL and remained searchable after stopping and starting the stack through Dokploy. The generated credentials and domain routing continued to work. +- Started a disposable container with a terminal shell. Falco detected the real **Terminal shell in container** event, and it reached both the UI and mock Telegram API with the configured hostname. +- Confirmed all four services stayed running with zero automatic restarts, used Dokploy's isolated network, and published no host ports. Falco's host bind mounts remained read-only. +- Checked service logs for unexpected runtime errors. Incorrect-password checks deliberately produced authentication errors in the UI log. + +The Dokploy test added a temporary `TELEGRAM_HOST` override to Falcosidekick to reach a local mock Telegram API. This override is not part of the template. The earlier standalone Compose test used temporary localhost port bindings; the Dokploy deployment published no service ports and was accessed through Traefik. No real Telegram credentials or chat were used. + +Delivery to the real Telegram API and public HTTPS certificate issuance have not been verified. + +In a fresh browser session, the upstream UI probes `/api/v1/auth` before login and receives HTTP 401, which it logs as an Axios error. It also requests a missing `/img/icons/favicon-32x32.png` and receives HTTP 404. These upstream UI behaviors did not prevent login, dashboard rendering, or event display. + +## Optional Telegram checks + +After making Telegram optional, repeated the following checks directly with Docker Compose and the same pinned images: + +| Telegram credentials | Result | +| --- | --- | +| Both variables unset | All four services start; a real container-shell event reaches the UI; no Telegram output or requests. | +| Both values empty | Telegram stays disabled; events reach the UI without errors. | +| Bot token only | Telegram stays disabled; events reach the UI without errors. | +| Chat ID only | Telegram stays disabled; events reach the UI without errors. | +| Both values set | Telegram is enabled; Notice events reach the mock API and Informational events are filtered from Telegram while remaining visible in the UI. | + +Removed both credentials after enabling Telegram and confirmed it disabled cleanly. Stopped and started the stack with Telegram absent, then confirmed the real and synthetic events remained visible, Redis was healthy, and all four services were running with zero automatic restarts. Temporary test resources were removed after validation. + +## Repeat the deployment checks in Dokploy + +1. Import the template from the PR preview, enable isolated deployments, and fill in the required values described in `instructions.md`. +2. Deploy and confirm that all four containers stay running, Redis is healthy, and the Falco logs show the modern BPF probe opening the syscall source. +3. Open the assigned UI domain and log in with the generated credentials. +4. Follow the container-shell check in `instructions.md`. Confirm the event appears in Falco's logs and the dashboard with the configured hostname. Check the Telegram chat if Telegram is configured. +5. Stop and start the Compose service without deleting its volume. Confirm the event remains visible and the generated credentials still work. +6. Optionally configure both Telegram credentials, redeploy, and repeat the event check to verify Telegram delivery. diff --git a/blueprints/firefly-iii/meta.json b/blueprints/firefly-iii/meta.json index d31960eab..c17ed1a6f 100644 --- a/blueprints/firefly-iii/meta.json +++ b/blueprints/firefly-iii/meta.json @@ -2,7 +2,7 @@ "id": "firefly-iii", "name": "Firefly III", "version": "6.6.3", - "description": "Firefly III is a self-hosted personal finance manager for tracking expenses, budgets, accounts, and financial reports.", + "description": "Firefly III is a personal finance manager for tracking budgets and expenses. Self-host it on your own server with Dokploy, which deploys Firefly III as a Docker Compose service in a few clicks.", "logo": "logo.svg", "links": { "github": "https://github.com/firefly-iii/firefly-iii", @@ -13,5 +13,8 @@ "finance", "budgeting", "self-hosted" - ] + ], + "seoTitle": "Firefly III Docker Compose Setup", + "seoDescription": "Personal finance manager for budgets and expenses. Deploy Firefly III with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/formbricks/meta.json b/blueprints/formbricks/meta.json index e2d537169..d5ab3b0ab 100644 --- a/blueprints/formbricks/meta.json +++ b/blueprints/formbricks/meta.json @@ -2,7 +2,7 @@ "id": "formbricks", "name": "Formbricks", "version": "v3.1.3", - "description": "Formbricks is an open-source survey and form platform for collecting user data.", + "description": "Formbricks is an open-source survey platform for collecting user and customer feedback. Self-host it on your own server with Dokploy, which deploys Formbricks as a Docker Compose service in a few clicks.", "logo": "formbricks.png", "links": { "github": "https://github.com/formbricks/formbricks", @@ -12,5 +12,7 @@ "tags": [ "forms", "analytics" - ] + ], + "seoTitle": "Self-Host Formbricks in minutes", + "seoDescription": "Survey and form platform for collecting feedback. Self-host Formbricks on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/freshrss/meta.json b/blueprints/freshrss/meta.json index 703984ef6..ea5a41aca 100644 --- a/blueprints/freshrss/meta.json +++ b/blueprints/freshrss/meta.json @@ -2,7 +2,7 @@ "id": "freshrss", "name": "FreshRSS", "version": "latest", - "description": "A free, self-hostable RSS and Atom feed aggregator. Lightweight, easy to work with, powerful, and customizable with themes and extensions.", + "description": "FreshRSS is a lightweight RSS and Atom feed aggregator. Self-host it on your own server with Dokploy, which deploys FreshRSS as a Docker Compose service in a few clicks.", "logo": "freshrss.svg", "links": { "github": "https://github.com/FreshRSS/FreshRSS", @@ -16,5 +16,8 @@ "self-hosted", "aggregator", "reader" - ] + ], + "seoTitle": "FreshRSS Docker Compose Setup", + "seoDescription": "Lightweight RSS and Atom feed reader. Deploy FreshRSS with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/ghost/meta.json b/blueprints/ghost/meta.json index 2973f00f3..b3f6c5f08 100644 --- a/blueprints/ghost/meta.json +++ b/blueprints/ghost/meta.json @@ -2,7 +2,7 @@ "id": "ghost", "name": "Ghost", "version": "6.0.0", - "description": "Ghost is a free and open source, professional publishing platform built on a modern Node.js technology stack.", + "description": "Ghost is an open-source publishing platform for blogs, newsletters, and paid memberships. Self-host it on your own server with Dokploy, which deploys Ghost as a Docker Compose service in a few clicks.", "logo": "ghost.jpeg", "links": { "github": "https://github.com/TryGhost/Ghost", @@ -11,5 +11,8 @@ }, "tags": [ "cms" - ] + ], + "seoTitle": "Ghost Docker Compose Setup", + "seoDescription": "Publishing platform for blogs and newsletters. Deploy Ghost with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/gitea-postgres/meta.json b/blueprints/gitea-postgres/meta.json index 6d5922e60..0c6f5c51f 100644 --- a/blueprints/gitea-postgres/meta.json +++ b/blueprints/gitea-postgres/meta.json @@ -2,7 +2,7 @@ "id": "gitea-postgres", "name": "Gitea (PostgreSQL)", "version": "1.27.3", - "description": "Gitea bundled with PostgreSQL.", + "description": "Gitea is a lightweight Git service for hosting code repositories. Self-host it on your own server with Dokploy, which deploys Gitea as a Docker Compose service in a few clicks.", "logo": "gitea.png", "links": { "github": "https://github.com/go-gitea/gitea", @@ -15,5 +15,8 @@ "postgres", "developer-tools", "self-hosted" - ] + ], + "seoTitle": "Gitea Docker Compose Setup", + "seoDescription": "Lightweight self-hosted Git service. Deploy Gitea with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/gitlab-ce/meta.json b/blueprints/gitlab-ce/meta.json index 4ca3e9599..e192a5bf2 100644 --- a/blueprints/gitlab-ce/meta.json +++ b/blueprints/gitlab-ce/meta.json @@ -2,7 +2,7 @@ "id": "gitlab-ce", "name": "GitLab CE", "version": "19.1.2", - "description": "GitLab Community Edition is a free and open source platform for managing Git repositories, CI/CD pipelines, and project management.", + "description": "GitLab CE is a platform for Git repositories, CI/CD, and project management. Self-host it on your own server with Dokploy, which deploys GitLab CE as a Docker Compose service in a few clicks.", "logo": "gitlab-ce.svg", "links": { "github": "https://gitlab.com/gitlab-org/gitlab-ce", @@ -14,5 +14,7 @@ "ci-cd", "version-control", "project-management" - ] + ], + "seoTitle": "Self-Host GitLab CE in minutes", + "seoDescription": "Git repos, CI/CD, and project management. Self-host GitLab CE on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/glances/meta.json b/blueprints/glances/meta.json index cad61cb2c..f2d549b50 100644 --- a/blueprints/glances/meta.json +++ b/blueprints/glances/meta.json @@ -2,7 +2,7 @@ "id": "glances", "name": "Glances", "version": "4.3.0.8", - "description": "Glances is a cross-platform system monitoring tool written in Python.", + "description": "Glances is a cross-platform system monitoring tool. Self-host it on your own server with Dokploy, which deploys Glances as a Docker Compose service in a few clicks.", "logo": "glances.svg", "links": { "github": "https://github.com/nicolargo/glances", @@ -11,5 +11,8 @@ }, "tags": [ "monitoring" - ] + ], + "seoTitle": "Glances Docker Compose Setup", + "seoDescription": "Cross-platform system monitoring tool. Deploy Glances with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/grafana/meta.json b/blueprints/grafana/meta.json index 4a0480014..891e927ba 100644 --- a/blueprints/grafana/meta.json +++ b/blueprints/grafana/meta.json @@ -2,7 +2,7 @@ "id": "grafana", "name": "Grafana", "version": "12.4", - "description": "Grafana is an open source platform for data visualization and monitoring.", + "description": "Grafana is an open-source dashboard tool for monitoring and visualizing data. Self-host it on your own server with Dokploy, which deploys Grafana as a Docker Compose service in a few clicks.", "logo": "grafana.svg", "links": { "github": "https://github.com/grafana/grafana", @@ -11,5 +11,8 @@ }, "tags": [ "monitoring" - ] + ], + "seoTitle": "Grafana Docker Compose Setup", + "seoDescription": "Dashboards for monitoring and data visualization. Deploy Grafana with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/habitica/meta.json b/blueprints/habitica/meta.json index 06d71fe15..235509c91 100644 --- a/blueprints/habitica/meta.json +++ b/blueprints/habitica/meta.json @@ -2,7 +2,7 @@ "id": "habitica", "name": "Habitica", "version": "latest", - "description": "Habitica is a free habit and productivity app that treats your real life like a game. With in-game rewards and punishments to motivate you and a strong social network to inspire you, Habitica can help you achieve your goals to become healthy and hard-working.", + "description": "Habitica is a habit and productivity tracker that works like a role-playing game. Self-host it on your own server with Dokploy, which deploys Habitica as a Docker Compose service in a few clicks.", "logo": "image.png", "links": { "github": "https://github.com/HabitRPG/habitica", @@ -14,5 +14,7 @@ "gamification", "habits", "self-hosted" - ] + ], + "seoTitle": "Self-Host Habitica in minutes", + "seoDescription": "Gamified habit and productivity tracker. Self-host Habitica on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/harbor/.dockerignore b/blueprints/harbor/.dockerignore new file mode 100644 index 000000000..08bafe228 --- /dev/null +++ b/blueprints/harbor/.dockerignore @@ -0,0 +1,9 @@ +* +!Dockerfile +!harbor/ +!harbor/*.py +!images.lock.json +!vendor/ +!vendor/harbor.yml.tmpl +!tests/ +!tests/*.py diff --git a/blueprints/harbor/.gitattributes b/blueprints/harbor/.gitattributes new file mode 100644 index 000000000..fcadb2cf9 --- /dev/null +++ b/blueprints/harbor/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/blueprints/harbor/Dockerfile b/blueprints/harbor/Dockerfile new file mode 100644 index 000000000..5a5ff8811 --- /dev/null +++ b/blueprints/harbor/Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 +ARG PREPARE_IMAGE=goharbor/prepare:v2.15.4@sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317 +FROM ${PREPARE_IMAGE} AS runtime +USER root +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 +COPY harbor/common.py harbor/initialize.py /app/ +COPY images.lock.json /app/images.lock.json +COPY vendor/harbor.yml.tmpl /app/harbor.yml.tmpl +ENTRYPOINT ["python3", "/app/initialize.py"] + +FROM runtime AS test +COPY tests /tests +RUN python3 -m unittest discover -s /tests -v diff --git a/blueprints/harbor/VALIDATION.md b/blueprints/harbor/VALIDATION.md new file mode 100644 index 000000000..86f479ba6 --- /dev/null +++ b/blueprints/harbor/VALIDATION.md @@ -0,0 +1,49 @@ +# Harbor validation record + +Date: 2026-10-20. Harbor: 2.15.4. Docker server: Linux, Engine 29.6.2. +Init source: `fc224d78800ffad4cd2a21ecd08f24c6fa122b82`. + +## Passed + +- All 573 template metadata entries validated with the upstream metadata checker. +- Upstream Compose and template validators accepted the Harbor blueprint. +- YAML and TOML parsing, generated Compose consistency, vendor checksum, + immutable Git source and local source/commit consistency checks passed. +- The Base64 import payload matches the catalogue's `{ compose, config }` format. +- Docker built the init image and ran all 14 Python unit tests successfully. +- After publication, Docker fetched the pinned source commit from the public + GitHub repository and built the runtime init image successfully. +- A fresh isolated Compose deployment completed init and reached healthy state + for all workloads, including Trivy. +- Harbor health, admin login, registry authentication challenge and public token + URL were verified using the deployment's domain as the HTTP Host header. +- A private Harbor project and signing/encryption keys survived restart and + forced container recreation. +- Explicit reconfiguration refused running workloads; changing worker count + with workloads stopped preserved the existing keys and private project. +Integration tests use a unique random `harbor-ci-*` instance and remove only +their own containers, volumes and default network. + +## Remaining deployment acceptance checks + +- Actual Dokploy Base64 import has not been exercised. Local integration builds + the same pinned support files from disk; remote Git building was verified + separately after publication. +- Public DNS, Let's Encrypt certificate issuance and Dokploy's generated + network/domain configuration require verification on the target server. +- End-to-end Docker login, push/pull over trusted HTTPS and a complete Trivy + vulnerability scan require the deployed domain and vulnerability DB download. +- No production data migration or Harbor version upgrade was attempted. + +See [instructions.md](instructions.md) for deployment acceptance steps. + +## Reproduce local checks + +```sh +node build-scripts/generate-meta.js --check +node blueprints/harbor/tools/check.mjs +docker build --target test -t harbor-template-init-test:local blueprints/harbor +node blueprints/harbor/tools/integration.mjs +``` + +Install the existing `app` dependencies with pnpm before running `check.mjs`. diff --git a/blueprints/harbor/docker-compose.yml b/blueprints/harbor/docker-compose.yml new file mode 100644 index 000000000..8b92ae416 --- /dev/null +++ b/blueprints/harbor/docker-compose.yml @@ -0,0 +1,838 @@ +# Generated by tools/render-compose.mjs from images.lock.json. +"services": + "harbor-init": + "build": + "context": "https://github.com/XnCN/harbor-dokploy-template.git#fc224d78800ffad4cd2a21ecd08f24c6fa122b82:blueprints/harbor" + "dockerfile": "Dockerfile" + "target": "runtime" + "args": + "PREPARE_IMAGE": "goharbor/prepare:v2.15.4@sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317" + "platform": "linux/amd64" + "restart": "no" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "init" + "traefik.enable": "false" + "environment": + "HARBOR_INSTANCE_ID": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "HARBOR_HOSTNAME": "${HARBOR_HOSTNAME:?Set HARBOR_HOSTNAME in Dokploy Environment}" + "HARBOR_ADMIN_PASSWORD": "${HARBOR_ADMIN_PASSWORD:?Set HARBOR_ADMIN_PASSWORD in Dokploy Environment}" + "HARBOR_DB_PASSWORD": "${HARBOR_DB_PASSWORD:?Set HARBOR_DB_PASSWORD in Dokploy Environment}" + "DB_MAX_IDLE_CONNS": "${DB_MAX_IDLE_CONNS:-10}" + "DB_MAX_OPEN_CONNS": "${DB_MAX_OPEN_CONNS:-100}" + "POSTGRES_MAX_CONNECTIONS": "${POSTGRES_MAX_CONNECTIONS:-256}" + "JOB_MAX_WORKERS": "${JOB_MAX_WORKERS:-5}" + "HARBOR_LOG_LEVEL": "${HARBOR_LOG_LEVEL:-info}" + "CACHE_ENABLED": "${CACHE_ENABLED:-true}" + "ALLOW_PRIVATE_NETWORK_ACCESS": "${ALLOW_PRIVATE_NETWORK_ACCESS:-false}" + "TRIVY_GITHUB_TOKEN": "${TRIVY_GITHUB_TOKEN:-}" + "TRIVY_DB_REPOSITORY": "${TRIVY_DB_REPOSITORY:-ghcr.io/aquasecurity/trivy-db}" + "TRIVY_JAVA_DB_REPOSITORY": "${TRIVY_JAVA_DB_REPOSITORY:-ghcr.io/aquasecurity/trivy-java-db}" + "HARBOR_HTTP_PROXY": "${HARBOR_HTTP_PROXY:-}" + "HARBOR_HTTPS_PROXY": "${HARBOR_HTTPS_PROXY:-}" + "HARBOR_NO_PROXY": "${HARBOR_NO_PROXY:-}" + "PROJECT_QUOTA_GIB": "${PROJECT_QUOTA_GIB:-20}" + "volumes": + - + "type": "volume" + "source": "config" + "target": "/config" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "data" + "target": "/data" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "database" + "target": "/data/database" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "registry" + "target": "/data/registry" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "redis" + "target": "/data/redis" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "jobs" + "target": "/data/job_logs" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "trivy-cache" + "target": "/data/trivy-adapter/trivy" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "trivy-reports" + "target": "/data/trivy-adapter/reports" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "control" + "target": "/control" + "read_only": false + "volume": + "nocopy": true + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "DAC_OVERRIDE" + - "FOWNER" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${INIT_MEMORY_LIMIT:-768m}" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "core": + "image": "goharbor/harbor-core:v2.15.4@sha256:7d2ba5304a729887cdec7cb222a82b500278423de230ae5150c386a6c332b112" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/etc/core/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/etc/core" + "read_only": true + "volume": + "nocopy": true + "subpath": "core" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "data" + "target": "/data" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "postgresql": + "condition": "service_healthy" + "redis": + "condition": "service_healthy" + "registry": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${CORE_MEMORY_LIMIT:-1g}" + "cpus": "${CORE_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "postgresql": + "image": "goharbor/harbor-db:v2.15.4@sha256:a32d51e636d76303e086a7c5251a2c8415c6f165b2f87bafa914efd866c5403f" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/run/harbor-config/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/run/harbor-config" + "read_only": true + "volume": + "nocopy": true + "subpath": "db" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "database" + "target": "/var/lib/postgresql/data" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + - "DAC_OVERRIDE" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${POSTGRESQL_MEMORY_LIMIT:-1536m}" + "cpus": "${POSTGRESQL_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "120s" + "test": + - "CMD" + - "/bin/sh" + - "/run/harbor-config/health.sh" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "shm_size": "256m" + "environment": + "POSTGRES_MAX_CONNECTIONS": "${POSTGRES_MAX_CONNECTIONS:-256}" + "redis": + "image": "goharbor/valkey-photon:v2.15.4@sha256:bb022e8f236f56f53cee7bb1f609595e497da67d829048e0af2284f97e6eb54f" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/run/harbor-config/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/run/harbor-config" + "read_only": true + "volume": + "nocopy": true + "subpath": "redis" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "redis" + "target": "/var/lib/redis" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${REDIS_MEMORY_LIMIT:-256m}" + "cpus": "${REDIS_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "registry": + "image": "goharbor/registry-photon:v2.15.4@sha256:dc0cb388a6445c69c8a0c406679290d2991c9698833d9e6e86a23bdfaf2f83c3" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/etc/registry/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/etc/registry" + "read_only": true + "volume": + "nocopy": true + "subpath": "registry" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "registry" + "target": "/storage" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "redis": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${REGISTRY_MEMORY_LIMIT:-768m}" + "cpus": "${REGISTRY_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "registryctl": + "image": "goharbor/harbor-registryctl:v2.15.4@sha256:30bd1ace4e3bb390d75b84673f98ddb4a69fcc6d116932ea764391df01943fb7" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/etc/registryctl/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/etc/registryctl" + "read_only": true + "volume": + "nocopy": true + "subpath": "registryctl" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "registry" + "target": "/storage" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/etc/registry" + "read_only": true + "volume": + "nocopy": true + "subpath": "registry" + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "registry": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${REGISTRYCTL_MEMORY_LIMIT:-128m}" + "cpus": "${REGISTRYCTL_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "jobservice": + "image": "goharbor/harbor-jobservice:v2.15.4@sha256:f143578ef30e2be5c48873298257ed0fca31545025958c534e536a06765d2b82" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/etc/jobservice/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/etc/jobservice" + "read_only": true + "volume": + "nocopy": true + "subpath": "jobservice" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "jobs" + "target": "/var/log/jobs" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "core": + "condition": "service_healthy" + "redis": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${JOBSERVICE_MEMORY_LIMIT:-512m}" + "cpus": "${JOBSERVICE_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "portal": + "image": "goharbor/harbor-portal:v2.15.4@sha256:01edf478505b21dba23a83c095f6ecac0c1e705690093edbab41e9a4d6754a75" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/run/harbor-config/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/run/harbor-config" + "read_only": true + "volume": + "nocopy": true + "subpath": "portal" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${PORTAL_MEMORY_LIMIT:-128m}" + "cpus": "${PORTAL_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "proxy": + "image": "goharbor/nginx-photon:v2.15.4@sha256:7eff0287d5ad0f35754e162c8481ced04bbde107153ce586cefb15ee74fa1311" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "true" + "entrypoint": + - "/bin/sh" + - "/etc/nginx/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/etc/nginx" + "read_only": true + "volume": + "nocopy": true + "subpath": "nginx" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "core": + "condition": "service_healthy" + "portal": + "condition": "service_healthy" + "registry": + "condition": "service_healthy" + "exporter": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${PROXY_MEMORY_LIMIT:-256m}" + "cpus": "${PROXY_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "expose": + - "8080" + "trivy-adapter": + "image": "goharbor/trivy-adapter-photon:v2.15.4@sha256:813ca81b4a4ea0f44f5fa81cca2645eeb58d4b1f6c66b428df9d506174bb71d9" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/run/harbor-config/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/run/harbor-config" + "read_only": true + "volume": + "nocopy": true + "subpath": "trivy-adapter" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + - + "type": "volume" + "source": "trivy-cache" + "target": "/home/scanner/.cache/trivy" + "read_only": false + "volume": + "nocopy": true + - + "type": "volume" + "source": "trivy-reports" + "target": "/home/scanner/.cache/reports" + "read_only": false + "volume": + "nocopy": true + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "redis": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${TRIVY_ADAPTER_MEMORY_LIMIT:-1536m}" + "cpus": "${TRIVY_ADAPTER_CPU_LIMIT:-2.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" + "exporter": + "image": "goharbor/harbor-exporter:v2.15.4@sha256:9ffede33dc519b4be035596a72f00f354754a22a3769077731f81ee7d6852f4f" + "platform": "linux/amd64" + "restart": "unless-stopped" + "labels": + "io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}" + "io.harbor-dokploy.role": "workload" + "traefik.enable": "false" + "entrypoint": + - "/bin/sh" + - "/run/harbor-config/launch.sh" + "command": [] + "volumes": + - + "type": "volume" + "source": "config" + "target": "/run/harbor-config" + "read_only": true + "volume": + "nocopy": true + "subpath": "exporter" + - + "type": "volume" + "source": "control" + "target": "/run/harbor-control" + "read_only": true + "volume": + "nocopy": true + - + "type": "volume" + "source": "config" + "target": "/harbor_cust_cert" + "read_only": true + "volume": + "nocopy": true + "subpath": "shared/trust-certificates" + "depends_on": + "harbor-init": + "condition": "service_completed_successfully" + "core": + "condition": "service_healthy" + "postgresql": + "condition": "service_healthy" + "redis": + "condition": "service_healthy" + "cap_drop": + - "ALL" + "cap_add": + - "CHOWN" + - "SETGID" + - "SETUID" + "security_opt": + - "no-new-privileges:true" + "mem_limit": "${EXPORTER_MEMORY_LIMIT:-128m}" + "cpus": "${EXPORTER_CPU_LIMIT:-1.0}" + "stop_grace_period": "120s" + "healthcheck": + "interval": "15s" + "timeout": "10s" + "retries": 10 + "start_period": "60s" + "test": + - "CMD-SHELL" + - "curl --fail -s http://localhost:8080/metrics >/dev/null" + "logging": + "driver": "local" + "options": + "max-size": "${DOCKER_LOG_MAX_SIZE:-20m}" + "max-file": "${DOCKER_LOG_MAX_FILES:-5}" +"volumes": + "data": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-data" + "database": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-database" + "registry": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-registry" + "redis": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-redis" + "jobs": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-jobs" + "config": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-config" + "trivy-cache": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-trivy-cache" + "trivy-reports": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-trivy-reports" + "control": + "name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-control" diff --git a/blueprints/harbor/harbor.svg b/blueprints/harbor/harbor.svg new file mode 100644 index 000000000..682254c30 --- /dev/null +++ b/blueprints/harbor/harbor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/blueprints/harbor/harbor/common.py b/blueprints/harbor/harbor/common.py new file mode 100644 index 000000000..a5984c070 --- /dev/null +++ b/blueprints/harbor/harbor/common.py @@ -0,0 +1,77 @@ +"""Shared validation and persistent state. No Docker or network side effects on import.""" +import contextlib +import fcntl +import json +import os +from pathlib import Path +import re +import tempfile + +class ConfigError(ValueError): + pass + +def boolean(env, name, default=False): + value = env.get(name, str(default)).lower() + if value not in ('true', 'false'): + raise ConfigError(f'{name} must be true or false') + return value == 'true' + +def text(env, name, default='', required=False): + value = env.get(name, default) + if any(c in value for c in ('\n', '\r', '\x00')): + raise ConfigError(f'{name} must be a single line') + if required and not value: + raise ConfigError(f'{name} is required') + return value + +def integer(env, name, default, minimum=1, maximum=100000): + try: + value = int(env.get(name, str(default))) + except ValueError: + raise ConfigError(f'{name} must be an integer') from None + if not minimum <= value <= maximum: + raise ConfigError(f'{name} must be between {minimum} and {maximum}') + return value + +def settings(env): + instance = text(env, 'HARBOR_INSTANCE_ID', required=True) + if not re.fullmatch(r'[a-z][a-z0-9-]{2,47}', instance): + raise ConfigError('HARBOR_INSTANCE_ID: use 3-48 lowercase letters, digits or hyphens') + host = text(env, 'HARBOR_HOSTNAME', required=True) + if (len(host) > 253 or '.' not in host or + not all(re.fullmatch(r'[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', p) for p in host.split('.')) or + re.fullmatch(r'[0-9.]+', host) or host in ('harbor.example.com', 'reg.mydomain.com')): + raise ConfigError('HARBOR_HOSTNAME must be your lowercase FQDN, without scheme, port or path') + return {'instance': instance, 'external_url': f'https://{host}'} + +def read_json(path): + return json.loads(Path(path).read_text()) + +def atomic_json(path, value, mode=0o600): + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(prefix='.' + path.name, dir=path.parent) + try: + with os.fdopen(fd, 'w') as stream: + os.fchmod(stream.fileno(), mode) + json.dump(value, stream, sort_keys=True, indent=2) + stream.write('\n') + stream.flush() + os.fsync(stream.fileno()) + os.replace(tmp, path) + finally: + Path(tmp).unlink(missing_ok=True) + +@contextlib.contextmanager +def operation_lock(directory): + directory = Path(directory) + directory.mkdir(parents=True, exist_ok=True) + with (directory / 'operation.lock').open('a') as stream: + try: + fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise ConfigError('Another initialization is running') from None + try: + yield None + finally: + fcntl.flock(stream, fcntl.LOCK_UN) diff --git a/blueprints/harbor/harbor/initialize.py b/blueprints/harbor/harbor/initialize.py new file mode 100644 index 000000000..26e0e1368 --- /dev/null +++ b/blueprints/harbor/harbor/initialize.py @@ -0,0 +1,237 @@ +"""Prepare Harbor with upstream tooling; only this container writes runtime configs.""" +import hashlib +import json +import os +from pathlib import Path +import platform +import re +import shlex +import socket +import subprocess +import sys +import tempfile + +from common import ConfigError, atomic_json, boolean, integer, operation_lock, read_json, settings, text + +APP = Path(__file__).resolve().parent +CONTROL = Path('/control') +DATA = Path('/data') +CONFIG = Path('/config') + +# Services consume only their own generated config directory. +SERVICES = { + 'core': ('core', '/etc/core', 'harbor-core', 10000), + 'postgresql': ('db', '/run/harbor-config', 'harbor-db', 999), + 'redis': ('redis', '/run/harbor-config', 'valkey-photon', 999), + 'registry': ('registry', '/etc/registry', 'registry-photon', 10000), + 'registryctl': ('registryctl', '/etc/registryctl', 'harbor-registryctl', 10000), + 'jobservice': ('jobservice', '/etc/jobservice', 'harbor-jobservice', 10000), + 'portal': ('portal', '/run/harbor-config', 'harbor-portal', 10000), + 'proxy': ('nginx', '/etc/nginx', 'nginx-photon', 10000), + 'trivy-adapter': ('trivy-adapter', '/run/harbor-config', 'trivy-adapter-photon', 10000), + 'exporter': ('exporter', '/run/harbor-config', 'harbor-exporter', 10000), +} + +def password(env, name): + value = text(env, name, required=True) + if len(value) < 20 or value in ('Harbor12345', 'root123'): + raise ConfigError(f'{name} must contain at least 20 characters') + return value + +def build_config(env, template): + # JSON serialization below is also valid YAML. Never interpolate secrets into YAML text. + conf = json.loads(json.dumps(template)) + conf['hostname'] = text(env, 'HARBOR_HOSTNAME', required=True) + conf['external_url'] = 'https://' + conf['hostname'] + conf.pop('https', None) + conf['http'] = {'port': 80} + conf['internal_tls'] = {'enabled': False} + conf['data_volume'] = '/data' + conf['harbor_admin_password'] = password(env, 'HARBOR_ADMIN_PASSWORD') + conf['database']['password'] = password(env, 'HARBOR_DB_PASSWORD') + conf['database']['max_idle_conns'] = integer(env, 'DB_MAX_IDLE_CONNS', 10, maximum=100) + conf['database']['max_open_conns'] = integer(env, 'DB_MAX_OPEN_CONNS', 100, maximum=500) + if conf['database']['max_idle_conns'] > conf['database']['max_open_conns']: + raise ConfigError('DB_MAX_IDLE_CONNS must not exceed DB_MAX_OPEN_CONNS') + db_limit = integer(env, 'POSTGRES_MAX_CONNECTIONS', 256, minimum=20, maximum=10000) + if conf['database']['max_open_conns'] * 2 + 10 > db_limit: + raise ConfigError('POSTGRES_MAX_CONNECTIONS must cover core/exporter pools (2 * DB_MAX_OPEN_CONNS + 10)') + conf['x-dokploy-database-connections'] = db_limit + conf['jobservice']['max_job_workers'] = integer(env, 'JOB_MAX_WORKERS', 5, maximum=100) + level = text(env, 'HARBOR_LOG_LEVEL', 'info') + if level not in ('debug', 'info', 'warning', 'error', 'fatal'): + raise ConfigError('Invalid HARBOR_LOG_LEVEL') + conf['log']['level'] = level + conf['log']['local']['location'] = '/data/log' + conf['metric'] = {'enabled': True, 'port': 9090, 'path': '/metrics'} + conf['cache'] = {'enabled': boolean(env, 'CACHE_ENABLED', True), 'expire_hours': 24} + conf['network'] = {'allow_private_network_access': boolean(env, 'ALLOW_PRIVATE_NETWORK_ACCESS', False)} + conf['content_trust'] = {'legacy_signer_pull_enabled': False} + conf['x-dokploy-security'] = {'auth_mode': 'db_auth', 'self_registration': False, + 'project_creation_restriction': 'adminonly', + 'storage_per_project': integer(env, 'PROJECT_QUOTA_GIB', 20, maximum=1048576) * 1024**3} + conf['trivy'].update({ + 'insecure': False, 'ignore_unfixed': False, 'skip_update': False, + 'skip_java_db_update': False, 'offline_scan': False, + 'github_token': text(env, 'TRIVY_GITHUB_TOKEN'), + 'db_repository': text(env, 'TRIVY_DB_REPOSITORY', 'ghcr.io/aquasecurity/trivy-db'), + 'java_db_repository': text(env, 'TRIVY_JAVA_DB_REPOSITORY', 'ghcr.io/aquasecurity/trivy-java-db'), + }) + conf['proxy'] = { + 'http_proxy': text(env, 'HARBOR_HTTP_PROXY'), 'https_proxy': text(env, 'HARBOR_HTTPS_PROXY'), + 'no_proxy': text(env, 'HARBOR_NO_PROXY'), 'components': ['core', 'jobservice', 'trivy'], + } + return conf + +def parse_literal_env(value): + """Upstream emits raw KEY=value, not shell syntax or Compose interpolation.""" + result = {} + for line in value.splitlines(): + if not line.strip() or line.lstrip().startswith('#'): + continue + key, sep, item = line.partition('=') + if not sep or not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', key): + raise ConfigError('Invalid environment key generated by upstream prepare') + if '\x00' in item: + raise ConfigError('NUL in generated environment') + result[key] = item + return result + +def launcher(environment, command): + if not command: + raise ConfigError('Missing upstream startup command') + lines = ['#!/bin/sh', 'set -eu', + 'if [ ! -f /run/harbor-control/ready ]; then', + ' echo "Harbor initialization incomplete" >&2; exit 75', 'fi'] + for key, value in environment.items(): + if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', key): + raise ConfigError('Invalid environment key') + lines.append('export ' + key + '=' + shlex.quote(value)) + lines.append('exec ' + ' '.join(shlex.quote(part) for part in command) + ' "$@"') + return '\n'.join(lines) + '\n' + +def owned_file(path, content, uid=10000, mode=0o600): + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + os.chown(path, uid, uid) + path.chmod(mode) + +def finalize(lock, conf): + # Match upstream's individual file mounts using directory subpaths instead. + for src, dst in ( + (DATA / 'secret/core/private_key.pem', CONFIG / 'core/private_key.pem'), + (DATA / 'secret/keys/secretkey', CONFIG / 'core/key'), + (DATA / 'secret/registry/root.crt', CONFIG / 'registry/root.crt'), + ): + owned_file(dst, src.read_text()) + for service, (folder, target, image, uid) in SERVICES.items(): + directory = CONFIG / folder + directory.mkdir(parents=True, exist_ok=True) + env_path = directory / 'env' + environment = parse_literal_env(env_path.read_text()) if env_path.exists() else {} + if service == 'core': + environment['CONFIG_OVERWRITE_JSON'] = json.dumps(conf['x-dokploy-security']) + if service == 'postgresql': + owned_file(directory / 'health.sh', launcher(environment, ['/docker-healthcheck.sh']), uid, 0o500) + image_conf = lock['images'][image] + command = image_conf['entrypoint'] + image_conf['command'] + owned_file(directory / 'launch.sh', launcher(environment, command), uid, 0o500) + # All files in one subdirectory are consumed by the same service UID. + for item in [directory, *directory.rglob('*')]: + if item.is_symlink(): + raise ConfigError('Unexpected symlink in generated configuration') + os.chown(item, uid, uid) + item.chmod(0o700 if item.is_dir() else (0o500 if item.name in ('launch.sh', 'health.sh') else 0o600)) + shared = CONFIG / 'shared/trust-certificates' + shared.mkdir(parents=True, exist_ok=True) + shared.chmod(0o755) + # Portal expects a single config file at this fixed path. + # Compose mounts the generated portal directory under /run/harbor-config. + portal = CONFIG / 'portal/launch.sh' + original = lock['images']['harbor-portal'] + owned_file(portal, launcher({}, original['entrypoint'] + ['nginx', '-c', '/run/harbor-config/nginx.conf', '-g', 'daemon off;']), 10000, 0o500) + + +def fingerprint(conf, lock): + content = json.dumps({'configuration': conf, 'images': lock}, sort_keys=True).encode() + for file in ('initialize.py', 'common.py'): + content += (APP / file).read_bytes() + return hashlib.sha256(content).hexdigest() + +def require_stopped_services(): + # An explicit reconfiguration must never rewrite configs of running workloads. + for host, port in [('core', 8080), ('postgresql', 5432), ('redis', 6379), + ('registry', 5000), ('registryctl', 8080), ('jobservice', 8080), + ('portal', 8080), ('proxy', 8080), ('trivy-adapter', 8080), ('exporter', 8080)]: + try: + connection = socket.create_connection((host, port), timeout=2) + except OSError: + continue + connection.close() + raise ConfigError('Stop all Harbor services before --reconfigure; reachable service: ' + host) + + +def check_persistent_state(old, runtime, digest, generated_complete, database_exists, secrets_complete, reconfigure): + if old and old['instance'] != runtime['instance']: + raise ConfigError('HARBOR_INSTANCE_ID must remain unchanged for existing volumes') + if (old or database_exists) and not secrets_complete: + raise ConfigError('Persistent encryption/signing keys are missing; restore the original data instead of regenerating keys') + if old and (old['fingerprint'] != digest or not generated_complete) and not reconfigure: + raise ConfigError('Configuration changed. Stop Harbor, then run harbor-init --reconfigure (see instructions.md)') + + +def main(reconfigure=False): + env = dict(os.environ) + runtime = settings(env) + if platform.machine() not in ('x86_64', 'AMD64'): + raise ConfigError('The locked Harbor distribution requires a Linux amd64 host') + lock = read_json(APP / 'images.lock.json') + runtime['harbor_version'] = lock['harbor_version'] + CONTROL.mkdir(parents=True, exist_ok=True) + CONTROL.chmod(0o755) + with operation_lock(CONTROL): + if reconfigure: + require_stopped_services() + import yaml + conf = build_config(env, yaml.safe_load((APP / 'harbor.yml.tmpl').read_text())) + digest = fingerprint(conf, lock) + state_file = DATA / 'render-state.json' + old = read_json(state_file) if state_file.exists() else None + required_secrets = [DATA / 'secret/keys/secretkey', DATA / 'secret/core/private_key.pem', DATA / 'secret/registry/root.crt'] + database_exists = any((DATA / 'database').iterdir()) + generated_complete = all((CONFIG / folder / 'launch.sh').is_file() for folder, _, _, _ in SERVICES.values()) + check_persistent_state(old, runtime, digest, generated_complete, database_exists, + all(item.is_file() for item in required_secrets), reconfigure) + if old and old['fingerprint'] == digest and generated_complete: + (CONTROL / 'ready').touch(mode=0o644) + print('Configuration unchanged; persistent secrets preserved') + return + (CONTROL / 'ready').unlink(missing_ok=True) + with tempfile.TemporaryDirectory() as temp: + config_input = Path(temp) / 'harbor.yml' + config_input.write_text(json.dumps(conf, indent=2)) + config_input.chmod(0o600) + Path('/compose_location').mkdir(parents=True, exist_ok=True) + # Prepare logs include paths only. Capture errors to avoid dumping secret-bearing configs. + result = subprocess.run(['python3', '/usr/src/app/main.py', 'prepare', '--with-trivy', '--conf', str(config_input)], + cwd='/usr/src/app', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode: + error_log = CONTROL / 'prepare-error.log' + error_log.write_bytes(result.stdout) + error_log.chmod(0o600) + raise ConfigError('Upstream prepare failed; configuration remains unready. Private details: control volume prepare-error.log') + (CONTROL / 'prepare-error.log').unlink(missing_ok=True) + finalize(lock, conf) + atomic_json(state_file, {'fingerprint': digest, 'harbor_version': lock['harbor_version'], 'instance': runtime['instance']}) + (CONTROL / 'ready').touch(mode=0o644) + print('Harbor configuration prepared') + +if __name__ == '__main__': + try: + if sys.argv[1:] not in ([], ['--reconfigure']): + raise ConfigError('Usage: initialize.py [--reconfigure]') + main(reconfigure=sys.argv[1:] == ['--reconfigure']) + except ConfigError as error: + print(str(error), file=sys.stderr) + sys.exit(1) diff --git a/blueprints/harbor/images.lock.json b/blueprints/harbor/images.lock.json new file mode 100644 index 000000000..0c5c52143 --- /dev/null +++ b/blueprints/harbor/images.lock.json @@ -0,0 +1,126 @@ +{ + "harbor_version": "v2.15.4", + "platform": "linux/amd64", + "verified_at": "2026-10-20", + "images": { + "prepare": { + "repository": "goharbor/prepare", + "tag": "v2.15.4", + "digest": "sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317", + "entrypoint": [ + "python3", + "main.py" + ], + "command": [], + "user": "root" + }, + "harbor-core": { + "repository": "goharbor/harbor-core", + "tag": "v2.15.4", + "digest": "sha256:7d2ba5304a729887cdec7cb222a82b500278423de230ae5150c386a6c332b112", + "entrypoint": [ + "/harbor/entrypoint.sh" + ], + "command": [], + "user": "harbor" + }, + "harbor-db": { + "repository": "goharbor/harbor-db", + "tag": "v2.15.4", + "digest": "sha256:a32d51e636d76303e086a7c5251a2c8415c6f165b2f87bafa914efd866c5403f", + "entrypoint": [ + "/docker-entrypoint.sh", + "15", + "18" + ], + "command": [], + "user": "postgres" + }, + "valkey-photon": { + "repository": "goharbor/valkey-photon", + "tag": "v2.15.4", + "digest": "sha256:bb022e8f236f56f53cee7bb1f609595e497da67d829048e0af2284f97e6eb54f", + "entrypoint": [], + "command": [ + "valkey-server", + "/etc/redis.conf" + ], + "user": "valkey" + }, + "registry-photon": { + "repository": "goharbor/registry-photon", + "tag": "v2.15.4", + "digest": "sha256:dc0cb388a6445c69c8a0c406679290d2991c9698833d9e6e86a23bdfaf2f83c3", + "entrypoint": [ + "/home/harbor/entrypoint.sh" + ], + "command": [], + "user": "harbor" + }, + "harbor-registryctl": { + "repository": "goharbor/harbor-registryctl", + "tag": "v2.15.4", + "digest": "sha256:30bd1ace4e3bb390d75b84673f98ddb4a69fcc6d116932ea764391df01943fb7", + "entrypoint": [ + "/home/harbor/start.sh" + ], + "command": [], + "user": "harbor" + }, + "harbor-jobservice": { + "repository": "goharbor/harbor-jobservice", + "tag": "v2.15.4", + "digest": "sha256:f143578ef30e2be5c48873298257ed0fca31545025958c534e536a06765d2b82", + "entrypoint": [ + "/harbor/entrypoint.sh" + ], + "command": [], + "user": "harbor" + }, + "harbor-portal": { + "repository": "goharbor/harbor-portal", + "tag": "v2.15.4", + "digest": "sha256:01edf478505b21dba23a83c095f6ecac0c1e705690093edbab41e9a4d6754a75", + "entrypoint": [], + "command": [ + "nginx", + "-g", + "daemon off;" + ], + "user": "nginx" + }, + "nginx-photon": { + "repository": "goharbor/nginx-photon", + "tag": "v2.15.4", + "digest": "sha256:7eff0287d5ad0f35754e162c8481ced04bbde107153ce586cefb15ee74fa1311", + "entrypoint": [], + "command": [ + "nginx", + "-g", + "daemon off;" + ], + "user": "nginx" + }, + "trivy-adapter-photon": { + "repository": "goharbor/trivy-adapter-photon", + "tag": "v2.15.4", + "digest": "sha256:813ca81b4a4ea0f44f5fa81cca2645eeb58d4b1f6c66b428df9d506174bb71d9", + "entrypoint": [ + "/home/scanner/entrypoint.sh" + ], + "command": [], + "user": "scanner" + }, + "harbor-exporter": { + "repository": "goharbor/harbor-exporter", + "tag": "v2.15.4", + "digest": "sha256:9ffede33dc519b4be035596a72f00f354754a22a3769077731f81ee7d6852f4f", + "entrypoint": [ + "/harbor/entrypoint.sh" + ], + "command": [], + "user": "harbor" + } + }, + "template_sha256": "0d3a9098005b202c28e5618c0082de271c0e4f726c27b6c475fc45e2a07c560a" +} diff --git a/blueprints/harbor/instructions.md b/blueprints/harbor/instructions.md new file mode 100644 index 000000000..f255e253d --- /dev/null +++ b/blueprints/harbor/instructions.md @@ -0,0 +1,87 @@ +# Harbor 2.15.4 with Trivy + +This template installs Harbor on one Linux amd64 Docker host. Start with 4 vCPU, +8 GB RAM and 160 GB SSD for Harbor, plus resources for Dokploy and other apps. +Docker Engine 27+ and Compose 2.35+ are required for named-volume subpaths. +Use Docker Compose mode, not Docker Stack/Swarm mode. + +## Before the first deployment + +1. Import Harbor from the template catalogue or paste its Base64 configuration + into **Compose → Advanced → Import**. +2. Keep the generated `HARBOR_INSTANCE_ID` and both generated passwords. + The instance ID is the permanent volume prefix; never reuse it for another + deployment. Re-importing generates new credentials and a new volume prefix. +3. Set the domain to a dedicated FQDN pointing to the Dokploy host. Ensure + `HARBOR_HOSTNAME` matches the domain exactly, without a scheme, port or path. + Change both before the first deployment if replacing the generated domain. +4. Confirm domain routing uses service **proxy**, port **8080**, path **/**, + HTTPS and your Let's Encrypt certificate. Enable HTTP-to-HTTPS redirection + in Dokploy. Do not add Basic Auth/ForwardAuth in front of Harbor. +5. Deploy. The init service must finish with exit code 0 before workloads start. + Log in at `https://YOUR_DOMAIN` as **admin**, with `HARBOR_ADMIN_PASSWORD`. + +Dokploy supplies network isolation and Traefik routing. Do not add host ports, +container names or a shared `dokploy-network`. Path-based hosting such as +`/harbor` is unsupported. TLS terminates at Dokploy; internal traffic is HTTP. + +## Registry and scanning + +Create projects and configure quotas, access, scan-on-push and retention in the +Harbor UI. The template does not automatically create projects or apply policies. +Trivy is registered as the default scanner; its initial vulnerability database +download may take time and needs outbound access to GHCR. Set +`TRIVY_GITHUB_TOKEN` if your environment needs an authenticated download. + +User self-registration is disabled. Only admins may create projects. Default +quota is 20 GiB per project; set `PROJECT_QUOTA_GIB` before installation to change +it. Advanced resource/configuration defaults are in `docker-compose.yml`. + +No automated backup, restore, scheduled maintenance, policy jobs or Docker socket +mounts are included. Arrange an independent backup of all persistent volumes. + +## Restart and configuration changes + +Regular redeploys preserve data and generated encryption/signing keys. Changing +the domain, init code, Harbor version or generated configuration requires an +explicit reconfiguration. Init fails clearly on unexpected changes. + +For a configuration change, use a terminal on the deployment host in the +Dokploy-generated Compose directory, with its existing Compose project name and +Environment values. The commands below assume `docker compose` already resolves +that exact deployment. Keep its volume prefix and volumes unchanged. + +1. Back up every persistent volume, then stop the deployment and scheduled redeploys. +2. Save the new Environment values and regenerate the Dokploy Compose file + without starting the workloads. +3. Stop all services, build the new init image, run init alone, then start Harbor: + +```sh +docker compose stop +docker compose build harbor-init +docker compose run --rm --no-deps harbor-init --reconfigure +docker compose up -d --wait --wait-timeout 600 +``` + +Do not use `down --volumes`. `--reconfigure` refuses reachable Harbor services +and preserves existing keys. It is not a database-version migration tool. +Do not change `HARBOR_DB_PASSWORD` for an existing database: changing the env +value does not change the database user's password. Similarly, changing +`HARBOR_ADMIN_PASSWORD` does not rotate the existing admin's password; use the UI. +Follow Harbor's official migration procedure before upgrading a populated install. + +Missing persistent keys are a fatal error. Restore the original complete data; +do not delete `render-state.json` or regenerate signing keys to bypass the check. +Prepare errors are captured privately as `prepare-error.log` in the control +volume and must be reviewed for sensitive data before sharing. + +## Acceptance checks on your Dokploy instance + +- Confirm the init service completed, all workloads are healthy and HTTPS is trusted. +- Log in to Harbor, create a private test project and enable scan-on-push. +- Run `docker login YOUR_DOMAIN`, push a small image and pull it back. +- Run a Trivy scan and confirm a report appears after the vulnerability DB downloads. +- Restart/redeploy and verify the project, image, credentials and signing keys persist. + +Local Docker tests do not validate a public domain, certificate issuance, a full +Trivy DB download or your Dokploy version's Compose/domain rewriting. diff --git a/blueprints/harbor/meta.json b/blueprints/harbor/meta.json new file mode 100644 index 000000000..111e42a7c --- /dev/null +++ b/blueprints/harbor/meta.json @@ -0,0 +1,13 @@ +{ + "id": "harbor", + "name": "Harbor", + "version": "2.15.4", + "description": "Harbor is an open source container and OCI artifact registry with access control and Trivy vulnerability scanning.", + "logo": "harbor.svg", + "links": { + "github": "https://github.com/goharbor/harbor", + "website": "https://goharbor.io/", + "docs": "https://goharbor.io/docs/" + }, + "tags": ["registry", "containers", "security"] +} diff --git a/blueprints/harbor/template.toml b/blueprints/harbor/template.toml new file mode 100644 index 000000000..c783e9c33 --- /dev/null +++ b/blueprints/harbor/template.toml @@ -0,0 +1,21 @@ +[variables] +main_domain = "${domain}" +instance_id = "harbor-${uuid}" +admin_password = "${password:32}" +db_password = "${password:32}" + +[config] +env = [ + "HARBOR_INSTANCE_ID=${instance_id}", + "HARBOR_HOSTNAME=${main_domain}", + "HARBOR_ADMIN_PASSWORD=${admin_password}", + "HARBOR_DB_PASSWORD=${db_password}" +] + +[[config.domains]] +serviceName = "proxy" +port = 8080 +host = "${main_domain}" +path = "/" +https = true +certificateType = "letsencrypt" diff --git a/blueprints/harbor/tests/test_configuration.py b/blueprints/harbor/tests/test_configuration.py new file mode 100644 index 000000000..b84c633e8 --- /dev/null +++ b/blueprints/harbor/tests/test_configuration.py @@ -0,0 +1,112 @@ +import shlex +import sys +import tempfile +import unittest +from unittest.mock import patch, Mock + +sys.path.insert(0, '/app') +from common import ConfigError, settings, operation_lock +from initialize import (build_config, check_persistent_state, launcher, parse_literal_env, + require_stopped_services) + +BASE = {'HARBOR_INSTANCE_ID': 'harbor-test', 'HARBOR_HOSTNAME': 'registry.company.test', + 'HARBOR_ADMIN_PASSWORD': 'admin-with-20-characters', + 'HARBOR_DB_PASSWORD': 'database-with-20-characters'} +TEMPLATE = {'database': {}, 'jobservice': {}, 'log': {'local': {}}, 'trivy': {}} + + +class ConfigurationTests(unittest.TestCase): + def test_minimal_configuration_needs_no_backup_credentials(self): + self.assertEqual(settings(BASE), {'instance': 'harbor-test', 'external_url': 'https://registry.company.test'}) + + def test_invalid_hosts_and_instances(self): + for host in ['localhost', '127.0.0.1', 'https://registry.company.test', 'registry.company.test:443', + 'registry.company.test/path', 'a..test', 'HOST.company.test', 'harbor.example.com']: + with self.subTest(host=host), self.assertRaises(ConfigError): + settings({**BASE, 'HARBOR_HOSTNAME': host}) + for instance in ['../data', 'harbor_prod', 'Xprod', 'ab']: + with self.subTest(instance=instance), self.assertRaises(ConfigError): + settings({**BASE, 'HARBOR_INSTANCE_ID': instance}) + + def test_password_validation(self): + for password in ['', 'Harbor12345', 'short', 'long-password-with\nnewline']: + with self.subTest(password=password), self.assertRaises(ConfigError): + build_config({**BASE, 'HARBOR_DB_PASSWORD': password}, TEMPLATE) + + def test_security_and_trivy_defaults(self): + conf = build_config(BASE, TEMPLATE) + self.assertEqual(conf['external_url'], 'https://registry.company.test') + self.assertNotIn('https', conf) + self.assertFalse(conf['x-dokploy-security']['self_registration']) + self.assertEqual(conf['x-dokploy-security']['project_creation_restriction'], 'adminonly') + self.assertFalse(conf['trivy']['skip_update']) + self.assertFalse(conf['trivy']['insecure']) + self.assertEqual(TEMPLATE['database'], {}) + + def test_special_characters_remain_literal(self): + secret = "space $TOKEN $(touch /tmp/injected) # \" ' \\ =" + parsed = parse_literal_env('PASSWORD=' + secret + '\nEMPTY=\nURL=a=b#c\n') + script = launcher(parsed, ['/some executable', 'literal;$HOME']) + line = next(line for line in script.splitlines() if line.startswith('export PASSWORD=')) + self.assertEqual(shlex.split(line), ['export', 'PASSWORD=' + secret]) + self.assertEqual(parsed['URL'], 'a=b#c') + self.assertNotIn('eval ', script) + + def test_invalid_upstream_env_is_rejected(self): + for value in ['BAD-KEY=x', 'export X=x', 'NO_EQUALS', 'KEY=bad\x00value']: + with self.subTest(value=value), self.assertRaises(ConfigError): + parse_literal_env(value) + + def test_connection_pool_bounds(self): + for extra in [{'DB_MAX_IDLE_CONNS': '11', 'DB_MAX_OPEN_CONNS': '10'}, + {'POSTGRES_MAX_CONNECTIONS': '100'}, {'DB_MAX_OPEN_CONNS': 'NaN'}]: + with self.subTest(extra=extra), self.assertRaises(ConfigError): + build_config({**BASE, **extra}, TEMPLATE) + + def test_unchanged_and_first_install_are_allowed(self): + runtime = settings(BASE) + check_persistent_state(None, runtime, 'digest', False, False, False, False) + check_persistent_state({'instance': 'harbor-test', 'fingerprint': 'digest'}, + runtime, 'digest', True, True, True, False) + + def test_changed_config_requires_explicit_reconfiguration(self): + old = {'instance': 'harbor-test', 'fingerprint': 'old'} + with self.assertRaisesRegex(ConfigError, '--reconfigure'): + check_persistent_state(old, settings(BASE), 'new', True, True, True, False) + check_persistent_state(old, settings(BASE), 'new', True, True, True, True) + + def test_missing_keys_never_regenerated_even_when_reconfiguring(self): + old = {'instance': 'harbor-test', 'fingerprint': 'old'} + for state in [None, old]: + for reconfigure in [False, True]: + with self.subTest(state=state, reconfigure=reconfigure), self.assertRaisesRegex(ConfigError, 'keys are missing'): + check_persistent_state(state, settings(BASE), 'new', False, True, False, reconfigure) + + def test_existing_volume_cannot_change_instance(self): + with self.assertRaisesRegex(ConfigError, 'INSTANCE_ID'): + check_persistent_state({'instance': 'other', 'fingerprint': 'old'}, + settings(BASE), 'new', True, True, True, True) + + @patch('initialize.socket.create_connection') + def test_reconfiguration_refuses_live_workloads(self, connect): + connection = Mock() + connect.return_value = connection + with self.assertRaisesRegex(ConfigError, 'Stop all Harbor'): + require_stopped_services() + connection.close.assert_called_once() + + @patch('initialize.socket.create_connection', side_effect=ConnectionRefusedError) + def test_stopped_workloads_allow_reconfiguration(self, connect): + require_stopped_services() + self.assertEqual(connect.call_count, 10) + + def test_concurrent_initialization_is_rejected(self): + with tempfile.TemporaryDirectory() as folder: + with operation_lock(folder): + with self.assertRaisesRegex(ConfigError, 'Another initialization'): + with operation_lock(folder): + self.fail('Concurrent initialization allowed') + + +if __name__ == '__main__': + unittest.main() diff --git a/blueprints/harbor/tools/build-source.json b/blueprints/harbor/tools/build-source.json new file mode 100644 index 000000000..c90800136 --- /dev/null +++ b/blueprints/harbor/tools/build-source.json @@ -0,0 +1,4 @@ +{ + "revision": "fc224d78800ffad4cd2a21ecd08f24c6fa122b82", + "repository": "https://github.com/XnCN/harbor-dokploy-template.git" +} diff --git a/blueprints/harbor/tools/check.mjs b/blueprints/harbor/tools/check.mjs new file mode 100644 index 000000000..899295a30 --- /dev/null +++ b/blueprints/harbor/tools/check.mjs @@ -0,0 +1,58 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import { execFileSync } from 'node:child_process'; +import { buildCompose, yaml } from './render-compose.mjs'; + +const root = new URL('../', import.meta.url); +const require = createRequire(new URL('../../app/package.json', root)); +const toml = require('@iarna/toml'); +const YAML = require('yaml'); +const read = async file => readFile(new URL(file, root), 'utf8'); +const lock = JSON.parse(await read('images.lock.json')); +const source = JSON.parse(await read('tools/build-source.json')); +const composeText = await read('docker-compose.yml'); +const compose = YAML.parse(composeText); +assert.deepEqual(compose, buildCompose(lock, source)); +assert.equal(composeText, '# Generated by tools/render-compose.mjs from images.lock.json.\n' + yaml(compose)); +assert.match(source.revision, /^[a-f0-9]{40}$/); +for (const file of ['Dockerfile', '.dockerignore', 'harbor/common.py', 'harbor/initialize.py', + 'images.lock.json', 'vendor/harbor.yml.tmpl']) { + const pinned = execFileSync('git', ['show', source.revision + ':blueprints/harbor/' + file], + { cwd: fileURLToPath(root), encoding: 'utf8' }); + assert.equal((await read(file)).replace(/\r\n/g, '\n'), pinned.replace(/\r\n/g, '\n'), + file + ': commit the updated init source and update tools/build-source.json'); +} +assert.equal(createHash('sha256').update(await read('vendor/harbor.yml.tmpl')).digest('hex'), lock.template_sha256); +assert.equal(Object.keys(compose.services).length, 11); +assert.ok(!compose.networks); +assert.ok(!compose.services.operations); +for (const [name, service] of Object.entries(compose.services)) { + for (const key of ['ports', 'networks', 'container_name', 'env_file', 'privileged']) assert.ok(!service[key], name + ': ' + key); + assert.ok(!JSON.stringify(service).match(/docker\.sock|BACKUP_|RESTIC_|POLICY_PROJECTS/)); + if (name === 'harbor-init') { + assert.equal(service.restart, 'no'); + assert.equal(service.build.context, source.repository + '#' + source.revision + ':blueprints/harbor'); + } else { + assert.match(service.image, /:v2\.15\.4@sha256:[a-f0-9]{64}$/); + assert.equal(service.depends_on['harbor-init'].condition, 'service_completed_successfully'); + } +} +const configText = await read('template.toml'); +const config = toml.parse(configText); +assert.equal(config.config.domains[0].serviceName, 'proxy'); +assert.equal(config.config.domains[0].port, 8080); +assert.equal(config.config.domains[0].https, true); +assert.ok(config.config.env.includes('HARBOR_HOSTNAME=${main_domain}')); +assert.notEqual(config.variables.admin_password, '${db_password}'); +// Match the catalogue's TemplateDialog payload exactly, including Unicode encoding. +const payload = { compose: composeText, config: configText }; +const encoded = Buffer.from(JSON.stringify(payload, null, 2)).toString('base64'); +assert.deepEqual(JSON.parse(Buffer.from(encoded, 'base64').toString('utf8')), payload); +const artifacts = new URL('../../.harbor-validation/', root); +await mkdir(artifacts, { recursive: true }); +await writeFile(new URL('harbor-import.base64.txt', artifacts), encoded + '\n'); +console.log('Harbor YAML, TOML, pinned build source, persistence, isolation and catalogue import checks passed.'); +console.log('Import artifact: ' + fileURLToPath(new URL('harbor-import.base64.txt', artifacts))); diff --git a/blueprints/harbor/tools/integration.mjs b/blueprints/harbor/tools/integration.mjs new file mode 100644 index 000000000..1f453a150 --- /dev/null +++ b/blueprints/harbor/tools/integration.mjs @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict'; +import { randomUUID } from 'node:crypto'; +import { mkdir, readFile, writeFile, rm } from 'node:fs/promises'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { buildCompose } from './render-compose.mjs'; + +const root = new URL('../', import.meta.url); +const id = 'harbor-ci-' + randomUUID(); +const folder = new URL('../../.harbor-validation/' + id + '/', root); +await mkdir(folder, { recursive: true }); +const filename = fileURLToPath(new URL('compose.json', folder)); +const lock = JSON.parse(await readFile(new URL('images.lock.json', root), 'utf8')); +const source = JSON.parse(await readFile(new URL('tools/build-source.json', root), 'utf8')); +const config = buildCompose(lock, source); +config.services['harbor-init'].build.context = fileURLToPath(root); +await writeFile(filename, JSON.stringify(config, null, 2)); +const env = { ...process.env, HARBOR_INSTANCE_ID: id, HARBOR_HOSTNAME: 'registry.company.test', + HARBOR_ADMIN_PASSWORD: 'test-admin-password-with-entropy', HARBOR_DB_PASSWORD: 'test-database-password-with-entropy' }; +const composeArgs = ['compose', '-p', id, '-f', filename]; +function run(args, capture = false, overrides = {}) { + const result = spawnSync('docker', [...composeArgs, ...args], { + env: { ...env, ...overrides }, encoding: 'utf8', stdio: capture ? 'pipe' : 'inherit', timeout: 900_000, + }); + if (result.status !== 0) throw new Error('Docker ' + args.slice(0, 3).join(' ') + ' failed: ' + (result.stderr || result.error || result.status)); + return (result.stdout || '').trim(); +} +const workloads = Object.keys(config.services).filter(name => name !== 'harbor-init'); +try { + run(['config', '--quiet']); + run(['up', '-d', '--build', '--wait', '--wait-timeout', '600']); + const health = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', 'http://localhost:8080/api/v2.0/health'], true)); + assert.equal(health.status, 'healthy'); + const challenge = run(['exec', '-T', 'proxy', 'curl', '-sSI', '-H', 'Host: registry.company.test', + '-H', 'X-Forwarded-Proto: https', 'http://localhost:8080/v2/'], true); + assert.match(challenge, /401/); + assert.match(challenge, /https:\/\/registry\.company\.test\/service\/token/); + const user = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD, + 'http://localhost:8080/api/v2.0/users/current'], true)); + assert.equal(user.username, 'admin'); + run(['exec', '-T', 'core', 'curl', '-fsS', 'http://trivy-adapter:8080/probe/healthy'], true); + run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD, + '-H', 'Content-Type: application/json', '-d', '{"project_name":"persistence-test","public":false}', + 'http://localhost:8080/api/v2.0/projects'], true); + const hashes = () => run(['exec', '-T', 'core', 'sha256sum', '/etc/core/key', '/etc/core/private_key.pem'], true); + const assertPersistent = before => { + assert.equal(hashes(), before, 'Signing/encryption keys must persist'); + const project = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD, + 'http://localhost:8080/api/v2.0/projects/persistence-test'], true)); + assert.equal(project.name, 'persistence-test', 'Database project must persist'); + }; + const before = hashes(); + const live = spawnSync('docker', [...composeArgs, 'run', '--rm', '--no-deps', 'harbor-init', '--reconfigure'], + { env, encoding: 'utf8', timeout: 60_000 }); + assert.notEqual(live.status, 0, 'Reconfiguration must refuse live services'); + assert.match(live.stdout + live.stderr, /Stop all Harbor/); + run(['restart', '--timeout', '20', ...workloads]); + run(['up', '-d', '--wait', '--wait-timeout', '600']); + assertPersistent(before); + run(['up', '-d', '--force-recreate', '--timeout', '20', '--wait', '--wait-timeout', '600']); + assertPersistent(before); + run(['stop', '--timeout', '20', ...workloads]); + run(['run', '--rm', '--no-deps', 'harbor-init', '--reconfigure'], false, { JOB_MAX_WORKERS: '6' }); + run(['up', '-d', '--wait', '--wait-timeout', '600'], false, { JOB_MAX_WORKERS: '6' }); + assertPersistent(before); + console.log('PASS: prepare, health, admin login, registry token URL, Trivy, restart, redeploy and reconfiguration.'); +} catch (error) { + run(['ps', '-a']); + run(['logs', '--no-color', '--tail', '35']); + throw error; +} finally { + // This file and every explicit volume name belong to this freshly generated CI ID only. + try { run(['down', '--timeout', '20', '--volumes', '--remove-orphans']); } + finally { await rm(folder, { recursive: true, force: true }); } +} diff --git a/blueprints/harbor/tools/render-compose.mjs b/blueprints/harbor/tools/render-compose.mjs new file mode 100644 index 000000000..4e5e4549a --- /dev/null +++ b/blueprints/harbor/tools/render-compose.mjs @@ -0,0 +1,105 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import path from 'node:path'; + +const repository = fileURLToPath(new URL('../', import.meta.url)); +const env = (name, fallback = '') => '$' + '{' + name + ':-' + fallback + '}'; +const required = name => '$' + '{' + name + ':?Set ' + name + ' in Dokploy Environment}'; +const mount = (source, target, subpath, readOnly = false) => ({ + type: 'volume', source, target, read_only: readOnly, + volume: { nocopy: true, ...(subpath ? { subpath } : {}) }, +}); +const runtimeConfigs = { + core: ['core', '/etc/core', 'harbor-core'], + postgresql: ['db', '/run/harbor-config', 'harbor-db'], + redis: ['redis', '/run/harbor-config', 'valkey-photon'], + registry: ['registry', '/etc/registry', 'registry-photon'], + registryctl: ['registryctl', '/etc/registryctl', 'harbor-registryctl'], + jobservice: ['jobservice', '/etc/jobservice', 'harbor-jobservice'], + portal: ['portal', '/run/harbor-config', 'harbor-portal'], + proxy: ['nginx', '/etc/nginx', 'nginx-photon'], + 'trivy-adapter': ['trivy-adapter', '/run/harbor-config', 'trivy-adapter-photon'], + exporter: ['exporter', '/run/harbor-config', 'harbor-exporter'], +}; +const memory = {core:'1g', postgresql:'1536m',redis:'256m',registry:'768m',registryctl:'128m',jobservice:'512m',portal:'128m',proxy:'256m','trivy-adapter':'1536m',exporter:'128m'}; +export function buildCompose(lock, buildSource) { + const image = name => { const item = lock.images[name]; return item.repository + ':' + item.tag + '@' + item.digest; }; + const labels = role => ({ 'io.harbor-dokploy.instance': required('HARBOR_INSTANCE_ID'), 'io.harbor-dokploy.role': role, 'traefik.enable':'false' }); + const logging = { driver:'local', options:{'max-size':env('DOCKER_LOG_MAX_SIZE','20m'),'max-file':env('DOCKER_LOG_MAX_FILES','5')} }; + const settingsEnv = { HARBOR_INSTANCE_ID:required('HARBOR_INSTANCE_ID'), HARBOR_HOSTNAME:required('HARBOR_HOSTNAME') }; + const configDefaults = { DB_MAX_IDLE_CONNS:'10', DB_MAX_OPEN_CONNS:'100',POSTGRES_MAX_CONNECTIONS:'256',JOB_MAX_WORKERS:'5',HARBOR_LOG_LEVEL:'info',CACHE_ENABLED:'true', + ALLOW_PRIVATE_NETWORK_ACCESS:'false',TRIVY_GITHUB_TOKEN:'',TRIVY_DB_REPOSITORY:'ghcr.io/aquasecurity/trivy-db', + TRIVY_JAVA_DB_REPOSITORY:'ghcr.io/aquasecurity/trivy-java-db',HARBOR_HTTP_PROXY:'',HARBOR_HTTPS_PROXY:'',HARBOR_NO_PROXY:'',PROJECT_QUOTA_GIB:'20' }; + const configEnv = { HARBOR_ADMIN_PASSWORD:required('HARBOR_ADMIN_PASSWORD'),HARBOR_DB_PASSWORD:required('HARBOR_DB_PASSWORD'), + ...Object.fromEntries(Object.entries(configDefaults).map(([k,v])=>[k,env(k,v)])) }; + const volumes = Object.fromEntries(['data','database','registry','redis','jobs','config','trivy-cache','trivy-reports','control'].map(name=>[name,{name:required('HARBOR_INSTANCE_ID')+'-'+name}])); + const initMounts = [mount('config','/config'),mount('data','/data'),mount('database','/data/database'),mount('registry','/data/registry'), + mount('redis','/data/redis'),mount('jobs','/data/job_logs'),mount('trivy-cache','/data/trivy-adapter/trivy'), + mount('trivy-reports','/data/trivy-adapter/reports'),mount('control','/control')]; + const services = { + 'harbor-init': { + build:{context:buildSource.repository+'#'+buildSource.revision+':blueprints/harbor',dockerfile:'Dockerfile',target:'runtime',args:{PREPARE_IMAGE:image('prepare')}}, + platform:lock.platform,restart:'no', labels:labels('init'), + environment:{...settingsEnv,...configEnv}, + volumes:initMounts, + cap_drop:['ALL'],cap_add:['CHOWN','DAC_OVERRIDE','FOWNER'],security_opt:['no-new-privileges:true'], + mem_limit:env('INIT_MEMORY_LIMIT','768m'),logging, + }, + }; + { + for (const [name,[folder,target,imageName]] of Object.entries(runtimeConfigs)) { + const variable = name.replaceAll('-','_').toUpperCase(); + services[name] = { + image:image(imageName),platform:lock.platform,restart:'unless-stopped',labels:labels('workload'), + entrypoint:['/bin/sh',target+'/launch.sh'],command:[], + volumes:[mount('config',target,folder,true),mount('control','/run/harbor-control',undefined,true), + mount('config','/harbor_cust_cert','shared/trust-certificates',true)], + depends_on:{'harbor-init':{condition:'service_completed_successfully'}}, + cap_drop:['ALL'],cap_add:['CHOWN','SETGID','SETUID'],security_opt:['no-new-privileges:true'], + mem_limit:env(variable+'_MEMORY_LIMIT',memory[name]),cpus:env(variable+'_CPU_LIMIT',name==='trivy-adapter'?'2.0':'1.0'), + stop_grace_period:'120s',healthcheck:{interval:'15s',timeout:'10s',retries:10,start_period:name==='postgresql'?'120s':'60s'},logging, + }; + } + const add = (service, ...entries) => services[service].volumes.push(...entries); + const depend = (service, names) => names.forEach(name=>services[service].depends_on[name]={condition:'service_healthy'}); + add('postgresql',mount('database','/var/lib/postgresql/data')); + services.postgresql.cap_add.push('DAC_OVERRIDE'); + services.postgresql.shm_size='256m'; + services.postgresql.environment={POSTGRES_MAX_CONNECTIONS:env('POSTGRES_MAX_CONNECTIONS','256')}; + services.postgresql.healthcheck.test=['CMD','/bin/sh','/run/harbor-config/health.sh']; + add('redis',mount('redis','/var/lib/redis')); + add('registry',mount('registry','/storage')); + depend('registry',['redis']); + add('registryctl',mount('registry','/storage'),mount('config','/etc/registry','registry',true)); + depend('registryctl',['registry']); + add('core',mount('data','/data')); + depend('core',['postgresql','redis','registry']); + add('jobservice',mount('jobs','/var/log/jobs')); + depend('jobservice',['core','redis']); + add('trivy-adapter',mount('trivy-cache','/home/scanner/.cache/trivy'),mount('trivy-reports','/home/scanner/.cache/reports')); + depend('trivy-adapter',['redis']); + depend('exporter',['core','postgresql','redis']); + services.exporter.healthcheck.test=['CMD-SHELL','curl --fail -s http://localhost:8080/metrics >/dev/null']; + services.proxy.expose=['8080']; + services.proxy.labels['traefik.enable']='true'; + depend('proxy',['core','portal','registry','exporter']); + } + return {services,volumes}; +} + +export function yaml(value, indent=0) { + const pad=' '.repeat(indent); + if (Array.isArray(value)) return value.map(item => typeof item === 'object' && item !== null + ? pad+'-\n'+yaml(item,indent+2) : pad+'- '+JSON.stringify(item)+'\n').join(''); + return Object.entries(value).map(([key,item])=> { + if (item && typeof item==='object' && Object.keys(item).length) return pad+JSON.stringify(key)+':\n'+yaml(item,indent+2); + return pad+JSON.stringify(key)+': '+JSON.stringify(item)+'\n'; + }).join(''); +} +export async function render() { + const lock=JSON.parse(await readFile(path.join(repository,'images.lock.json'),'utf8')); + const buildSource=JSON.parse(await readFile(path.join(repository,'tools/build-source.json'),'utf8')); + await writeFile(path.join(repository,'docker-compose.yml'), + '# Generated by tools/render-compose.mjs from images.lock.json.\n'+yaml(buildCompose(lock,buildSource))); +} +if (typeof process !== 'undefined' && process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href===import.meta.url) await render(); diff --git a/blueprints/harbor/vendor/HARBOR-LICENSE b/blueprints/harbor/vendor/HARBOR-LICENSE new file mode 100644 index 000000000..4b9cffeef --- /dev/null +++ b/blueprints/harbor/vendor/HARBOR-LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Project Harbor Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/blueprints/harbor/vendor/NOTICE.md b/blueprints/harbor/vendor/NOTICE.md new file mode 100644 index 000000000..666769277 --- /dev/null +++ b/blueprints/harbor/vendor/NOTICE.md @@ -0,0 +1,9 @@ +The harbor.yml.tmpl file is copied without modification from: +https://github.com/goharbor/harbor/blob/v2.15.4/make/harbor.yml.tmpl + +Copyright Harbor Authors. Licensed under Apache License 2.0; see HARBOR-LICENSE. +The template checksum and image references are recorded in ../images.lock.json. +Update the vendor template, image metadata/digests and Dockerfile default together, +then rerun unit and integration tests before pinning a new init source commit. + +harbor.svg is the official favicon from https://github.com/goharbor/website/blob/main/static/favicon.svg. diff --git a/blueprints/harbor/vendor/harbor.yml.tmpl b/blueprints/harbor/vendor/harbor.yml.tmpl new file mode 100644 index 000000000..6b4540c61 --- /dev/null +++ b/blueprints/harbor/vendor/harbor.yml.tmpl @@ -0,0 +1,342 @@ +# Configuration file of Harbor + +# The IP address or hostname to access admin UI and registry service. +# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. +hostname: reg.mydomain.com + +# http related config +http: + # port for http, default is 80. If https enabled, this port will redirect to https port + port: 80 + +# https related config +https: + # https port for harbor, default is 443 + port: 443 + # The path of cert and key files for nginx + certificate: /your/certificate/path + private_key: /your/private/key/path + # enable strong ssl ciphers (default: false) + # strong_ssl_ciphers: false + +# # Harbor will set ipv4 enabled only by default if this block is not configured +# # Otherwise, please uncomment this block to configure your own ip_family stacks +# ip_family: +# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component +# ipv6: +# enabled: false +# # ipv4Enabled set to true by default, currently it affected the nginx related component +# ipv4: +# enabled: true + +# # Uncomment following will enable tls communication between all harbor components +# internal_tls: +# # set enabled to true means internal tls is enabled +# enabled: true +# # put your cert and key files on dir +# dir: /etc/harbor/tls/internal + + +# Uncomment external_url if you want to enable external proxy +# And when it enabled the hostname will no longer used +# external_url: https://reg.mydomain.com:8433 + +# The initial password of Harbor admin +# It only works in first time to install harbor +# Remember Change the admin password from UI after launching Harbor. +harbor_admin_password: Harbor12345 + +# Harbor DB configuration +database: + # The password for the user('postgres' by default) of Harbor DB. Change this before any production use. + password: root123 + # The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. + max_idle_conns: 100 + # The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. + # Note: the default number of connections is 1024 for postgres of harbor. + max_open_conns: 900 + # The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + conn_max_lifetime: 5m + # The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time. + # The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + conn_max_idle_time: 0 + +# The default data volume +data_volume: /data + +# Harbor Storage settings by default is using /data dir on local filesystem +# Uncomment storage_service setting If you want to using external storage +# storage_service: +# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore +# # of registry's containers. This is usually needed when the user hosts a internal storage with self signed certificate. +# ca_bundle: + +# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss +# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/ +# # and https://distribution.github.io/distribution/storage-drivers/ +# filesystem: +# maxthreads: 100 +# # set disable to true when you want to disable registry redirect +# redirect: +# disable: false + +# Trivy configuration +# +# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. +# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached +# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it +# should download a newer version from the Internet or use the cached one. Currently, the database is updated every +# 12 hours and published as a new release to GitHub. +trivy: + # ignoreUnfixed The flag to display only fixed vulnerabilities + ignore_unfixed: false + # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub + # + # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues. + # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and + # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path. + skip_update: false + # + # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the + # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path + skip_java_db_update: false + # + # OCI repository to retrieve the trivy vulnerability database from. + db_repository: ghcr.io/aquasecurity/trivy-db + # OCI repository to retrieve the Java trivy vulnerability database from. + java_db_repository: ghcr.io/aquasecurity/trivy-java-db + # + # The offline_scan option prevents Trivy from sending API requests to identify dependencies. + # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. + # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't + # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode. + # It would work if all the dependencies are in local. + # This option doesn't affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment. + offline_scan: false + # + # Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`. + security_check: vuln + # + # insecure The flag to skip verifying registry certificate + insecure: false + # + # timeout The duration to wait for scan completion. + # There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s. + timeout: 5m0s + # + # github_token The GitHub access token to download Trivy DB + # + # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough + # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 + # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult + # https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting + # + # You can create a GitHub token by following the instructions in + # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + # + # github_token: xxx + +jobservice: + # Maximum number of job workers in job service + max_job_workers: 10 + # Maximum hours of task duration in job service, default 24 + max_job_duration_hours: 24 + # The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB" + job_loggers: + - STD_OUTPUT + - FILE + # - DB + # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`) + logger_sweeper_duration: 1 #days + +notification: + # Maximum retry count for webhook job + webhook_job_max_retry: 3 + # HTTP client timeout for webhook job + webhook_job_http_client_timeout: 3 #seconds + +# Log configurations +log: + # options are debug, info, warning, error, fatal + level: info + # configs for logs in local storage + local: + # Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated. + rotate_count: 50 + # Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes. + # If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G + # are all valid. + rotate_size: 200M + # The directory on your host that store log + location: /var/log/harbor + + # Uncomment following lines to enable external syslog endpoint. + # external_endpoint: + # # protocol used to transmit log to external endpoint, options is tcp or udp + # protocol: tcp + # # The host of external endpoint + # host: localhost + # # Port of external endpoint + # port: 5140 + +#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY! +_version: 2.15.0 + +# Uncomment external_database if using external database. +# external_database: +# harbor: +# host: harbor_db_host +# port: harbor_db_port +# db_name: harbor_db_name +# username: harbor_db_username +# password: harbor_db_password +# ssl_mode: disable +# max_idle_conns: 2 +# max_open_conns: 0 + +# Uncomment redis if need to customize redis db +# redis: +# # db_index 0 is for core, it's unchangeable +# # registry_db_index: 1 +# # jobservice_db_index: 2 +# # trivy_db_index: 5 +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 + +# Uncomment external_redis if using external Redis server +# external_redis: +# # support redis, redis+sentinel +# # host for redis: : +# # host for redis+sentinel: +# # :,:,: +# host: redis:6379 +# password: +# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH form. +# # username: +# # sentinel_master_set must be set to support redis+sentinel +# #sentinel_master_set: +# # tls configuration for redis connection +# # only server-authentication is supported +# # mtls for redis connection is not supported +# # tls connection will be disable by default +# tlsOptions: +# enable: false +# # if it is a self-signed ca, please set the ca path specifically. +# rootCA: +# # db_index 0 is for core, it's unchangeable +# registry_db_index: 1 +# jobservice_db_index: 2 +# trivy_db_index: 5 +# idle_timeout_seconds: 30 +# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it. +# # harbor_db_index: 6 +# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it. +# # cache_layer_db_index: 7 + +# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert. +# uaa: +# ca_file: /path/to/ca + +# Global proxy +# Config http proxy for components, e.g. http://my.proxy.com:3128 +# Components doesn't need to connect to each others via http proxy. +# Remove component from `components` array if want disable proxy +# for it. If you want use proxy for replication, MUST enable proxy +# for core and jobservice, and set `http_proxy` and `https_proxy`. +# Add domain to the `no_proxy` field, when you want disable proxy +# for some special registry. +proxy: + http_proxy: + https_proxy: + no_proxy: + components: + - core + - jobservice + - trivy + +# Outbound webhook and Slack notification requests may reach private, loopback, link-local and +# cloud-metadata (169.254.169.254) addresses unless this is false. Set it to false to reject them +# (CWE-918) when no webhook receiver runs on an internal network. +network: + allow_private_network_access: true + +# Lets a cosign/notation client pull the still-unsigned manifest it is about to sign while the +# project has a content trust policy enabled. The client must already hold push permission on +# the project; its User-Agent only narrows that authorization, and it is spoofable, so set this +# to false if you would rather such a client could not read unsigned manifests. +content_trust: + legacy_signer_pull_enabled: true + +# metric: +# enabled: false +# port: 9090 +# path: /metrics + +# Trace related config +# only can enable one trace provider(jaeger or otel) at the same time, +# and when using jaeger as provider, can only enable it with agent mode or collector mode. +# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed +# if using jaeger agetn mode uncomment agent_host and agent_port +# trace: +# enabled: true +# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth +# sample_rate: 1 +# # # namespace used to differentiate different harbor services +# # namespace: +# # # attributes is a key value dict contains user defined attributes used to initialize trace provider +# # attributes: +# # application: harbor +# # # jaeger should be 1.26 or newer. +# # jaeger: +# # endpoint: http://hostname:14268/api/traces +# # username: +# # password: +# # agent_host: hostname +# # # export trace data by jaeger.thrift in compact mode +# # agent_port: 6831 +# # otel: +# # endpoint: hostname:4318 +# # url_path: /v1/traces +# # compression: false +# # insecure: true +# # # timeout is in seconds +# # timeout: 10 + +# Enable purge _upload directories +upload_purging: + enabled: true + # remove files in _upload directories which exist for a period of time, default is one week. + age: 168h + # the interval of the purge operations + interval: 24h + dryrun: false + +# Cache layer configurations +# If this feature enabled, harbor will cache the resource +# `project/project_metadata/repository/artifact/manifest` in the redis +# which can especially help to improve the performance of high concurrent +# manifest pulling. +# NOTICE +# If you are deploying Harbor in HA mode, make sure that all the harbor +# instances have the same behaviour, all with caching enabled or disabled, +# otherwise it can lead to potential data inconsistency. +cache: + # not enabled by default + enabled: false + # keep cache for one day by default + expire_hours: 24 + +# Harbor core configurations +# Uncomment to enable the following harbor core related configuration items. +# core: +# # The provider for updating project quota(usage), there are 2 options, redis or db, +# # by default is implemented by db but you can switch the updation via redis which +# # can improve the performance of high concurrent pushing to the same project, +# # and reduce the database connections spike and occupies. +# # By redis will bring up some delay for quota usage updation for display, so only +# # suggest switch provider to redis if you were ran into the db connections spike around +# # the scenario of high concurrent pushing to same project, no improvement for other scenes. +# quota_update_provider: redis # Or db diff --git a/blueprints/hivemq-ce/docker-compose.yml b/blueprints/hivemq-ce/docker-compose.yml new file mode 100644 index 000000000..5c1cfc562 --- /dev/null +++ b/blueprints/hivemq-ce/docker-compose.yml @@ -0,0 +1,58 @@ +# Native MQTT over TLS requires a Traefik TCP entrypoint named "mqtts" +# on port 8883 and a corresponding port mapping on the Traefik container. +# WebSocket clients connect using wss:///mqtt via Dokploy HTTPS. +services: + hivemq-ce-init: + image: curlimages/curl:8.22.0 + restart: "no" + user: "0:0" + environment: + - MQTT_USERNAME=${MQTT_USERNAME} + - MQTT_PASSWORD=${MQTT_PASSWORD} + entrypoint: ["/bin/sh", "/tmp/hivemq-ce-init.sh"] + volumes: + - ../files/hivemq-ce-init.sh:/tmp/hivemq-ce-init.sh:ro + - hivemq_extensions:/extensions + + hivemq-ce: + image: hivemq/hivemq-ce:2026.5 + restart: unless-stopped + depends_on: + hivemq-ce-init: + condition: service_completed_successfully + environment: + - HIVEMQ_ALLOW_ALL_CLIENTS=false + entrypoint: + - /bin/bash + - -ec + - | + test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/hivemq-extension.xml + test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/hivemq-file-rbac-extension-4.6.17.jar + test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/conf/credentials.xml + test ! -e /opt/hivemq/extensions/hivemq-allow-all-extension + exec /opt/docker-entrypoint.sh /opt/hivemq/bin/run.sh + expose: + - 1883 # MQTT over TCP (TLS terminated by Traefik on 8883) + - 8000 # MQTT over WebSockets (WSS via Dokploy HTTPS, path /mqtt) + volumes: + - hivemq_data:/opt/hivemq/data + - hivemq_log:/opt/hivemq/log + - hivemq_extensions:/opt/hivemq/extensions + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/1883'"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 60s + labels: + - "traefik.tcp.routers.hivemq-ce-mqtts.entrypoints=mqtts" + - "traefik.tcp.routers.hivemq-ce-mqtts.rule=HostSNI(`${MQTT_DOMAIN}`)" + - "traefik.tcp.routers.hivemq-ce-mqtts.tls=true" + - "traefik.tcp.routers.hivemq-ce-mqtts.tls.certresolver=letsencrypt" + - "traefik.tcp.routers.hivemq-ce-mqtts.service=hivemq-ce-mqtts" + - "traefik.tcp.services.hivemq-ce-mqtts.loadbalancer.server.port=1883" + +volumes: + hivemq_data: + hivemq_log: + hivemq_extensions: diff --git a/blueprints/hivemq-ce/hivemq-ce.svg b/blueprints/hivemq-ce/hivemq-ce.svg new file mode 100644 index 000000000..c6f94c294 --- /dev/null +++ b/blueprints/hivemq-ce/hivemq-ce.svg @@ -0,0 +1,8 @@ + + HiveMQ + + + + + + diff --git a/blueprints/hivemq-ce/meta.json b/blueprints/hivemq-ce/meta.json new file mode 100644 index 000000000..5a676b353 --- /dev/null +++ b/blueprints/hivemq-ce/meta.json @@ -0,0 +1,13 @@ +{ + "id": "hivemq-ce", + "name": "HiveMQ Community Edition", + "version": "2026.5", + "description": "Open-source MQTT 3.x and MQTT 5 broker with persistent sessions, WebSocket transport, and file-based client authentication.", + "logo": "hivemq-ce.svg", + "links": { + "github": "https://github.com/hivemq/hivemq-community-edition", + "website": "https://www.hivemq.com/", + "docs": "https://github.com/hivemq/hivemq-community-edition/wiki" + }, + "tags": ["broker", "iot", "mqtt"] +} diff --git a/blueprints/hivemq-ce/template.toml b/blueprints/hivemq-ce/template.toml new file mode 100644 index 000000000..4aa5fca52 --- /dev/null +++ b/blueprints/hivemq-ce/template.toml @@ -0,0 +1,88 @@ +[variables] +broker_domain = "${domain}" +mqtt_username = "mqtt" +mqtt_password = "${password:32}" + +[config] +env = [ + "MQTT_DOMAIN=${broker_domain}", + "MQTT_USERNAME=${mqtt_username}", + "MQTT_PASSWORD=${mqtt_password}" +] + +[[config.domains]] +serviceName = "hivemq-ce" +port = 8000 +host = "${broker_domain}" + +[[config.mounts]] +filePath = "hivemq-ce-init.sh" +content = ''' +#!/bin/sh +set -eu + +test -n "$MQTT_USERNAME" || { echo "MQTT_USERNAME is required" >&2; exit 1; } +test -n "$MQTT_PASSWORD" || { echo "MQTT_PASSWORD is required" >&2; exit 1; } +case "$MQTT_USERNAME" in + *[!a-zA-Z0-9_.-]*) echo 'Invalid MQTT username' >&2; exit 1 ;; +esac + +extension=/extensions/hivemq-file-rbac-extension +work_dir=$(mktemp -d) +trap 'rm -rf "$work_dir"' EXIT +if [ ! -f "$extension/hivemq-file-rbac-extension-4.6.17.jar" ]; then + curl --fail --silent --show-error --location --retry 3 \ + --proto '=https' --proto-redir '=https' \ + --output "$work_dir/rbac.zip" \ + https://github.com/hivemq/hivemq-file-rbac-extension/releases/download/4.6.17/hivemq-file-rbac-extension-4.6.17.zip + unzip -oq "$work_dir/rbac.zip" -d "$work_dir" + test -f "$work_dir/hivemq-file-rbac-extension/hivemq-extension.xml" + mkdir -p "$extension" + cp -R "$work_dir/hivemq-file-rbac-extension/." "$extension/" +fi + +# The CE image enables anonymous MQTT by default. Never retain that extension. +rm -rf /extensions/hivemq-allow-all-extension + +escape_xml() { + printf '%s' "$1" | sed \ + -e 's/\&/\&/g' -e 's//\>/g' \ + -e 's/"/\"/g' -e "s/'/\'/g" +} +username=$(escape_xml "$MQTT_USERNAME") +password=$(escape_xml "$MQTT_PASSWORD") +mkdir -p "$extension/conf" +cat > "$extension/conf/config.xml" <<'CONFIG' + + + PLAIN + false + +CONFIG + +credentials=$(mktemp "$extension/conf/.credentials.XXXXXX") +cat > "$credentials" < + + + + $username + $password + mqtt + + + + + mqtt + # + + + +CREDENTIALS +chown -R 10000:10000 "$extension" +chmod 600 "$credentials" +mv -f "$credentials" "$extension/conf/credentials.xml" + +# Remove deprecated paths from existing deployments after the new files are ready. +rm -f "$extension/extension-config.xml" "$extension/credentials.xml" +''' diff --git a/blueprints/homarr/meta.json b/blueprints/homarr/meta.json index 21c8161ca..136943bfc 100644 --- a/blueprints/homarr/meta.json +++ b/blueprints/homarr/meta.json @@ -2,7 +2,7 @@ "id": "homarr", "name": "Homarr", "version": "latest", - "description": "A sleek, modern dashboard that puts all your apps and services in one place with Docker integration.", + "description": "Homarr is a dashboard for organizing and accessing all your apps from one place. Self-host it on your own server with Dokploy, which deploys Homarr as a Docker Compose service in a few clicks.", "logo": "homarr.png", "links": { "github": "https://github.com/homarr-labs/homarr", @@ -12,5 +12,8 @@ "tags": [ "dashboard", "monitoring" - ] + ], + "seoTitle": "Homarr Docker Compose Setup", + "seoDescription": "Dashboard for organizing self-hosted apps. Deploy Homarr with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/homeassistant/meta.json b/blueprints/homeassistant/meta.json index 65deab665..c077cb5a7 100644 --- a/blueprints/homeassistant/meta.json +++ b/blueprints/homeassistant/meta.json @@ -2,7 +2,7 @@ "id": "homeassistant", "name": "Home Assistant", "version": "stable", - "description": "Open source home automation that puts local control and privacy first.", + "description": "Home Assistant is a home automation platform that makes sure you can control your smart devices locally. Self-host it on your own server with Dokploy, which deploys Home Assistant as a Docker Compose service in a few clicks.", "logo": "homeassistant.svg", "links": { "github": "https://github.com/home-assistant/core", @@ -15,5 +15,8 @@ "internet-of-things", "self-hosted", "server" - ] + ], + "seoTitle": "Home Assistant Docker Compose Setup", + "seoDescription": "Home automation platform with local control. Deploy Home Assistant with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/homebridge/meta.json b/blueprints/homebridge/meta.json index 023fe9c41..2cdc5b60c 100644 --- a/blueprints/homebridge/meta.json +++ b/blueprints/homebridge/meta.json @@ -2,7 +2,7 @@ "id": "homebridge", "name": "Homebridge", "version": "latest", - "description": "Bringing HomeKit support where there is none. Homebridge allows you to integrate with smart home devices that do not natively support HomeKit.", + "description": "Homebridge is a tool that brings Apple HomeKit support to smart home devices that don't support it natively. Self-host it on your own server with Dokploy, which deploys Homebridge as a Docker Compose service in a few clicks.", "logo": "homebridge.svg", "links": { "github": "https://github.com/homebridge/homebridge", @@ -15,5 +15,8 @@ "internet-of-things", "self-hosted", "server" - ] + ], + "seoTitle": "Homebridge Docker Compose Setup", + "seoDescription": "Brings HomeKit support to smart home devices. Deploy Homebridge with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/homepage/meta.json b/blueprints/homepage/meta.json index da47417d4..c6e930ff9 100644 --- a/blueprints/homepage/meta.json +++ b/blueprints/homepage/meta.json @@ -2,7 +2,7 @@ "id": "homepage", "name": "Homepage", "version": "1.13.1", - "description": "Homepage is a highly customizable self-hosted application dashboard and startpage with service and widget integrations.", + "description": "Homepage is a customizable dashboard and start page for your apps and services. Self-host it on your own server with Dokploy, which deploys Homepage as a Docker Compose service in a few clicks.", "logo": "homepage.png", "links": { "github": "https://github.com/gethomepage/homepage", @@ -14,5 +14,7 @@ "startpage", "self-hosted", "productivity" - ] + ], + "seoTitle": "Self-Host Homepage in minutes", + "seoDescription": "Customizable startpage for self-hosted apps. Self-host Homepage on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/huly/meta.json b/blueprints/huly/meta.json index 1cc60e1b2..eb388ebcb 100644 --- a/blueprints/huly/meta.json +++ b/blueprints/huly/meta.json @@ -2,7 +2,7 @@ "id": "huly", "name": "Huly", "version": "0.6.377", - "description": "Huly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)", + "description": "Huly is an all-in-one project management platform and an alternative to Linear and Jira. Self-host it on your own server with Dokploy, which deploys Huly as a Docker Compose service in a few clicks.", "logo": "huly.svg", "links": { "github": "https://github.com/hcengineering/huly-selfhost", @@ -13,5 +13,7 @@ "project-management", "community", "discussion" - ] + ], + "seoTitle": "Self-Host Huly in minutes", + "seoDescription": "All-in-one project management, an alternative to Linear and Jira. Self-host Huly on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/immich/meta.json b/blueprints/immich/meta.json index cf1aa6344..ebec8194c 100644 --- a/blueprints/immich/meta.json +++ b/blueprints/immich/meta.json @@ -2,7 +2,7 @@ "id": "immich", "name": "Immich", "version": "v3.0.2", - "description": "High performance self-hosted photo and video backup solution directly from your mobile phone.", + "description": "Immich is a photo and video backup app and an alternative to Google Photos. Self-host it on your own server with Dokploy, which deploys Immich as a Docker Compose service in a few clicks.", "logo": "immich.svg", "links": { "github": "https://github.com/immich-app/immich", @@ -14,5 +14,8 @@ "videos", "backup", "media" - ] + ], + "seoTitle": "Immich Docker Compose Setup", + "seoDescription": "Photo and video backup solution – a self-hosted alternative to Google Photos. Deploy with Docker Compose on Dokploy in minutes.", + "newConfig": true } diff --git a/blueprints/jellyfin/meta.json b/blueprints/jellyfin/meta.json index 3767dca35..c72155f0d 100644 --- a/blueprints/jellyfin/meta.json +++ b/blueprints/jellyfin/meta.json @@ -2,7 +2,7 @@ "id": "jellyfin", "name": "jellyfin", "version": "v10.9.7", - "description": "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. ", + "description": "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. Self-host it on your own server with Dokploy, which deploys Jellyfin as a Docker Compose service in a few clicks.", "logo": "jellyfin.svg", "links": { "github": "https://github.com/jellyfin/jellyfin", @@ -11,5 +11,8 @@ }, "tags": [ "media-system" - ] + ], + "seoTitle": "Jellyfin Docker Compose Setup", + "seoDescription": "Free media server for movies, TV, and music. Deploy Jellyfin with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/jellyseerr/meta.json b/blueprints/jellyseerr/meta.json index d4c1dbfde..c443474ba 100644 --- a/blueprints/jellyseerr/meta.json +++ b/blueprints/jellyseerr/meta.json @@ -2,7 +2,7 @@ "id": "jellyseerr", "name": "Jellyseerr", "version": "2.7.3", - "description": "Jellyseerr is a media request and discovery manager for Jellyfin, Plex, and Emby users.", + "description": "Jellyseerr is a media request manager for Jellyfin, Emby, and Plex. Self-host it on your own server with Dokploy, which deploys Jellyseerr as a Docker Compose service in a few clicks.", "logo": "jellyseerr.svg", "links": { "github": "https://github.com/Fallenbagel/jellyseerr", @@ -13,5 +13,8 @@ "media", "requests", "jellyfin" - ] + ], + "seoTitle": "Jellyseerr Docker Compose Setup", + "seoDescription": "Media request manager for Jellyfin and Plex. Deploy Jellyseerr with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/jenkins/meta.json b/blueprints/jenkins/meta.json index ca92ed793..bc51ad5c9 100644 --- a/blueprints/jenkins/meta.json +++ b/blueprints/jenkins/meta.json @@ -2,7 +2,7 @@ "id": "jenkins", "name": "jenkins", "version": "latest", - "description": "Jenkins is a free, open-source automation server that helps developers build, test, and deploy software by automating repetitive tasks in the software delivery pipeline.", + "description": "Jenkins is an open-source automation server for building CI/CD pipelines. Self-host it on your own server with Dokploy, which deploys Jenkins as a Docker Compose service in a few clicks.", "logo": "jenkins.svg", "links": { "github": "https://github.com/jenkinsci/jenkins", @@ -15,5 +15,8 @@ "automation", "pipelines", "open-source" - ] + ], + "seoTitle": "Jenkins Docker Compose Setup", + "seoDescription": "Automation server for CI/CD pipelines. Deploy Jenkins with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/joplin-server/meta.json b/blueprints/joplin-server/meta.json index df61780e3..305aa39d4 100644 --- a/blueprints/joplin-server/meta.json +++ b/blueprints/joplin-server/meta.json @@ -2,7 +2,7 @@ "id": "joplin-server", "name": "Joplin Server", "version": "3.7.1", - "description": "Joplin Server is the self-hosted sync backend for Joplin notes, notebooks, and attachments.", + "description": "Joplin Server is the sync backend for the Joplin note-taking app. Self-host it on your own server with Dokploy, which deploys Joplin Server as a Docker Compose service in a few clicks.", "logo": "joplin-server.svg", "links": { "github": "https://github.com/laurent22/joplin", @@ -13,5 +13,8 @@ "notes", "sync", "productivity" - ] + ], + "seoTitle": "Joplin Server Docker Compose Setup", + "seoDescription": "Sync backend for the Joplin note-taking app. Deploy Joplin Server with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/kavita/meta.json b/blueprints/kavita/meta.json index e7ea31a97..8a51dd15f 100644 --- a/blueprints/kavita/meta.json +++ b/blueprints/kavita/meta.json @@ -2,7 +2,7 @@ "id": "kavita", "name": "Kavita", "version": "latest", - "description": "Kavita is a self-hosted digital library server for reading manga, comics, and ebooks from any device.", + "description": "Kavita is a digital library server for comics, manga, and ebooks. Self-host it on your own server with Dokploy, which deploys Kavita as a Docker Compose service in a few clicks.", "logo": "kavita.svg", "links": { "github": "https://github.com/Kareadita/Kavita", @@ -14,5 +14,8 @@ "ebooks", "comics", "library" - ] + ], + "seoTitle": "Kavita Docker Compose Setup", + "seoDescription": "Digital library server for comics and ebooks. Deploy Kavita with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/keycloak/meta.json b/blueprints/keycloak/meta.json index 60c7fa3ad..9a3fc4418 100644 --- a/blueprints/keycloak/meta.json +++ b/blueprints/keycloak/meta.json @@ -2,7 +2,7 @@ "id": "keycloak", "name": "Keycloak", "version": "26.0", - "description": "Keycloak is an open source Identity and Access Management solution for modern applications and services.", + "description": "Keycloak is an open-source identity and access management tool with single sign-on (SSO). Self-host it on your own server with Dokploy, which deploys Keycloak as a Docker Compose service in a few clicks.", "logo": "keycloak.svg", "links": { "github": "https://github.com/keycloak/keycloak", @@ -15,5 +15,8 @@ "sso", "oauth2", "openid-connect" - ] + ], + "seoTitle": "Keycloak Docker Compose Setup", + "seoDescription": "Identity and access management with SSO. Deploy Keycloak with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/kimai/meta.json b/blueprints/kimai/meta.json index 8ce586b8c..2493e3c67 100644 --- a/blueprints/kimai/meta.json +++ b/blueprints/kimai/meta.json @@ -2,7 +2,7 @@ "id": "kimai", "name": "Kimai", "version": "2.31.0", - "description": "Kimai is a web-based multi-user time-tracking application. Works great for everyone: freelancers, companies, organizations - everyone can track their times, generate reports, create invoices and do so much more.", + "description": "Kimai is a time-tracking app for freelancers and teams. Self-host it on your own server with Dokploy, which deploys Kimai as a Docker Compose service in a few clicks.", "logo": "kimai.svg", "links": { "github": "https://github.com/kimai/kimai", @@ -13,5 +13,8 @@ "invoice", "business", "finance" - ] + ], + "seoTitle": "Kimai Docker Compose Setup", + "seoDescription": "Time-tracking app for freelancers and teams. Deploy Kimai with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/kuukura/docker-compose.yml b/blueprints/kuukura/docker-compose.yml new file mode 100644 index 000000000..61b38c629 --- /dev/null +++ b/blueprints/kuukura/docker-compose.yml @@ -0,0 +1,32 @@ +# The master key is generated on the first start and kept in the kuukura-keys volume, apart from the database in +# kuukura-data. After the first start, open the service terminal and run `/ku-server key show` once to back it up. +services: + kuukura: + image: ghcr.io/victorlunam/kuukura:2026.10.13 + restart: unless-stopped + environment: + VAULT_DOMAIN: ${VAULT_DOMAIN} + VAULT_STORAGE: sqlite + VAULT_SQLITE_PATH: /data/kuukura.db + VAULT_KEY_PROVIDER: manual + VAULT_MASTER_KEY_FILE: /keys/master_key + VAULT_MASTER_KEY_GENERATE: "true" + VAULT_OIDC_CLIENT_ID: ${VAULT_OIDC_CLIENT_ID} + VAULT_OIDC_CLIENT_SECRET: ${VAULT_OIDC_CLIENT_SECRET} + # set to `cloudflare` when Cloudflare's proxy is in front, so the audit log sees the real client IP + VAULT_TRUSTED_PROXIES: ${VAULT_TRUSTED_PROXIES:-} + volumes: + - kuukura-data:/data + - kuukura-keys:/keys + expose: + - 8080 + healthcheck: + test: ["CMD", "/ku-server", "healthcheck"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + +volumes: + kuukura-data: {} + kuukura-keys: {} diff --git a/blueprints/kuukura/kuukura.svg b/blueprints/kuukura/kuukura.svg new file mode 100644 index 000000000..aac0d2607 --- /dev/null +++ b/blueprints/kuukura/kuukura.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/blueprints/kuukura/meta.json b/blueprints/kuukura/meta.json new file mode 100644 index 000000000..ea42f41e0 --- /dev/null +++ b/blueprints/kuukura/meta.json @@ -0,0 +1,13 @@ +{ + "id": "kuukura", + "name": "kuukura", + "version": "2026.10.13", + "description": "kuukura is a self-hosted secrets vault for small teams: values are decrypted only on your server, reveals require a passkey, and every access is audit-logged. Requires a Google OAuth client ID and secret, set in the Environment tab before deploying.", + "logo": "kuukura.svg", + "links": { + "github": "https://github.com/victorlunam/kuukura", + "website": "https://kuukura-main-landing.neodev-f72.workers.dev", + "docs": "https://github.com/victorlunam/kuukura/blob/main/docs/deploy.md" + }, + "tags": ["security", "secrets", "self-hosted"] +} diff --git a/blueprints/kuukura/template.toml b/blueprints/kuukura/template.toml new file mode 100644 index 000000000..1b273d212 --- /dev/null +++ b/blueprints/kuukura/template.toml @@ -0,0 +1,22 @@ +# VAULT_OIDC_CLIENT_ID and VAULT_OIDC_CLIENT_SECRET are a Google OAuth client (type "Web application", redirect URI +# https:///auth/callback). Fill them in the Environment tab before deploying: while empty, the server +# exits at startup with a message saying which setting is missing. +# Set your real domain in the Domains tab, turn HTTPS on, and set VAULT_DOMAIN to the same domain. +[variables] +main_domain = "${domain}" + +[config] +mounts = [] +env = [ + "VAULT_DOMAIN=${main_domain}", + # Required: a Google OAuth client (see the comment at the top). + "VAULT_OIDC_CLIENT_ID=", + "VAULT_OIDC_CLIENT_SECRET=", + # Optional: `cloudflare` when Cloudflare's proxy is in front, so the audit log sees the real client IP. + "VAULT_TRUSTED_PROXIES=", +] + +[[config.domains]] +serviceName = "kuukura" +port = 8080 +host = "${main_domain}" diff --git a/blueprints/languagetool/meta.json b/blueprints/languagetool/meta.json index 5cdf905f1..4815c4fc5 100644 --- a/blueprints/languagetool/meta.json +++ b/blueprints/languagetool/meta.json @@ -2,7 +2,7 @@ "id": "languagetool", "name": "LanguageTool", "version": "6.6", - "description": "LanguageTool is an open-source grammar, style, and spell checker that can run as a self-hosted HTTP service.", + "description": "LanguageTool is an open-source grammar, spelling, and style checker. Self-host it on your own server with Dokploy, which deploys LanguageTool as a Docker Compose service in a few clicks.", "logo": "languagetool.svg", "links": { "github": "https://github.com/languagetool-org/languagetool", @@ -14,5 +14,7 @@ "grammar", "spellcheck", "api" - ] + ], + "seoTitle": "Self-Host LanguageTool in minutes", + "seoDescription": "Grammar and style checking tool. Self-host LanguageTool on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/legejo/docker-compose.yml b/blueprints/legejo/docker-compose.yml new file mode 100644 index 000000000..94fcbf1d1 --- /dev/null +++ b/blueprints/legejo/docker-compose.yml @@ -0,0 +1,15 @@ +services: + legejo: + image: slurvdjur/legejo:1.23.1 + restart: unless-stopped + expose: + - "3000" + # Every variable in the template's environment reaches Legejo, so one + # added in Dokploy (mail, single sign-on, ...) takes effect on redeploy. + env_file: + - .env + volumes: + - legejo-data:/data + +volumes: + legejo-data: diff --git a/blueprints/legejo/legejo.svg b/blueprints/legejo/legejo.svg new file mode 100644 index 000000000..64badfa4e --- /dev/null +++ b/blueprints/legejo/legejo.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blueprints/legejo/meta.json b/blueprints/legejo/meta.json new file mode 100644 index 000000000..82832730f --- /dev/null +++ b/blueprints/legejo/meta.json @@ -0,0 +1,19 @@ +{ + "id": "legejo", + "name": "Legejo", + "version": "1.23.1", + "description": "Legejo is a self-hosted library for your EPUB books, with a web reader, Kobo sync, OPDS for reading apps, KOReader progress sync, shared shelves and audiobooks. Runs on SQLite with no other services needed.", + "logo": "legejo.svg", + "links": { + "github": "https://github.com/jularboolean/legejo", + "website": "https://github.com/jularboolean/legejo", + "docs": "https://github.com/jularboolean/legejo#readme" + }, + "tags": [ + "ebooks", + "library", + "reading", + "kobo", + "opds" + ] +} diff --git a/blueprints/legejo/template.toml b/blueprints/legejo/template.toml new file mode 100644 index 000000000..58622ddb9 --- /dev/null +++ b/blueprints/legejo/template.toml @@ -0,0 +1,36 @@ +[variables] +main_domain = "${domain}" +admin_password = "${password:24}" + +[config] +[[config.domains]] +serviceName = "legejo" +port = 3000 +host = "${main_domain}" + +# Every LEGEJO_* variable is described in the README: +# https://github.com/jularboolean/legejo#configuration +# An empty value counts as unset. +[config.env] +# The first admin account, created on the first start; sign in with these. +LEGEJO_ADMIN_USER = "admin" +LEGEJO_ADMIN_PASSWORD = "${admin_password}" +# Change to https:// once the domain has a certificate: session cookies are +# then marked Secure, and federation and mailed links use it. +LEGEJO_PUBLIC_URL = "http://${main_domain}" +# Mail, for self-registration, invitations and password reset. +LEGEJO_SMTP_HOST = "" +LEGEJO_SMTP_PORT = "587" +LEGEJO_SMTP_USER = "" +LEGEJO_SMTP_PASSWORD = "" +LEGEJO_MAIL_FROM = "" +# Searching inside the books; keeps the text of the EPUBs in the database. +LEGEJO_FULLTEXT = "false" +# Read-only MCP server for AI assistants at /api/mcp. +LEGEJO_MCP = "false" +# The librarian: book suggestions from an OpenAI-compatible language model. +LEGEJO_AI_API_KEY = "" +# Single sign-on with OpenID Connect. +LEGEJO_OIDC_ISSUER = "" +LEGEJO_OIDC_CLIENT_ID = "" +LEGEJO_OIDC_CLIENT_SECRET = "" diff --git a/blueprints/libredb-studio/docker-compose.yml b/blueprints/libredb-studio/docker-compose.yml index bac0fd85b..5ee5af3b3 100644 --- a/blueprints/libredb-studio/docker-compose.yml +++ b/blueprints/libredb-studio/docker-compose.yml @@ -1,8 +1,18 @@ services: libredb-studio: - image: ghcr.io/libredb/libredb-studio:0.18.0 + image: ghcr.io/libredb/libredb-studio:1.0.0 restart: unless-stopped environment: + # Since 0.18.0 ADMIN_PASSWORD and USER_PASSWORD seed the account table only + # while it is empty; editing them afterwards is logged and ignored. The app + # tells the operator to set ADMIN_PASSWORD_RESET=true for one start, so it is + # passed through here: this environment list is an allowlist, and without the + # line the variable never reaches the container and the advice cannot be + # followed from Dokploy's Environment panel. Empty is falsy, so it is inert + # until someone sets it, and it must be removed again afterwards because every + # start applies it while it is set. There is no USER equivalent; that account + # is changed from Studio's own Accounts screen. + - ADMIN_PASSWORD_RESET=${ADMIN_PASSWORD_RESET:-} - ADMIN_EMAIL=admin@libredb.org - ADMIN_PASSWORD=${ADMIN_PASSWORD} - USER_EMAIL=user@libredb.org diff --git a/blueprints/libredb-studio/meta.json b/blueprints/libredb-studio/meta.json index 903ca705e..b12666ff7 100644 --- a/blueprints/libredb-studio/meta.json +++ b/blueprints/libredb-studio/meta.json @@ -1,8 +1,8 @@ { "id": "libredb-studio", "name": "LibreDB Studio", - "version": "0.18.0", - "description": "The open-source SQL IDE you host yourself. Query PostgreSQL, MySQL, Oracle, SQL Server, MongoDB, Redis, ClickHouse, Cassandra and more from your browser, twenty-six drivers reaching fifty-four named engines.", + "version": "1.0.0", + "description": "The open-source SQL IDE you host yourself. Query PostgreSQL, MySQL, Oracle, SQL Server, MongoDB, Redis, ClickHouse, Cassandra and more from your browser, twenty-seven drivers reaching fifty-four named engines.", "logo": "libredb-studio.svg", "links": { "github": "https://github.com/libredb/libredb-studio", diff --git a/blueprints/limesurvey/meta.json b/blueprints/limesurvey/meta.json index 099d1b411..6e7f8dfcb 100644 --- a/blueprints/limesurvey/meta.json +++ b/blueprints/limesurvey/meta.json @@ -2,7 +2,7 @@ "id": "limesurvey", "name": "LimeSurvey", "version": "6.17.2+260507", - "description": "LimeSurvey is a powerful, open-source survey platform, making it simple to create online surveys and forms with unmatched flexibility.", + "description": "LimeSurvey is an open-source research platform for building and running online surveys. Self-host it on your own server with Dokploy, which deploys LimeSurvey as a Docker Compose service in a few clicks.", "logo": "limesurvey.svg", "links": { "github": "https://github.com/LimeSurvey/LimeSurvey", @@ -13,5 +13,8 @@ "forms", "questionnaire", "survey" - ] + ], + "seoTitle": "LimeSurvey Docker Compose Setup", + "seoDescription": "Platform for building online surveys. Deploy LimeSurvey with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/linkwarden/meta.json b/blueprints/linkwarden/meta.json index 5235966df..d155f053a 100644 --- a/blueprints/linkwarden/meta.json +++ b/blueprints/linkwarden/meta.json @@ -2,7 +2,7 @@ "id": "linkwarden", "name": "Linkwarden", "version": "2.9.3", - "description": "Self-hosted, open-source collaborative bookmark manager to collect, organize and archive webpages.", + "description": "Linkwarden is an open-source bookmark manager for saving, organizing, and archiving links. Self-host it on your own server with Dokploy, which deploys Linkwarden as a Docker Compose service in a few clicks.", "logo": "linkwarden.png", "links": { "github": "https://github.com/linkwarden/linkwarden", @@ -12,5 +12,7 @@ "tags": [ "bookmarks", "link-sharing" - ] + ], + "seoTitle": "Self-Host Linkwarden in minutes", + "seoDescription": "Bookmark manager for saving and organizing links. Self-host Linkwarden on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/matomo/meta.json b/blueprints/matomo/meta.json index 91fd4e0d0..470dd1a02 100644 --- a/blueprints/matomo/meta.json +++ b/blueprints/matomo/meta.json @@ -2,7 +2,7 @@ "id": "matomo", "name": "Matomo", "version": "5.1-apache", - "description": "Leading open-source web analytics platform with global data ownership.", + "description": "Matomo is an open-source web analytics platform that gives you full ownership of your data. Self-host it on your own server with Dokploy, which deploys Matomo as a Docker Compose service in a few clicks.", "logo": "matomo.png", "links": { "github": "https://github.com/matomo-org/matomo", @@ -14,5 +14,8 @@ "metrics", "privacy", "self-hosted" - ] + ], + "seoTitle": "Matomo Docker Compose Setup", + "seoDescription": "Web analytics platform with full data ownership. Deploy Matomo with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/mattermost/meta.json b/blueprints/mattermost/meta.json index 2bd5d42bc..501fb565a 100644 --- a/blueprints/mattermost/meta.json +++ b/blueprints/mattermost/meta.json @@ -2,7 +2,7 @@ "id": "mattermost", "name": "Mattermost", "version": "10.6.1", - "description": "A single point of collaboration. Designed specifically for digital operations.", + "description": "Mattermost is a team chat platform and an alternative to Slack. Self-host it on your own server with Dokploy, which deploys Mattermost as a Docker Compose service in a few clicks.", "logo": "mattermost.png", "links": { "github": "https://github.com/mattermost/mattermost", @@ -12,5 +12,7 @@ "tags": [ "chat", "self-hosted" - ] + ], + "seoTitle": "Self-Host Mattermost in minutes", + "seoDescription": "Team chat platform, an alternative to Slack. Self-host Mattermost on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/metabase/meta.json b/blueprints/metabase/meta.json index aa696952a..0df13fb5d 100644 --- a/blueprints/metabase/meta.json +++ b/blueprints/metabase/meta.json @@ -2,7 +2,7 @@ "id": "metabase", "name": "Metabase", "version": "v0.63.18", - "description": "Metabase is an open source business intelligence tool that allows you to ask questions and visualize data.", + "description": "Metabase is an open-source business intelligence tool for exploring and visualizing data. Self-host it on your own server with Dokploy, which deploys Metabase as a Docker Compose service in a few clicks.", "logo": "metabase.png", "links": { "github": "https://github.com/metabase/metabase", @@ -12,5 +12,8 @@ "tags": [ "database", "dashboard" - ] + ], + "seoTitle": "Metabase Docker Compose Setup", + "seoDescription": "Business intelligence and data visualization. Deploy Metabase with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/minio/meta.json b/blueprints/minio/meta.json index 423c0c828..a5db7f9a1 100644 --- a/blueprints/minio/meta.json +++ b/blueprints/minio/meta.json @@ -1,7 +1,7 @@ { "id": "minio", "name": "Minio", - "description": "MinIO is an open source object storage server compatible with Amazon S3 cloud storage service. This template uses pgsty/minio, the actively maintained community fork, because MinIO Inc. stopped publishing Docker images in October 2025 and archived the upstream open source project in February 2026.", + "description": "MinIO is an open-source, S3-compatible object storage server. Self-host it on your own server with Dokploy, which deploys MinIO as a Docker Compose service in a few clicks.", "logo": "minio.png", "version": "RELEASE.2026-06-18T00-00-00Z", "links": { @@ -11,5 +11,8 @@ }, "tags": [ "storage" - ] + ], + "seoTitle": "MinIO Docker Compose Setup", + "seoDescription": "S3-compatible object storage server. Deploy MinIO with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/mosquitto/docker-compose.yml b/blueprints/mosquitto/docker-compose.yml new file mode 100644 index 000000000..fbe912a26 --- /dev/null +++ b/blueprints/mosquitto/docker-compose.yml @@ -0,0 +1,49 @@ +# MQTT over TLS requires a Traefik TCP entrypoint named "mqtts" on port 8883 +# and a corresponding port mapping on the Traefik container. +# Dokploy exposes the internal WebSocket listener (9001) over WSS on port 443. +services: + mosquitto: + image: eclipse-mosquitto:2.1.2-alpine + restart: unless-stopped + environment: + - MQTT_USERNAME=${MQTT_USERNAME} + - MQTT_PASSWORD=${MQTT_PASSWORD} + entrypoint: + - /bin/sh + - -ec + - | + umask 077 + password_file=$$(mktemp /mosquitto/data/passwordfile.XXXXXX) + trap 'rm -f "$$password_file"' EXIT + mosquitto_passwd -b -c - "$$MQTT_USERNAME" "$$MQTT_PASSWORD" > "$$password_file" + chown 1883:1883 "$$password_file" + chmod 600 "$$password_file" + mv -f "$$password_file" /mosquitto/data/passwordfile + trap - EXIT + exec /docker-entrypoint.sh /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf + expose: + - 1883 # MQTT over TCP (TLS terminated by Traefik on 8883) + - 9001 # MQTT over WebSockets (WSS via Dokploy HTTPS) + volumes: + - ../files/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro + - mosquitto_data:/mosquitto/data + healthcheck: + test: + - CMD-SHELL + - >- + mosquitto_pub -h 127.0.0.1 -p 1883 -u "$$MQTT_USERNAME" + -P "$$MQTT_PASSWORD" -t __healthcheck -m ok -q 1 >/dev/null 2>&1 + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + labels: + - "traefik.tcp.routers.mosquitto-mqtts.entrypoints=mqtts" + - "traefik.tcp.routers.mosquitto-mqtts.rule=HostSNI(`${MQTT_DOMAIN}`)" + - "traefik.tcp.routers.mosquitto-mqtts.tls=true" + - "traefik.tcp.routers.mosquitto-mqtts.tls.certresolver=letsencrypt" + - "traefik.tcp.routers.mosquitto-mqtts.service=mosquitto-mqtts" + - "traefik.tcp.services.mosquitto-mqtts.loadbalancer.server.port=1883" + +volumes: + mosquitto_data: diff --git a/blueprints/mosquitto/meta.json b/blueprints/mosquitto/meta.json new file mode 100644 index 000000000..8b2408d2a --- /dev/null +++ b/blueprints/mosquitto/meta.json @@ -0,0 +1,13 @@ +{ + "id": "mosquitto", + "name": "Eclipse Mosquitto", + "version": "2.1.2", + "description": "A lightweight, open-source MQTT broker for IoT and home automation with persistent messaging and WebSocket support.", + "logo": "mosquitto.svg", + "links": { + "github": "https://github.com/eclipse-mosquitto/mosquitto", + "website": "https://mosquitto.org/", + "docs": "https://mosquitto.org/documentation/" + }, + "tags": ["broker", "iot", "mqtt"] +} diff --git a/blueprints/mosquitto/mosquitto.svg b/blueprints/mosquitto/mosquitto.svg new file mode 100644 index 000000000..3e5b2f9ad --- /dev/null +++ b/blueprints/mosquitto/mosquitto.svg @@ -0,0 +1,5 @@ + + Eclipse Mosquitto + + diff --git a/blueprints/mosquitto/template.toml b/blueprints/mosquitto/template.toml new file mode 100644 index 000000000..a01969a6a --- /dev/null +++ b/blueprints/mosquitto/template.toml @@ -0,0 +1,33 @@ +[variables] +broker_domain = "${domain}" +mqtt_username = "mqtt" +mqtt_password = "${password:32}" + +[config] +env = [ + "MQTT_DOMAIN=${broker_domain}", + "MQTT_USERNAME=${mqtt_username}", + "MQTT_PASSWORD=${mqtt_password}" +] + +[[config.domains]] +serviceName = "mosquitto" +port = 9001 +host = "${broker_domain}" + +[[config.mounts]] +filePath = "mosquitto.conf" +content = """ +persistence true +persistence_location /mosquitto/data/ +log_dest stdout + +listener 1883 +protocol mqtt + +listener 9001 +protocol websockets + +allow_anonymous false +password_file /mosquitto/data/passwordfile +""" diff --git a/blueprints/n8n/meta.json b/blueprints/n8n/meta.json index 43745166f..05121750c 100644 --- a/blueprints/n8n/meta.json +++ b/blueprints/n8n/meta.json @@ -2,7 +2,7 @@ "id": "n8n", "name": "n8n", "version": "1.104.0", - "description": "n8n is an open source low-code platform for automating workflows and integrations.", + "description": "n8n is an open-source, low-code platform for automating workflows and integrations. Self-host it on your own server with Dokploy, which deploys n8n as a Docker Compose service in minutes.", "logo": "n8n.png", "links": { "github": "https://github.com/n8n-io/n8n", @@ -11,5 +11,8 @@ }, "tags": [ "automation" - ] + ], + "seoTitle": "n8n: Self-Hosted & Docker Compose", + "seoDescription": "Workflow automation and integration tool. Self-host n8n via Docker Compose on Dokploy in minutes – no manual setup needed.", + "newConfig": true } diff --git a/blueprints/netdata/meta.json b/blueprints/netdata/meta.json index c7ce2b895..ba0207e44 100644 --- a/blueprints/netdata/meta.json +++ b/blueprints/netdata/meta.json @@ -2,7 +2,7 @@ "id": "netdata", "name": "Netdata", "version": "latest", - "description": "Netdata is a real-time performance monitoring tool that provides comprehensive system metrics, application monitoring, and infrastructure health insights.", + "description": "Netdata is a real-time monitoring tool for system and application performance. Self-host it on your own server with Dokploy, which deploys Netdata as a Docker Compose service in a few clicks.", "logo": "netdata.svg", "links": { "github": "https://github.com/netdata/netdata", @@ -15,5 +15,8 @@ "analytics", "performance", "infrastructure" - ] + ], + "seoTitle": "Netdata Docker Compose Setup", + "seoDescription": "Real-time system performance monitoring. Deploy Netdata with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/nextcloud-aio/meta.json b/blueprints/nextcloud-aio/meta.json index 6d4001dec..69d59d3ce 100644 --- a/blueprints/nextcloud-aio/meta.json +++ b/blueprints/nextcloud-aio/meta.json @@ -2,7 +2,7 @@ "id": "nextcloud-aio", "name": "Nextcloud", "version": "33.0.6", - "description": "Nextcloud is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.", + "description": "Nextcloud is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant, and more into a single platform. Self-host it on your own server with Dokploy, which deploys Nextcloud as a Docker Compose service in a few clicks.", "logo": "nextcloud.png", "links": { "github": "https://github.com/nextcloud/docker", @@ -12,5 +12,8 @@ "tags": [ "file-manager", "sync" - ] + ], + "seoTitle": "Nextcloud Docker Compose Setup", + "seoDescription": "File storage, sync, and collaboration platform. Deploy Nextcloud with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/nginx/meta.json b/blueprints/nginx/meta.json index bb5102d35..affc62812 100644 --- a/blueprints/nginx/meta.json +++ b/blueprints/nginx/meta.json @@ -2,7 +2,7 @@ "id": "nginx", "name": "Nginx", "version": "latest", - "description": "Nginx is an High performance web server", + "description": "Nginx is a high-performance web server and reverse proxy. Self-host it on your own server with Dokploy, which deploys Nginx as a Docker Compose service in a few clicks.", "logo": "nginx.png", "links": { "github": "https://github.com/nginx/nginx", @@ -11,5 +11,8 @@ }, "tags": [ "webserver" - ] + ], + "seoTitle": "Nginx Docker Compose Setup", + "seoDescription": "High-performance web server and reverse proxy. Deploy Nginx with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/nocodb/meta.json b/blueprints/nocodb/meta.json index 736bb7530..01d62de48 100644 --- a/blueprints/nocodb/meta.json +++ b/blueprints/nocodb/meta.json @@ -2,7 +2,7 @@ "id": "nocodb", "name": "NocoDB", "version": "latest", - "description": "NocoDB is an opensource Airtable alternative that turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadsheet.", + "description": "NocoDB is an open-source alternative to Airtable that turns your database into a smart spreadsheet. Self-host it on your own server with Dokploy, which deploys NocoDB as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/nocodb/nocodb", "website": "https://nocodb.com/", @@ -14,5 +14,8 @@ "spreadsheet", "low-code", "nocode" - ] + ], + "seoTitle": "NocoDB Docker Compose Setup", + "seoDescription": "Turns databases into spreadsheets, an Airtable alternative. Deploy NocoDB with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/odoo_19/meta.json b/blueprints/odoo_19/meta.json index 450ecb137..9075c4bcb 100644 --- a/blueprints/odoo_19/meta.json +++ b/blueprints/odoo_19/meta.json @@ -2,7 +2,7 @@ "id": "odoo_19", "name": "Odoo", "version": "19.0", - "description": "Odoo is a free and open source business management software that helps you manage your company's operations.", + "description": "Odoo is a suite of business management apps, including ERP, CRM, and accounting. Self-host it on your own server with Dokploy, which deploys Odoo as a Docker Compose service in a few clicks.", "logo": "odoo.png", "links": { "github": "https://github.com/odoo/odoo", @@ -11,5 +11,8 @@ }, "tags": [ "erp" - ] + ], + "seoTitle": "Odoo Docker Compose Setup", + "seoDescription": "Business management software and ERP. Deploy Odoo with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/open-webui/meta.json b/blueprints/open-webui/meta.json index 3766cb5d1..9ac3c2a53 100644 --- a/blueprints/open-webui/meta.json +++ b/blueprints/open-webui/meta.json @@ -2,7 +2,7 @@ "id": "open-webui", "name": "Open WebUI", "version": "v0.3.7", - "description": "Open WebUI is a free and open source chatgpt alternative. Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI designed to operate entirely offline. It supports various LLM runners, including Ollama and OpenAI-compatible APIs. The template include ollama and webui services.", + "description": "Open WebUI is an OpenSource ChatGPT-style chat interface for running AI models. Self-host it on your own server with Dokploy, which deploys Open WebUI as a Docker Compose service in a few clicks.", "logo": "open-webui.png", "links": { "github": "https://github.com/open-webui/open-webui", @@ -11,5 +11,8 @@ }, "tags": [ "chat" - ] + ], + "seoTitle": "Open WebUI Docker Compose Setup", + "seoDescription": "Self-hosted ChatGPT-style AI chat interface. Deploy Open WebUI with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/openwa/docker-compose.yml b/blueprints/openwa/docker-compose.yml new file mode 100644 index 000000000..ecf423167 --- /dev/null +++ b/blueprints/openwa/docker-compose.yml @@ -0,0 +1,46 @@ +services: + openwa: + image: ghcr.io/rmyndharis/openwa:0.24.0 + restart: unless-stopped + # Hardened runtime matching the upstream production defaults: Chromium (used by the + # whatsapp-web.js engine) launches with --no-sandbox, so the container is the confinement + # boundary. The caps that remain are only what the entrypoint needs to fix named-volume + # ownership and drop to the non-root `openwa` user via gosu. + security_opt: + - "no-new-privileges:true" + cap_drop: + - ALL + cap_add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + - KILL + - SETGID + - SETUID + read_only: true + tmpfs: + - /tmp + pids_limit: 2048 + environment: + - NODE_ENV=production + - PORT=2785 + # Writable HOME/XDG dirs on tmpfs so Chromium does not write to the read-only rootfs. + - HOME=/tmp + - XDG_CONFIG_HOME=/tmp/.config + - XDG_CACHE_HOME=/tmp/.cache + - ENGINE_TYPE=${ENGINE_TYPE} + - AUTO_START_SESSIONS=${AUTO_START_SESSIONS} + - TZ=${TZ} + # Production OpenWA sets the CSP "upgrade-insecure-requests" directive. When the dashboard is + # reached over plain HTTP the browser upgrades the SPA's own /assets/*.js requests to https://, + # which a non-TLS server cannot answer, so no JavaScript runs and the page renders blank. + # Off by default so the template works over HTTP or HTTPS; set "true" when you only ever reach + # it through a TLS-terminating proxy. + - CSP_UPGRADE_INSECURE_REQUESTS=${CSP_UPGRADE_INSECURE_REQUESTS} + expose: + - "2785" + volumes: + - openwa-data:/app/data + +volumes: + openwa-data: diff --git a/blueprints/openwa/meta.json b/blueprints/openwa/meta.json new file mode 100644 index 000000000..965ef7ca0 --- /dev/null +++ b/blueprints/openwa/meta.json @@ -0,0 +1,19 @@ +{ + "id": "openwa", + "name": "OpenWA", + "version": "0.24.0", + "description": "OpenWA is a free, self-hosted WhatsApp API gateway with a REST API, webhooks, multi-session support, and a bundled web dashboard.", + "logo": "openwa.svg", + "links": { + "github": "https://github.com/rmyndharis/OpenWA", + "website": "https://www.open-wa.org", + "docs": "https://github.com/rmyndharis/OpenWA/tree/main/docs" + }, + "tags": [ + "whatsapp", + "api", + "gateway", + "automation", + "messaging" + ] +} diff --git a/blueprints/openwa/openwa.svg b/blueprints/openwa/openwa.svg new file mode 100644 index 000000000..408772705 --- /dev/null +++ b/blueprints/openwa/openwa.svg @@ -0,0 +1,278 @@ + + + + + + + diff --git a/blueprints/openwa/template.toml b/blueprints/openwa/template.toml new file mode 100644 index 000000000..8381560da --- /dev/null +++ b/blueprints/openwa/template.toml @@ -0,0 +1,24 @@ +[variables] +main_domain = "${domain}" +engine_type = "whatsapp-web.js" +auto_start_sessions = "true" +timezone = "UTC" +# Set to "true" only when the dashboard is reached exclusively through a TLS-terminating proxy. +# Left "false" so the bundled SPA loads over plain HTTP (see comments in docker-compose.yml). +csp_upgrade_insecure_requests = "false" + +[config] +env = [ + "NODE_ENV=production", + "PORT=2785", + "ENGINE_TYPE=${engine_type}", + "AUTO_START_SESSIONS=${auto_start_sessions}", + "TZ=${timezone}", + "CSP_UPGRADE_INSECURE_REQUESTS=${csp_upgrade_insecure_requests}", +] +mounts = [] + +[[config.domains]] +serviceName = "openwa" +port = 2_785 +host = "${main_domain}" diff --git a/blueprints/outline/meta.json b/blueprints/outline/meta.json index 9ee0497d8..cb1e8e745 100644 --- a/blueprints/outline/meta.json +++ b/blueprints/outline/meta.json @@ -2,7 +2,7 @@ "id": "outline", "name": "Outline", "version": "0.82.0", - "description": "Outline is a self-hosted knowledge base and documentation platform that allows you to build and manage your own knowledge base applications.", + "description": "Outline is a self-hosted knowledge base for team documentation. Self-host it on your own server with Dokploy, which deploys Outline as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/outline/outline", "website": "https://getoutline.com/", @@ -13,5 +13,7 @@ "documentation", "knowledge-base", "self-hosted" - ] + ], + "seoTitle": "Self-Host Outline in minutes", + "seoDescription": "Knowledge base and team documentation tool. Self-host Outline on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/paperless-ngx/meta.json b/blueprints/paperless-ngx/meta.json index ac712c4e7..1ce28d396 100644 --- a/blueprints/paperless-ngx/meta.json +++ b/blueprints/paperless-ngx/meta.json @@ -2,7 +2,7 @@ "id": "paperless-ngx", "name": "Paperless-ngx", "version": "2.20.15", - "description": "Paperless-ngx is a self-hosted document management system for scanning, indexing, and archiving documents.", + "description": "Paperless-ngx is a document management system that turns scanned paperwork into a searchable archive. Self-host it on your own server with Dokploy, which deploys Paperless-ngx as a Docker Compose service in a few clicks.", "logo": "paperless-ngx.svg", "links": { "github": "https://github.com/paperless-ngx/paperless-ngx", @@ -14,5 +14,8 @@ "ocr", "productivity", "self-hosted" - ] + ], + "seoTitle": "Paperless-ngx Docker Compose Setup", + "seoDescription": "Document scanning and archiving system. Deploy Paperless-ngx with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/passbolt/meta.json b/blueprints/passbolt/meta.json index 6c4068e6a..7b9d8c546 100644 --- a/blueprints/passbolt/meta.json +++ b/blueprints/passbolt/meta.json @@ -2,7 +2,7 @@ "id": "passbolt", "name": "Passbolt", "version": "latest-ce", - "description": "Passbolt is an open source credential platform for modern teams. A versatile, battle-tested solution to manage and collaborate on passwords, accesses, and secrets. All in one.", + "description": "Passbolt is an open-source password manager built for teams. Self-host it on your own server with Dokploy, which deploys Passbolt as a Docker Compose service in a few clicks.", "logo": "passbolt.svg", "links": { "github": "https://github.com/passbolt/passbolt_api", @@ -14,5 +14,8 @@ "security", "team-collaboration", "encryption" - ] + ], + "seoTitle": "Passbolt Docker Compose Setup", + "seoDescription": "Password manager built for teams. Deploy Passbolt with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/pgadmin/meta.json b/blueprints/pgadmin/meta.json index 7ef63f8ed..09bea027d 100644 --- a/blueprints/pgadmin/meta.json +++ b/blueprints/pgadmin/meta.json @@ -2,7 +2,7 @@ "id": "pgadmin", "name": "pgAdmin", "version": "8.3", - "description": "pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.", + "description": "pgAdmin is an administration and management tool for PostgreSQL databases. Self-host it on your own server with Dokploy, which deploys pgAdmin as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/pgadmin-org/pgadmin4", "website": "https://www.pgadmin.org/", @@ -13,5 +13,8 @@ "database", "postgres", "admin" - ] + ], + "seoTitle": "pgAdmin Docker Compose Setup", + "seoDescription": "Admin tool for PostgreSQL databases. Deploy pgAdmin with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/photoprism/meta.json b/blueprints/photoprism/meta.json index e9c9c6f7a..767e70a52 100644 --- a/blueprints/photoprism/meta.json +++ b/blueprints/photoprism/meta.json @@ -2,7 +2,7 @@ "id": "photoprism", "name": "Photoprism", "version": "latest", - "description": "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.", + "description": "PhotoPrism is an AI-powered app for browsing, organizing, and sharing your photo library. Self-host it on your own server with Dokploy, which deploys PhotoPrism as a Docker Compose service in a few clicks.", "logo": "photoprism.svg", "links": { "github": "https://github.com/photoprism/photoprism", @@ -13,5 +13,7 @@ "media", "photos", "self-hosted" - ] + ], + "seoTitle": "Self-Host Photoprism in minutes", + "seoDescription": "AI-powered photo management app. Self-host Photoprism on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/phpmyadmin/meta.json b/blueprints/phpmyadmin/meta.json index 3de273e90..1161fd47c 100644 --- a/blueprints/phpmyadmin/meta.json +++ b/blueprints/phpmyadmin/meta.json @@ -2,7 +2,7 @@ "id": "phpmyadmin", "name": "Phpmyadmin", "version": "5.2.1", - "description": "Phpmyadmin is a free and open-source web interface for MySQL and MariaDB that allows you to manage your databases.", + "description": "phpMyAdmin is an open-source web interface for managing MySQL and MariaDB databases. Self-host it on your own server with Dokploy, which deploys phpMyAdmin as a Docker Compose service in a few clicks.", "logo": "phpmyadmin.png", "links": { "github": "https://github.com/phpmyadmin/phpmyadmin", @@ -11,5 +11,8 @@ }, "tags": [ "database" - ] + ], + "seoTitle": "Phpmyadmin Docker Compose Setup", + "seoDescription": "Web interface for managing MySQL databases. Deploy Phpmyadmin with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/plane/meta.json b/blueprints/plane/meta.json index f7d90ea92..6de06e346 100644 --- a/blueprints/plane/meta.json +++ b/blueprints/plane/meta.json @@ -2,7 +2,7 @@ "id": "plane", "name": "Plane", "version": "v1.3.1", - "description": "Open source project management — alternative to Jira, Linear, Asana", + "description": "Plane is a project management tool and an open-source alternative to Jira and Asana. Self-host it on your own server with Dokploy, which deploys Plane as a Docker Compose service in a few clicks.", "logo": "plane.png", "links": { "github": "https://github.com/makeplane/plane", @@ -12,5 +12,7 @@ "tags": [ "kanban", "project-management" - ] + ], + "seoTitle": "Self-Host Plane in minutes", + "seoDescription": "Project management, an alternative to Jira and Asana. Self-host Plane on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/plausible/meta.json b/blueprints/plausible/meta.json index 5c3e27d89..3fa3a8841 100644 --- a/blueprints/plausible/meta.json +++ b/blueprints/plausible/meta.json @@ -2,7 +2,7 @@ "id": "plausible", "name": "Plausible", "version": "v3.2.1", - "description": "Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.", + "description": "Plausible is an open-source, lightweight, and privacy-friendly web analytics tool. Self-host it on your own server with Dokploy, which deploys Plausible as a Docker Compose service in a few clicks.", "logo": "logo.svg", "links": { "github": "https://github.com/plausible/plausible", @@ -11,5 +11,7 @@ }, "tags": [ "analytics" - ] + ], + "seoTitle": "Self-Host Plausible in minutes", + "seoDescription": "Privacy-friendly web analytics. Self-host Plausible on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/ponos/docker-compose.yml b/blueprints/ponos/docker-compose.yml new file mode 100644 index 000000000..b3270460e --- /dev/null +++ b/blueprints/ponos/docker-compose.yml @@ -0,0 +1,32 @@ +services: + ponos: + image: ghcr.io/salvadorcardona/ponos:0.1.0 + restart: unless-stopped + expose: + - "8787" + environment: + PONOS_WEB_EMAIL: ${PONOS_WEB_EMAIL} + PONOS_WEB_PASSWORD: ${PONOS_WEB_PASSWORD} + PONOS_WEB_TOKEN: ${PONOS_WEB_TOKEN} + PONOS_LANGUAGE: ${PONOS_LANGUAGE} + CLAUDE_CODE_OAUTH_TOKEN: ${CLAUDE_CODE_OAUTH_TOKEN} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + GH_TOKEN: ${GH_TOKEN} + PONOS_NOTION_TOKEN: ${PONOS_NOTION_TOKEN} + volumes: + - ponos-data:/data + - ponos-workspace:/workspace + - ponos-claude:/home/ponos/.claude + - ponos-gh:/home/ponos/.config/gh + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8787/healthz"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 60s + +volumes: + ponos-data: + ponos-workspace: + ponos-claude: + ponos-gh: diff --git a/blueprints/ponos/meta.json b/blueprints/ponos/meta.json new file mode 100644 index 000000000..0e72e1c75 --- /dev/null +++ b/blueprints/ponos/meta.json @@ -0,0 +1,17 @@ +{ + "id": "ponos", + "name": "Ponos", + "version": "0.1.0", + "description": "Ponos runs your tickets with Claude Code: a ticket set to Ready becomes a session on a throwaway git worktree, and comes back as a pull request. Board in Notion or in Markdown files, with a web console.", + "logo": "ponos.svg", + "links": { + "github": "https://github.com/SalvadorCardona/ponos", + "website": "https://the-ponos.app/", + "docs": "https://github.com/SalvadorCardona/ponos#install-with-dokploy" + }, + "tags": [ + "ai", + "automation", + "developer-tools" + ] +} diff --git a/blueprints/ponos/ponos.svg b/blueprints/ponos/ponos.svg new file mode 100644 index 000000000..21f537fd2 --- /dev/null +++ b/blueprints/ponos/ponos.svg @@ -0,0 +1,244 @@ + +Ponos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/blueprints/ponos/template.toml b/blueprints/ponos/template.toml new file mode 100644 index 000000000..f29fbe1c5 --- /dev/null +++ b/blueprints/ponos/template.toml @@ -0,0 +1,33 @@ +[variables] +main_domain = "${domain}" +admin_password = "${password:32}" +api_token = "${password:48}" + +[config] +mounts = [] +env = [ + # Generated: the console's sign-in, and the token scripts carry. Change the email + # to yours if you like; the password is in this tab whenever you need it. + "PONOS_WEB_EMAIL=admin@${main_domain}", + "PONOS_WEB_PASSWORD=${admin_password}", + "PONOS_WEB_TOKEN=${api_token}", + # en or fr: the reports Ponos writes, and the console until you pick another. + "PONOS_LANGUAGE=en", + # Required, one of the two. Your Claude subscription: run `claude setup-token` + # on a machine with a browser and paste what it prints... + "CLAUDE_CODE_OAUTH_TOKEN=", + # ...or an Anthropic API key, billed by the token (https://console.anthropic.com). + "ANTHROPIC_API_KEY=", + # Optional: a GitHub token (repo scope), so Ponos clones, pushes and opens pull + # requests. The console's GitHub step says what is missing without it. + "GH_TOKEN=", + # Optional: a Notion integration token. Without one the board is kept in Markdown + # files on the ponos-data volume, and the console is all you need. + "PONOS_NOTION_TOKEN=", +] + +# The console: sign in with PONOS_WEB_EMAIL and PONOS_WEB_PASSWORD above. +[[config.domains]] +serviceName = "ponos" +port = 8_787 +host = "${main_domain}" diff --git a/blueprints/portainer/meta.json b/blueprints/portainer/meta.json index e72a2e469..3f41113a4 100644 --- a/blueprints/portainer/meta.json +++ b/blueprints/portainer/meta.json @@ -2,7 +2,7 @@ "id": "portainer", "name": "Portainer", "version": "latest", - "description": "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT.", + "description": "Portainer is a container management tool for deploying, troubleshooting, and securing applications across cloud, data centers, and IoT. Self-host it on your own server with Dokploy, which deploys Portainer as a Docker Compose service in a few clicks.", "logo": "portainer.png", "links": { "github": "https://github.com/portainer/portainer", @@ -12,5 +12,8 @@ "tags": [ "cloud", "monitoring" - ] + ], + "seoTitle": "Portainer Docker Compose Setup", + "seoDescription": "Web UI for managing Docker containers. Deploy Portainer with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/posthog/meta.json b/blueprints/posthog/meta.json index 015dec456..e14994d68 100644 --- a/blueprints/posthog/meta.json +++ b/blueprints/posthog/meta.json @@ -2,7 +2,7 @@ "id": "posthog", "name": "PostHog", "version": "master-2026-07-14", - "description": "PostHog is an open-source product analytics platform with feature flags, session replay, surveys, A/B testing and a customer data pipeline, all in one tool.", + "description": "PostHog is an open-source product analytics platform with feature flags and session replay. Self-host it on your own server with Dokploy, which deploys PostHog as a Docker Compose service in a few clicks.", "logo": "posthog.svg", "links": { "github": "https://github.com/PostHog/posthog", @@ -15,5 +15,7 @@ "feature-flags", "session-replay", "ab-testing" - ] + ], + "seoTitle": "Self-Host PostHog in minutes", + "seoDescription": "Product analytics with feature flags and session replay. Self-host PostHog on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/prometheus/meta.json b/blueprints/prometheus/meta.json index e970ecd6a..c0ecbabec 100644 --- a/blueprints/prometheus/meta.json +++ b/blueprints/prometheus/meta.json @@ -2,7 +2,7 @@ "id": "prometheus", "name": "Prometheus", "version": "latest", - "description": "Prometheus is an open-source monitoring and alerting toolkit designed for reliability and scalability.", + "description": "Prometheus is an open-source monitoring and alerting toolkit that collects metrics from your systems. Self-host it on your own server with Dokploy, which deploys Prometheus as a Docker Compose service in a few clicks.", "logo": "Prometheus.svg", "links": { "github": "https://github.com/prometheus/prometheus", @@ -13,5 +13,8 @@ "monitoring", "alerting", "metrics" - ] + ], + "seoTitle": "Prometheus Docker Compose Setup", + "seoDescription": "Monitoring and alerting toolkit. Deploy Prometheus with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/qbittorrent/meta.json b/blueprints/qbittorrent/meta.json index 29d2b9eeb..35a2b3f19 100644 --- a/blueprints/qbittorrent/meta.json +++ b/blueprints/qbittorrent/meta.json @@ -2,7 +2,7 @@ "id": "qbittorrent", "name": "qBittorrent", "version": "latest", - "description": "A free and open-source BitTorrent client with web interface for remote management. Default login: admin (check container logs for temporary password on first startup).", + "description": "qBittorrent is an open-source BitTorrent client you can control from a web interface. Self-host it on your own server with Dokploy, which deploys qBittorrent as a Docker Compose service in a few clicks.", "logo": "qbittorrent.svg", "links": { "github": "https://github.com/qbittorrent/qBittorrent", @@ -13,5 +13,8 @@ "torrent", "download", "file-sharing" - ] + ], + "seoTitle": "qBittorrent Docker Compose Setup", + "seoDescription": "Open source BitTorrent client. Deploy qBittorrent with Docker Compose on Dokploy – check the container logs for your temporary admin password on first login.", + "newConfig": true } diff --git a/blueprints/rocketchat/meta.json b/blueprints/rocketchat/meta.json index a2557512f..1fe379abc 100644 --- a/blueprints/rocketchat/meta.json +++ b/blueprints/rocketchat/meta.json @@ -2,7 +2,7 @@ "id": "rocketchat", "name": "Rocketchat", "version": "6.9.2", - "description": "Rocket.Chat is a free and open-source web chat platform that allows you to build and manage your own chat applications.", + "description": "Rocket.Chat is a team communication platform and an open-source alternative to Slack. Self-host it on your own server with Dokploy, which deploys Rocket.Chat as a Docker Compose service in a few clicks.", "logo": "rocketchat.png", "links": { "github": "https://github.com/RocketChat/Rocket.Chat", @@ -11,5 +11,7 @@ }, "tags": [ "chat" - ] + ], + "seoTitle": "Self-Host Rocket.Chat in minutes", + "seoDescription": "Team chat platform, an alternative to Slack. Self-host Rocket.Chat on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/rustdesk/meta.json b/blueprints/rustdesk/meta.json index 1c19feea4..daa592fbc 100644 --- a/blueprints/rustdesk/meta.json +++ b/blueprints/rustdesk/meta.json @@ -2,7 +2,7 @@ "id": "rustdesk", "name": "RustDesk", "version": "latest", - "description": "RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration.", + "description": "RustDesk is a remote desktop tool and an open-source alternative to TeamViewer. Self-host it on your own server with Dokploy, which deploys RustDesk as a Docker Compose service in a few clicks.", "logo": "rustdesk.png", "links": { "github": "https://github.com/rustdesk/rustdesk-server", @@ -13,5 +13,8 @@ "remote-desktop", "self-hosted", "productivity" - ] + ], + "seoTitle": "RustDesk Docker Compose Setup", + "seoDescription": "Open source remote desktop, an alternative to TeamViewer. Deploy RustDesk with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/seafile/meta.json b/blueprints/seafile/meta.json index ea9c7bc82..325e550a9 100644 --- a/blueprints/seafile/meta.json +++ b/blueprints/seafile/meta.json @@ -2,7 +2,7 @@ "id": "seafile", "name": "Seafile", "version": "13.0-latest", - "description": "Open source cloud storage system for file sync, share and document collaboration with SeaDoc", + "description": "Seafile is an open-source file sync and sharing platform. Self-host it on your own server with Dokploy, which deploys Seafile as a Docker Compose service in a few clicks.", "logo": "seafile.svg", "links": { "github": "https://github.com/haiwen/seafile", @@ -13,5 +13,7 @@ "file-manager", "file-sharing", "storage" - ] + ], + "seoTitle": "Self-Host Seafile in minutes", + "seoDescription": "Cloud storage for file sync and sharing. Self-host Seafile on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/searxng/meta.json b/blueprints/searxng/meta.json index 1c872b330..e39a70347 100644 --- a/blueprints/searxng/meta.json +++ b/blueprints/searxng/meta.json @@ -2,7 +2,7 @@ "id": "searxng", "name": "SearXNG", "version": "latest", - "description": "SearXNG is a privacy-respecting, hackable metasearch engine that aggregates results from various search engines without tracking users.", + "description": "SearXNG is a privacy-respecting metasearch engine that combines results from many search engines. Self-host it on your own server with Dokploy, which deploys SearXNG as a Docker Compose service in a few clicks.", "logo": "searxng.png", "links": { "github": "https://github.com/searxng/searxng", @@ -15,5 +15,8 @@ "privacy", "self-hosted", "aggregator" - ] + ], + "seoTitle": "SearXNG Docker Compose Setup", + "seoDescription": "Privacy-respecting metasearch engine. Deploy SearXNG with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/senddock/docker-compose.yml b/blueprints/senddock/docker-compose.yml index 8f78e2875..ecfed8941 100644 --- a/blueprints/senddock/docker-compose.yml +++ b/blueprints/senddock/docker-compose.yml @@ -26,7 +26,7 @@ services: retries: 5 senddock: - image: ghcr.io/arkhe-systems/senddock:latest + image: ghcr.io/arkhe-systems/senddock:0.8.3 restart: unless-stopped depends_on: postgres: diff --git a/blueprints/senddock/meta.json b/blueprints/senddock/meta.json index a91cbcd66..f1c2aa7e4 100644 --- a/blueprints/senddock/meta.json +++ b/blueprints/senddock/meta.json @@ -1,7 +1,7 @@ { "id": "senddock", "name": "SendDock", - "version": "0.8.0", + "version": "0.8.3", "description": "Open-source, self-hosted email marketing platform with BYO SMTP. Manage subscribers, templates, broadcasts, and scheduled campaigns from one place.", "logo": "senddock.svg", "links": { diff --git a/blueprints/shoutrrr/docker-compose.yml b/blueprints/shoutrrr/docker-compose.yml new file mode 100644 index 000000000..d0f4c155d --- /dev/null +++ b/blueprints/shoutrrr/docker-compose.yml @@ -0,0 +1,54 @@ +services: + shoutrrr: + image: ghcr.io/coollabsio/shoutrrr:1.5.4 + restart: unless-stopped + # Dokploy writes its Environment tab to .env. Pass optional settings through + # without replacing upstream's nullable defaults with empty strings. + env_file: + - path: .env + required: false + environment: + APP_ENV: production + APP_DEBUG: "false" + APP_KEY: "${APP_KEY:?Keep the generated APP_KEY}" + APP_URL: "${APP_URL:?Set APP_URL to the public URL}" + SELF_HOSTED: "true" + TRUSTED_PROXIES: "${TRUSTED_PROXIES:-*}" + OCTANE_SERVER: frankenphp + OCTANE_HTTPS: "${OCTANE_HTTPS:-false}" + SESSION_SECURE_COOKIE: "${SESSION_SECURE_COOKIE:-false}" + DB_CONNECTION: "${DB_CONNECTION:-sqlite}" + DB_DATABASE: "${DB_DATABASE:-/var/www/html/database/sqlite/database.sqlite}" + CACHE_STORE: "${CACHE_STORE:-database}" + QUEUE_CONNECTION: "${QUEUE_CONNECTION:-database}" + SESSION_DRIVER: "${SESSION_DRIVER:-database}" + FILESYSTEM_DISK: "${FILESYSTEM_DISK:-public}" + MAIL_MAILER: "${MAIL_MAILER:-log}" + LOG_CHANNEL: "${LOG_CHANNEL:-stderr}" + LOG_LEVEL: "${LOG_LEVEL:-info}" + INSTANCE_REGISTRATIONS_ENABLED: "${INSTANCE_REGISTRATIONS_ENABLED:-false}" + QUEUE_WORKER_ENABLED: "${QUEUE_WORKER_ENABLED:-true}" + QUEUE_WORKER_COUNT: "${QUEUE_WORKER_COUNT:-1}" + SCHEDULER_ENABLED: "${SCHEDULER_ENABLED:-true}" + INERTIA_SSR_ENABLED: "${INERTIA_SSR_ENABLED:-false}" + expose: + - "8080" + volumes: + - shoutrrr-storage:/var/www/html/storage + - shoutrrr-sqlite:/var/www/html/database/sqlite + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "--show-error", "http://127.0.0.1:8080/up"] + start_period: 60s + interval: 30s + timeout: 10s + retries: 3 + stop_grace_period: 60s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +volumes: + shoutrrr-storage: + shoutrrr-sqlite: diff --git a/blueprints/shoutrrr/environment.md b/blueprints/shoutrrr/environment.md new file mode 100644 index 000000000..1ce433e80 --- /dev/null +++ b/blueprints/shoutrrr/environment.md @@ -0,0 +1,525 @@ +# Shoutrrr 1.5.4 environment reference + +Set variables in Dokploy **Environment** and redeploy. Its `.env` file is passed to the container, matching upstream’s production Compose approach. Only the core settings appear initially; every runtime variable below can be added without editing Compose. Omit unused optional keys rather than setting them to empty strings. Never commit credentials. + +This inventory covers the application’s runtime configuration in [the pinned v1.5.4 source](https://github.com/coollabsio/shoutrrr/tree/v1.5.4/config), its documented environment example, and Shoutrrr-specific image controls. It does not enumerate generic PHP/base-image environment extensions. Defaults below are effective template values where overridden; otherwise they show upstream fallback values or expressions. `unset` means no explicit fallback. Database-driver-specific alternatives are separated with semicolons. + +## Required and automatic settings + +- **Required, generated:** `APP_KEY` (32 random bytes encoded as `base64:…`) and `APP_URL` (the generated domain). No credentials need to be supplied to boot. +- **Required for public production use:** enable HTTPS in Dokploy and set `APP_URL=https://…`, `OCTANE_HTTPS=true`, and `SESSION_SECURE_COOKIE=true` before using real accounts. +- **Automatic defaults:** self-hosted mode, SQLite, database cache/queue/sessions, public local media, one worker, scheduler, non-debug production mode, and closed registration after the first owner. +- **Conditional:** mail delivery credentials for delivered email; provider OAuth credentials for the relevant platforms; object-storage or external database/queue credentials only when choosing those services. +- **Fixed by Compose:** `APP_ENV=production`, `APP_DEBUG=false`, `SELF_HOSTED=true`, and `OCTANE_SERVER=frankenphp`. Other mapped variables can be overridden in Environment. + +## Runtime variables + +### Application and proxy + +`APP_KEY` and `APP_URL` are required and generated at import. Preserve the key across upgrades. Locale and maintenance settings are optional. `APP_ENV`, `APP_DEBUG`, and self-hosted mode are fixed by Compose for this template. + +| Variable | Default | +| --- | --- | +| `APP_NAME` | `Shoutrrr` | +| `APP_VERSION` | `1.5.4 (baked into image)` | +| `APP_ENV` | `production (fixed)` | +| `APP_DEBUG` | `false (fixed)` | +| `APP_URL` | `generated HTTP URL; required; change to public HTTPS URL` | +| `TRUSTED_PROXIES` | `*` | +| `APP_LOCALE` | `en` | +| `APP_FALLBACK_LOCALE` | `en` | +| `APP_FAKER_LOCALE` | `en_US` | +| `APP_KEY` | `generated: base64 + 32 random bytes; required` | +| `APP_PREVIOUS_KEYS` | `empty string` | +| `APP_MAINTENANCE_DRIVER` | `file` | +| `APP_MAINTENANCE_STORE` | `database` | + +### Authentication + +Advanced overrides; keep the supplied authentication guard/model. Email verification is automatically disabled with the `log` or `array` mailer and enabled with a delivery transport. Signed-URL exceptions accommodate mail click tracking. + +| Variable | Default | +| --- | --- | +| `AUTH_GUARD` | `web` | +| `AUTH_PASSWORD_BROKER` | `users` | +| `MAIL_MAILER` | `log` | +| `SIGNED_URL_IGNORED_PARAMETERS` | `utm_source,utm_medium,utm_campaign,utm_term,utm_content,bento_uuid` | +| `AUTH_MODEL` | `App\Models\User` | +| `AUTH_PASSWORD_RESET_TOKEN_TABLE` | `password_reset_tokens` | +| `AUTH_PASSWORD_TIMEOUT` | `10800` | + +### Cache backends + +Optional tuning. Database cache needs no extra service. Memcached, Redis, DynamoDB, or other backends require their own infrastructure and credentials; this template does not provision them. Omit nullable connection/lock settings to inherit the primary connection. + +| Variable | Default | +| --- | --- | +| `CACHE_STORE` | `database` | +| `DB_CACHE_CONNECTION` | `unset` | +| `DB_CACHE_TABLE` | `cache` | +| `DB_CACHE_LOCK_CONNECTION` | `unset` | +| `DB_CACHE_LOCK_TABLE` | `unset` | +| `CACHE_STORAGE_DISK` | `unset` | +| `CACHE_STORAGE_PATH` | `framework/cache/data` | +| `MEMCACHED_PERSISTENT_ID` | `unset` | +| `MEMCACHED_USERNAME` | `unset` | +| `MEMCACHED_PASSWORD` | `unset` | +| `MEMCACHED_HOST` | `127.0.0.1` | +| `MEMCACHED_PORT` | `11211` | +| `REDIS_CACHE_CONNECTION` | `cache` | +| `REDIS_CACHE_LOCK_CONNECTION` | `default` | +| `AWS_ACCESS_KEY_ID` | `unset` | +| `AWS_SECRET_ACCESS_KEY` | `unset` | +| `AWS_DEFAULT_REGION` | `us-east-1` | +| `DYNAMODB_CACHE_TABLE` | `cache` | +| `DYNAMODB_ENDPOINT` | `unset` | +| `CACHE_PREFIX` | `derived from APP_NAME` | + +### Database and Redis + +SQLite requires no database credentials. Keep its database file in the mounted directory. External databases require an appropriate connection, reachable host, database name, username, and password (or `DB_URL`). Ports, charset, collation, and SSL options depend on the selected driver. Redis settings are used only if a Redis-backed driver is selected; Redis is not included. + +| Variable | Default | +| --- | --- | +| `DB_CONNECTION` | `sqlite` | +| `DB_URL` | `unset` | +| `DB_DATABASE` | `/var/www/html/database/sqlite/database.sqlite` | +| `DB_FOREIGN_KEYS` | `true` | +| `DB_HOST` | `127.0.0.1; localhost` | +| `DB_PORT` | `3306; 5432; 1433` | +| `DB_USERNAME` | `root` | +| `DB_PASSWORD` | `empty string` | +| `DB_SOCKET` | `empty string` | +| `DB_CHARSET` | `utf8mb4; utf8` | +| `DB_COLLATION` | `utf8mb4_unicode_ci` | +| `MYSQL_ATTR_SSL_CA` | `unset` | +| `DB_SSLMODE` | `prefer` | +| `REDIS_CLIENT` | `phpredis` | +| `REDIS_CLUSTER` | `redis` | +| `REDIS_PREFIX` | `derived from APP_NAME` | +| `REDIS_PERSISTENT` | `false` | +| `REDIS_URL` | `unset` | +| `REDIS_HOST` | `127.0.0.1` | +| `REDIS_USERNAME` | `unset` | +| `REDIS_PASSWORD` | `unset` | +| `REDIS_PORT` | `6379` | +| `REDIS_DB` | `0` | +| `REDIS_MAX_RETRIES` | `3` | +| `REDIS_BACKOFF_ALGORITHM` | `decorrelated_jitter` | +| `REDIS_BACKOFF_BASE` | `100` | +| `REDIS_BACKOFF_CAP` | `1000` | +| `REDIS_CACHE_DB` | `1` | + +### Engagement polling + +Optional polling/backoff tuning. Values with `MINUTES` or `DAYS` use the named units; rate limits are per minute. Instance UI settings may override feature/polling defaults. + +| Variable | Default | +| --- | --- | +| `ENGAGEMENT_ENABLED` | `true` | +| `ENGAGEMENT_WINDOW_DAYS` | `7` | +| `ENGAGEMENT_STEADY_INTERVAL_MINUTES` | `1440` | +| `ENGAGEMENT_MAX_EMPTY_BACKOFF` | `8` | +| `ENGAGEMENT_FETCH_RATE_PER_MINUTE` | `12` | +| `ENGAGEMENT_DEFAULT_RATE_LIMIT_BACKOFF` | `900` | +| `ENGAGEMENT_MAX_SEARCH_PAGES` | `5` | + +### Feedback delivery + +Optional. Enable the feedback widget only with a delivery webhook. This webhook sends application feedback and is separate from a publishing account configured in the UI. + +| Variable | Default | +| --- | --- | +| `FEEDBACK_ENABLED` | `false` | +| `FEEDBACK_DISCORD_WEBHOOK_URL` | `unset` | + +### Media and object storage + +The template uses local public storage. S3 requires credentials, region, bucket, and an accessible public media URL. Endpoint/path-style settings support S3-compatible providers. A separate public-images disk/bucket is optional. + +| Variable | Default | +| --- | --- | +| `FILESYSTEM_DISK` | `public` | +| `PUBLIC_IMAGES_DISK` | `unset` | +| `AWS_BUCKET` | `unset` | +| `AWS_URL` | `unset` | +| `AWS_ENDPOINT` | `unset` | +| `AWS_USE_PATH_STYLE_ENDPOINT` | `false` | +| `PUBLIC_IMAGES_BUCKET` | `env('AWS_BUCKET')` | +| `PUBLIC_IMAGES_URL` | `env('AWS_URL')` | + +### Passkeys + +Optional stable secret for passkey user handles. Leave omitted to use upstream behavior; do not replace it casually after enrolling passkeys. + +| Variable | Default | +| --- | --- | +| `PASSKEYS_USER_HANDLE_SECRET` | `config('app.key')` | + +### Image processing + +Optional image-processing driver override; keep the image-compatible default. + +| Variable | Default | +| --- | --- | +| `IMAGE_DRIVER` | `gd` | + +### Server-side rendering + +Optional SSR. The container includes the SSR bundle/runtime and starts its SSR process when enabled; it is disabled by default. + +| Variable | Default | +| --- | --- | +| `INERTIA_SSR_ENABLED` | `false` | +| `INERTIA_SSR_URL` | `http://127.0.0.1:13714` | + +### Instance defaults + +`SELF_HOSTED=true` is fixed to disable cloud billing requirements. Instance feature settings seed database defaults; owner changes in the UI take precedence. Public registration is off after the first owner is created. + +| Variable | Default | +| --- | --- | +| `SELF_HOSTED` | `true (fixed)` | +| `SHOUTRRR_GITHUB_REPO` | `coollabsio/shoutrrr` | +| `SHOUTRRR_SPONSOR_URL` | `https://github.com/sponsors/coollabsio` | +| `INSTANCE_REGISTRATIONS_ENABLED` | `false` | +| `INSTANCE_WORKSPACE_CREATION_ENABLED` | `env('WORKSPACES_CAN_CREATE_WORKSPACE', true)` | +| `WORKSPACES_CAN_CREATE_WORKSPACE` | `true` | +| `INSTANCE_USAGE_TRACKING_ENABLED` | `false` | +| `INSTANCE_QUOTE_TWEETS_ENABLED` | `false` | +| `INSTANCE_LINKEDIN_COMMUNITY_MANAGEMENT_ENABLED` | `false` | + +### Workspaces and social sign-in + +Optional workspace behavior and social sign-in. Social sign-in also needs credentials for each enabled provider in the integration table; it is independent of publishing-account connections. + +| Variable | Default | +| --- | --- | +| `WORKSPACES_ENABLED` | `true` | +| `WORKSPACE_INVITATION_TTL_DAYS` | `7` | +| `SOCIALITE_ENABLED` | `false` | +| `SOCIALITE_PROVIDERS` | `google` | + +### Logging + +Docker receives application logs through `stderr` at `info` level by default and rotates them. Other channels require their destination credentials. File channels write into persistent storage and need their own retention policy. + +| Variable | Default | +| --- | --- | +| `LOG_CHANNEL` | `stderr` | +| `LOG_DEPRECATIONS_CHANNEL` | `null` | +| `LOG_DEPRECATIONS_TRACE` | `false` | +| `LOG_STACK` | `single` | +| `LOG_LEVEL` | `info` | +| `LOG_DAILY_DAYS` | `14` | +| `LOG_SLACK_WEBHOOK_URL` | `unset` | +| `LOG_SLACK_USERNAME` | `env('APP_NAME', 'Shoutrrr')` | +| `LOG_SLACK_EMOJI` | `:boom:` | +| `LOG_PAPERTRAIL_HANDLER` | `SyslogUdpHandler::class` | +| `PAPERTRAIL_URL` | `unset` | +| `PAPERTRAIL_PORT` | `unset` | +| `LOG_STDERR_FORMATTER` | `unset` | +| `LOG_SYSLOG_FACILITY` | `LOG_USER` | + +### Email + +Optional for initial setup. A delivery transport is needed for emailed invitations, verification, and password resets. SMTP needs a host/port and usually username/password, scheme, and a valid sender. API transports use the corresponding integration credentials. `MAIL_URL` can supply transport settings. The log mailer records messages without sending them. + +| Variable | Default | +| --- | --- | +| `MAIL_SCHEME` | `unset` | +| `MAIL_URL` | `unset` | +| `MAIL_HOST` | `127.0.0.1` | +| `MAIL_PORT` | `2525` | +| `MAIL_USERNAME` | `unset` | +| `MAIL_PASSWORD` | `unset` | +| `MAIL_EHLO_DOMAIN` | `parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)` | +| `MAIL_SENDMAIL_PATH` | `/usr/sbin/sendmail -bs -i` | +| `MAIL_LOG_CHANNEL` | `unset` | +| `MAIL_FROM_ADDRESS` | `hello@example.com` | +| `MAIL_FROM_NAME` | `env('APP_NAME', 'Shoutrrr')` | +| `MAIL_MARKDOWN_THEME` | `default` | + +### Media limits + +Optional positive pixel limit for image decode memory protection; the image default is 16,000,000 pixels. PHP upload/request limits are listed under container controls. + +| Variable | Default | +| --- | --- | +| `MEDIA_MAX_IMAGE_PIXELS` | `16000000` | + +### Direct messages + +Optional inbox/DM polling controls. Poll intervals use minutes, rate-limit backoff uses seconds, and fetch rates are per minute. Platform permissions and instance UI settings also control availability. + +| Variable | Default | +| --- | --- | +| `MESSAGES_ENABLED` | `true` | +| `DIRECT_MESSAGES_ENABLED` | `true` | +| `MESSAGES_POLL_INTERVAL_X` | `30` | +| `MESSAGES_POLL_INTERVAL_BLUESKY` | `10` | +| `MESSAGES_POLL_INTERVAL_INSTAGRAM` | `15` | +| `MESSAGES_POLL_INTERVAL_FACEBOOK` | `15` | +| `MESSAGES_DEFAULT_RATE_LIMIT_BACKOFF` | `900` | +| `MESSAGES_FETCH_RATE_PER_MINUTE` | `12` | + +### Metrics + +Optional metrics polling and unchanged-result backoff. + +| Variable | Default | +| --- | --- | +| `METRICS_ENABLED` | `true` | +| `METRICS_MAX_UNCHANGED_BACKOFF` | `8` | + +### Web server + +The image serves HTTP on internal port 8080. Dokploy terminates TLS. Set `OCTANE_HTTPS=true` together with a public HTTPS `APP_URL` and secure cookies. + +| Variable | Default | +| --- | --- | +| `OCTANE_SERVER` | `frankenphp (fixed)` | +| `OCTANE_HTTPS` | `false; set true for HTTPS` | + +### API signing keys + +Optional external RSA PEM keys for API/MCP token signing. Otherwise keys are generated on first API-key creation and persisted under storage. Supply both keys together or retain the storage volume. Omit `PASSPORT_CONNECTION` to use the application database; an empty connection string is not a replacement for omission. + +| Variable | Default | +| --- | --- | +| `PASSPORT_PRIVATE_KEY` | `unset` | +| `PASSPORT_PUBLIC_KEY` | `unset` | +| `PASSPORT_AUTO_GENERATE_KEYS` | `true` | +| `PASSPORT_CONNECTION` | `unset` | + +### Missed posts + +Optional threshold in minutes before an overdue post is marked missed. + +| Variable | Default | +| --- | --- | +| `POSTS_MISSED_AFTER_MINUTES` | `2880` | + +### Queue backends + +The bundled worker consumes the database queue by default. External queue backends need their own services/credentials. Retry-after values must exceed the publishing job timeout (900 seconds); upstream uses 1200 seconds. Disabling the worker prevents queued jobs from running unless a replacement worker exists. + +| Variable | Default | +| --- | --- | +| `QUEUE_CONNECTION` | `database` | +| `DB_QUEUE_CONNECTION` | `unset` | +| `DB_QUEUE_TABLE` | `jobs` | +| `DB_QUEUE` | `default` | +| `DB_QUEUE_RETRY_AFTER` | `1200` | +| `BEANSTALKD_QUEUE_HOST` | `localhost` | +| `BEANSTALKD_QUEUE` | `default` | +| `BEANSTALKD_QUEUE_RETRY_AFTER` | `1200` | +| `SQS_PREFIX` | `https://sqs.us-east-1.amazonaws.com/your-account-id` | +| `SQS_QUEUE` | `default` | +| `SQS_SUFFIX` | `unset` | +| `REDIS_QUEUE_CONNECTION` | `default` | +| `REDIS_QUEUE` | `default` | +| `REDIS_QUEUE_RETRY_AFTER` | `1200` | +| `QUEUE_FAILED_DRIVER` | `database-uuids` | + +### Automatic reposting + +Optional repost feature and defaults; individual account settings may override them. Delays/backfill windows use the named hours/days units. + +| Variable | Default | +| --- | --- | +| `REPOST_ENABLED` | `true` | +| `REPOST_MAX_BACKFILL_DAYS` | `30` | +| `REPOST_MIN_DELAY_HOURS` | `24` | +| `REPOST_MAX_DELAY_HOURS` | `168` | +| `REPOST_PLATEAU_STREAK` | `2` | +| `REPOST_MIN_PERCENTILE` | `0.5` | +| `REPOST_BASELINE_WINDOW_DAYS` | `90` | +| `REPOST_BASELINE_MIN_SAMPLES` | `5` | + +### API documentation + +Optional version label for API documentation. + +| Variable | Default | +| --- | --- | +| `API_VERSION` | `1.0.0` | + +### Browser error reporting + +Optional browser telemetry. Omit the frontend DSN to disable it. Browser DSNs are intentionally public; do not put private API credentials here. + +| Variable | Default | +| --- | --- | +| `SENTRY_RELEASE` | `APP_VERSION (1.5.4)` | +| `SENTRY_FRONTEND_DSN` | `unset` | +| `SENTRY_ENVIRONMENT` | `APP_ENV (production)` | +| `SENTRY_FRONTEND_TRACES_SAMPLE_RATE` | `0.2` | + +### Server error reporting + +Optional server telemetry and sampling. Omit the DSN to disable delivery. The following are advanced SDK overrides, including optional PII collection and trace/breadcrumb controls; they are not installation requirements. + +| Variable | Default | +| --- | --- | +| `SENTRY_LARAVEL_DSN` | `SENTRY_DSN if configured` | +| `SENTRY_DSN` | `unset` | +| `SENTRY_ORG_ID` | `unset` | +| `SENTRY_SAMPLE_RATE` | `1.0` | +| `SENTRY_TRACES_SAMPLE_RATE` | `0.2` | +| `SENTRY_PROFILES_SAMPLE_RATE` | `null` | +| `SENTRY_STRICT_TRACE_CONTINUATION` | `false` | +| `SENTRY_ENABLE_LOGS` | `false` | +| `SENTRY_LOG_FLUSH_THRESHOLD` | `null` | +| `SENTRY_LOG_LEVEL` | `SENTRY_LOGS_LEVEL, then LOG_LEVEL (info)` | +| `SENTRY_LOGS_LEVEL` | `LOG_LEVEL (info)` | +| `SENTRY_SEND_DEFAULT_PII` | `false` | +| `SENTRY_BREADCRUMBS_LOGS_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_CACHE_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_SQL_BINDINGS_ENABLED` | `false` | +| `SENTRY_BREADCRUMBS_QUEUE_INFO_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_COMMAND_JOBS_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_HTTP_CLIENT_REQUESTS_ENABLED` | `true` | +| `SENTRY_BREADCRUMBS_NOTIFICATIONS_ENABLED` | `true` | +| `SENTRY_TRACE_QUEUE_ENABLED` | `true` | +| `SENTRY_TRACE_QUEUE_JOBS_ENABLED` | `true` | +| `SENTRY_TRACE_SQL_QUERIES_ENABLED` | `true` | +| `SENTRY_TRACE_SQL_BINDINGS_ENABLED` | `false` | +| `SENTRY_TRACE_SQL_ORIGIN_ENABLED` | `true` | +| `SENTRY_TRACE_SQL_ORIGIN_THRESHOLD_MS` | `100` | +| `SENTRY_TRACE_VIEWS_ENABLED` | `true` | +| `SENTRY_TRACE_LIVEWIRE_ENABLED` | `true` | +| `SENTRY_TRACE_HTTP_CLIENT_REQUESTS_ENABLED` | `true` | +| `SENTRY_TRACE_CACHE_ENABLED` | `true` | +| `SENTRY_TRACE_REDIS_COMMANDS` | `false` | +| `SENTRY_TRACE_REDIS_ORIGIN_ENABLED` | `true` | +| `SENTRY_TRACE_NOTIFICATIONS_ENABLED` | `true` | +| `SENTRY_TRACE_MISSING_ROUTES_ENABLED` | `false` | +| `SENTRY_TRACE_CONTINUE_AFTER_RESPONSE` | `true` | +| `SENTRY_TRACE_DEFAULT_INTEGRATIONS_ENABLED` | `true` | + +### Integration credentials + +Conditional credentials: only configure integrations you use. Mail providers, Slack notifications, Google sign-in, social publishing, and KLIPY are independent. For OAuth configure a client ID, client secret, and redirect URI together; see the callback table in instructions.md. AWS values are listed under cache and storage. + +| Variable | Default | +| --- | --- | +| `POSTMARK_API_KEY` | `unset` | +| `RESEND_API_KEY` | `unset` | +| `SLACK_BOT_USER_OAUTH_TOKEN` | `unset` | +| `SLACK_BOT_USER_DEFAULT_CHANNEL` | `unset` | +| `GOOGLE_CLIENT_ID` | `unset` | +| `GOOGLE_CLIENT_SECRET` | `unset` | +| `GOOGLE_REDIRECT_URI` | `unset` | +| `X_CLIENT_ID` | `unset` | +| `X_CLIENT_SECRET` | `unset` | +| `X_REDIRECT_URI` | `unset` | +| `X_BEARER_TOKEN` | `unset` | +| `LINKEDIN_CLIENT_ID` | `unset` | +| `LINKEDIN_CLIENT_SECRET` | `unset` | +| `LINKEDIN_REDIRECT_URI` | `unset` | +| `LINKEDIN_API_VERSION` | `202605` | +| `FACEBOOK_CLIENT_ID` | `unset` | +| `FACEBOOK_CLIENT_SECRET` | `unset` | +| `FACEBOOK_REDIRECT_URI` | `unset` | +| `FACEBOOK_GRAPH_VERSION` | `v25.0` | +| `THREADS_CLIENT_ID` | `unset` | +| `THREADS_CLIENT_SECRET` | `unset` | +| `THREADS_REDIRECT_URI` | `unset` | +| `KLIPY_API_KEY` | `unset` | +| `KLIPY_SHARE_TRIGGER` | `true` | +| `KLIPY_RATING` | `pg-13` | + +### Sessions and cookies + +Database sessions are persistent by default. Secure cookies must be enabled for public HTTPS deployments. Omit domain/connection/store to inherit upstream defaults. Keep HTTP-only cookies and SameSite protection enabled. + +| Variable | Default | +| --- | --- | +| `SESSION_DRIVER` | `database` | +| `SESSION_LIFETIME` | `10080` | +| `SESSION_EXPIRE_ON_CLOSE` | `false` | +| `SESSION_ENCRYPT` | `false` | +| `SESSION_CONNECTION` | `unset` | +| `SESSION_TABLE` | `sessions` | +| `SESSION_STORE` | `unset` | +| `SESSION_COOKIE` | `derived from APP_NAME` | +| `SESSION_PATH` | `/` | +| `SESSION_DOMAIN` | `unset` | +| `SESSION_SECURE_COOKIE` | `false; set true for HTTPS` | +| `SESSION_HTTP_ONLY` | `true` | +| `SESSION_SAME_SITE` | `lax` | +| `SESSION_PARTITIONED_COOKIE` | `false` | + +### Cloud subscription settings + +Unused in this template because `SELF_HOSTED=true`. These upstream cloud billing/quota settings are listed for completeness; do not switch self-hosted mode off to install the application. + +| Variable | Default | +| --- | --- | +| `STRIPE_SUBSCRIPTION_PRICE_ID` | `price_shoutrrr_monthly_test` | +| `SHOUTRRR_SUBSCRIPTION_MONTHLY_CENTS` | `1000` | +| `SHOUTRRR_X_MONTHLY_BUDGET_CENTS` | `500` | +| `SUBSCRIPTIONS_MAX_SYNC_PIPELINES` | `3` | +| `SUBSCRIPTIONS_MAX_NATIVE_TRACKED` | `3` | + +### Account synchronization + +Optional connected-account sync controls; lookback uses minutes. + +| Variable | Default | +| --- | --- | +| `SYNC_ENABLED` | `true` | +| `SYNC_RECONCILE_LOOKBACK_MINUTES` | `60` | + +### Usage retention + +Optional retention in days for usage records. + +| Variable | Default | +| --- | --- | +| `USAGE_RETENTION_DAYS` | `180` | + +## Container controls + +These are read by the image/supervisor scripts rather than Laravel configuration. The bundled worker and scheduler must remain enabled for normal single-container operation. Do not change the application directory, internal port, or HTTP-only listener behind Dokploy. A graceful stop allows 60 seconds; avoid interrupting active publishing jobs when maintaining the service. These controls are optional unless marked fixed. + +| Variable | Image/template default and purpose | +| --- | --- | +| `QUEUE_WORKER_ENABLED` | `true`; start the bundled queue worker | +| `QUEUE_WORKER_COUNT` | `1`; positive integer, retain one with SQLite | +| `QUEUE_WORKER_MEMORY_MB` | derived from PHP memory limit; optional positive MB override (minimum 64) | +| `SCHEDULER_ENABLED` | `true`; run the bundled scheduler | +| `PHP_MEMORY_LIMIT` | `512M`; PHP process memory limit | +| `PHP_POST_MAX_SIZE` | `1100M`; request body size limit | +| `PHP_UPLOAD_MAX_FILE_SIZE` | `1100M`; individual upload size limit | +| `PHP_OPCACHE_ENABLE` | `1`; production opcode cache | +| `SSL_MODE` | `off`; TLS terminates at Dokploy | +| `AUTORUN_ENABLED` | `true`; image initialization and migrations | +| `AUTORUN_LARAVEL_CONFIG_CACHE` | `true`; cache environment configuration at startup | +| `AUTORUN_LARAVEL_EVENT_CACHE` | `true`; cache event discovery | +| `AUTORUN_LARAVEL_ROUTE_CACHE` | `true`; cache routes | +| `AUTORUN_LARAVEL_VIEW_CACHE` | `true`; cache views | +| `AUTORUN_LARAVEL_STORAGE_LINK` | `true`; create public media symlink | +| `APP_BASE_DIR` | `/var/www/html`; keep the image layout | +| `HEALTHCHECK_PATH` | `/up`; image default; this template checks `/up` explicitly | + +## Example-only, build, and development variables + +These appear in upstream’s full example or development tooling. They are not required deployment inputs and do not reconfigure the prebuilt frontend or add services to this template. + +| Variable | Scope | +| --- | --- | +| `APP_PORT` | Upstream local Compose host port; Dokploy uses service port 8080 instead | +| `ASSET_URL` | Laravel/vendor asset helper override; leave omitted to serve bundled assets from the application | +| `VITE_APP_NAME` | Frontend build input; assets are already compiled | +| `VITE_HMR_HOST`, `VITE_PORT` | Development asset server/HMR | +| `SKIP_WAYFINDER_GENERATE` | Frontend development/build route generation | +| `POSTGRES_VERSION` | Upstream Docker build argument; does not install a database server | +| `POSTGRES_DB` | Initialization variable for upstream’s optional PostgreSQL service; no such service is included here | +| `POSTMARK_MESSAGE_STREAM_ID` | Upstream example; the corresponding mail option is commented out in this release | +| `DB_ENCRYPT`, `DB_TRUST_SERVER_CERTIFICATE` | Present in upstream example but commented out in the shipped SQL Server configuration | + +For deployment steps, HTTPS, SMTP examples, OAuth callbacks, and backup requirements, see [instructions.md](instructions.md). Full defaults remain version-specific; review this reference when upgrading the pinned image. diff --git a/blueprints/shoutrrr/instructions.md b/blueprints/shoutrrr/instructions.md new file mode 100644 index 000000000..df5050794 --- /dev/null +++ b/blueprints/shoutrrr/instructions.md @@ -0,0 +1,60 @@ +# Shoutrrr + +Deploys [Coollabs' Shoutrrr](https://github.com/coollabsio/shoutrrr), the social media scheduler requested in [#1075](https://github.com/Dokploy/templates/issues/1075). This is a different project from the Containrrr notification library with the same name. + +The pinned image runs the web server, queue worker, and scheduler together. SQLite stores accounts, drafts, schedules, sessions, and queued jobs. Separate volumes persist the database and storage, including uploads and generated API signing keys. PostgreSQL, Redis, email, and social-provider credentials are **not required to start**. Use one application replica with these SQLite defaults. + +## Deploy + +1. Import the template into a Docker Compose service in Dokploy. Keep its generated `APP_KEY`; it encrypts saved credentials. The image initializes the database and runs migrations automatically. +2. Before creating accounts or connecting social platforms, point your domain at Dokploy and enable HTTPS with a certificate in **Domains**. Keep the service port at **8080**. Set the following in **Environment**, then redeploy: + + ```dotenv + APP_URL=https://social.example.com + OCTANE_HTTPS=true + SESSION_SECURE_COOKIE=true + ``` + + The generated HTTP address supports initial deployment checks. For local HTTP testing, use the generated defaults instead. `TRUSTED_PROXIES=*` supports Dokploy's TLS termination; the application has no published host port. Restrict it to your proxy IPs/CIDRs if other untrusted containers can reach the application directly. +3. Open the domain and register the first account. It becomes the instance owner even though public registration defaults to disabled. Claim it promptly, or restrict access at your proxy until setup finishes. Later users need an invitation unless the owner enables registration. Instance settings saved in the UI take precedence over the environment defaults. +4. Connect the platforms you use under **Accounts**, then create a draft or schedule a post. Workers and the scheduler must remain enabled for scheduled publishing. + +Dokploy passes additional variables from **Environment** through the service's optional `.env` file. Add only settings you need, then redeploy to rebuild Laravel's configuration cache. See the [complete environment reference](https://github.com/Dokploy/templates/blob/canary/blueprints/shoutrrr/environment.md) for required, conditional, advanced, and build-only variables. Empty values and omitted values can behave differently; do not paste the entire upstream example with blank entries. + +## Optional integrations + +**Email:** `MAIL_MAILER=log` allows local setup without an email server; messages go to application logs and are not delivered. In this mode Shoutrrr does not enforce email verification. Configure a working mail transport before relying on password resets or emailed invitations. Switching to SMTP also enables email verification, so test delivery before inviting users. + +```dotenv +MAIL_MAILER=smtp +MAIL_SCHEME=smtp +MAIL_HOST=smtp.example.com +MAIL_PORT=587 +MAIL_USERNAME=your-user +MAIL_PASSWORD=your-password +MAIL_FROM_ADDRESS=social@example.com +MAIL_FROM_NAME=Shoutrrr +``` + +Use your provider's TLS settings (`smtps`/465 for implicit TLS). Mail API providers can use `postmark`, `resend`, or `ses` with the corresponding credentials listed in the environment reference. + +**Publishing accounts:** X, LinkedIn, Facebook/Instagram, and Threads each require the relevant developer application's credentials only when connecting that platform. Set its client ID, client secret, and redirect URI before connecting. Register the same redirect URI with the provider: + +| Platform | Variables | Redirect path on `APP_URL` | +| --- | --- | --- | +| X | `X_CLIENT_ID`, `X_CLIENT_SECRET`, `X_REDIRECT_URI` | `/accounts/callback/x` | +| LinkedIn | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_REDIRECT_URI` | `/accounts/callback/linkedin` | +| Facebook and Instagram (shared Meta app) | `FACEBOOK_CLIENT_ID`, `FACEBOOK_CLIENT_SECRET`, `FACEBOOK_REDIRECT_URI` | `/accounts/callback/meta` | +| Threads (separate app) | `THREADS_CLIENT_ID`, `THREADS_CLIENT_SECRET`, `THREADS_REDIRECT_URI` | `/accounts/callback/threads` | + +Provider permissions, app review, and account eligibility still apply. Bluesky app passwords and Discord webhooks are entered in the UI and need no deployment credentials. Bluesky OAuth requires publicly reachable HTTPS metadata. Social sign-in is separate from publishing: enable `SOCIALITE_ENABLED=true`, select `SOCIALITE_PROVIDERS`, and configure the matching credentials. Google sign-in uses `/auth/google/callback`. + +**Media:** Local public storage works by default. Social platforms must be able to fetch your HTTPS media URLs. To use S3-compatible storage, configure `FILESYSTEM_DISK=s3`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, and `AWS_BUCKET`; optionally set `AWS_ENDPOINT`, `AWS_URL`, and `AWS_USE_PATH_STYLE_ENDPOINT`. Configure public access for the media you intend to publish. `PUBLIC_IMAGES_DISK` can select a separate public image disk. + +## Backups and maintenance + +Back up **both volumes** and the original **`APP_KEY`** together. Stop the service while taking a filesystem backup of SQLite to keep the database and any journal/WAL files consistent. Storage holds uploads and Passport signing keys. Never replace `APP_KEY` during an ordinary upgrade or redeployment. + +Keep one replica and the bundled worker/scheduler for this deployment. For larger installations, provision an external database/queue and follow upstream's scaling guidance; changing `DB_CONNECTION` alone does not migrate existing SQLite data or add a database service. Keep `DB_DATABASE` at its mounted path with SQLite. The health check covers the web endpoint; also check database access, worker/scheduler status, and failed jobs when diagnosing missed posts. + +Upgrade by changing the pinned image version after backing up, then redeploy and check migrations, health, and a scheduled test post. The container runs as upstream's non-root user, and Docker logs rotate at three 10 MB files. See [testing notes](https://github.com/Dokploy/templates/blob/canary/blueprints/shoutrrr/testing.md) for validation steps and their scope. diff --git a/blueprints/shoutrrr/meta.json b/blueprints/shoutrrr/meta.json new file mode 100644 index 000000000..ba64764b1 --- /dev/null +++ b/blueprints/shoutrrr/meta.json @@ -0,0 +1,13 @@ +{ + "id": "shoutrrr", + "name": "Shoutrrr", + "version": "1.5.4", + "description": "Shoutrrr is an open source social media scheduler for drafting, scheduling, and publishing posts across multiple accounts and workspaces.", + "logo": "shoutrrr.svg", + "links": { + "github": "https://github.com/coollabsio/shoutrrr", + "website": "https://shoutrrr.com", + "docs": "https://shoutrrr.com/docs/getting-started" + }, + "tags": ["social-media", "marketing", "scheduling"] +} diff --git a/blueprints/shoutrrr/shoutrrr.svg b/blueprints/shoutrrr/shoutrrr.svg new file mode 100644 index 000000000..c61804af2 --- /dev/null +++ b/blueprints/shoutrrr/shoutrrr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/blueprints/shoutrrr/template.toml b/blueprints/shoutrrr/template.toml new file mode 100644 index 000000000..d14325902 --- /dev/null +++ b/blueprints/shoutrrr/template.toml @@ -0,0 +1,18 @@ +[variables] +main_domain = "${domain}" +app_key = "${base64:32}" + +[config] +env = [ + "APP_KEY=base64:${app_key}", + "APP_URL=http://${main_domain}", + "OCTANE_HTTPS=false", + "SESSION_SECURE_COOKIE=false", + "INSTANCE_REGISTRATIONS_ENABLED=false", + "MAIL_MAILER=log" +] + +[[config.domains]] +serviceName = "shoutrrr" +port = 8080 +host = "${main_domain}" diff --git a/blueprints/shoutrrr/testing.md b/blueprints/shoutrrr/testing.md new file mode 100644 index 000000000..134d30540 --- /dev/null +++ b/blueprints/shoutrrr/testing.md @@ -0,0 +1,47 @@ +# Testing Shoutrrr + +Use a disposable Dokploy project and test accounts. Never publish test posts to real destinations without permission. + +## Repository checks + +From the repository root: + +```sh +node build-scripts/generate-meta.js --check +cd build-scripts +pnpm exec tsx validate-docker-compose.ts --file ../blueprints/shoutrrr/docker-compose.yml --verbose +pnpm exec tsx validate-template.ts --dir ../blueprints/shoutrrr --verbose +cd ../app +pnpm install --frozen-lockfile +pnpm run build +``` + +Confirm that metadata and the pinned image both identify version 1.5.4. With a temporary `.env` containing `APP_KEY` and `APP_URL`, run `docker compose config --quiet` in the blueprint directory. Removing either required value must fail. No social credentials, SMTP settings, database passwords, or Redis settings should be needed for the default deployment. + +## Dokploy integration + +1. Open the PR preview, search for **Shoutrrr**, and copy the Base64 import configuration. Import it into a new Compose service. Confirm that `APP_KEY` is a `base64:` value decoding to 32 bytes and that the generated domain routes to `shoutrrr:8080`. +2. Deploy with the default environment. Wait for `/up` and the Docker health check to succeed. Check the deployment log for completed migrations, then confirm the web server, worker, and scheduler are running. There should be no host port bindings on the application container. +3. Open the domain in a browser, register the first owner, and reach the dashboard without configuring email or social credentials. In a fresh session, confirm further public registration is unavailable and unauthenticated API requests receive 401. +4. Generate a workspace API key. Create a draft, upload an image, and verify that both are saved. Connect a disposable publishing destination, schedule a post, and let the bundled scheduler and queue worker publish it. Verify the saved remote ID and the destination's received content. A local mock can exercise this flow without sending real messages. +5. Stop/start the service and force a container recreation through Dokploy. Confirm that the original owner, API token, draft, connected-account credentials, uploads, and original `APP_KEY` still work. Check that the container is healthy with no unexpected restarts or application errors. +6. Follow the HTTPS instructions. Verify HTTPS redirects, asset loading, secure session cookies, login, and API access through Dokploy's proxy. A local certificate tests TLS termination; public DNS and certificate issuance require a reachable deployment. +7. To test an optional variable, add it only in Dokploy Environment and redeploy. Confirm it reaches the container and Laravel's cached configuration. Test optional SMTP with a local capture server before using a real provider; verify password-reset mail delivery and the change in email-verification behavior. + +## Scope + +These checks validate the deployment template and selected application workflows. Live OAuth permissions, provider app review, public certificate issuance, real social-platform delivery, external databases/queues, S3 permissions, and all advanced upstream feature combinations need their own credentials and infrastructure. They are not implied by a healthy container or a successful mock response. + +## Verified configuration + +Tested on 2026-10-18 with Dokploy 0.30.8, Traefik 3.6.7, and `ghcr.io/coollabsio/shoutrrr:1.5.4` on Linux amd64. The application image digest was `sha256:9292ade51bae9477232aebaaf419e43b8f7bf8877391a5db2d9227e3af379ca2`. + +- Repository metadata, Compose/TOML validators, required-variable guards, and catalog production build passed. The catalog build reports its existing JavaScript chunk-size warning. +- The PR preview's exported Base64 contained byte-identical Compose and TOML files and was imported into a fresh Dokploy Compose service. +- Default deployment, database migrations, `/up`, non-root execution, isolated routing, first-owner signup, and dashboard rendering passed without optional credentials. Subsequent public registration was unavailable; unauthenticated API calls returned 401. +- A workspace API key created a draft, uploaded an image, and scheduled another post. The bundled scheduler and worker published that post to a **local HTTPS Discord mock**, which captured the expected content; the app saved its remote ID. +- Stop/start and container recreation preserved the original owner login, draft, media bytes, usable API token, RSA signing-key hashes, application key, and decrypted connected-account secret. +- Additional variables entered only through Dokploy Environment reached Laravel's cached configuration. Optional SMTP delivered a verification message to a **local capture server**, and its signed HTTPS link verified the owner. +- HTTPS routing with a **local/default certificate**, secure cookies, fresh login, public media access, and saved posts passed in Chrome. No JavaScript errors were observed in these browser flows; the application finished healthy with zero unexpected restarts and no application errors in its logs. + +No messages were sent to real social accounts. Public ACME certificate issuance, live provider OAuth/delivery, external databases/queues, and S3 were not exercised. diff --git a/blueprints/snipe-it/meta.json b/blueprints/snipe-it/meta.json index ac5cf1eb0..023639fd0 100644 --- a/blueprints/snipe-it/meta.json +++ b/blueprints/snipe-it/meta.json @@ -2,7 +2,7 @@ "id": "snipe-it", "name": "Snipe-IT", "version": "v8.5.0", - "description": "Snipe-IT is an open-source asset management system for tracking hardware, software licenses, accessories, and consumables.", + "description": "Snipe-IT is an open-source IT asset management system for tracking hardware, software licenses, and more. Self-host it on your own server with Dokploy, which deploys Snipe-IT as a Docker Compose service in a few clicks.", "logo": "snipe-it.png", "links": { "github": "https://github.com/grokability/snipe-it", @@ -14,5 +14,8 @@ "inventory", "business", "self-hosted" - ] + ], + "seoTitle": "Snipe-IT Docker Compose Setup", + "seoDescription": "IT asset management and tracking system. Deploy Snipe-IT with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/strapi/meta.json b/blueprints/strapi/meta.json index 2a812945d..8ea150f71 100644 --- a/blueprints/strapi/meta.json +++ b/blueprints/strapi/meta.json @@ -2,7 +2,7 @@ "id": "strapi", "name": "Strapi", "version": "v5.33.0", - "description": "Open-source headless CMS to build powerful APIs with built-in content management.", + "description": "Strapi is an open-source headless CMS for building APIs and managing content. Self-host it on your own server with Dokploy, which deploys Strapi as a Docker Compose service in a few clicks.", "logo": "strapi.svg", "links": { "github": "https://github.com/strapi/strapi", @@ -14,5 +14,7 @@ "headless", "cms", "content-management" - ] + ], + "seoTitle": "Self-Host Strapi in minutes", + "seoDescription": "Headless CMS for building APIs. Self-host Strapi on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/supabase/meta.json b/blueprints/supabase/meta.json index df9e08ffa..496654d3e 100644 --- a/blueprints/supabase/meta.json +++ b/blueprints/supabase/meta.json @@ -2,7 +2,7 @@ "id": "supabase", "name": "SupaBase", "version": "2026.08.03-3 / dokploy >= 0.22.5", - "description": "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. This require at least version 0.22.5 of dokploy.", + "description": "Supabase is a Postgres-based backend platform and an open-source alternative to Firebase. Self-host it on your own server with Dokploy, which deploys Supabase as a Docker Compose service in a few clicks.", "links": { "github": "https://github.com/supabase/supabase", "website": "https://supabase.com/", @@ -14,5 +14,8 @@ "firebase", "postgres" ], - "dokploy_version": ">=0.22.5" + "dokploy_version": ">=0.22.5", + "seoTitle": "Supabase: Self-Hosted & Docker Compose", + "seoDescription": "Open source Firebase alternative with Postgres. Self-host Supabase via Docker Compose on Dokploy in minutes – no manual setup needed.", + "newConfig": true } diff --git a/blueprints/syncthing/meta.json b/blueprints/syncthing/meta.json index 1c6b75022..04324b8a4 100644 --- a/blueprints/syncthing/meta.json +++ b/blueprints/syncthing/meta.json @@ -2,7 +2,7 @@ "id": "syncthing", "name": "Syncthing", "version": "latest", - "description": "Syncthing is a continuous file synchronization program that synchronizes files between two or more computers in real time.", + "description": "Syncthing keeps your devices and files continuously in sync. Self-host it on your own server with Dokploy, which deploys Syncthing as a Docker Compose service in a few clicks.", "logo": "syncthing.svg", "links": { "github": "https://github.com/syncthing/syncthing", @@ -13,5 +13,8 @@ "file-sync", "synchronization", "backup" - ] + ], + "seoTitle": "Syncthing Docker Compose Setup", + "seoDescription": "Continuous file sync between devices. Deploy Syncthing with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/trmnl-larapaper/docker-compose.yml b/blueprints/trmnl-larapaper/docker-compose.yml index ee2349cc3..382472aa0 100644 --- a/blueprints/trmnl-larapaper/docker-compose.yml +++ b/blueprints/trmnl-larapaper/docker-compose.yml @@ -1,6 +1,6 @@ services: trmnl-larapaper: - image: ghcr.io/usetrmnl/larapaper:0.40.0 + image: ghcr.io/usetrmnl/larapaper:0.45.0 environment: - APP_URL=${APP_URL} - PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE} diff --git a/blueprints/trmnl-larapaper/meta.json b/blueprints/trmnl-larapaper/meta.json index cb5c9659f..b3882a56c 100644 --- a/blueprints/trmnl-larapaper/meta.json +++ b/blueprints/trmnl-larapaper/meta.json @@ -1,7 +1,7 @@ { "id": "trmnl-larapaper", "name": "TRMNL LaraPaper", - "version": "0.40.0", + "version": "0.45.0", "description": "LaraPaper is a self-hosted application (BYOS) to manage TRMNL e-ink devices.", "logo": "larapaper.svg", "links": { diff --git a/blueprints/trypost/docker-compose.yml b/blueprints/trypost/docker-compose.yml new file mode 100644 index 000000000..9acfe5618 --- /dev/null +++ b/blueprints/trypost/docker-compose.yml @@ -0,0 +1,154 @@ +services: + trypost: + # Rebuild only browser assets: the published bundle points at localhost. + build: + context: . + dockerfile_inline: | + FROM ghcr.io/trypostit/trypost:2.0.0 AS assets + RUN apk add --no-cache nodejs npm + ARG VITE_REVERB_HOST + ARG VITE_REVERB_PORT + ARG VITE_REVERB_SCHEME + ARG VITE_REVERB_APP_KEY + RUN APP_URL=http://localhost npm run build + FROM ghcr.io/trypostit/trypost:2.0.0 + COPY --from=assets /var/www/html/public/build /var/www/html/public/build + args: + VITE_REVERB_HOST: "${TRYPOST_HOST:?Set TRYPOST_HOST}" + VITE_REVERB_PORT: "${TRYPOST_PUBLIC_PORT:-80}" + VITE_REVERB_SCHEME: "${TRYPOST_PUBLIC_SCHEME:-http}" + VITE_REVERB_APP_KEY: "${REVERB_APP_KEY:?Keep the generated Reverb key}" + restart: unless-stopped + env_file: + - path: .env + required: false + environment: + APP_ENV: production + APP_DEBUG: "false" + APP_KEY: "${APP_KEY:?Keep the generated APP_KEY}" + APP_URL: "${APP_URL:?Set APP_URL}" + SELF_HOSTED: "true" + TRYPOST_TARGET: production + DB_CONNECTION: pgsql + DB_HOST: trypost-postgres + DB_PORT: "5432" + DB_DATABASE: trypost + DB_USERNAME: trypost + DB_PASSWORD: "${DB_PASSWORD:?Keep the generated database password}" + REDIS_HOST: trypost-redis + REDIS_PORT: "6379" + REDIS_PASSWORD: "${REDIS_PASSWORD:?Keep the generated Redis password}" + QUEUE_CONNECTION: redis + CACHE_STORE: redis + SESSION_DRIVER: database + SESSION_SECURE_COOKIE: "${SESSION_SECURE_COOKIE:-false}" + BROADCAST_CONNECTION: reverb + REVERB_APP_ID: "${REVERB_APP_ID:?Keep the generated Reverb app ID}" + REVERB_APP_KEY: "${REVERB_APP_KEY:?Keep the generated Reverb key}" + REVERB_APP_SECRET: "${REVERB_APP_SECRET:?Keep the generated Reverb secret}" + # Server broadcasts stay inside the container; browser uses build args. + REVERB_HOST: 127.0.0.1 + REVERB_PORT: "8080" + REVERB_SCHEME: http + PASSPORT_PRIVATE_KEY: file:///var/www/html/storage/oauth-private.key + PASSPORT_PUBLIC_KEY: file:///var/www/html/storage/oauth-public.key + FILESYSTEM_DISK: "${FILESYSTEM_DISK:-public}" + MAIL_MAILER: "${MAIL_MAILER:-log}" + LOG_CHANNEL: "${LOG_CHANNEL:-stderr}" + LOG_LEVEL: "${LOG_LEVEL:-info}" + NIGHTWATCH_ENABLED: "false" + TELESCOPE_ENABLED: "false" + POSTHOG_ENABLED: "false" + entrypoint: ["/bin/sh", "-ec"] + command: + - | + cd /var/www/html + if [ ! -f storage/oauth-private.key ] && [ ! -f storage/oauth-public.key ]; then + php artisan passport:keys --no-interaction + elif [ ! -s storage/oauth-private.key ] || [ ! -s storage/oauth-public.key ]; then + echo 'Incomplete Passport key pair: restore both keys from backup.' >&2 + exit 1 + fi + php artisan migrate --force --no-interaction + php <<'PHP' + make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + if (! App\Models\User::query()->exists()) { + $$email = getenv('INITIAL_USER_EMAIL'); + $$password = getenv('INITIAL_USER_PASSWORD'); + if (! filter_var($$email, FILTER_VALIDATE_EMAIL) || strlen($$password ?: '') < 16) { + throw new RuntimeException('Set INITIAL_USER_EMAIL and a password of at least 16 characters for first startup.'); + } + App\Actions\User\CreateUser::execute([ + 'name' => getenv('INITIAL_USER_NAME') ?: 'Administrator', + 'email' => $$email, + 'password' => $$password, + 'email_verified_at' => now(), + ]); + } + PHP + exec /usr/local/bin/entrypoint.sh + expose: + - "80" + volumes: + - trypost-storage:/var/www/html/storage + depends_on: + trypost-postgres: + condition: service_healthy + trypost-redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1/up"] + start_period: 120s + interval: 30s + timeout: 10s + retries: 5 + stop_grace_period: 60s + logging: &logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + + trypost-postgres: + image: postgres:18.6-bookworm + restart: unless-stopped + environment: + POSTGRES_DB: trypost + POSTGRES_USER: trypost + POSTGRES_PASSWORD: "${DB_PASSWORD:?Keep the generated database password}" + volumes: + - trypost-postgres:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trypost -d trypost"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + stop_grace_period: 60s + logging: *logging + + trypost-redis: + image: redis:8.10.2-alpine + restart: unless-stopped + environment: + REDIS_PASSWORD: "${REDIS_PASSWORD:?Keep the generated Redis password}" + REDISCLI_AUTH: "${REDIS_PASSWORD:?Keep the generated Redis password}" + command: ["/bin/sh", "-ec", "exec redis-server --requirepass \"$$REDIS_PASSWORD\" --appendonly yes --maxmemory-policy noeviction"] + volumes: + - trypost-redis:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + stop_grace_period: 30s + logging: *logging + +volumes: + trypost-storage: + trypost-postgres: + trypost-redis: diff --git a/blueprints/trypost/environment.md b/blueprints/trypost/environment.md new file mode 100644 index 000000000..cdf1b5d6a --- /dev/null +++ b/blueprints/trypost/environment.md @@ -0,0 +1,691 @@ +# TryPost 2.0.0 environment reference + +Set runtime settings in Dokploy **Environment**, then redeploy to rebuild Laravel’s configuration cache. The optional `.env` is passed into the container. Add only the settings you use: omission and an empty string can have different meanings. Never commit real credentials. + +This inventory covers active application configuration in the [pinned 2.0.0 source](https://github.com/trypostit/trypost/tree/v2.0.0/config), the platform enum’s dynamic feature switches, and upstream’s environment examples. It excludes generic PHP/Nginx/vendor-only extensions. Table defaults show template overrides where applicable; otherwise they show upstream env fallbacks/expressions. Driver-specific alternatives are separated with semicolons. `unset` means no explicit env fallback; surrounding application code may derive a value. + +## Required and generated + +- **Generated and required:** `APP_KEY`, `DB_PASSWORD`, `REDIS_PASSWORD`, `REVERB_APP_ID`, `REVERB_APP_KEY`, and `REVERB_APP_SECRET`. Keep them stable across deployments. +- **Public address:** `TRYPOST_HOST` (hostname only), `APP_URL`, `TRYPOST_PUBLIC_SCHEME` (`http`/`https`), and `TRYPOST_PUBLIC_PORT`. The hostname/scheme/port and public Reverb key are build inputs. Rebuild after changing them. For production HTTPS also set `SESSION_SECURE_COOKIE=true` and enable HTTPS in Dokploy Domains. +- **First-user bootstrap:** `INITIAL_USER_EMAIL` (default `admin@example.com`; change to your address), `INITIAL_USER_PASSWORD` (generated, minimum 16 characters), and optional `INITIAL_USER_NAME` (default `Administrator`). These are used only if the database has no users. They do not reset existing accounts. The bootstrap password can be removed after setup. +- **Automatic:** PostgreSQL, authenticated Redis, migrations, one persistent RSA key pair for API/MCP signing, the initial verified account/workspace, Horizon, scheduler, and Reverb. No PEM values need to be supplied. +- **Conditional only:** mail delivery, social-provider credentials, Telegram, AI providers, object storage, and external integrations. None is required for initial login. Public registration remains disabled by upstream self-hosted mode. + +The following Compose settings are fixed: production/non-debug mode, self-hosted mode, bundled database/Redis connections, queue/cache/session/broadcast drivers, internal Reverb endpoint, Passport key file paths, and disabled telemetry/debugging. Variables with “fixed” in their default require editing Compose to change them. External database/queue/SSR alternatives are not provisioned by changing an env value. + +## Application runtime variables + +### AI providers + +Optional AI providers. No key is required to install or use the scheduler. Configure only the provider/capability you select. Model overrides are provider-specific; custom compatible endpoints also need an appropriate model and URL. + +| Variable | Default | +| --- | --- | +| `AI_TEXT_PROVIDER` | `openai` | +| `AI_AUDIO_PROVIDER` | `openai` | +| `AI_TRANSCRIPTION_PROVIDER` | `openai` | +| `AI_EMBEDDINGS_PROVIDER` | `openai` | +| `AI_RERANKING_PROVIDER` | `cohere` | +| `CACHE_STORE` | `redis (fixed)` | +| `ANTHROPIC_API_KEY` | `unset` | +| `ANTHROPIC_URL` | `https://api.anthropic.com/v1` | +| `ANTHROPIC_TEXT_MODEL` | `unset` | +| `AZURE_OPENAI_API_KEY` | `unset` | +| `AZURE_OPENAI_URL` | `unset` | +| `AZURE_OPENAI_API_VERSION` | `2025-04-01-preview` | +| `AZURE_OPENAI_DEPLOYMENT` | `gpt-4o` | +| `AZURE_OPENAI_EMBEDDING_DEPLOYMENT` | `text-embedding-3-small` | +| `AZURE_OPENAI_STORE` | `true` | +| `AWS_BEDROCK_REGION` | `us-east-1` | +| `AWS_BEARER_TOKEN_BEDROCK` | `unset` | +| `AWS_ACCESS_KEY_ID` | `unset` | +| `AWS_SECRET_ACCESS_KEY` | `unset` | +| `AWS_SESSION_TOKEN` | `unset` | +| `AWS_USE_DEFAULT_CREDENTIALS` | `true` | +| `AWS_BEDROCK_ASSUME_ROLE_ARN` | `unset` | +| `AWS_BEDROCK_ASSUME_ROLE_SESSION_NAME` | `unset` | +| `AWS_BEDROCK_ASSUME_ROLE_DURATION_SECONDS` | `unset` | +| `AWS_BEDROCK_ASSUME_ROLE_EXTERNAL_ID` | `unset` | +| `AWS_BEDROCK_TEXT_MODEL` | `unset` | +| `AWS_BEDROCK_EMBEDDINGS_MODEL` | `unset` | +| `COHERE_API_KEY` | `unset` | +| `COHERE_EMBEDDINGS_MODEL` | `unset` | +| `COHERE_RERANKING_MODEL` | `unset` | +| `DEEPSEEK_API_KEY` | `unset` | +| `DEEPSEEK_TEXT_MODEL` | `unset` | +| `ELEVENLABS_API_KEY` | `unset` | +| `ELEVENLABS_AUDIO_MODEL` | `unset` | +| `ELEVENLABS_TRANSCRIPTION_MODEL` | `unset` | +| `GEMINI_API_KEY` | `unset` | +| `GEMINI_URL` | `https://generativelanguage.googleapis.com/v1beta/` | +| `GEMINI_TEXT_MODEL` | `unset` | +| `GEMINI_AUDIO_MODEL` | `unset` | +| `GEMINI_TRANSCRIPTION_MODEL` | `unset` | +| `GEMINI_EMBEDDINGS_MODEL` | `unset` | +| `GROQ_API_KEY` | `unset` | +| `GROQ_TEXT_MODEL` | `unset` | +| `JINA_API_KEY` | `unset` | +| `JINA_EMBEDDINGS_MODEL` | `unset` | +| `JINA_RERANKING_MODEL` | `unset` | +| `MISTRAL_API_KEY` | `unset` | +| `MISTRAL_TEXT_MODEL` | `unset` | +| `MISTRAL_TRANSCRIPTION_MODEL` | `unset` | +| `MISTRAL_EMBEDDINGS_MODEL` | `unset` | +| `OLLAMA_API_KEY` | `empty string` | +| `OLLAMA_URL` | `http://localhost:11434` | +| `OLLAMA_TEXT_MODEL` | `unset` | +| `OLLAMA_EMBEDDINGS_MODEL` | `unset` | +| `OPENAI_API_KEY` | `unset` | +| `OPENAI_URL` | `https://api.openai.com/v1` | +| `OPENAI_STORE` | `true` | +| `OPENAI_TEXT_MODEL` | `unset` | +| `OPENAI_AUDIO_MODEL` | `unset` | +| `OPENAI_TRANSCRIPTION_MODEL` | `unset` | +| `OPENAI_EMBEDDINGS_MODEL` | `unset` | +| `OPENAI_COMPATIBLE_URL` | `unset` | +| `OPENAI_COMPATIBLE_API_KEY` | `unset` | +| `OPENAI_COMPATIBLE_TEXT_MODEL` | `unset` | +| `OPENAI_COMPATIBLE_EMBEDDINGS_MODEL` | `unset` | +| `OPENROUTER_API_KEY` | `unset` | +| `OPENROUTER_TEXT_MODEL` | `unset` | +| `OPENROUTER_AUDIO_MODEL` | `unset` | +| `OPENROUTER_TRANSCRIPTION_MODEL` | `unset` | +| `OPENROUTER_EMBEDDINGS_MODEL` | `unset` | +| `VOYAGEAI_API_KEY` | `unset` | +| `VOYAGEAI_EMBEDDINGS_MODEL` | `unset` | +| `VOYAGEAI_RERANKING_MODEL` | `unset` | +| `XAI_API_KEY` | `unset` | +| `XAI_TEXT_MODEL` | `unset` | + +### Application + +Application identity, locale, maintenance, and encryption. Keep the generated application key. Public URLs must match Dokploy routing. Production/debug mode is fixed by Compose. + +| Variable | Default | +| --- | --- | +| `APP_NAME` | `TryPost.it` | +| `APP_ENV` | `production (fixed)` | +| `APP_DEBUG` | `false (fixed)` | +| `APP_URL` | `generated HTTP URL; set public HTTPS URL` | +| `WEBHOOK_URL` | `env('APP_URL', 'https://app.trypost.it')` | +| `APP_LOCALE` | `en` | +| `APP_FALLBACK_LOCALE` | `en` | +| `APP_FAKER_LOCALE` | `en_US` | +| `APP_KEY` | `generated base64: value containing 32 random bytes; required` | +| `APP_PREVIOUS_KEYS` | `empty string` | +| `APP_MAINTENANCE_DRIVER` | `file` | +| `APP_MAINTENANCE_STORE` | `database` | + +### Authentication + +Advanced authentication overrides; keep the shipped user model/guard. Bootstrap creates the initial user as verified; invitations/password recovery still need delivered email. + +| Variable | Default | +| --- | --- | +| `AUTH_GUARD` | `web` | +| `AUTH_PASSWORD_BROKER` | `users` | +| `AUTH_MODEL` | `User::class` | +| `AUTH_PASSWORD_RESET_TOKEN_TABLE` | `password_reset_tokens` | +| `AUTH_PASSWORD_TIMEOUT` | `10800` | + +### Development tooling + +Development tooling configuration; not an installation requirement for the production image. + +| Variable | Default | +| --- | --- | +| `BOOST_ENABLED` | `true` | +| `BOOST_RULES_ENABLED` | `true` | +| `BOOST_RULES_SCOPED_GUIDELINES` | `false` | +| `BOOST_PHP_EXECUTABLE_PATH` | `unset` | +| `BOOST_COMPOSER_EXECUTABLE_PATH` | `unset` | +| `BOOST_NPM_EXECUTABLE_PATH` | `unset` | +| `BOOST_VENDOR_BIN_EXECUTABLE_PATH` | `unset` | +| `BOOST_CURRENT_DIRECTORY_EXECUTABLE_PATH` | `unset` | +| `BOOST_BROWSER_LOGS_WATCHER` | `true` | +| `BOOST_BROWSER_LOG_LEVELS` | `error,warning,info,debug` | + +### Broadcasting + +Reverb is included. Its public key must match the browser build. Server-side broadcasts use the internal listener; third-party Pusher/Ably settings are unused unless the template is deliberately adapted. + +| Variable | Default | +| --- | --- | +| `BROADCAST_CONNECTION` | `reverb (fixed)` | +| `REVERB_APP_KEY` | `generated public browser key; required at build and runtime` | +| `REVERB_APP_SECRET` | `generated secret; required only at runtime` | +| `REVERB_APP_ID` | `generated UUID; required` | +| `REVERB_HOST` | `127.0.0.1 (fixed server-side destination)` | +| `REVERB_PORT` | `8080 (fixed server-side destination)` | +| `REVERB_SCHEME` | `http (fixed internal connection)` | +| `PUSHER_APP_KEY` | `unset` | +| `PUSHER_APP_SECRET` | `unset` | +| `PUSHER_APP_ID` | `unset` | +| `PUSHER_APP_CLUSTER` | `unset; mt1` | +| `PUSHER_HOST` | `unset` | +| `PUSHER_PORT` | `443` | +| `PUSHER_SCHEME` | `https` | +| `ABLY_KEY` | `unset` | + +### Cache + +Redis is included, authenticated, and persistent. Alternate cache drivers and their connection/table/disk options are advanced overrides that require compatible infrastructure. + +| Variable | Default | +| --- | --- | +| `DB_CACHE_CONNECTION` | `unset` | +| `DB_CACHE_TABLE` | `cache` | +| `DB_CACHE_LOCK_CONNECTION` | `unset` | +| `DB_CACHE_LOCK_TABLE` | `unset` | +| `MEMCACHED_PERSISTENT_ID` | `unset` | +| `MEMCACHED_USERNAME` | `unset` | +| `MEMCACHED_PASSWORD` | `unset` | +| `MEMCACHED_HOST` | `127.0.0.1` | +| `MEMCACHED_PORT` | `11211` | +| `REDIS_CACHE_CONNECTION` | `cache` | +| `REDIS_CACHE_LOCK_CONNECTION` | `default` | +| `AWS_DEFAULT_REGION` | `us-east-1` | +| `DYNAMODB_CACHE_TABLE` | `cache` | +| `DYNAMODB_ENDPOINT` | `unset` | +| `CACHE_PREFIX` | `derived from APP_NAME` | + +### Cloud billing + +Cloud billing settings. They are not required with this template’s fixed SELF_HOSTED=true. Leave Stripe keys, price IDs, and trial/coupon settings omitted for a normal self-hosted installation. + +| Variable | Default | +| --- | --- | +| `STRIPE_KEY` | `unset` | +| `STRIPE_SECRET` | `unset` | +| `CASHIER_PATH` | `stripe` | +| `STRIPE_WEBHOOK_SECRET` | `unset` | +| `STRIPE_WEBHOOK_TOLERANCE` | `300` | +| `CASHIER_CURRENCY` | `usd` | +| `CASHIER_CURRENCY_LOCALE` | `en` | +| `CASHIER_PAYMENT_NOTIFICATION` | `unset` | +| `CASHIER_INVOICE_RENDERER` | `DompdfInvoiceRenderer::class` | +| `CASHIER_PAPER` | `letter` | +| `CASHIER_REMOTE_ENABLED` | `false` | +| `CASHIER_LOGGER` | `unset` | +| `CASHIER_TRIAL_DAYS` | `8` | +| `STRIPE_SOCIALS_FIRST_MONTH_COUPON_ID` | `unset` | +| `STRIPE_WORKSPACES_FIRST_MONTH_COUPON_ID` | `unset` | +| `CASHIER_ALLOW_PROMOTION_CODES` | `false` | + +### Database and Redis + +The template bundles PostgreSQL and Redis. Their service names, database/user names, ports, and drivers are fixed by Compose. Credentials are generated. Other driver/connection options are upstream advanced settings; selecting them does not provision or migrate another database. + +| Variable | Default | +| --- | --- | +| `DB_CONNECTION` | `pgsql (fixed)` | +| `DB_URL` | `unset` | +| `DB_DATABASE` | `trypost (fixed)` | +| `DB_FOREIGN_KEYS` | `true` | +| `DB_HOST` | `trypost-postgres (fixed)` | +| `DB_PORT` | `5432 (fixed)` | +| `DB_USERNAME` | `trypost (fixed)` | +| `DB_PASSWORD` | `generated; required by application and PostgreSQL` | +| `DB_SOCKET` | `empty string` | +| `DB_CHARSET` | `utf8mb4; utf8` | +| `DB_COLLATION` | `utf8mb4_unicode_ci` | +| `MYSQL_ATTR_SSL_CA` | `unset` | +| `DB_SSLMODE` | `prefer` | +| `REDIS_CLIENT` | `predis` | +| `REDIS_CLUSTER` | `redis` | +| `REDIS_PREFIX` | `derived from APP_NAME` | +| `REDIS_PERSISTENT` | `false` | +| `REDIS_URL` | `unset` | +| `REDIS_HOST` | `trypost-redis (fixed)` | +| `REDIS_USERNAME` | `unset` | +| `REDIS_PASSWORD` | `generated; required by application and Redis` | +| `REDIS_PORT` | `6379 (fixed)` | +| `REDIS_DB` | `0` | +| `REDIS_MAX_RETRIES` | `3` | +| `REDIS_BACKOFF_ALGORITHM` | `decorrelated_jitter` | +| `REDIS_BACKOFF_BASE` | `100` | +| `REDIS_BACKOFF_CAP` | `1000` | +| `REDIS_CACHE_DB` | `1` | + +### Storage + +Local public storage persists in the storage volume. S3/R2/Spaces are optional and need their own credentials, bucket, region/endpoint, and publicly reachable media URL. Omit unused nullable settings. + +| Variable | Default | +| --- | --- | +| `FILESYSTEM_DISK` | `public` | +| `AWS_BUCKET` | `unset` | +| `AWS_URL` | `unset` | +| `AWS_ENDPOINT` | `unset` | +| `AWS_USE_PATH_STYLE_ENDPOINT` | `false` | +| `SPACES_ACCESS_KEY_ID` | `unset` | +| `SPACES_SECRET_ACCESS_KEY` | `unset` | +| `SPACES_ENDPOINT` | `unset` | +| `SPACES_REGION` | `unset` | +| `SPACES_BUCKET` | `unset` | +| `R2_ACCESS_KEY_ID` | `unset` | +| `R2_SECRET_ACCESS_KEY` | `unset` | +| `R2_REGION` | `auto` | +| `R2_BUCKET` | `unset` | +| `R2_URL` | `unset` | +| `R2_ENDPOINT` | `unset` | + +### Workers + +Horizon and publishing queues are included. Advanced worker tuning affects memory and concurrency. Keep a single scheduler for this template and monitor failed jobs; some upstream publishing/media jobs have long timeouts. + +| Variable | Default | +| --- | --- | +| `HORIZON_NAME` | `unset` | +| `HORIZON_DOMAIN` | `unset` | +| `HORIZON_ALLOWED_EMAILS` | `empty string` | +| `HORIZON_PATH` | `horizon` | +| `HORIZON_PREFIX` | `derived from APP_NAME` | +| `HORIZON_MEDIA_ADOPTION_PROCESSES` | `20` | + +### Inertia and SSR + +SSR is disabled upstream and this image does not run an SSR server by default. Do not enable it without providing the runtime/service. Other values are advanced UI/protocol settings. + +| Variable | Default | +| --- | --- | +| `INERTIA_SSR_ENABLED` | `false` | +| `INERTIA_SSR_RUNTIME` | `node` | +| `INERTIA_SSR_ENSURE_RUNTIME_EXISTS` | `false` | +| `INERTIA_SSR_URL` | `http://127.0.0.1:13714` | +| `INERTIA_SSR_HOT_URL` | `unset` | +| `INERTIA_SSR_TIMEOUT` | `unset` | +| `INERTIA_SSR_ENSURE_BUNDLE_EXISTS` | `true` | +| `INERTIA_SSR_THROW_ON_ERROR` | `false` | +| `INERTIA_ENCRYPT_HISTORY` | `false` | +| `INERTIA_DEVTOOLS_ENABLED` | `false` | +| `INERTIA_DEVTOOLS_TTL_HOURS` | `24` | +| `INERTIA_DEVTOOLS_PRUNE_INTERVAL_SECONDS` | `300` | +| `INERTIA_DEVTOOLS_LIMIT` | `100` | +| `INERTIA_DEVTOOLS_GATE` | `unset` | +| `INERTIA_PRESERVE_BIG_INTEGERS` | `false` | + +### Logging + +Application logs go to Docker stderr at info level with Docker log rotation. Other channels need their own destinations/credentials and, for file logs, retention management. + +| Variable | Default | +| --- | --- | +| `LOG_CHANNEL` | `stderr` | +| `LOG_DEPRECATIONS_CHANNEL` | `null` | +| `LOG_DEPRECATIONS_TRACE` | `false` | +| `LOG_STACK` | `single` | +| `LOG_LEVEL` | `info` | +| `LOG_DAILY_DAYS` | `14` | +| `LOG_SLACK_WEBHOOK_URL` | `unset` | +| `LOG_SLACK_USERNAME` | `Laravel Log` | +| `LOG_SLACK_EMOJI` | `:boom:` | +| `LOG_PAPERTRAIL_HANDLER` | `SyslogUdpHandler::class` | +| `PAPERTRAIL_URL` | `unset` | +| `PAPERTRAIL_PORT` | `unset` | +| `LOG_STDERR_FORMATTER` | `unset` | +| `LOG_SYSLOG_FACILITY` | `LOG_USER` | + +### Email + +Mail is optional for initial login. MAIL_MAILER=log records messages without delivering them. SMTP normally needs host, port, scheme, sender, and authentication; API transports need their provider keys. Test invitations/password resets with a working delivery transport. + +| Variable | Default | +| --- | --- | +| `MAIL_MAILER` | `log` | +| `MAIL_SCHEME` | `unset` | +| `MAIL_URL` | `unset` | +| `MAIL_HOST` | `127.0.0.1` | +| `MAIL_PORT` | `2525` | +| `MAIL_USERNAME` | `unset` | +| `MAIL_PASSWORD` | `unset` | +| `MAIL_EHLO_DOMAIN` | `parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)` | +| `MAIL_SENDMAIL_PATH` | `/usr/sbin/sendmail -bs -i` | +| `MAIL_LOG_CHANNEL` | `unset` | +| `MAIL_FROM_ADDRESS` | `no-reply@mail.trypost.it` | +| `MAIL_FROM_NAME` | `TryPost` | + +### API signing keys + +Signing keys are generated once into persistent storage, and Compose fixes these two paths. Preserve both keys with the database and APP_KEY. Environment PEM injection requires an intentional Compose change; it is not needed for this template. + +| Variable | Default | +| --- | --- | +| `PASSPORT_PRIVATE_KEY` | `file:///var/www/html/storage/oauth-private.key (fixed)` | +| `PASSPORT_PUBLIC_KEY` | `file:///var/www/html/storage/oauth-public.key (fixed)` | +| `PASSPORT_CONNECTION` | `unset` | + +### Queues + +Redis queues are included. Alternative queues require compatible infrastructure. Retry-after values must remain greater than job/worker timeouts to avoid duplicate publishing. + +| Variable | Default | +| --- | --- | +| `QUEUE_CONNECTION` | `redis (fixed)` | +| `DB_QUEUE_CONNECTION` | `unset` | +| `DB_QUEUE_TABLE` | `jobs` | +| `DB_QUEUE` | `default` | +| `DB_QUEUE_RETRY_AFTER` | `90` | +| `BEANSTALKD_QUEUE_HOST` | `localhost` | +| `BEANSTALKD_QUEUE` | `default` | +| `BEANSTALKD_QUEUE_RETRY_AFTER` | `90` | +| `SQS_PREFIX` | `https://sqs.us-east-1.amazonaws.com/your-account-id` | +| `SQS_QUEUE` | `default` | +| `SQS_SUFFIX` | `unset` | +| `REDIS_QUEUE_CONNECTION` | `default` | +| `REDIS_QUEUE` | `default` | +| `REDIS_QUEUE_RETRY_AFTER` | `960` | +| `QUEUE_FAILED_DRIVER` | `database-uuids` | + +### WebSockets + +The bundled WebSocket server listens internally on 8080 and is proxied by Nginx through the main domain. REVERB_HOST/PORT/SCHEME configure server-side broadcasting; browser settings are separate build inputs listed below. Scaling is not enabled by this template. + +| Variable | Default | +| --- | --- | +| `REVERB_SERVER` | `reverb` | +| `REVERB_SERVER_HOST` | `0.0.0.0` | +| `REVERB_SERVER_PORT` | `8080` | +| `REVERB_SERVER_PATH` | `empty string` | +| `REVERB_MAX_REQUEST_SIZE` | `10_000` | +| `REVERB_SCALING_ENABLED` | `false` | +| `REVERB_SCALING_CHANNEL` | `reverb` | +| `REDIS_TIMEOUT` | `60` | +| `REVERB_PULSE_INGEST_INTERVAL` | `15` | +| `REVERB_TELESCOPE_INGEST_INTERVAL` | `15` | +| `REVERB_APP_PING_INTERVAL` | `60` | +| `REVERB_APP_ACTIVITY_TIMEOUT` | `30` | +| `REVERB_APP_MAX_CONNECTIONS` | `unset` | +| `REVERB_APP_MAX_MESSAGE_SIZE` | `10_000` | + +### Integration credentials + +Conditional integration credentials only. OAuth providers need the relevant client ID, secret, and exact redirect URI together. Google/GitHub login is separate from publishing. Email, AI, media services, analytics, Slack, and link shortening each use their own credentials. + +| Variable | Default | +| --- | --- | +| `POSTMARK_API_KEY` | `unset` | +| `RESEND_API_KEY` | `unset` | +| `SLACK_BOT_USER_OAUTH_TOKEN` | `unset` | +| `SLACK_BOT_USER_DEFAULT_CHANNEL` | `unset` | +| `LINKEDIN_CLIENT_ID` | `unset` | +| `LINKEDIN_CLIENT_SECRET` | `unset` | +| `LINKEDIN_CLIENT_REDIRECT` | `unset` | +| `X_CLIENT_ID` | `unset` | +| `X_CLIENT_SECRET` | `unset` | +| `X_CLIENT_REDIRECT` | `unset` | +| `TIKTOK_CLIENT_ID` | `unset` | +| `TIKTOK_CLIENT_SECRET` | `unset` | +| `TIKTOK_CLIENT_REDIRECT` | `unset` | +| `GOOGLE_CLIENT_ID` | `unset` | +| `GOOGLE_CLIENT_SECRET` | `unset` | +| `GOOGLE_CLIENT_REDIRECT` | `unset` | +| `GOOGLE_AUTH_CALLBACK` | `unset` | +| `GITHUB_CLIENT_ID` | `unset` | +| `GITHUB_CLIENT_SECRET` | `unset` | +| `GITHUB_AUTH_CALLBACK` | `unset` | +| `FACEBOOK_CLIENT_ID` | `unset` | +| `FACEBOOK_CLIENT_SECRET` | `unset` | +| `FACEBOOK_CLIENT_REDIRECT` | `unset` | +| `INSTAGRAM_CLIENT_ID` | `unset` | +| `INSTAGRAM_CLIENT_SECRET` | `unset` | +| `INSTAGRAM_CLIENT_REDIRECT` | `unset` | +| `THREADS_CLIENT_ID` | `unset` | +| `THREADS_CLIENT_SECRET` | `unset` | +| `THREADS_CLIENT_REDIRECT` | `unset` | +| `PINTEREST_CLIENT_ID` | `unset` | +| `PINTEREST_CLIENT_SECRET` | `unset` | +| `PINTEREST_CLIENT_REDIRECT` | `unset` | +| `DISCORD_CLIENT_ID` | `unset` | +| `DISCORD_CLIENT_SECRET` | `unset` | +| `DISCORD_CLIENT_REDIRECT` | `unset` | +| `GOOGLE_BUSINESS_CLIENT_ID` | `unset` | +| `GOOGLE_BUSINESS_CLIENT_SECRET` | `unset` | +| `GOOGLE_BUSINESS_CLIENT_REDIRECT` | `unset` | +| `GTM_ID` | `unset` | +| `POSTHOG_ENABLED` | `false (fixed)` | +| `POSTHOG_API_KEY` | `unset` | +| `POSTHOG_HOST` | `https://us.i.posthog.com` | +| `UNSPLASH_ACCESS_KEY` | `unset` | +| `UNSPLASH_SECRET_KEY` | `unset` | +| `GOOGLE_MEDIA_CLIENT_ID` | `unset` | +| `GOOGLE_MEDIA_CLIENT_SECRET` | `unset` | +| `GOOGLE_MEDIA_CLIENT_REDIRECT` | `unset` | +| `GOOGLE_MEDIA_API_KEY` | `unset` | +| `GOOGLE_MEDIA_APP_ID` | `unset` | +| `CANVA_CLIENT_ID` | `unset` | +| `CANVA_CLIENT_SECRET` | `unset` | +| `BITLY_CLIENT_ID` | `unset` | +| `BITLY_CLIENT_SECRET` | `unset` | +| `BITLY_CLIENT_REDIRECT` | `unset` | + +### Sessions + +Persistent database sessions are included. Enable secure cookies for public HTTPS. Omit cookie domain/store/connection to retain upstream defaults; keep HTTP-only and SameSite protection. + +| Variable | Default | +| --- | --- | +| `SESSION_DRIVER` | `database (fixed)` | +| `SESSION_LIFETIME` | `120` | +| `SESSION_EXPIRE_ON_CLOSE` | `false` | +| `SESSION_ENCRYPT` | `false` | +| `SESSION_CONNECTION` | `unset` | +| `SESSION_TABLE` | `sessions` | +| `SESSION_STORE` | `unset` | +| `SESSION_COOKIE` | `trypost-session` | +| `SESSION_PATH` | `/` | +| `SESSION_DOMAIN` | `unset` | +| `SESSION_SECURE_COOKIE` | `false; set true for HTTPS` | +| `SESSION_HTTP_ONLY` | `true` | +| `SESSION_SAME_SITE` | `lax` | +| `SESSION_PARTITIONED_COOKIE` | `false` | + +### Diagnostics + +Development diagnostics are disabled by Compose. These advanced upstream settings are not needed for installation. + +| Variable | Default | +| --- | --- | +| `TELESCOPE_ENABLED` | `false (fixed)` | +| `TELESCOPE_DOMAIN` | `unset` | +| `TELESCOPE_PATH` | `debug` | +| `TELESCOPE_DRIVER` | `database` | +| `TELESCOPE_QUEUE_CONNECTION` | `unset` | +| `TELESCOPE_QUEUE` | `unset` | +| `TELESCOPE_QUEUE_DELAY` | `10` | +| `TELESCOPE_BATCH_WATCHER` | `true` | +| `TELESCOPE_CACHE_WATCHER` | `true` | +| `TELESCOPE_CLIENT_REQUEST_WATCHER` | `true` | +| `TELESCOPE_COMMAND_WATCHER` | `true` | +| `TELESCOPE_DUMP_WATCHER` | `true` | +| `TELESCOPE_DUMP_WATCHER_ALWAYS` | `false` | +| `TELESCOPE_EVENT_WATCHER` | `true` | +| `TELESCOPE_EXCEPTION_WATCHER` | `true` | +| `TELESCOPE_GATE_WATCHER` | `true` | +| `TELESCOPE_JOB_WATCHER` | `true` | +| `TELESCOPE_LOG_WATCHER` | `true` | +| `TELESCOPE_MAIL_WATCHER` | `true` | +| `TELESCOPE_MODEL_WATCHER` | `true` | +| `TELESCOPE_NOTIFICATION_WATCHER` | `true` | +| `TELESCOPE_QUERY_WATCHER` | `true` | +| `TELESCOPE_REDIS_WATCHER` | `true` | +| `TELESCOPE_REQUEST_WATCHER` | `true` | +| `TELESCOPE_RESPONSE_SIZE_LIMIT` | `64` | +| `TELESCOPE_SCHEDULE_WATCHER` | `true` | +| `TELESCOPE_VIEW_WATCHER` | `true` | + +### TryPost features and platform settings + +Optional application features, media limits/retention, platform flags, polling, external API endpoints, and integration controls. Provider settings are only needed for the platforms/features you use. Keep TRYPOST_ALLOW_PRIVATE_NETWORK=false on normal deployments; local integration tests may override it for their mock endpoints. + +| Variable | Default | +| --- | --- | +| `SELF_HOSTED` | `true (fixed)` | +| `LEGAL_TERMS_URL` | `https://trypost.it/terms` | +| `LEGAL_PRIVACY_URL` | `https://trypost.it/privacy` | +| `META_PAGE_WALK_SECONDS` | `20` | +| `TRYPOST_ALLOW_PRIVATE_NETWORK` | `false` | +| `REQUIRE_CARD_FOR_TRIAL` | `true` | +| `MEDIA_IMAGE_MAX_SIZE_MB` | `10` | +| `MEDIA_VIDEO_MAX_SIZE_MB` | `1024` | +| `MEDIA_DOCUMENT_MAX_SIZE_MB` | `100` | +| `MEDIA_IMAGE_MAX_PIXELS` | `8192 * 8192` | +| `MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES` | `unset` | +| `MCP_UPLOAD_URL_TTL_MINUTES` | `15` | +| `MEDIA_SIGNED_UPLOAD_PER_WORKSPACE_PER_MINUTE` | `60` | +| `MEDIA_SIGNED_UPLOAD_PER_IP_PER_MINUTE` | `1200` | +| `MEDIA_UPLOAD_RETENTION_HOURS` | `24` | +| `MEDIA_HEIC_CONVERSION` | `true` | +| `MEDIA_HEIC_MEMORY_LIMIT_MB` | `256` | +| `MEDIA_HEIC_MAP_LIMIT_MB` | `512` | +| `MEDIA_HEIC_DISK_LIMIT_MB` | `1024` | +| `MEDIA_HEIC_AREA_LIMIT_MB` | `1024` | +| `MEDIA_HEIC_WIDTH_LIMIT_PX` | `16384` | +| `MEDIA_HEIC_HEIGHT_LIMIT_PX` | `16384` | +| `UNSPLASH_API_URL` | `https://api.unsplash.com` | +| `UNSPLASH_WEBSITE_URL` | `https://unsplash.com` | +| `UNSPLASH_DOWNLOAD_HOSTS` | `images.unsplash.com` | +| `UNSPLASH_REQUESTS_PER_MINUTE` | `30` | +| `GOOGLE_MEDIA_AUTHORIZE_URL` | `https://accounts.google.com/o/oauth2/v2/auth` | +| `GOOGLE_MEDIA_OAUTH_API` | `https://oauth2.googleapis.com` | +| `GOOGLE_DRIVE_ENABLED` | `true` | +| `GOOGLE_DRIVE_API_URL` | `https://www.googleapis.com/drive/v3` | +| `GOOGLE_PICKER_SDK_URL` | `https://apis.google.com/js/api.js` | +| `GOOGLE_DRIVE_DOWNLOAD_HOSTS` | `www.googleapis.com,*.googleusercontent.com` | +| `GOOGLE_PHOTOS_ENABLED` | `false` | +| `GOOGLE_PHOTOS_PICKER_API_URL` | `https://photospicker.googleapis.com` | +| `GOOGLE_PHOTOS_DOWNLOAD_HOSTS` | `lh3.googleusercontent.com,lh4.googleusercontent.com,lh5.googleusercontent.com,lh6.googleusercontent.com,video-downloads.googleusercontent.com` | +| `GOOGLE_PHOTOS_POLLS_PER_MINUTE` | `60` | +| `GOOGLE_PHOTOS_MAX_ITEMS` | `10` | +| `CANVA_ENABLED` | `true` | +| `CANVA_API_URL` | `https://api.canva.com/rest/v1` | +| `CANVA_AUTHORIZE_URL` | `https://www.canva.com/api/oauth/authorize` | +| `CANVA_CODE_CHALLENGE_METHOD` | `s256` | +| `CANVA_DOWNLOAD_HOSTS` | `*.canva.com` | +| `CANVA_EDITOR_HOSTS` | `*.canva.com` | +| `CANVA_EXPORT_TIMEOUT_SECONDS` | `120` | +| `CANVA_EXPORT_POLL_SECONDS` | `2` | +| `MEDIA_IMPORT_TIMEOUT_SECONDS` | `600` | +| `MEDIA_IMPORTS_PER_MINUTE` | `30` | +| `POST_HISTORY_RETENTION_DAYS` | `730` | +| `EXTERNAL_POSTS_IMPORT_LIMIT` | `50` | +| `X_EXTERNAL_POSTS_IMPORT_DAYS` | `30` | +| `EXTERNAL_POSTS_MATCH_WINDOW_MINUTES` | `120` | +| `EXTERNAL_POSTS_MEDIA_REQUESTS_PER_MINUTE` | `30` | +| `PUBLICATION_DISCOVERY_INTERVAL_HOURS` | `3` | +| `X_PUBLICATION_DISCOVERY_INTERVAL_HOURS` | `24` | +| `PUBLICATION_DISCOVERY_OVERLAP_HOURS` | `6` | +| `TRYPOST_USER_AGENT` | `TryPost.it/1.0 (+https://trypost.it)` | +| `REPURPOSE_POLL_INTERVAL_MINUTES` | `15` | +| `REPURPOSE_BACKOFF_MINUTES` | `60` | +| `FAVICON_API` | `https://icons.duckduckgo.com` | +| `RSS_FEEDS_POLL_INTERVAL_MINUTES` | `480` | +| `RSS_FEEDS_MAX_BACKOFF_MINUTES` | `1440` | +| `RSS_FEEDS_REFRESH_COOLDOWN_MINUTES` | `5` | +| `RSS_FEEDS_MAX_ITEMS_PER_FEED` | `100` | +| `RSS_FEEDS_MAX_PER_WORKSPACE` | `50` | +| `RSS_FEEDS_OG_IMAGE_MAX_ITEMS` | `20` | +| `RSS_FEEDS_OG_IMAGE_MAX_AGE_DAYS` | `14` | +| `RSS_FEEDS_OG_IMAGE_MAX_PAGE_BYTES` | `256 * 1024` | +| `RSS_FEEDS_MAX_RESPONSE_BYTES` | `5 * 1024 * 1024` | +| `RSS_FEEDS_FETCH_BUDGET_SECONDS` | `10` | +| `GOOGLE_AUTH_ENABLED` | `false` | +| `GITHUB_AUTH_ENABLED` | `false` | +| `LINKEDIN_ENABLED` | `true` | +| `LINKEDIN_API` | `https://api.linkedin.com` | +| `LINKEDIN_OAUTH_API` | `https://www.linkedin.com` | +| `LINKEDIN_SCOPES` | `openid,profile,email,w_member_social` | +| `LINKEDIN_PAGE_ENABLED` | `true` | +| `LINKEDIN_PAGE_API` | `https://api.linkedin.com` | +| `LINKEDIN_PAGE_SCOPES` | `openid,profile,email,w_organization_social,r_organization_social,rw_organization_admin,w_member_social` | +| `X_ENABLED` | `true` | +| `X_API` | `https://api.x.com/2` | +| `X_DEFUSE_LINKS` | `false` | +| `TIKTOK_ENABLED` | `true` | +| `TIKTOK_API` | `https://open.tiktokapis.com/v2` | +| `TIKTOK_SCOPES` | `user.info.basic,user.info.profile,user.info.stats,video.publish,video.upload,video.list` | +| `YOUTUBE_ENABLED` | `true` | +| `YOUTUBE_DATA_API` | `https://www.googleapis.com/youtube/v3` | +| `YOUTUBE_ANALYTICS_API` | `https://youtubeanalytics.googleapis.com/v2` | +| `YOUTUBE_OAUTH_API` | `https://oauth2.googleapis.com` | +| `FACEBOOK_ENABLED` | `true` | +| `FACEBOOK_GRAPH_API` | `https://graph.facebook.com/v25.0` | +| `FACEBOOK_RUPLOAD_HOST` | `rupload.facebook.com` | +| `INSTAGRAM_ENABLED` | `true` | +| `INSTAGRAM_GRAPH_API` | `https://graph.instagram.com/v25.0` | +| `INSTAGRAM_AUTH_API` | `https://graph.instagram.com` | +| `INSTAGRAM_OAUTH_URL` | `https://www.instagram.com` | +| `INSTAGRAM_OAUTH_API` | `https://api.instagram.com` | +| `INSTAGRAM_FACEBOOK_ENABLED` | `true` | +| `INSTAGRAM_FACEBOOK_GRAPH_API` | `https://graph.facebook.com/v25.0` | +| `THREADS_ENABLED` | `true` | +| `THREADS_GRAPH_API` | `https://graph.threads.net/v1.0` | +| `THREADS_AUTH_API` | `https://graph.threads.net` | +| `THREADS_OAUTH_URL` | `https://threads.net` | +| `PINTEREST_ENABLED` | `true` | +| `PINTEREST_API` | `https://api.pinterest.com/v5` | +| `BLUESKY_ENABLED` | `true` | +| `BLUESKY_PUBLIC_APPVIEW` | `https://public.api.bsky.app` | +| `BLUESKY_DEFAULT_SERVICE` | `https://bsky.social` | +| `BLUESKY_WEB_APP` | `https://bsky.app` | +| `BLUESKY_VIDEO_SERVICE` | `https://video.bsky.app` | +| `BLUESKY_VIDEO_SERVICE_DID` | `did:web:video.bsky.app` | +| `BLUESKY_VIDEO_POLL_SECONDS` | `2` | +| `BLUESKY_VIDEO_POLL_MAX_SECONDS` | `30` | +| `BLUESKY_VIDEO_MAX_BYTES` | `300_000_000` | +| `BLUESKY_PLC_DIRECTORY` | `https://plc.directory` | +| `MASTODON_ENABLED` | `true` | +| `MASTODON_DEFAULT_INSTANCE` | `https://mastodon.social` | +| `TELEGRAM_ENABLED` | `true` | +| `TELEGRAM_BOT_TOKEN` | `unset` | +| `TELEGRAM_BOT_USERNAME` | `unset` | +| `TELEGRAM_DEEP_LINK` | `https://t.me` | +| `TELEGRAM_API` | `https://api.telegram.org` | +| `TELEGRAM_WEBHOOK_SECRET` | `unset` | +| `DISCORD_ENABLED` | `true` | +| `DISCORD_BOT_TOKEN` | `unset` | +| `DISCORD_API` | `https://discord.com/api/v10` | +| `DISCORD_OAUTH_API` | `https://discord.com/api/oauth2` | +| `DISCORD_PERMISSIONS` | `248832` | +| `DISCORD_SCOPES` | `bot,identify,guilds` | +| `GOOGLE_BUSINESS_ENABLED` | `true` | +| `GOOGLE_BUSINESS_ACCOUNT_MANAGEMENT_API` | `https://mybusinessaccountmanagement.googleapis.com/v1` | +| `GOOGLE_BUSINESS_BUSINESS_INFORMATION_API` | `https://mybusinessbusinessinformation.googleapis.com/v1` | +| `GOOGLE_BUSINESS_LOCAL_POSTS_API` | `https://mybusiness.googleapis.com/v4` | +| `GOOGLE_BUSINESS_OAUTH_API` | `https://oauth2.googleapis.com` | +| `GOOGLE_BUSINESS_DASHBOARD` | `https://business.google.com` | + +## Image, build, example, and deployment controls + +`TRYPOST_TARGET=production` is fixed by Compose. Keep `TRYPOST_SKIP_BOOTSTRAP` unset so migrations, permission setup, caches, and the bundled services initialize. `TRYPOST_DOCKER_BOOTSTRAP`, `UID`, and `GID` are development controls; this deployment does not mount source code or create a development `.env`. + +| Variable | Scope | +| --- | --- | +| `TRYPOST_TARGET` | `production`, fixed | +| `TRYPOST_SKIP_BOOTSTRAP` | upstream default `0`; keep disabled | +| `TRYPOST_DOCKER_BOOTSTRAP` | upstream default `0`; development only | +| `UID`, `GID` | upstream development user mapping, unused here | +| `TRYPOST_HOST` | generated domain; browser build input | +| `TRYPOST_PUBLIC_SCHEME` | `http`; use `https` for TLS and rebuild | +| `TRYPOST_PUBLIC_PORT` | `80`; use `443` for normal HTTPS and rebuild | +| `INITIAL_USER_EMAIL`, `INITIAL_USER_PASSWORD`, `INITIAL_USER_NAME` | first-empty-database bootstrap, described above | +| `VITE_REVERB_HOST`, `VITE_REVERB_PORT`, `VITE_REVERB_SCHEME`, `VITE_REVERB_APP_KEY` | populated as build arguments from the public settings and generated Reverb key; runtime changes alone have no effect | +| `NIGHTWATCH_ENABLED`, `NIGHTWATCH_TOKEN` | vendor telemetry; disabled by Compose | +| `BCRYPT_ROUNDS` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: 12 | +| `MAIL_ENCRYPTION` | Legacy example setting; this release uses MAIL_SCHEME in its mail configuration. | +| `STRIPE_WORKSPACE_MONTHLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `STRIPE_WORKSPACE_YEARLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `STRIPE_SOCIALS_MONTHLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `STRIPE_SOCIALS_YEARLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `STRIPE_WORKSPACES_MONTHLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `STRIPE_WORKSPACES_YEARLY` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: | +| `VITE_APP_NAME` | Upstream frontend build input; not forwarded as a build argument by this template. Prebuilt assets are unchanged by runtime env. | +| `VITE_POSTHOG_ENABLED` | Upstream frontend build input; not forwarded as a build argument by this template. Prebuilt assets are unchanged by runtime env. | +| `VITE_POSTHOG_API_KEY` | Upstream frontend build input; not forwarded as a build argument by this template. Prebuilt assets are unchanged by runtime env. | +| `VITE_POSTHOG_HOST` | Upstream frontend build input; not forwarded as a build argument by this template. Prebuilt assets are unchanged by runtime env. | +| `LINKEDIN_PAGE_CLIENT_REDIRECT` | Upstream example/vendor or build setting; optional and not an additional installation requirement. Example: "${APP_URL}/accounts/linkedin-page/callback" | + +## Optional integration setup + +See [instructions.md](instructions.md) for SMTP configuration, exact OAuth callback paths, Telegram/Discord requirements, storage, HTTPS, and backups. Social permissions and public provider delivery require the provider’s own setup. Keep private endpoint allowances disabled outside deliberate isolated tests. Cloud billing values are not required in self-hosted mode. diff --git a/blueprints/trypost/instructions.md b/blueprints/trypost/instructions.md new file mode 100644 index 000000000..2f2c7db3a --- /dev/null +++ b/blueprints/trypost/instructions.md @@ -0,0 +1,75 @@ +# TryPost + +Deploys [TryPost](https://github.com/trypostit/trypost) 2.0.0, requested in [#1214](https://github.com/Dokploy/templates/issues/1214), with PostgreSQL, authenticated Redis, persistent media/API keys, Horizon workers, the scheduler, and Reverb WebSockets. + +## First deployment + +1. Import the template into a Dokploy Docker Compose service. Change `INITIAL_USER_EMAIL` to your address before deploying. The generated `INITIAL_USER_PASSWORD` is the first login password; keep all generated application/database/Redis/Reverb secrets. +2. Point your domain at Dokploy and configure HTTPS in **Domains**, with the `trypost` service on port **80**. Set these environment values before using real accounts: + + ```dotenv + TRYPOST_HOST=post.example.com + APP_URL=https://post.example.com + TRYPOST_PUBLIC_SCHEME=https + TRYPOST_PUBLIC_PORT=443 + SESSION_SECURE_COOKIE=true + ``` + + `TRYPOST_HOST` contains only the hostname. The generated HTTP domain and port 80 support initial local checks. For a custom public port, include it in `APP_URL` and set `TRYPOST_PUBLIC_PORT` to the same value. +3. Deploy and allow the asset build and database migrations to finish. The template starts from the official versioned image and rebuilds **only its browser assets**, because the published bundle points WebSockets at `localhost:8080`. It leaves the application source and production PHP dependencies intact. A build-capable Docker Compose installation is required. Allow several minutes and sufficient RAM/disk for the first build. +4. Open the domain and sign in using `INITIAL_USER_EMAIL` and the generated password from Dokploy Environment. The template creates this verified user and workspace **only when the database has no users**. Public registration stays disabled in upstream self-hosted mode. Changing these bootstrap variables later does not reset an existing account or password; use the app's account settings/password recovery instead. After the first successful deployment, the initial password variable can be removed. +5. Finish any welcome steps, connect the platforms you use, and create a draft or schedule a test post. Additional users join through invitations. Configure delivered email before relying on invitations or password recovery. + +The public hostname, scheme, port, and Reverb public key are browser **build inputs**. After changing any of them, redeploy with a rebuild; changing only runtime `VITE_*` variables cannot alter compiled assets. Both the app and WebSockets use the same domain/port through the bundled Nginx proxy; do not expose Redis, PostgreSQL, or Reverb's internal port 8080. + +## Required and optional configuration + +Dokploy generates `APP_KEY`, `DB_PASSWORD`, `REDIS_PASSWORD`, `REVERB_APP_ID`, `REVERB_APP_KEY`, `REVERB_APP_SECRET`, and the first-user password. On first startup, RSA signing keys are generated once into the persistent storage volume and loaded through Passport's `file://` settings. A partial/missing key pair after setup must be restored from backup; never replace a valid pair during an upgrade. + +**No Telegram, SMTP, AI, Stripe, or social-provider credentials are required to start or log in.** `SELF_HOSTED=true` bypasses cloud billing, and telemetry is disabled. Add only the integrations you need in Dokploy **Environment**; the optional `.env` is passed through to the container. Redeploy after runtime changes so Laravel's configuration cache is rebuilt. See the [environment reference](https://github.com/Dokploy/templates/blob/canary/blueprints/trypost/environment.md) for the full versioned inventory and defaults. + +### Optional email + +The default `MAIL_MAILER=log` records messages without delivering them. The initial user is provisioned as verified, so a mail server is not needed for first login. For invitations and password resets, configure your SMTP provider and test delivery: + +```dotenv +MAIL_MAILER=smtp +MAIL_SCHEME=smtp +MAIL_HOST=smtp.example.com +MAIL_PORT=587 +MAIL_USERNAME=your-user +MAIL_PASSWORD=your-password +MAIL_FROM_ADDRESS=post@example.com +MAIL_FROM_NAME=TryPost +``` + +Use your provider's TLS settings (`smtps` and port 465 for implicit TLS). API transports can use the supported Postmark, Resend, or SES credentials instead. + +### Optional publishing and other services + +OAuth publishing integrations need the provider's client ID, client secret, and exact callback URI registered in its developer portal. Use the `*_CLIENT_REDIRECT` variable names for this application: + +| Platform | Variable prefix | Callback path on `APP_URL` | +| --- | --- | --- | +| LinkedIn | `LINKEDIN` | `/accounts/linkedin/callback` | +| X | `X` | `/accounts/x/callback` | +| TikTok | `TIKTOK` | `/accounts/tiktok/callback` | +| YouTube | `GOOGLE` | `/accounts/youtube/callback` | +| Facebook | `FACEBOOK` | `/accounts/facebook/callback` | +| Instagram | `INSTAGRAM` | `/accounts/instagram/callback` | +| Threads | `THREADS` | `/accounts/threads/callback` | +| Pinterest | `PINTEREST` | `/accounts/pinterest/callback` | +| Discord | `DISCORD` | `/accounts/discord/callback` | +| Google Business | `GOOGLE_BUSINESS` | `/accounts/google-business/callback` | + +Discord additionally needs `DISCORD_BOT_TOKEN`. Telegram publishing uses `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_USERNAME`, and `TELEGRAM_WEBHOOK_SECRET`; only configure it if you want Telegram. Follow upstream's webhook setup when enabling it. Bluesky/Mastodon use their connection flows in the UI. Provider app review, scopes, account eligibility, and public HTTPS still apply. Google/GitHub social login is separate from publishing and disabled by default. + +AI features need the selected provider's API key; media imports need the selected service's credentials. Local public media storage works without S3/R2. When selecting object storage, provide the matching credentials, bucket, region/endpoint, and public media URL. Social platforms must be able to fetch media from your public HTTPS deployment. + +## Operations and backups + +Use one application replica with the bundled scheduler. The container runs upstream's Nginx, PHP-FPM, Reverb, Horizon, and scheduler processes; its root supervisor performs initialization and PHP-FPM workers use `www-data`. The web health check covers `/up`, so also monitor Horizon, Redis, database access, failed jobs, and WebSocket connectivity. + +Back up PostgreSQL, Redis, the **entire storage volume**, and Dokploy Environment together. Storage includes uploaded media and RSA signing keys; `APP_KEY` is required to decrypt saved credentials. Redis persists queued jobs with AOF. Do not flush it or delete its volume while publishing jobs are pending. Coordinate maintenance around long-running jobs; the container allows a 60-second graceful stop, while some upstream jobs can run longer. + +Keep `APP_URL`, domain settings, and browser build inputs in sync. Rebuild after changes to the public connection settings or the pinned upstream version. Back up before upgrades, read upstream's migration notes, and validate an upgrade against a restored copy first. This template targets a **fresh 2.0.0 installation**; it does not migrate an existing deployment from another template or version. Test procedures and verified boundaries are in [testing.md](https://github.com/Dokploy/templates/blob/canary/blueprints/trypost/testing.md). diff --git a/blueprints/trypost/meta.json b/blueprints/trypost/meta.json new file mode 100644 index 000000000..e08cc37e7 --- /dev/null +++ b/blueprints/trypost/meta.json @@ -0,0 +1,13 @@ +{ + "id": "trypost", + "name": "TryPost", + "version": "2.0.0", + "description": "TryPost is an open source social media scheduler with team workspaces, a post composer, publishing queues, analytics, a REST API, and MCP tools.", + "logo": "trypost.svg", + "links": { + "github": "https://github.com/trypostit/trypost", + "website": "https://trypost.it", + "docs": "https://docs.trypost.it" + }, + "tags": ["social-media", "marketing", "scheduling"] +} diff --git a/blueprints/trypost/template.toml b/blueprints/trypost/template.toml new file mode 100644 index 000000000..710227157 --- /dev/null +++ b/blueprints/trypost/template.toml @@ -0,0 +1,33 @@ +[variables] +main_domain = "${domain}" +app_key = "${base64:32}" +database_password = "${password:48}" +redis_password = "${password:48}" +reverb_key = "${hash:32}" +reverb_secret = "${password:64}" +reverb_app_id = "${uuid}" +initial_password = "${password:32}" + +[config] +env = [ + "TRYPOST_HOST=${main_domain}", + "APP_URL=http://${main_domain}", + "APP_KEY=base64:${app_key}", + "DB_PASSWORD=${database_password}", + "REDIS_PASSWORD=${redis_password}", + "REVERB_APP_ID=${reverb_app_id}", + "REVERB_APP_KEY=${reverb_key}", + "REVERB_APP_SECRET=${reverb_secret}", + "TRYPOST_PUBLIC_SCHEME=http", + "TRYPOST_PUBLIC_PORT=80", + "SESSION_SECURE_COOKIE=false", + "INITIAL_USER_EMAIL=admin@example.com", + "INITIAL_USER_PASSWORD=${initial_password}", + "INITIAL_USER_NAME=Administrator", + "MAIL_MAILER=log" +] + +[[config.domains]] +serviceName = "trypost" +port = 80 +host = "${main_domain}" diff --git a/blueprints/trypost/testing.md b/blueprints/trypost/testing.md new file mode 100644 index 000000000..bac87d247 --- /dev/null +++ b/blueprints/trypost/testing.md @@ -0,0 +1,49 @@ +# Testing TryPost + +Use a disposable Dokploy project, generated test credentials, and local integration endpoints. Do not publish test posts to real accounts without permission. + +## Repository validation + +From the repository root: + +```sh +node build-scripts/generate-meta.js --check +cd build-scripts +pnpm exec tsx validate-docker-compose.ts --file ../blueprints/trypost/docker-compose.yml --verbose +pnpm exec tsx validate-template.ts --dir ../blueprints/trypost --verbose +cd ../app +pnpm install --frozen-lockfile +pnpm run build +``` + +Use a temporary `.env` with the generated values to run `docker compose config --quiet`. Missing required application, database, Redis, Reverb, or domain values must fail clearly. Optional mail, AI, Telegram, and OAuth credentials must not be needed for the default deployment. + +## Dokploy checks + +1. Open the PR preview, search for TryPost, copy its Base64, and import into a fresh Docker Compose service. Confirm the generated key decodes to 32 bytes and the domain targets `trypost:80`. +2. Deploy with the generated defaults. Confirm the browser-assets build, migrations, initial-user provisioning, web health, PostgreSQL, and authenticated Redis succeed. Verify Horizon, the scheduler, and Reverb start, with no application host ports published. +3. Sign in using the initial account. Verify that the calendar works without optional integrations and public registration is unavailable. Unauthenticated API calls must receive 401. Create a workspace API token and exercise a saved draft and image upload. +4. Enable a disposable publishing destination using a local API mock. Schedule a post, let the bundled scheduler/Horizon process it, and verify the captured content and saved remote publication ID. Verify browser WebSocket handshake, authenticated channel subscription, and an event delivered through Reverb. +5. Follow the HTTPS setup, rebuild the assets for HTTPS, and check login, secure cookies, media access, and WebSockets through Dokploy's proxy. A local certificate can test TLS routing; public ACME issuance requires a reachable deployment. +6. Stop/start and force container recreation. Check that database records, media bytes, decrypted credentials, `APP_KEY`, RSA key hashes, and the original API token survive. Changing initial-user variables must not create another user or reset the existing password. Removing the bootstrap password after setup must still allow restart. +7. Test an optional runtime variable entered only in Dokploy Environment, then confirm Laravel's cached configuration receives it. Test mail delivery with a local SMTP capture server before using a real provider. +8. Inspect container logs, health, and restart counts. Test restored backups and version-specific migrations separately before upgrading an existing installation. + +## Limits + +These checks cover the template and selected workflows, not every combination of the upstream application's environment variables. Live OAuth, provider permissions/review, real social delivery, public certificate issuance, external storage/database/queue services, and paid AI providers need separate credentials and infrastructure. A mock success does not verify a real provider's configuration. + +## Verified results + +Tested on 2026-10-18 on Linux amd64 with Dokploy 0.30.8, Docker Compose 5.6.0, Traefik 3.6.7, PostgreSQL 18.6, and Redis 8.10.2. The official application image was `ghcr.io/trypostit/trypost:2.0.0`, manifest digest `sha256:04c8e9ba48f97091106daa247afeb3be9ec5c395671bbc7ad73adf7f42334155`, source revision `7b9eeec9c68a68f4ae72a63e9eae2726b9fb44d0`. + +- Metadata, Compose/TOML validation, required-value guards, and catalog production build passed. The catalog reports its existing JavaScript chunk-size warning. The environment inventory covers all 500 active application variables and the 239 keys in the upstream environment examples. +- The PR-preview catalog exported byte-identical Compose/TOML files. Its Base64 payload imported into a fresh Dokploy service, rebuilt browser assets for the generated domain, initialized the database/account, and became healthy without optional integration credentials. +- The initial user signed in and reached the calendar. Public registration returned 404, and unauthenticated API calls returned 401. Horizon, scheduler, Reverb, Nginx, and PHP-FPM ran successfully. Redis rejected unauthenticated commands. +- An API key created a draft and uploaded an image. A signed Telegram connection code plus authenticated incoming webhook linked a **local mock channel**. The actual scheduler and Horizon published scheduled posts to a **local Telegram API mock**, which captured the expected content; TryPost saved publication IDs/URLs. +- Browser checks confirmed WebSocket handshake, private-channel subscription, and live post events through the Dokploy domain. After an HTTPS asset rebuild, the same checks passed over WSS, along with fresh login, secure cookies, API access, and public media access. TLS used a **local/default certificate**. +- Stop/start and forced container recreation preserved the original user/password, API token, draft, exact media bytes, application key, RSA key hashes, and a decrypted synthetic account token. Changing initial-user variables and removing the bootstrap password did not reset or duplicate the existing user. +- Additional runtime variables entered only through Dokploy Environment reached Laravel's cached configuration. Optional SMTP delivered a password-reset message to a **local capture server** with the correct public HTTPS origin. +- The three application services finished healthy, with zero unexpected restarts, no published host ports, and no application errors in their logs. No JavaScript errors were observed in the verified browser flows. + +No messages were sent to real social accounts. Live OAuth/provider delivery, paid AI calls, public ACME issuance, external storage, and other database/queue combinations were not exercised. Existing-version upgrade paths were not tested; this is a fresh-install template. diff --git a/blueprints/trypost/trypost.svg b/blueprints/trypost/trypost.svg new file mode 100644 index 000000000..dff2c4b23 --- /dev/null +++ b/blueprints/trypost/trypost.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/blueprints/twenty/meta.json b/blueprints/twenty/meta.json index 0934be604..61a537f46 100644 --- a/blueprints/twenty/meta.json +++ b/blueprints/twenty/meta.json @@ -2,7 +2,7 @@ "id": "twenty", "name": "Twenty CRM", "version": "latest", - "description": "Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.", + "description": "Twenty is a modern CRM and an open-source alternative to Salesforce. Self-host it on your own server with Dokploy, which deploys Twenty as a Docker Compose service in a few clicks.", "logo": "twenty.svg", "links": { "github": "https://github.com/twentyhq/twenty", @@ -13,5 +13,7 @@ "crm", "sales", "business" - ] + ], + "seoTitle": "Self-Host Twenty CRM in minutes", + "seoDescription": "Modern CRM, an alternative to Salesforce. Self-host Twenty CRM on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/umami/docker-compose.yml b/blueprints/umami/docker-compose.yml index 6cd51601d..79b80c897 100644 --- a/blueprints/umami/docker-compose.yml +++ b/blueprints/umami/docker-compose.yml @@ -1,6 +1,6 @@ services: umami: - image: ghcr.io/umami-software/umami:3.0.3 + image: ghcr.io/umami-software/umami:3.4.0 restart: always healthcheck: test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] @@ -11,7 +11,7 @@ services: db: condition: service_healthy environment: - DATABASE_URL: postgresql://umami:umami@db:5432/umami + DATABASE_URL: postgresql://umami:${DB_PASSWORD}@db:5432/umami DATABASE_TYPE: postgresql APP_SECRET: ${APP_SECRET} db: @@ -28,7 +28,7 @@ services: environment: POSTGRES_DB: umami POSTGRES_USER: umami - POSTGRES_PASSWORD: umami + POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: db-data: diff --git a/blueprints/umami/meta.json b/blueprints/umami/meta.json index 73651d5e0..2714d8179 100644 --- a/blueprints/umami/meta.json +++ b/blueprints/umami/meta.json @@ -1,8 +1,8 @@ { "id": "umami", "name": "Umami", - "version": "v3.0.3", - "description": "Umami is a simple, fast, privacy-focused alternative to Google Analytics.", + "version": "v3.4.0", + "description": "Umami is a simple website analytics tool and an alternative to Google Analytics. Self-host it on your own server with Dokploy, which deploys Umami as a Docker Compose service in a few clicks.", "logo": "umami.png", "links": { "github": "https://github.com/umami-software/umami", @@ -11,5 +11,8 @@ }, "tags": [ "analytics" - ] + ], + "seoTitle": "Umami Docker Compose Setup", + "seoDescription": "Simple website analytics, an alternative to Google Analytics. Deploy Umami with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/umami/template.toml b/blueprints/umami/template.toml index a8f079fa5..22f21e729 100644 --- a/blueprints/umami/template.toml +++ b/blueprints/umami/template.toml @@ -11,3 +11,4 @@ host = "${main_domain}" [config.env] APP_SECRET = "${base64:64}" +DB_PASSWORD = "${password:32}" diff --git a/blueprints/uptime-kuma/meta.json b/blueprints/uptime-kuma/meta.json index 09998471e..51c7fa8a6 100644 --- a/blueprints/uptime-kuma/meta.json +++ b/blueprints/uptime-kuma/meta.json @@ -2,7 +2,7 @@ "id": "uptime-kuma", "name": "Uptime Kuma", "version": "2.1.0", - "description": "Uptime Kuma is a free and open source monitoring tool that allows you to monitor your websites and applications.", + "description": "Uptime Kuma is an open-source monitoring tool that tracks the uptime of your websites and services. Self-host it on your own server with Dokploy, which deploys Uptime Kuma as a Docker Compose service in a few clicks.", "logo": "uptime-kuma.png", "links": { "github": "https://github.com/louislam/uptime-kuma", @@ -11,5 +11,8 @@ }, "tags": [ "monitoring" - ] + ], + "seoTitle": "Uptime Kuma Docker Compose Setup", + "seoDescription": "Uptime and website monitoring tool. Deploy Uptime Kuma with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/uptimekit/docker-compose.yml b/blueprints/uptimekit/docker-compose.yml index 3a7b9fa6c..6bc4d4aa2 100644 --- a/blueprints/uptimekit/docker-compose.yml +++ b/blueprints/uptimekit/docker-compose.yml @@ -1,10 +1,11 @@ services: dash: - image: ghcr.io/uptimekit/dash:latest + image: ghcr.io/uptimekit/dash:5.2.1 env_file: - .env environment: - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/uptimekit + - TIMESERIES_BACKEND=clickhouse - CLICKHOUSE_URL=http://clickhouse:8123 - CLICKHOUSE_USER=default - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD} @@ -26,27 +27,6 @@ services: start_period: 20s restart: always - status-page: - image: ghcr.io/uptimekit/status-page:latest - env_file: - - .env - environment: - - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/uptimekit - - CLICKHOUSE_URL=http://clickhouse:8123 - - CLICKHOUSE_USER=default - - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD} - - REDIS_URL=redis://redis:6379 - depends_on: - db: - condition: service_healthy - redis: - condition: service_started - clickhouse: - condition: service_started - dash: - condition: service_healthy - restart: always - db: image: postgres:18-alpine environment: @@ -70,7 +50,7 @@ services: restart: always clickhouse: - image: clickhouse/clickhouse-server:latest + image: clickhouse/clickhouse-server:26.8-alpine volumes: - clickhouse_data:/var/lib/clickhouse environment: diff --git a/blueprints/uptimekit/meta.json b/blueprints/uptimekit/meta.json index 5673afad9..c22b967b5 100644 --- a/blueprints/uptimekit/meta.json +++ b/blueprints/uptimekit/meta.json @@ -1,7 +1,7 @@ { "id": "uptimekit", "name": "Uptimekit", - "version": "3.1.0", + "version": "5.2.1", "description": "The modern open-source status page and monitoring solution.", "logo": "uptimekit.svg", "links": { diff --git a/blueprints/uptimekit/template.toml b/blueprints/uptimekit/template.toml index 45e02badd..f65bdd3f1 100644 --- a/blueprints/uptimekit/template.toml +++ b/blueprints/uptimekit/template.toml @@ -10,10 +10,6 @@ clickhouse_password = "${password:32}" serviceName = "dash" port = 3000 host = "${main_domain}" -[[config.domains]] -serviceName = "status-page" -port = 3001 -host = "${second_domain}" [config.env] diff --git a/blueprints/vault/meta.json b/blueprints/vault/meta.json index 8f2f35a7a..b80b6aab2 100644 --- a/blueprints/vault/meta.json +++ b/blueprints/vault/meta.json @@ -2,7 +2,7 @@ "id": "vault", "name": "Vault", "version": "2.0.3", - "description": "Vault is a tool for securely accessing secrets. A secret is anything that you want to tightly control access to, such as API keys, passwords, certificates, and more. Vault provides a unified interface to any secret, while providing tight access control and recording a detailed audit log. To sign in: In the Vault UI, select 'Token' as the authentication method (not GitHub), then enter the root token from the VAULT_DEV_ROOT_TOKEN_ID environment variable (auto-generated).", + "description": "Vault is HashiCorp's tool for securely storing and managing secrets and encryption keys. Self-host it on your own server with Dokploy, which deploys Vault as a Docker Compose service in a few clicks.", "logo": "vault.svg", "links": { "github": "https://github.com/hashicorp/vault", @@ -14,5 +14,8 @@ "secrets", "devops", "infrastructure" - ] + ], + "seoTitle": "Vault Docker Compose Setup", + "seoDescription": "Tool for securely storing secrets and keys. Deploy Vault with Docker Compose on Dokploy – sign in using the auto-generated root token env var.", + "newConfig": true } diff --git a/blueprints/vaultwarden/meta.json b/blueprints/vaultwarden/meta.json index 970e878e1..a035ee7aa 100644 --- a/blueprints/vaultwarden/meta.json +++ b/blueprints/vaultwarden/meta.json @@ -2,7 +2,7 @@ "id": "vaultwarden", "name": "Vaultwarden", "version": "1.36.0", - "description": "Unofficial Bitwarden compatible server written in Rust, formerly known as bitwarden_rs", + "description": "Vaultwarden is a lightweight password manager that works with Bitwarden apps. Self-host it on your own server with Dokploy, which deploys Vaultwarden as a Docker Compose service in a few clicks.", "logo": "vaultwarden.svg", "links": { "github": "https://github.com/dani-garcia/vaultwarden", @@ -11,5 +11,8 @@ }, "tags": [ "open-source" - ] + ], + "seoTitle": "Vaultwarden Docker Compose Setup", + "seoDescription": "Lightweight Bitwarden-compatible password manager. Deploy Vaultwarden with Docker Compose on Dokploy in minutes – free, open source, self-hosted.", + "newConfig": true } diff --git a/blueprints/vikunja/meta.json b/blueprints/vikunja/meta.json index 7d926d206..e557ff5f2 100644 --- a/blueprints/vikunja/meta.json +++ b/blueprints/vikunja/meta.json @@ -2,7 +2,7 @@ "id": "vikunja", "name": "Vikunja", "version": "0.23.0", - "description": "Vikunja is a self-hosted, open-source to-do list application to organize tasks, projects, and notes.", + "description": "Vikunja is an open-source to-do list and task management app. Self-host it on your own server with Dokploy, which deploys Vikunja as a Docker Compose service in a few clicks.", "logo": "image.png", "links": { "github": "https://github.com/go-vikunja/vikunja", @@ -14,5 +14,7 @@ "tasks", "self-hosted", "project-management" - ] + ], + "seoTitle": "Self-Host Vikunja in minutes", + "seoDescription": "To-do list and task management app. Self-host Vikunja on Dokploy in minutes – no manual setup needed." } diff --git a/blueprints/wazuh/docker-compose.yml b/blueprints/wazuh/docker-compose.yml new file mode 100644 index 000000000..700cf159e --- /dev/null +++ b/blueprints/wazuh/docker-compose.yml @@ -0,0 +1,201 @@ +# dokploy: allow-host-ports +# 1514/tcp (agent events) and 1515/tcp (agent enrollment) are raw TCP protocols +# that Wazuh agents connect to directly, so they can't go through Traefik. +services: + # One-shot: generates the internal TLS PKI (root CA + node/admin certs) on first deploy. + # The other services mount wazuh-certs with nocopy so the demo certs shipped in + # their images are never copied into the volume. + wazuh-certs: + image: wazuh/wazuh-certs-generator:0.0.4 + restart: "no" + environment: + - CERT_TOOL_VERSION=4.14 + entrypoint: ["/bin/bash", "-c"] + command: + - | + set -e + if [ ! -f /certificates/wazuh.indexer.pem ]; then + bash /entrypoint.sh + fi + # entrypoint.sh exits 0 even when the cert tool fails + test -f /certificates/wazuh.indexer.pem + # The generator leaves the directory root-only (500); the indexer and + # dashboard run as uid 1000 and must be able to traverse it. + chmod 755 /certificates + volumes: + - wazuh-certs:/certificates + - ../files/certs.yml:/config/certs.yml:ro + + # One-shot: writes internal_users.yml with bcrypt hashes of the generated passwords + # before the indexer initializes its security index on first start. + wazuh-indexer-init: + image: wazuh/wazuh-indexer:4.14.8 + restart: "no" + environment: + - INDEXER_PASSWORD=${INDEXER_PASSWORD} + - DASHBOARD_PASSWORD=${DASHBOARD_PASSWORD} + entrypoint: ["/bin/bash", "-c"] + command: + - | + set -e + SEC=/usr/share/wazuh-indexer/config/opensearch-security + [ -f "$$SEC/.dokploy-users" ] && exit 0 + export JAVA_HOME=/usr/share/wazuh-indexer/jdk + HASH=/usr/share/wazuh-indexer/plugins/opensearch-security/tools/hash.sh + ADMIN_HASH=$$(bash $$HASH -p "$$INDEXER_PASSWORD" | tail -1) + KIBANA_HASH=$$(bash $$HASH -p "$$DASHBOARD_PASSWORD" | tail -1) + cat > "$$SEC/internal_users.yml" < + + yes + yes + no + no + no + smtp.example.wazuh.com + wazuh@example.wazuh.com + recipient@example.wazuh.com + 12 + alerts.log + 10m + 0 + + + + 3 + 12 + + + + + plain + + + + secure + 1514 + tcp + 131072 + + + + + no + yes + yes + yes + yes + yes + yes + yes + + + 43200 + + etc/rootcheck/rootkit_files.txt + etc/rootcheck/rootkit_trojans.txt + + yes + + + + yes + 1800 + 1d + yes + + wodles/java + wodles/ciscat + + + + + yes + yes + /var/log/osquery/osqueryd.results.log + /etc/osquery/osquery.conf + yes + + + + + no + 1h + yes + yes + yes + yes + yes + yes + yes + + + + 10 + + + + + yes + yes + 12h + yes + + + + yes + yes + 60m + + + + yes + + https://wazuh.indexer:9200 + + + + /etc/ssl/wazuh/root-ca-manager.pem + + /etc/ssl/wazuh/wazuh.manager.pem + /etc/ssl/wazuh/wazuh.manager-key.pem + + + + + + no + + + 43200 + + yes + + + yes + + + no + + + /etc,/usr/bin,/usr/sbin + /bin,/sbin,/boot + + + /etc/mtab + /etc/hosts.deny + /etc/mail/statistics + /etc/random-seed + /etc/random.seed + /etc/adjtime + /etc/httpd/logs + /etc/utmpx + /etc/wtmpx + /etc/cups/certs + /etc/dumpdates + /etc/svc/volatile + + + .log$|.swp$ + + + /etc/ssl/private.key + + yes + yes + yes + yes + + + 10 + + + 100 + + + + yes + 5m + 1h + 10 + + + + + + 127.0.0.1 + ^localhost.localdomain$ + + + + disable-account + disable-account + yes + + + + restart-wazuh + restart-wazuh + + + + firewall-drop + firewall-drop + yes + + + + host-deny + host-deny + yes + + + + route-null + route-null + yes + + + + win_route-null + route-null.exe + yes + + + + netsh + netsh.exe + yes + + + + + + + command + df -P + 360 + + + + full_command + netstat -tulpn | sed 's/\([[:alnum:]]\+\)\ \+[[:digit:]]\+\ \+[[:digit:]]\+\ \+\(.*\):\([[:digit:]]*\)\ \+\([0-9\.\:\*]\+\).\+\ \([[:digit:]]*\/[[:alnum:]\-]*\).*/\1 \2 == \3 == \4 \5/' | sort -k 4 -g | sed 's/ == \(.*\) ==/:\1/' | sed 1,2d + netstat listening ports + 360 + + + + full_command + last -n 20 + 360 + + + + + ruleset/decoders + ruleset/rules + 0215-policy_rules.xml + etc/lists/audit-keys + etc/lists/amazon/aws-eventnames + etc/lists/security-eventchannel + etc/lists/malicious-ioc/malicious-ip + etc/lists/malicious-ioc/malicious-domains + etc/lists/malicious-ioc/malware-hashes + + + etc/decoders + etc/rules + + + + yes + 1 + 64 + 15m + + + + + no + 1515 + no + yes + no + HIGH:!ADH:!EXP:!MD5:!RC4:!3DES:!CAMELLIA:@STRENGTH + + no + etc/sslmanager.cert + etc/sslmanager.key + no + + + + wazuh + node01 + master + aa093264ef885029653eea20dfcf51ae + 1516 + 0.0.0.0 + + wazuh.manager + + no + yes + + + + + + + syslog + /var/ossec/logs/active-responses.log + + + +''' diff --git a/blueprints/wazuh/wazuh.png b/blueprints/wazuh/wazuh.png new file mode 100644 index 000000000..7bccfde28 Binary files /dev/null and b/blueprints/wazuh/wazuh.png differ diff --git a/blueprints/wise-mcp/docker-compose.yml b/blueprints/wise-mcp/docker-compose.yml new file mode 100644 index 000000000..06c41aa52 --- /dev/null +++ b/blueprints/wise-mcp/docker-compose.yml @@ -0,0 +1,16 @@ +services: + wise-mcp: + image: ghcr.io/blanketbyte/wise-mcp:1.1.1 + restart: unless-stopped + expose: + - 3000 + environment: + - PUBLIC_URL=${PUBLIC_URL} + - TOKEN_ENCRYPTION_KEY=${TOKEN_ENCRYPTION_KEY} + - ALLOWED_WISE_USERS=${ALLOWED_WISE_USERS} + - WRITES_ENABLED=${WRITES_ENABLED} + volumes: + - wise-mcp-data:/data + +volumes: + wise-mcp-data: {} diff --git a/blueprints/wise-mcp/meta.json b/blueprints/wise-mcp/meta.json new file mode 100644 index 000000000..05304f93d --- /dev/null +++ b/blueprints/wise-mcp/meta.json @@ -0,0 +1,13 @@ +{ + "id": "wise-mcp", + "name": "Wise MCP (unofficial)", + "version": "1.1.1", + "description": "Self-hosted MCP connector that lets Claude and ChatGPT read your Wise balances, card payments and transfers, and prepare transfers you approve twice.", + "logo": "wise-mcp.svg", + "links": { + "github": "https://github.com/BlanketByte/wise-mcp", + "website": "https://blanketbyte.com", + "docs": "https://github.com/BlanketByte/wise-mcp#readme" + }, + "tags": ["ai", "mcp", "finance"] +} diff --git a/blueprints/wise-mcp/template.toml b/blueprints/wise-mcp/template.toml new file mode 100644 index 000000000..a0ef60f77 --- /dev/null +++ b/blueprints/wise-mcp/template.toml @@ -0,0 +1,19 @@ +[variables] +main_domain = "${domain}" +encryption_key = "${password:48}" + +[config] +mounts = [] + +[[config.domains]] +serviceName = "wise-mcp" +port = 3000 +host = "${main_domain}" + +[config.env] +PUBLIC_URL = "https://${main_domain}" +TOKEN_ENCRYPTION_KEY = "${encryption_key}" +# Leave empty for the first login: the "Not allowed" page shows your Wise user id. Then set it and redeploy. +ALLOWED_WISE_USERS = "" +# false = read-only (no transfer/recipient tools). Writes are always two-step and paid by you in the Wise app. +WRITES_ENABLED = "true" diff --git a/blueprints/wise-mcp/wise-mcp.svg b/blueprints/wise-mcp/wise-mcp.svg new file mode 100644 index 000000000..770c26356 --- /dev/null +++ b/blueprints/wise-mcp/wise-mcp.svg @@ -0,0 +1,7 @@ + + Wise MCP + + + + + diff --git a/blueprints/wud/docker-compose.yml b/blueprints/wud/docker-compose.yml new file mode 100644 index 000000000..5b0d437f1 --- /dev/null +++ b/blueprints/wud/docker-compose.yml @@ -0,0 +1,85 @@ +services: + wud: + image: ghcr.io/getwud/wud:9.5.0 + restart: unless-stopped + env_file: + - path: .env + required: false + environment: + WUD_AUTH_ADMIN_USER: "${WUD_AUTH_ADMIN_USER:-admin}" + WUD_AUTH_ADMIN_PASSWORD: "${WUD_AUTH_ADMIN_PASSWORD:?Keep the generated administrator password}" + WUD_SERVER_PORT: "3000" + WUD_SERVER_TLS_ENABLED: "false" + WUD_STORE_PATH: /store + WUD_STORE_FILE: wud.sqlite + WUD_WATCHER_DOCKER_LOCAL_HOST: wud-socket-proxy + WUD_WATCHER_DOCKER_LOCAL_PORT: "2375" + WUD_WATCHER_DOCKER_LOCAL_WATCHBYDEFAULT: "${WUD_WATCHER_DOCKER_LOCAL_WATCHBYDEFAULT:-true}" + WUD_WATCHER_DOCKER_LOCAL_WATCHALL: "${WUD_WATCHER_DOCKER_LOCAL_WATCHALL:-false}" + WUD_WATCHER_DOCKER_LOCAL_WATCHATSTART: "true" + WUD_WATCHER_DOCKER_LOCAL_CRON: "${WUD_WATCHER_DOCKER_LOCAL_CRON:-0 * * * *}" + WUD_LOG_LEVEL: "${WUD_LOG_LEVEL:-info}" + WUD_LOG_FORMAT: json + TZ: "${TZ:-UTC}" + expose: + - "3000" + volumes: + - wud-data:/store + depends_on: + wud-socket-proxy: + condition: service_healthy + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:3000/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + stop_grace_period: 30s + logging: &logging + driver: json-file + options: + max-size: "10m" + max-file: "3" + + wud-socket-proxy: + image: ghcr.io/tecnativa/docker-socket-proxy:v0.5.0 + restart: unless-stopped + environment: + CONTAINERS: "1" + IMAGES: "1" + EVENTS: "1" + PING: "1" + VERSION: "1" + POST: "0" + expose: + - "2375" + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + read_only: true + bind: + create_host_path: false + read_only: true + tmpfs: + - /run + - /tmp + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "wget", "--quiet", "--output-document=-", "http://127.0.0.1:2375/_ping"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + stop_grace_period: 30s + logging: *logging + +volumes: + wud-data: diff --git a/blueprints/wud/environment.md b/blueprints/wud/environment.md new file mode 100644 index 000000000..4077464e6 --- /dev/null +++ b/blueprints/wud/environment.md @@ -0,0 +1,67 @@ +# Environment variables + +This template targets WUD **9.5.0**. Provider settings are read from the environment at startup; add optional values in Dokploy's Environment tab and redeploy. The optional `.env` file forwards additional WUD settings without requiring a Compose edit. Values explicitly set in Compose take precedence over `.env`. + +## Required administrator settings + +| Variable | Template value | Purpose | +| --- | --- | --- | +| `WUD_AUTH_ADMIN_USER` | `admin` | Local administrator username; editable. | +| `WUD_AUTH_ADMIN_PASSWORD` | Generated random hexadecimal string | Required by this template. Plaintext bootstrap input; WUD stores a bcrypt hash. Reapplied to this account on **every startup**. Rotate it here rather than only in the UI. | + +No provider credential or separate session secret is required. Upstream also supports `WUD_AUTH_ADMIN_HASH`, legacy `WUD_AUTH_BASIC_*`, and OIDC; those are alternative authentication designs, not additional installation requirements. Do not set a conflicting admin hash alongside this template's password. See [authentication](https://getwud.app/docs/configuration/authentications/). + +## Editable defaults + +| Variable | Template value | Purpose | +| --- | --- | --- | +| `WUD_WATCHER_DOCKER_LOCAL_WATCHBYDEFAULT` | `true` | Monitor running containers that pass the filters. Set `false` for explicit opt-in labels. | +| `WUD_WATCHER_DOCKER_LOCAL_EXCLUDE` | `^dokploy([._-]\|$)` | Skip standard Dokploy infrastructure names by default. Remove this variable and redeploy to include them. Explicit `wud.watch=true` labels override the filter. See [monitoring scope](instructions.md#dokploy-infrastructure-is-ignored-by-default). | +| `WUD_WATCHER_DOCKER_LOCAL_WATCHALL` | `false` | Set `true` to include stopped containers. | +| `WUD_WATCHER_DOCKER_LOCAL_CRON` | `0 * * * *` | Hourly scan in `TZ`; upstream adds up to 60 seconds of random jitter. | +| `WUD_LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, `error`, or `fatal`. | +| `TZ` | `UTC` | Time zone for cron and timestamps, for example `Europe/Paris`. | + +The exclusion matches container names, not image names: application Redis/PostgreSQL containers with other names remain monitored. To include Dokploy infrastructure, remove the entire exclusion line, save and redeploy, then select **Containers → Watch now**. Do not leave an empty `WUD_WATCHER_DOCKER_LOCAL_EXCLUDE=` entry: WUD rejects empty filter values. No Compose edit is needed. + +## Fixed service settings + +These values are set in Compose. Change them there only as part of a coordinated customization. + +| Variable | Value | Reason | +| --- | --- | --- | +| `WUD_WATCHER_DOCKER_LOCAL_HOST` | `wud-socket-proxy` | Keeps the Docker socket out of the WUD container. | +| `WUD_WATCHER_DOCKER_LOCAL_PORT` | `2375` | Private proxy port, never published. | +| `WUD_WATCHER_DOCKER_LOCAL_WATCHATSTART` | `true` | Enables startup discovery when the stored container list is empty. For a populated store, use the watch action or scheduled scan. | +| `WUD_SERVER_PORT` | `3000` | Matches the Dokploy domain and health check. | +| `WUD_SERVER_TLS_ENABLED` | `false` | Traefik handles public HTTPS. | +| `WUD_STORE_PATH` | `/store` | Persistent volume target. | +| `WUD_STORE_FILE` | `wud.sqlite` | SQLite database filename. | +| `WUD_LOG_FORMAT` | `json` | Structured logs and direct Node process startup. | + +The socket proxy permits `CONTAINERS=1`, `IMAGES=1`, `EVENTS=1`, `PING=1`, and `VERSION=1`. `POST=0` blocks write methods; other API sections retain their denied defaults. These are fixed proxy service settings, not WUD environment variables. See [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy). + +## Optional application and provider settings + +WUD's provider names are dynamic. For example, `LOCAL` identifies this template's Docker watcher and `ALERTS` can identify a notification trigger. The following are optional; use the linked official schemas for the fields of the providers you actually enable. + +| Variable or family | Default / use | Documentation | +| --- | --- | --- | +| `WUD_WATCHER_DOCKER_LOCAL_INCLUDE` | Unset; optional container-name or `label:` allowlist. The template's default exclusion is documented above. Explicit `wud.watch` labels take precedence over both filters. | [Docker watcher](https://getwud.app/docs/configuration/watchers/) | +| `WUD_WATCHER_DOCKER_LOCAL_WATCHEVENTS` | `true`; follows lifecycle events. | [Docker watcher](https://getwud.app/docs/configuration/watchers/) | +| `WUD_WATCHER_DOCKER_LOCAL_JITTER` | `60000` milliseconds; random scan delay. | [Docker watcher](https://getwud.app/docs/configuration/watchers/) | +| `WUD_REGISTRY_{TYPE}_{NAME}_*` | Optional private-registry credentials and registry-specific options. Public images can use anonymous access, subject to rate limits. | [Registries](https://getwud.app/docs/configuration/registries/) | +| `WUD_TRIGGER_{TYPE}_{NAME}_*` | Optional notification provider settings. Required credentials depend on the chosen provider. No triggers are configured by default. | [Triggers](https://getwud.app/docs/configuration/triggers/) | +| `WUD_AUTH_OIDC_{NAME}_*` | Optional SSO provider settings; issuer/client credentials and group mapping depend on the provider. | [OIDC](https://getwud.app/docs/configuration/authentications/oidc/) | +| `WUD_PUBLIC_URL` | Inferred from the request; set to your canonical HTTPS URL if needed for links or SSO. | [Authentication](https://getwud.app/docs/configuration/authentications/) | +| `WUD_SERVER_FEATURE_DELETE` | Upstream `true`; allows deleting a tracked record from WUD, not deleting its Docker container. | [Server](https://getwud.app/docs/configuration/server/) | +| `WUD_SERVER_CORS_ENABLED` | `false`; keep disabled for the normal same-origin dashboard. | [Server](https://getwud.app/docs/configuration/server/) | +| `WUD_SERVER_BASEPATH` | `/`; use a dedicated Dokploy domain with this template. | [Server](https://getwud.app/docs/configuration/server/) | +| `WUD_SERVER_ENABLED` | `true`; leave enabled for the dashboard and health check. | [Server](https://getwud.app/docs/configuration/server/) | +| `WUD_RUN_MODE` | `server`; leave unchanged. `oneshot` is a separate CLI deployment mode. | [Configuration](https://getwud.app/docs/configuration/) | +| `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` | Unset; optional outbound proxy. Include `wud-socket-proxy,localhost,127.0.0.1` in `NO_PROXY` when enabling a proxy. | [Proxy settings](https://getwud.app/docs/configuration/#proxy--tls-configuration) | +| `NODE_EXTRA_CA_CERTS` | Optional path to a mounted PEM CA bundle for private registries/proxies. | [Proxy settings](https://getwud.app/docs/configuration/#proxy--tls-configuration) | + +WUD supports `WUD_*__FILE` secret references. Mount a readable secret file and point the corresponding variable at its container path; adding a path alone does not create the file. The file value is read and trimmed at startup. This is an advanced alternative to storing a value directly in Dokploy's Environment tab. + +Per-container `wud.*` labels are not environment variables. Configure tag inclusion/exclusion and digest tracking on each monitored application; see the [labels guide](https://getwud.app/docs/configuration/watchers/labels/). In Compose, escape a literal dollar sign as `$$` where interpolation would otherwise occur. No `PUID`/`PGID` or database credentials are needed by the official WUD image. diff --git a/blueprints/wud/instructions.md b/blueprints/wud/instructions.md new file mode 100644 index 000000000..b726d57b0 --- /dev/null +++ b/blueprints/wud/instructions.md @@ -0,0 +1,72 @@ +# WUD (What's Up Docker?) + +WUD watches Docker containers, checks their image registries for newer versions, and displays available updates in a web dashboard. This template runs WUD 9.5.0 with persistent SQLite storage and a Docker socket proxy that permits monitoring requests and blocks Docker writes. Notifications are optional; automatic container updates are not configured. + +Official documentation: [Quick start](https://getwud.app/docs/quickstart/), [configuration](https://getwud.app/docs/configuration/), [Docker watcher](https://getwud.app/docs/configuration/watchers/), and [source code](https://github.com/getwud/wud). + +## Deploy in Dokploy + +1. Import the template. Dokploy generates `WUD_AUTH_ADMIN_PASSWORD`; the username defaults to `admin`. Keep the generated password private, or replace it with your own strong value. +2. Enable **Isolated Deployment** for this Compose service before deploying. Dokploy should place WUD and its socket proxy on the stack's private network. Do not add a domain or publish a port for `wud-socket-proxy`. +3. Set your domain to service `wud`, port `3000`, and enable HTTPS with HTTP redirection in Dokploy. Traefik terminates TLS; WUD uses HTTP inside the stack. +4. Deploy and wait for both services to become healthy. Open the domain and sign in using the values from the Environment tab. +5. Inspect the Containers page. The local watcher monitors running containers on the Docker host where this stack is deployed, excluding Dokploy infrastructure by default as described below. Registry checks run hourly, with up to 60 seconds of jitter. Use the UI's watch action when you need an immediate scan. + +No external database, registry account, notification service, or password-hashing command is required. Public registries work anonymously within their rate limits. Large hosts or frequent scans may need registry credentials. + +## Monitoring scope and Docker access + +### Dokploy infrastructure is ignored by default + +The template adds this editable filter to Dokploy's Environment tab: + +```dotenv +WUD_WATCHER_DOCKER_LOCAL_EXCLUDE=^dokploy([._-]|$) +``` + +It skips version tracking for containers named `dokploy` or beginning with `dokploy-`, `dokploy_`, or `dokploy.`. This covers standard Dokploy infrastructure names such as `dokploy-traefik`, `dokploy-postgres`, and `dokploy-redis`, including their usual Compose/Swarm suffixes. Dokploy upgrades remain managed through Dokploy. + +This is a **container-name** filter. Your application databases and other workloads with different names are still monitored; an application deliberately named with a matching `dokploy` prefix is also excluded. Customize the expression if your infrastructure uses different names. + +To track Dokploy infrastructure too, remove the entire `WUD_WATCHER_DOCKER_LOCAL_EXCLUDE` line from Dokploy's Environment tab, save, and redeploy. Do not leave an empty `WUD_WATCHER_DOCKER_LOCAL_EXCLUDE=` entry: WUD rejects empty filter values. Then select **Containers → Watch now** to run a fresh scan. To include just one excluded container, add `wud.watch: "true"` to that service's Docker labels and recreate it; this explicit label takes precedence over the exclusion filter. + +### Other monitoring controls + +`WUD_WATCHER_DOCKER_LOCAL_WATCHBYDEFAULT=true` discovers running containers that pass the filter. Set it to `false` for opt-in monitoring, then add `wud.watch: "true"` to each monitored service's Docker labels. A `wud.watch: "false"` label excludes a container. These labels belong on the services being monitored, not in WUD's Environment tab. + +Stopped containers are excluded by default. Set `WUD_WATCHER_DOCKER_LOCAL_WATCHALL=true` to include them. Optional include/exclude patterns, tag filters, digest tracking, and release links are covered in [environment.md](environment.md) and the [container label guide](https://getwud.app/docs/configuration/watchers/labels/). + +Only the proxy mounts `/var/run/docker.sock`. This system socket is the intentional host bind mount; it must already exist on a normal Linux Docker host. WUD connects to `wud-socket-proxy:2375`, with container/image reads and Docker events allowed. `POST=0` blocks write methods, including container start/stop, exec, image pulls, and deletion. A read-only socket mount alone would not block those Docker API operations. + +Container inspection can expose host workload metadata and environment values, so keep proxy access within the isolated stack. Do not expose port 2375 or broaden proxy permissions for normal monitoring. This template observes one Docker host; it does not automatically discover remote Dokploy servers or configure a Swarm/Kubernetes watcher. + +## Notifications and updates + +Add notification variables in Dokploy's Environment tab, then redeploy. WUD loads optional values from `.env`. For example, an optional Ntfy trigger needs: + +```dotenv +WUD_TRIGGER_NTFY_ALERTS_URL=https://ntfy.sh +WUD_TRIGGER_NTFY_ALERTS_TOPIC=your-private-topic +``` + +Use your server's authentication settings where needed. See the official [trigger documentation](https://getwud.app/docs/configuration/triggers/) for supported providers and required credentials. No notification provider is required to install WUD. + +Watcher, registry, and trigger settings are configured through the environment; their configuration pages in WUD display the effective settings. User accounts, preferences, and API tokens can be managed in the app. + +The template does not configure Docker or Docker Compose update triggers. Keep changes to Dokploy-managed applications in Dokploy so its deployment configuration stays authoritative. Adding an updater would require a separate, deliberate change to the proxy permissions and deployment design. Triggers can run automatically when configured; do not add a Docker updater merely to test notifications. + +## Passwords, persistence, and backups + +WUD reapplies `WUD_AUTH_ADMIN_USER` and `WUD_AUTH_ADMIN_PASSWORD` on every startup, including an existing account. Rotate this administrator's password in Dokploy's Environment tab and redeploy. Changing only that account's password in WUD will be overwritten on the next restart. Additional users are stored in the database and can be managed in WUD. + +The `wud-data` volume stores `/store`, including `wud.sqlite`, its WAL files, users, token hashes, preferences, monitored containers, history, and snoozes. Keep one WUD instance per volume. Back up the complete volume while WUD is stopped, or use a SQLite-aware backup method. Copying only the live `wud.sqlite` file can miss recent writes in the WAL. Keep an off-server copy and protect it as sensitive data. + +The official image handles store creation and migrations. It runs as root; this template drops all Linux capabilities and prevents privilege escalation. Do not force a different UID without preparing and checking volume ownership. Back up before changing the pinned image version and review the [release notes](https://github.com/getwud/wud/releases). + +### HTTPS and health behavior in 9.5.0 + +Use HTTPS and HTTP redirection for regular access. WUD 9.5.0 sets an HttpOnly session cookie, but its upstream implementation does not add Secure or SameSite cookie attributes. This template does not modify the published application image. You may need to sign in again after a container recreation or upgrade because WUD derives its session secret from the version and network interface. + +GET `/health` is public HTTP liveness. It does not prove that Docker discovery or registry checks succeeded: also check the local watcher, discovered containers, and scan errors. The API and dashboard require authentication. + +See [environment.md](environment.md) for settings and [testing.md](testing.md) for verification steps. diff --git a/blueprints/wud/meta.json b/blueprints/wud/meta.json new file mode 100644 index 000000000..80cd54174 --- /dev/null +++ b/blueprints/wud/meta.json @@ -0,0 +1,13 @@ +{ + "id": "wud", + "name": "WUD (What's Up Docker?)", + "version": "9.5.0", + "description": "WUD monitors Docker containers for image updates and shows available versions in a web dashboard. This template includes administrator authentication, persistent storage, and a Docker socket proxy that blocks write requests.", + "logo": "wud.svg", + "links": { + "github": "https://github.com/getwud/wud", + "website": "https://getwud.app/", + "docs": "https://getwud.app/docs/quickstart/" + }, + "tags": ["monitoring", "docker", "devops"] +} diff --git a/blueprints/wud/template.toml b/blueprints/wud/template.toml new file mode 100644 index 000000000..e7375831a --- /dev/null +++ b/blueprints/wud/template.toml @@ -0,0 +1,20 @@ +[variables] +main_domain = "${domain}" +admin_password = "${hash:48}" + +[config] +env = [ + "WUD_AUTH_ADMIN_USER=admin", + "WUD_AUTH_ADMIN_PASSWORD=${admin_password}", + "WUD_WATCHER_DOCKER_LOCAL_WATCHBYDEFAULT=true", + "WUD_WATCHER_DOCKER_LOCAL_EXCLUDE=^dokploy([._-]|$)", + "WUD_WATCHER_DOCKER_LOCAL_WATCHALL=false", + "WUD_WATCHER_DOCKER_LOCAL_CRON=0 * * * *", + "WUD_LOG_LEVEL=info", + "TZ=UTC" +] + +[[config.domains]] +serviceName = "wud" +port = 3000 +host = "${main_domain}" diff --git a/blueprints/wud/testing.md b/blueprints/wud/testing.md new file mode 100644 index 000000000..594b919f8 --- /dev/null +++ b/blueprints/wud/testing.md @@ -0,0 +1,15 @@ +# Testing WUD + +Use a disposable Dokploy instance and synthetic workloads. Do not apply update triggers to production containers during template verification. + +1. Run the repository's metadata, Compose, and TOML validators and build the catalog. Confirm that only the new blueprint files changed. +2. Import the Base64 configuration from the PR preview into a fresh Dokploy Compose service. Enable isolated deployment, retain the generated credentials, and deploy without registry or notification credentials. +3. Check both service health states. Anonymous GET `/health` should return 200, while `/api/containers` must return 401 and the dashboard must require login. Verify wrong-password rejection and successful login. +4. Inspect the effective local watcher: host `wud-socket-proxy`, port `2375`, exclusion `^dokploy([._-]|$)`. Scan with disposable workloads named like Dokploy infrastructure and an ordinary application: matching `dokploy` names should be absent while the application remains monitored. Remove the exclusion from Environment, redeploy, and click **Watch now**: the previously excluded workloads should appear. Also verify that `wud.watch=true` can opt in a matching container. Health alone does not verify scanning. Confirm that no update triggers are registered. +5. From inside the isolated stack, check the proxy: GET `/_ping`, `/containers/json`, and `/images/json` should work. Requests to disabled groups such as `/volumes` and Docker write requests should return 403. Use a disposable nonexistent container ID for a stop request, then verify real containers are unchanged. The WUD container must have no Docker socket mount. +6. Change a preference, create a read-only test user, issue a read-scoped API token, and snooze a discovered container. Recreate WUD through Dokploy while preserving its volume. Sign in again if necessary and verify the user, preference, token, snooze, and stored container records survive. +7. In the disposable installation, change the bootstrap administrator's password only in WUD, restart, and verify that the Environment value is reapplied. Then rotate it through Dokploy and confirm the new value works. Additional users must remain intact. +8. Exercise opt-in monitoring with `WATCHBYDEFAULT=false` and a `wud.watch=true` workload. If testing notifications, use a local mock endpoint and assert its received payload rather than sending real messages. Keep Docker write access blocked throughout. +9. Enable HTTPS in Dokploy and verify login, API access, dashboard assets, and live events through Traefik. Check browser errors and server logs. Record the upstream cookie behavior described in [instructions.md](instructions.md); do not claim Secure/SameSite flags that the application does not set. + +Record image tags, Dokploy version, registry results, tested optional providers, and any limits in the PR. Use synthetic data in screenshots. Public certificate issuance and every external notification/registry provider require separate checks. Remove only the disposable test resources after collecting evidence. diff --git a/blueprints/wud/wud.svg b/blueprints/wud/wud.svg new file mode 100644 index 000000000..ca76e7a66 --- /dev/null +++ b/blueprints/wud/wud.svg @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +