From b519c78d135c2104cb7668e8437579e0cc31e0b0 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 28 Jan 2026 01:11:36 -0600 Subject: [PATCH] New Templates & Updates (#685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(librechat): add LibreChat blueprint with compose, toml, metadata, links and tags * fix: rename templates to template.toml * fix(librechat): rename api service to librechat in docker-compose.yml * Update blueprints/librechat/template.toml * Update blueprints/librechat/template.toml * fix(librechat): add version under [config] and remove stray [config.mounts] header * fix(librechat): remove predefined persistent volume mounts from template.toml * docs(librechat): add authentication reference link to docker-compose.yml * feat: add Rote template - Add Rote deployment template with frontend, backend, and PostgreSQL services - Configure domain routing for frontend (port 80) and backend (port 3000) - Set up automatic password generation and environment variables - Use latest image tag by default - Add logo and metadata to meta.json * fix: process meta.json to fix formatting and sorting * Update GitHub workflows to target 'canary' branch for meta validation * Update pnpm-lock.yaml to upgrade various dependencies, including '@codemirror/autocomplete', '@radix-ui/react-dialog', and React packages to their latest versions. This includes updates to '@types/react' and '@types/react-dom' for improved compatibility and performance. * Enhance GitHub workflows: add production deployment configuration and target 'canary' branch for pull requests. * Refactor GitHub workflow: comment out build preview steps for clarity and future modifications. * Remove unnecessary blank line in deploy-preview.yml for improved readability. * Refactor GitHub workflow: uncomment build preview steps for improved deployment process and clarity. * Update template.toml (#555) * Update template.toml * Update template.toml * Update template.toml * fix: change VITE_API_BASE to http:// for traefik.me compatibility * changed image from sknnr/enshrouded-dedicated-server to mornedhels/enshrouded-server for autoupdate and easier config * Add Openinary Template (#567) * feat: add Openinary template * feat: update Openinary configuration to support ALLOWED_ORIGIN and refactor domain variable * fix: correct DEFAULT_DOMAIN environment variable reference in docker-compose.yml (#562) * add rustfs template (#568) * feat: add pull request template for improved contribution guidelines * fix: update pull request template to clarify issue closing keywords * feat: add validation scripts and configuration for Docker Compose and template files - Introduced a GitHub Actions workflow to validate Docker Compose files and template.toml on pull requests. - Added helper functions for generating random values and processing variables in templates. - Implemented validation scripts for checking the structure, syntax, and best practices of Docker Compose and template files. - Created necessary TypeScript types and configuration files for the build scripts. * Add Passbolt template blueprint to Dokploy templates (#376) * feat(templates): add Passbolt blueprint for Dokploy - Add docker-compose.yml defining services for Passbolt and MariaDB - Create template.toml with configurable domain, email, and database credentials - Add meta.json with metadata, tags, and link to logo * fix(meta): sort meta.json entries * fix: passbolt template had several issues that broke deployment - env variables were using old array format, changed to new table format - mariadb healthcheck was broken (wrong command for mariadb 11) - missing volume mounts for gpg keys, jwt tokens, and database - setup instructions weren't visible to users, moved to docker-compose - email config had circular references causing warnings - tested admin user creation and confirmed working everything works now, fully tested * Update blueprints/passbolt/template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: Add Kokoro TTS FastAPI template (#353) (#403) * feat: Add Kokoro TTS FastAPI template (#353) - Add CPU-optimized docker-compose.yml with source build - Add GPU-optimized docker-compose-gpu.yml for NVIDIA support - Add comprehensive template.toml with OpenAI-compatible API docs - Add kokoro-tts.svg logo and meta.json entry - Support streaming audio, timestamps, and multi-language TTS - Resolves #353 * updated the meta.json for the build errors * removed the docker-compose-gpu.yml file * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * chore: remove package-lock.json file from the app directory * chore: update Tolgee to latest version and fix SMTP config typo (#432) * chore: update Tolgee to latest version and fix SMTP config typo * Update docker-compose.yml * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix: improve Docker Compose validation workflow to handle subshell issues - Converted the handling of COMPOSE_FILES from a pipe to an array to ensure error propagation in the parent shell. - Updated the loop to iterate over the array for better reliability in the validation process. * refactor: enhance Docker Compose validation workflow to improve error handling - Replaced the pipe with an array to handle directory names, ensuring that errors within the loop propagate correctly to the parent shell. - Updated the loop structure for better reliability in processing the directories. * Feat: Add parseable (#460) * Add parseable * Update docker-compose.yml * Update docker-compose.yml * Update blueprints/parseable/template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * feat: add ChirpStack LoRaWAN Network Server template (#486) * feat: add ChirpStack LoRaWAN Network Server template Add complete ChirpStack v4 template with: - Main ChirpStack server with web UI - UDP and Basics Station gateway bridges - REST API interface - PostgreSQL database with PostGIS extensions - Redis cache - Mosquitto MQTT broker Default configuration for EU868 region with secure random credentials. Supports all LoRaWAN frequency bands globally. * fix(chirpstack): use original configurations from chirpstack-docker repo Update template.toml to use exact configuration files from the chirpstack-docker repository instead of simplified versions: - Use original chirpstack.toml with all 15 enabled regions - Use original gateway bridge configuration with documentation links - Use complete Basics Station EU868 config with frequency plans - Keep original Mosquitto and PostgreSQL initialization scripts Template size increased from 131 to 219 lines (4.7KB) to include comprehensive default configurations that match the official setup. * feat: add all 38 region configuration files * fix(chirpstack): add volume mounts to expose config files to containers * fix(chirpstack): remove read-only flag * fix(chirpstack): correct file paths for configuration mounts in docker-compose and template files * fix: update volume paths to be on correct directory level * fix: configure template for dokploy-network with proper DNS resolution - Add dokploy-network configuration to docker-compose.yml - Replace environment variable placeholders with actual service hostnames - Change PostgreSQL DSN from $POSTGRESQL_HOST to postgres - Change Redis server from $REDIS_HOST to redis - Replace $MQTT_BROKER_HOST with mosquitto in all 39 region configurations These changes ensure Docker DNS resolution works correctly by: - Using dokploy-network (overlay) instead of bridge network - Using service names directly in TOML config files (TOML doesn't expand env vars) - Enabling proper service discovery between containers This resolves DNS resolution failures that caused ChirpStack to fail connecting to PostgreSQL and MQTT services during deployment. * fix: add missing network configurations for all services in docker-compose * feat: add internal services to config.domains for proper network configuration * Update docker-compose.yml * fix: enhance domain validation in template validator - Updated the TemplateValidator to ensure that if the 'host' field is provided, it must be a valid string. - Added comments to clarify that 'host' is optional for internal services. * refactor: remove redundant host validation in template validator - Removed the validation for the 'host' field in the TemplateValidator, as it is optional for internal services and does not require a type check if not provided. * refactor: remove internal service domain configurations from template - Eliminated the domain configurations for internal services (Postgres, Redis, Mosquitto) from the template.toml file, streamlining the configuration for better clarity and maintainability. --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * Update section title from 'Suggestions' to 'Requirements' * Feat : Add MCSManager template support (#521) (#522) * feat: Add MCSManager template support (#521) * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: Add MediaCMS template (#524) * Feat : Add Quant-Ux template -#173 (#525) * Feat : Add Quant-Ux template -#173 * Remove extra newline in docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix(rustdesk): use explicit ports, use port 21118 on hbbs instead of hbbr (#526) * fix: use explicit ports, use port 21118 on hbbs instead of hbbr * fix: whitespace character in rustdesk * feat: Add anytype template (#527) * add anytype template * sort * Update name field for Anytype in meta.json * Update meta.json * Update docker-compose.yml * Update blueprints/anytype/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * refactor: remove dokploy-network configurations from multiple docker-compose files - Removed the external dokploy-network configuration from various services' docker-compose.yml files to streamline network management. - This change simplifies the setup and ensures consistency across blueprints. * chore: upgrade Infisical from v0.90.1 to v0.135.0 (#529) * chore: upgrade Infisical from v0.90.1 to v0.135.0 * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix: update pull request template link for clarity - Changed the link in the pull request template from 'general suggestions' to 'general requirements' to better reflect the content and ensure users follow the correct guidelines when creating templates. * chore: add section for screenshots or videos in pull request template - Introduced a new section in the pull request template to encourage contributors to include screenshots or videos, enhancing the clarity and context of their submissions. * Feat : Add MuleSoft ESB Runtime Template (#498) * added the mulesoft esb template * updated the compose and the meta.json * feat(mulesoft-esb): update image and add dynamic env configuration - Updated image to hari1367709/mule-esb:latest - Added dynamic HTTP_PORT for runtime port configuration - Added MULE_VERSION environment variable for Mule ESB version selection * updated the meta.json to use the version as latest * added a comment line to the template file * updated the mule runtime image * fix(mulesoft-esb): update ports configuration to follow guidelines * updated the port to use the env(HTTP_PORT) * Update docker-compose.yml * Update docker-compose.yml * Update blueprints/mulesoft-esb/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat(blueprint): update trmnl-byos-laravel template (#533) * feat(blueprint): update trmnl-byos-laravel template * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat(blueprint): peerdb template (#579) * feat(blueprint): initial attempt at peerdb template * fix: entrypoint and healthcheck * fix: entrypoint * fix: temporarily remove network * fix: temporal port * chore: remove 36987 for minio * fix: remove peerdb 9900 port exposure * fix: port for console * fix: minio env fix * fix: expose peerdb and minio to dokploy network * fix(peerdb): add defaults * fix: remove extra hosts * fix: remove network entries * fix: use consistent environment variables * feat: add Bluesky PDS template (#542) * feat: Bluesky PDS template * chore: add bluesky pds svg * chore: metadata for bluesky pds * yaml > yml * pnpm lock * fix: correct rotation key config * fix volumes * fix: volumes in the pds compose * define volumes in compose * fix: 32 bit rotation key * create pds.env correctly * some extra fixes * more extra fixes * a blank line * update pnpm lock * Add dokploy-prom-monitoring-extension template with comprehensive tests and documentation (#548) * Add dokploy-prom-monitoring-extension template with comprehensive tests and documentation * Fix METRICS_CONFIG environment variable: use single-line JSON format * Fix template.toml: use correct [config.env] syntax for environment variables * Fix docker-compose.yml: add env_file reference to load environment variables * Delete blueprints/dokploy-prom-monitoring-extension/README.md * Delete test-dokploy-prom-monitoring-extension.sh --------- Co-authored-by: Sanjeevi Subramani Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: improve RustDesk template configuration (#571) * feat: improve RustDesk template configuration - Add comprehensive environment variables for RustDesk server - Add RELAY_HOST, API_SERVER, ID_SERVER, and ENCRYPTION_KEY variables - Follow Dokploy best practices (no container_name, proper port format) - Use restart: unless-stopped policy - Add encryption key generation with password helper * fix: use explicit port mapping for RustDesk services RustDesk requires explicit port bindings (host:container format) to function properly. The service uses specific ports for: - 21115-21116 (TCP/UDP): hbbs service for ID and NAT traversal - 21117-21119 (TCP): hbbr relay service Without explicit port mapping, RustDesk clients cannot establish connections to the server. This is an exception to Dokploy's general port guidelines due to RustDesk's specific networking requirements. --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: add Mumble voice chat server template (#572) * feat: add Mumble voice chat server template - Add Mumble VoIP server blueprint with docker-compose.yml - Configure environment variables for superuser password, welcome text, and max users - Add template.toml with auto-generated secure password - Follow Dokploy best practices (no container_name, proper port format) - Add Mumble metadata to meta.json with proper tags - Support for TCP and UDP on port 64738 * Update template.toml * fix: correct JSON formatting in meta.json for Mumble template entry --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * fix: update WireGuard Easy template for proper functionality (#573) * fix: update WireGuard Easy template for proper functionality - Changed to named volume (etc_wireguard) instead of host path mount - Added explicit port mappings (51820:51820/udp, 51821:51821/tcp) required for WireGuard - Updated environment variables to use correct WG_HOST and PASSWORD format - Added all required WireGuard environment variables: - WG_PORT, PORT, WG_MTU, WG_DEFAULT_DNS, WG_ALLOWED_IPS - WG_POST_UP/WG_POST_DOWN for iptables rules - Added NET_RAW capability for proper network operations - Simplified template.toml to use WIREGUARD_HOST and WIREGUARD_PASSWORD - Removed explicit networks config to enable Dokploy's isolated deployment - Template now works with Dokploy's automatic network isolation This configuration has been tested and confirmed working with isolated deployment enabled. * Update template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * add: restart policy to MinIO service (#576) restart: unless-stopped is a Docker restart policy that automatically restarts a container if it stops due to an error or Docker daemon restart * Updating copilot instructions.md and AGENTS.md (#452) * docs: add copilot instructions for project development Added comprehensive documentation file (.github/copilot-instructions.md) that provides detailed guidance on the Dokploy Open Source Templates project structure, development workflow, and conventions. The document covers the project overview, key files and directories, development workflow for adding/updating templates, local development setup, CI/CD processes, and established conventions and patterns. This documentation will help onboard new contributors and ensure consistent development practices across the project. * docs(copilot-instructions): enhance project overview and development guide Updated copilot instructions to reflect expanded app capabilities (200+ apps, TypeScript integration, Fuse.js search) and detailed processes for templates, local dev, and CI/CD for improved clarity and accuracy. * refactor: remove frontend development instructions from AGENTS.md and update contributing guidelines - Removed frontend development commands from AGENTS.md to streamline the document. - Updated CONTRIBUTING.md by removing the recommendation to set `restart: unless-stopped` for services. --------- Co-authored-by: Mauricio Siu * Add trailbase template (#590) * Add trailbase * Add comment on mounting local directory * Update meta.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: openpanel and migrate to v2 (#594) * refactor: update docker-compose.yml to include version and restart policy (#599) * feat(blueprint): mage ai template (#601) * feat(blueprint): template for mage-ai * fix: add healthcheck for mage-ai * docs: update copilot instructions to emphasize version pinning in docker-compose.yml Clarified the importance of pinning image versions in docker-compose.yml and explicitly stated to avoid using the `latest` tag to prevent potential issues with template functionality when upstream images change. * refactor: enhance Docker Compose validation workflow Updated the GitHub Actions workflow for validating Docker Compose files by streamlining the detection of changed blueprints and improving the validation process. Removed redundant steps and consolidated the validation of docker-compose.yml and template.toml files into a more efficient structure. Added clearer output messages for validation results and ensured that best practices are checked for each blueprint. This refactor aims to improve maintainability and clarity in the CI/CD process. * fix: update zitadel default instance features (#544) * Add Reactive Resume template (#603) * Initial plan * Add Reactive Resume template with docker-compose, template.toml, and meta.json entry Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Fix MinIO storage path alignment between template.toml and docker-compose.yml Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Update docker-compose.yml --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * [WIP] Fix mounted prometheus.yml file not working (#605) * Initial plan * Fix Prometheus mounted prometheus.yml file by adding volume mount Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Update blueprints/prometheus/template.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * docs: update copilot instructions to enforce open source requirement Added a clear requirement stating that all services must be open source and only applications with open-source licenses (e.g., MIT, Apache, GPL, AGPL) are allowed. This update aims to ensure compliance and maintain the integrity of the project. * Fix Cap.so template MinIO deployment failure (#604) * Initial plan * Fix Cap.so MinIO image to use official quay.io image Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Fix MinIO healthcheck to use curl instead of mc Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Update .gitignore --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Fix Discourse template manifest error - update to bitnamilegacy/discourse:3.5.0 (#606) * Initial plan * Fix Discourse template by updating to bitnamilegacy/discourse:3.5.0 Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Remove pull request trigger from deploy-preview workflow * Enhance copilot instructions by adding verification steps for Docker images and clarifying URL variable usage. Emphasize the importance of verifying image existence before committing to prevent deployment failures. * Add Easy!Appointments template (#608) * Initial plan * Add Easyappointments template with docker-compose, template.toml, and meta.json entry Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Update blueprints/easyappointments/template.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update template.toml --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add Dolibarr ERP & CRM template (#610) * Initial plan * Add Dolibarr ERP & CRM template Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> * Update template.toml * Update blueprints/dolibarr/docker-compose.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: add Syncthing Template (#636) * Adiciona template do Syncthing com arquivos de configuração e ícone * Remove versão do Docker Compose do template do Syncthing * Update Umami to version v2.20.2 (#641) * Feat : Add InstantDB template (#224) (#530) * feat: add InstantDB template * added the server service also * removed the external network * Update docker-compose.yml * Update blueprints/instantdb/docker-compose.yml * Update blueprints/instantdb/docker-compose.yml * Update docker-compose.yml * Update blueprints/instantdb/template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * [New Template] Added Lavalink template for dokploy (#535) * Add initial Lavalink configuration template * Added lavalink.svg logo * Create docker-compose.yml for Lavalink setup Add Docker Compose configuration for Lavalink service. * Modify healthcheck interval in docker-compose.yml Increased healthcheck interval from 10s to 100s. * Add Lavalink metadata to meta.json Added metadata for Lavalink audio sending node. * Update server_port variable to use ${port} * Change server port variable to randomPort * Change port variable to randomPort in template.toml * Change port variable in template.toml * Update server port in template.toml * Fix formatting in template.toml for server config * style: Reformat lavalink entry in meta.json. * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * chore(blueprint): update Appwrite services to v1.8 (#383) * Update Appwrite version docker images and environment variables * Add separate domains for functions and sites, update environment variables, replace insecure default passwords * Update Appwrite version from 1.6.1 to 1.7.4 in `meta.json` * Fix envs and update domains * Add missing volume mounts for sites * Fix config file syntax * Update runtimes network configuration in template.toml * Update Appwrite to 1.8 * Update appwrite image version to 1.8.0 * Add logging configuration to docker-compose * New Templates (#586) * feat(librechat): add LibreChat blueprint with compose, toml, metadata, links and tags * fix: rename templates to template.toml * fix(librechat): rename api service to librechat in docker-compose.yml * Update blueprints/librechat/template.toml * Update blueprints/librechat/template.toml * fix(librechat): add version under [config] and remove stray [config.mounts] header * fix(librechat): remove predefined persistent volume mounts from template.toml * docs(librechat): add authentication reference link to docker-compose.yml * feat: add Rote template - Add Rote deployment template with frontend, backend, and PostgreSQL services - Configure domain routing for frontend (port 80) and backend (port 3000) - Set up automatic password generation and environment variables - Use latest image tag by default - Add logo and metadata to meta.json * fix: process meta.json to fix formatting and sorting * Update GitHub workflows to target 'canary' branch for meta validation * Update pnpm-lock.yaml to upgrade various dependencies, including '@codemirror/autocomplete', '@radix-ui/react-dialog', and React packages to their latest versions. This includes updates to '@types/react' and '@types/react-dom' for improved compatibility and performance. * Enhance GitHub workflows: add production deployment configuration and target 'canary' branch for pull requests. * Refactor GitHub workflow: comment out build preview steps for clarity and future modifications. * Remove unnecessary blank line in deploy-preview.yml for improved readability. * Refactor GitHub workflow: uncomment build preview steps for improved deployment process and clarity. * Update template.toml (#555) * Update template.toml * Update template.toml * Update template.toml * fix: change VITE_API_BASE to http:// for traefik.me compatibility * changed image from sknnr/enshrouded-dedicated-server to mornedhels/enshrouded-server for autoupdate and easier config * Add Openinary Template (#567) * feat: add Openinary template * feat: update Openinary configuration to support ALLOWED_ORIGIN and refactor domain variable * fix: correct DEFAULT_DOMAIN environment variable reference in docker-compose.yml (#562) * add rustfs template (#568) * feat: add pull request template for improved contribution guidelines * fix: update pull request template to clarify issue closing keywords * feat: add validation scripts and configuration for Docker Compose and template files - Introduced a GitHub Actions workflow to validate Docker Compose files and template.toml on pull requests. - Added helper functions for generating random values and processing variables in templates. - Implemented validation scripts for checking the structure, syntax, and best practices of Docker Compose and template files. - Created necessary TypeScript types and configuration files for the build scripts. * Add Passbolt template blueprint to Dokploy templates (#376) * feat(templates): add Passbolt blueprint for Dokploy - Add docker-compose.yml defining services for Passbolt and MariaDB - Create template.toml with configurable domain, email, and database credentials - Add meta.json with metadata, tags, and link to logo * fix(meta): sort meta.json entries * fix: passbolt template had several issues that broke deployment - env variables were using old array format, changed to new table format - mariadb healthcheck was broken (wrong command for mariadb 11) - missing volume mounts for gpg keys, jwt tokens, and database - setup instructions weren't visible to users, moved to docker-compose - email config had circular references causing warnings - tested admin user creation and confirmed working everything works now, fully tested * Update blueprints/passbolt/template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: Add Kokoro TTS FastAPI template (#353) (#403) * feat: Add Kokoro TTS FastAPI template (#353) - Add CPU-optimized docker-compose.yml with source build - Add GPU-optimized docker-compose-gpu.yml for NVIDIA support - Add comprehensive template.toml with OpenAI-compatible API docs - Add kokoro-tts.svg logo and meta.json entry - Support streaming audio, timestamps, and multi-language TTS - Resolves #353 * updated the meta.json for the build errors * removed the docker-compose-gpu.yml file * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * chore: remove package-lock.json file from the app directory * chore: update Tolgee to latest version and fix SMTP config typo (#432) * chore: update Tolgee to latest version and fix SMTP config typo * Update docker-compose.yml * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix: improve Docker Compose validation workflow to handle subshell issues - Converted the handling of COMPOSE_FILES from a pipe to an array to ensure error propagation in the parent shell. - Updated the loop to iterate over the array for better reliability in the validation process. * refactor: enhance Docker Compose validation workflow to improve error handling - Replaced the pipe with an array to handle directory names, ensuring that errors within the loop propagate correctly to the parent shell. - Updated the loop structure for better reliability in processing the directories. * Feat: Add parseable (#460) * Add parseable * Update docker-compose.yml * Update docker-compose.yml * Update blueprints/parseable/template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * feat: add ChirpStack LoRaWAN Network Server template (#486) * feat: add ChirpStack LoRaWAN Network Server template Add complete ChirpStack v4 template with: - Main ChirpStack server with web UI - UDP and Basics Station gateway bridges - REST API interface - PostgreSQL database with PostGIS extensions - Redis cache - Mosquitto MQTT broker Default configuration for EU868 region with secure random credentials. Supports all LoRaWAN frequency bands globally. * fix(chirpstack): use original configurations from chirpstack-docker repo Update template.toml to use exact configuration files from the chirpstack-docker repository instead of simplified versions: - Use original chirpstack.toml with all 15 enabled regions - Use original gateway bridge configuration with documentation links - Use complete Basics Station EU868 config with frequency plans - Keep original Mosquitto and PostgreSQL initialization scripts Template size increased from 131 to 219 lines (4.7KB) to include comprehensive default configurations that match the official setup. * feat: add all 38 region configuration files * fix(chirpstack): add volume mounts to expose config files to containers * fix(chirpstack): remove read-only flag * fix(chirpstack): correct file paths for configuration mounts in docker-compose and template files * fix: update volume paths to be on correct directory level * fix: configure template for dokploy-network with proper DNS resolution - Add dokploy-network configuration to docker-compose.yml - Replace environment variable placeholders with actual service hostnames - Change PostgreSQL DSN from $POSTGRESQL_HOST to postgres - Change Redis server from $REDIS_HOST to redis - Replace $MQTT_BROKER_HOST with mosquitto in all 39 region configurations These changes ensure Docker DNS resolution works correctly by: - Using dokploy-network (overlay) instead of bridge network - Using service names directly in TOML config files (TOML doesn't expand env vars) - Enabling proper service discovery between containers This resolves DNS resolution failures that caused ChirpStack to fail connecting to PostgreSQL and MQTT services during deployment. * fix: add missing network configurations for all services in docker-compose * feat: add internal services to config.domains for proper network configuration * Update docker-compose.yml * fix: enhance domain validation in template validator - Updated the TemplateValidator to ensure that if the 'host' field is provided, it must be a valid string. - Added comments to clarify that 'host' is optional for internal services. * refactor: remove redundant host validation in template validator - Removed the validation for the 'host' field in the TemplateValidator, as it is optional for internal services and does not require a type check if not provided. * refactor: remove internal service domain configurations from template - Eliminated the domain configurations for internal services (Postgres, Redis, Mosquitto) from the template.toml file, streamlining the configuration for better clarity and maintainability. --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * Update section title from 'Suggestions' to 'Requirements' * Feat : Add MCSManager template support (#521) (#522) * feat: Add MCSManager template support (#521) * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: Add MediaCMS template (#524) * Feat : Add Quant-Ux template -#173 (#525) * Feat : Add Quant-Ux template -#173 * Remove extra newline in docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml * Update blueprints/quant-ux/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix(rustdesk): use explicit ports, use port 21118 on hbbs instead of hbbr (#526) * fix: use explicit ports, use port 21118 on hbbs instead of hbbr * fix: whitespace character in rustdesk * feat: Add anytype template (#527) * add anytype template * sort * Update name field for Anytype in meta.json * Update meta.json * Update docker-compose.yml * Update blueprints/anytype/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * refactor: remove dokploy-network configurations from multiple docker-compose files - Removed the external dokploy-network configuration from various services' docker-compose.yml files to streamline network management. - This change simplifies the setup and ensures consistency across blueprints. * chore: upgrade Infisical from v0.90.1 to v0.135.0 (#529) * chore: upgrade Infisical from v0.90.1 to v0.135.0 * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * fix: update pull request template link for clarity - Changed the link in the pull request template from 'general suggestions' to 'general requirements' to better reflect the content and ensure users follow the correct guidelines when creating templates. * chore: add section for screenshots or videos in pull request template - Introduced a new section in the pull request template to encourage contributors to include screenshots or videos, enhancing the clarity and context of their submissions. * Feat : Add MuleSoft ESB Runtime Template (#498) * added the mulesoft esb template * updated the compose and the meta.json * feat(mulesoft-esb): update image and add dynamic env configuration - Updated image to hari1367709/mule-esb:latest - Added dynamic HTTP_PORT for runtime port configuration - Added MULE_VERSION environment variable for Mule ESB version selection * updated the meta.json to use the version as latest * added a comment line to the template file * updated the mule runtime image * fix(mulesoft-esb): update ports configuration to follow guidelines * updated the port to use the env(HTTP_PORT) * Update docker-compose.yml * Update docker-compose.yml * Update blueprints/mulesoft-esb/docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat(blueprint): update trmnl-byos-laravel template (#533) * feat(blueprint): update trmnl-byos-laravel template * Update docker-compose.yml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat(blueprint): peerdb template (#579) * feat(blueprint): initial attempt at peerdb template * fix: entrypoint and healthcheck * fix: entrypoint * fix: temporarily remove network * fix: temporal port * chore: remove 36987 for minio * fix: remove peerdb 9900 port exposure * fix: port for console * fix: minio env fix * fix: expose peerdb and minio to dokploy network * fix(peerdb): add defaults * fix: remove extra hosts * fix: remove network entries * fix: use consistent environment variables * feat: add Bluesky PDS template (#542) * feat: Bluesky PDS template * chore: add bluesky pds svg * chore: metadata for bluesky pds * yaml > yml * pnpm lock * fix: correct rotation key config * fix volumes * fix: volumes in the pds compose * define volumes in compose * fix: 32 bit rotation key * create pds.env correctly * some extra fixes * more extra fixes * a blank line * update pnpm lock * Add dokploy-prom-monitoring-extension template with comprehensive tests and documentation (#548) * Add dokploy-prom-monitoring-extension template with comprehensive tests and documentation * Fix METRICS_CONFIG environment variable: use single-line JSON format * Fix template.toml: use correct [config.env] syntax for environment variables * Fix docker-compose.yml: add env_file reference to load environment variables * Delete blueprints/dokploy-prom-monitoring-extension/README.md * Delete test-dokploy-prom-monitoring-extension.sh --------- Co-authored-by: Sanjeevi Subramani Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: improve RustDesk template configuration (#571) * feat: improve RustDesk template configuration - Add comprehensive environment variables for RustDesk server - Add RELAY_HOST, API_SERVER, ID_SERVER, and ENCRYPTION_KEY variables - Follow Dokploy best practices (no container_name, proper port format) - Use restart: unless-stopped policy - Add encryption key generation with password helper * fix: use explicit port mapping for RustDesk services RustDesk requires explicit port bindings (host:container format) to function properly. The service uses specific ports for: - 21115-21116 (TCP/UDP): hbbs service for ID and NAT traversal - 21117-21119 (TCP): hbbr relay service Without explicit port mapping, RustDesk clients cannot establish connections to the server. This is an exception to Dokploy's general port guidelines due to RustDesk's specific networking requirements. --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * feat: add Mumble voice chat server template (#572) * feat: add Mumble voice chat server template - Add Mumble VoIP server blueprint with docker-compose.yml - Configure environment variables for superuser password, welcome text, and max users - Add template.toml with auto-generated secure password - Follow Dokploy best practices (no container_name, proper port format) - Add Mumble metadata to meta.json with proper tags - Support for TCP and UDP on port 64738 * Update template.toml * fix: correct JSON formatting in meta.json for Mumble template entry --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Mauricio Siu * fix: update WireGuard Easy template for proper functionality (#573) * fix: update WireGuard Easy template for proper functionality - Changed to named volume (etc_wireguard) instead of host path mount - Added explicit port mappings (51820:51820/udp, 51821:51821/tcp) required for WireGuard - Updated environment variables to use correct WG_HOST and PASSWORD format - Added all required WireGuard environment variables: - WG_PORT, PORT, WG_MTU, WG_DEFAULT_DNS, WG_ALLOWED_IPS - WG_POST_UP/WG_POST_DOWN for iptables rules - Added NET_RAW capability for proper network operations - Simplified template.toml to use WIREGUARD_HOST and WIREGUARD_PASSWORD - Removed explicit networks config to enable Dokploy's isolated deployment - Template now works with Dokploy's automatic network isolation This configuration has been tested and confirmed working with isolated deployment enabled. * Update template.toml --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> * add: restart policy to MinIO service (#576) restart: unless-stopped is a Docker restart policy that automatically restarts a container if it stops due to an error or Docker daemon restart --------- Co-authored-by: Sunil Shrestha Co-authored-by: Rabithua Co-authored-by: Mauricio Siu Co-authored-by: Scan <103391616+scanash00@users.noreply.github.com> Co-authored-by: Crackvignoule Co-authored-by: florianheysen <39408021+florianheysen@users.noreply.github.com> Co-authored-by: Thiago MadPin Co-authored-by: BlinkStrike <18644035+BlinkStrike@users.noreply.github.com> Co-authored-by: M Jupri Amin <127651222+Juupeee@users.noreply.github.com> Co-authored-by: Harikrishnan Dhanasekaran Co-authored-by: Kamil Dzieniszewski Co-authored-by: Nick Anderson Co-authored-by: lefolalan Co-authored-by: Chris <31969757+ChrisvanChip@users.noreply.github.com> Co-authored-by: kipavy <88386090+kipavy@users.noreply.github.com> Co-authored-by: Benjamin Nussbaum Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Co-authored-by: Vidhya LKG for IT <24915474+VidhyaSanjeevi@users.noreply.github.com> Co-authored-by: Sanjeevi Subramani Co-authored-by: Muzaffer Kadir YILMAZ <34358176+muzafferkadir@users.noreply.github.com> Co-authored-by: Jemg * Remove `container_name` from all containers * Remove expliced networks * Update blueprints/appwrite/docker-compose.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update password variables to use 32-character length * Update Appwrite description and tags in meta.json * Fix JSON formatting in meta.json by adding missing closing bracket * Fix formatting in meta.json by removing trailing spaces in tags array * Add missing SMS and Backblaze storage configuration options in template.toml * Update docker-compose.yml --------- Co-authored-by: Pascal Oberbeck Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Co-authored-by: Sunil Shrestha Co-authored-by: Rabithua Co-authored-by: Mauricio Siu Co-authored-by: Scan <103391616+scanash00@users.noreply.github.com> Co-authored-by: Crackvignoule Co-authored-by: florianheysen <39408021+florianheysen@users.noreply.github.com> Co-authored-by: Thiago MadPin Co-authored-by: BlinkStrike <18644035+BlinkStrike@users.noreply.github.com> Co-authored-by: M Jupri Amin <127651222+Juupeee@users.noreply.github.com> Co-authored-by: Harikrishnan Dhanasekaran Co-authored-by: Kamil Dzieniszewski Co-authored-by: Nick Anderson Co-authored-by: lefolalan Co-authored-by: Chris <31969757+ChrisvanChip@users.noreply.github.com> Co-authored-by: kipavy <88386090+kipavy@users.noreply.github.com> Co-authored-by: Benjamin Nussbaum Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Co-authored-by: Vidhya LKG for IT <24915474+VidhyaSanjeevi@users.noreply.github.com> Co-authored-by: Sanjeevi Subramani Co-authored-by: Muzaffer Kadir YILMAZ <34358176+muzafferkadir@users.noreply.github.com> Co-authored-by: Jemg Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Mauricio Siu * feat: emqx template (MQTT Broker) (#556) * feat: template for emqx * chore: process meta * fix: use websocket on port 443 instead of 8084 * docs: improve comments for emqx template * fix: use emqx service name * fix: use dummy domain instead of traefik * fix: explicitly list dokplok-network * fix(emqx): restart unless stopped * fix(emqx): add healthcheck * Dokploy Deployment for Mautic 5 (#564) * first draft * second * try 3 * 4 * Update docker-compose.yml * Update template.toml * Enhance healthchecks and service dependencies in Docker Compose Updated healthcheck configurations for Mautic and MySQL services to improve service reliability. Added conditions to ensure services wait for dependencies to be healthy before starting. * Update Mautic docker-compose with health checks and roles * fix: searxng template (#651) * fix: use major version for openpanel (#680) * Feat/minepanel template (#657) * feat: add Minepanel template * fix: pin images to v1.7.1 and use http for URLs * fix: remove explicit ports, Dokploy handles proxying * chore: process and sort meta.json * fix: consolidate domain variables to main_domain * fix: add version field to docker-compose.yml * Add Komari Monitor template with Docker Compose, icon, and metadata (#XXX) (#623) * Introduce Komari Monitor, a self-hosted server monitoring tool, with its corresponding Docker Compose configuration, icon, and metadata entry. * Add template.toml for configuration and environment variables. * Include necessary links for GitHub and documentation. * Add Misaka Danmu Server template with Docker Compose, icon, and metadata (#624) * Introduce Misaka Danmu Server, a self-hosted danmaku server for live streaming, with its corresponding Docker Compose configuration, icon, and metadata entry. * Add template.toml for configuration and environment variables. * Include necessary links for GitHub and documentation. * fix: pyrodactyl no longer uses main tag for it's latest release (#626) * fix: pyrodactyl no longer uses main tag for it's latest release * Remove custom network settings from docker-compose Removed custom network configuration from docker-compose. * Update MariaDB and Pyrodactyl images in Docker Compose * Add Jenkins blueprint template configuration (#634) - Created a new TOML configuration file for Jenkins blueprint. - Defined main domain variable and service configuration for Jenkins. - Set serviceName to "jenkins" with port 8080 and host as the main domain. * Update Autobase to version 2.5 (#647) * Update Autobase to version 2.5 Bump the autobase-console Docker image from version 2.2.0 to 2.5.2 in docker-compose.yml to use the latest features and fixes. * Update docker-compose.yml * Update docker-compose.yml * Update meta.json * Update Autobase description in meta.json Revised the Autobase package description to clarify it as a self-hosted DBaaS alternative to cloud-managed databases. * Add Cloudreve template (#649) Self-hosted file management and sharing system with multi-cloud storage support. Includes PostgreSQL and Redis for database and caching. * Update to Umami 3.0.3 (#668) * Update Umami version from v2.20.2 to v3.0.3 * Update Umami Docker image to version 3.0.3 * fix: tailscale exitnode authkey env (#669) * Add qbitwebui (#675) * Add qBittorrent Web UI template * Fix healthcheck bug --------- Co-authored-by: Sunil Shrestha Co-authored-by: Rabithua Co-authored-by: Mauricio Siu Co-authored-by: Scan <103391616+scanash00@users.noreply.github.com> Co-authored-by: Crackvignoule Co-authored-by: florianheysen <39408021+florianheysen@users.noreply.github.com> Co-authored-by: Thiago MadPin Co-authored-by: BlinkStrike <18644035+BlinkStrike@users.noreply.github.com> Co-authored-by: M Jupri Amin <127651222+Juupeee@users.noreply.github.com> Co-authored-by: Harikrishnan Dhanasekaran Co-authored-by: Kamil Dzieniszewski Co-authored-by: Nick Anderson Co-authored-by: lefolalan Co-authored-by: Chris <31969757+ChrisvanChip@users.noreply.github.com> Co-authored-by: kipavy <88386090+kipavy@users.noreply.github.com> Co-authored-by: Benjamin Nussbaum Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Co-authored-by: Vidhya LKG for IT <24915474+VidhyaSanjeevi@users.noreply.github.com> Co-authored-by: Sanjeevi Subramani Co-authored-by: Muzaffer Kadir YILMAZ <34358176+muzafferkadir@users.noreply.github.com> Co-authored-by: Jemg Co-authored-by: Jainil Prajapati <86187588+jaainil@users.noreply.github.com> Co-authored-by: Ye Liu Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Carl-Gerhard Lindesvärd <1987198+lindesvard@users.noreply.github.com> Co-authored-by: Huy Pham Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Louan Fontenele Co-authored-by: Alexey Fedorov Co-authored-by: 0zul <26915998+0zul@users.noreply.github.com> Co-authored-by: Pascal Oberbeck <35005267+poberbeck@users.noreply.github.com> Co-authored-by: Pascal Oberbeck Co-authored-by: Christus Vincent Co-authored-by: Huba Tuba <57007485+floxay@users.noreply.github.com> Co-authored-by: Pablo Moraga Sandoval <72164630+Ketbome@users.noreply.github.com> Co-authored-by: Firefly <77047823+Yuri-NagaSaki@users.noreply.github.com> Co-authored-by: Naterfute <106989409+naterfute@users.noreply.github.com> Co-authored-by: Vitaliy Kukharik <37010174+vitabaks@users.noreply.github.com> Co-authored-by: aurorarissime Co-authored-by: фырат ёздэн <31664778+fir4tozden@users.noreply.github.com> --- .github/copilot-instructions.md | 21 +- .github/workflows/deploy-preview.yml | 3 - .github/workflows/validate-docker-compose.yml | 296 ++------- .gitignore | 4 +- AGENTS.md | 212 ++++++ CONTRIBUTING.md | 1 - blueprints/appwrite/docker-compose.yml | 342 ++++------ blueprints/appwrite/template.toml | 54 +- blueprints/autobase/docker-compose.yml | 2 +- blueprints/capso/docker-compose.yml | 13 +- blueprints/cloudreve/cloudreve.png | Bin 0 -> 6938 bytes blueprints/cloudreve/docker-compose.yml | 48 ++ blueprints/cloudreve/template.toml | 16 + blueprints/discourse/docker-compose.yml | 4 +- blueprints/dolibarr/docker-compose.yml | 40 ++ blueprints/dolibarr/logo.svg | 1 + blueprints/dolibarr/template.toml | 23 + .../easyappointments/docker-compose.yml | 29 + blueprints/easyappointments/logo.png | Bin 0 -> 5535 bytes blueprints/easyappointments/template.toml | 13 + blueprints/emqx/docker-compose.yml | 58 ++ blueprints/emqx/emqx.svg | 6 + blueprints/emqx/template.toml | 11 + blueprints/excalidraw/docker-compose.yml | 3 + blueprints/instantdb/docker-compose.yml | 64 ++ blueprints/instantdb/instant.svg | 4 + blueprints/instantdb/template.toml | 29 + blueprints/jenkins/docker-compose.yml | 13 + blueprints/jenkins/jenkins.svg | 1 + blueprints/jenkins/template.toml | 12 + blueprints/komari-monitor/docker-compose.yml | 13 + blueprints/komari-monitor/komari-monitor.ico | Bin 0 -> 40832 bytes blueprints/komari-monitor/template.toml | 15 + blueprints/lavalink/docker-compose.yml | 41 ++ blueprints/lavalink/lavalink.svg | 50 ++ blueprints/lavalink/template.toml | 123 ++++ blueprints/mage-ai/docker-compose.yml | 26 + blueprints/mage-ai/mage-ai.svg | 38 ++ blueprints/mage-ai/template.toml | 11 + blueprints/mautic/docker-compose.yml | 131 ++++ blueprints/mautic/mautic.svg | 76 +++ blueprints/mautic/template.toml | 52 ++ blueprints/minepanel/docker-compose.yml | 29 + blueprints/minepanel/minepanel.webp | Bin 0 -> 10978 bytes blueprints/minepanel/template.toml | 28 + .../misaka-danmu-server/docker-compose.yml | 43 ++ .../misaka-danmu-server.png | Bin 0 -> 13821 bytes blueprints/misaka-danmu-server/template.toml | 30 + blueprints/openpanel/docker-compose.yml | 103 ++- blueprints/openpanel/template.toml | 107 +-- blueprints/prometheus/docker-compose.yml | 1 + blueprints/prometheus/template.toml | 5 +- blueprints/pyrodactyl/docker-compose.yml | 11 +- blueprints/qbitwebui/docker-compose.yml | 39 ++ blueprints/qbitwebui/qbitwebui.png | Bin 0 -> 440710 bytes blueprints/qbitwebui/template.toml | 16 + blueprints/reactive-resume/docker-compose.yml | 67 ++ blueprints/reactive-resume/logo.svg | 20 + blueprints/reactive-resume/template.toml | 33 + blueprints/searxng/docker-compose.yml | 10 +- blueprints/searxng/template.toml | 6 +- blueprints/syncthing/docker-compose.yml | 19 + blueprints/syncthing/syncthing.svg | 54 ++ blueprints/syncthing/template.toml | 9 + blueprints/tailscale-exitnode/template.toml | 2 +- blueprints/trailbase/docker-compose.yml | 24 + blueprints/trailbase/logo.svg | 194 ++++++ blueprints/trailbase/template.toml | 8 + blueprints/umami/docker-compose.yml | 2 +- blueprints/zitadel/docker-compose.yml | 3 + build-scripts/package-lock.json | 615 ++++++++++++++++++ meta.json | 296 ++++++++- 72 files changed, 3057 insertions(+), 616 deletions(-) create mode 100644 AGENTS.md create mode 100644 blueprints/cloudreve/cloudreve.png create mode 100644 blueprints/cloudreve/docker-compose.yml create mode 100644 blueprints/cloudreve/template.toml create mode 100644 blueprints/dolibarr/docker-compose.yml create mode 100644 blueprints/dolibarr/logo.svg create mode 100644 blueprints/dolibarr/template.toml create mode 100644 blueprints/easyappointments/docker-compose.yml create mode 100644 blueprints/easyappointments/logo.png create mode 100644 blueprints/easyappointments/template.toml create mode 100644 blueprints/emqx/docker-compose.yml create mode 100644 blueprints/emqx/emqx.svg create mode 100644 blueprints/emqx/template.toml create mode 100644 blueprints/instantdb/docker-compose.yml create mode 100644 blueprints/instantdb/instant.svg create mode 100644 blueprints/instantdb/template.toml create mode 100644 blueprints/jenkins/docker-compose.yml create mode 100644 blueprints/jenkins/jenkins.svg create mode 100644 blueprints/jenkins/template.toml create mode 100644 blueprints/komari-monitor/docker-compose.yml create mode 100644 blueprints/komari-monitor/komari-monitor.ico create mode 100644 blueprints/komari-monitor/template.toml create mode 100644 blueprints/lavalink/docker-compose.yml create mode 100644 blueprints/lavalink/lavalink.svg create mode 100644 blueprints/lavalink/template.toml create mode 100644 blueprints/mage-ai/docker-compose.yml create mode 100644 blueprints/mage-ai/mage-ai.svg create mode 100644 blueprints/mage-ai/template.toml create mode 100644 blueprints/mautic/docker-compose.yml create mode 100644 blueprints/mautic/mautic.svg create mode 100644 blueprints/mautic/template.toml create mode 100644 blueprints/minepanel/docker-compose.yml create mode 100644 blueprints/minepanel/minepanel.webp create mode 100644 blueprints/minepanel/template.toml create mode 100644 blueprints/misaka-danmu-server/docker-compose.yml create mode 100644 blueprints/misaka-danmu-server/misaka-danmu-server.png create mode 100644 blueprints/misaka-danmu-server/template.toml create mode 100644 blueprints/qbitwebui/docker-compose.yml create mode 100644 blueprints/qbitwebui/qbitwebui.png create mode 100644 blueprints/qbitwebui/template.toml create mode 100644 blueprints/reactive-resume/docker-compose.yml create mode 100644 blueprints/reactive-resume/logo.svg create mode 100644 blueprints/reactive-resume/template.toml create mode 100644 blueprints/syncthing/docker-compose.yml create mode 100644 blueprints/syncthing/syncthing.svg create mode 100644 blueprints/syncthing/template.toml create mode 100644 blueprints/trailbase/docker-compose.yml create mode 100644 blueprints/trailbase/logo.svg create mode 100644 blueprints/trailbase/template.toml create mode 100644 build-scripts/package-lock.json diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 597c33bf..e17f1964 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -8,7 +8,6 @@ Key components: - **Blueprints**: Self-contained templates with `docker-compose.yml` (service definitions) and `template.toml` (Dokploy-specific configuration for domains, env vars, mounts). - **meta.json**: Centralized index of all templates, aggregated from blueprint metadata. Entries include `id`, `name`, `version`, `description`, `logo`, `links`, and `tags`. -- **app/**: Vite-based React frontend for local preview/development (runs at http://localhost:5173). Copies blueprints and meta.json to dist during build. - **Scripts**: Node.js tools in root and `build-scripts/` for maintaining `meta.json` (deduplication, sorting, validation). Data flow: New templates added to `blueprints/` → Metadata updated in `meta.json` → Processing scripts ensure consistency → App builds include static blueprints/meta for preview. @@ -22,7 +21,6 @@ The "why": Enables rapid, standardized deployment of 200+ OSS apps on Dokploy wi - `docker-compose.yml`: Standard Docker Compose v3.8. Avoid `ports`, `container_name`, `networks`—Dokploy handles isolation via internal networks. - `template.toml`: Defines variables (e.g., `${domain}`), domains (service:port → host), env vars, and mounts. Use helpers like `${password:32}`, `${uuid}`, `${jwt:secret_var}`. - `logo.svg/png`: Service icon, referenced in `meta.json`. -- `app/vite.config.ts`: Configures build to copy `blueprints/*` and `meta.json` to dist root for static serving. - `dedupe-and-sort-meta.js`: Standalone script—reads `meta.json`, removes duplicate `id`s (keeps first), sorts by `id` (case-insensitive), creates timestamped backup. - `build-scripts/process-meta.js`: Advanced processor with CLI options (`--verbose`, `--no-backup`, `--input`/`--output`), JSON schema validation (required: `id`, `name`, `version`, `description`, `links.github`, `logo`, `tags` array). @@ -32,6 +30,8 @@ Exemplary blueprint: `blueprints/ghost/`—`docker-compose.yml` exposes port 236 1. **Add/Update Template**: + - **REQUIREMENT**: Service **MUST** be open source. Only add templates for applications with an open-source license (e.g., MIT, Apache, GPL, AGPL). Proprietary or closed-source services are not allowed. + - **Verify Docker Images**: Before using any Docker image in `docker-compose.yml`, verify it exists using `docker manifest inspect ` (e.g., `docker manifest inspect docker.io/bitnami/discourse:3.5.0`). This ensures the image is available and prevents deployment failures. - Create `blueprints//` (e.g., `ghost`). - Implement `docker-compose.yml` (single service typical; use volumes for persistence). - Configure `template.toml`—reference vars in `[config.domains]`, `[config.env]`, `[config.mounts]`. @@ -39,14 +39,7 @@ Exemplary blueprint: `blueprints/ghost/`—`docker-compose.yml` exposes port 236 - Run `node dedupe-and-sort-meta.js --backup` to validate/sort. - Commit; PR triggers Dokploy preview (base64 import for testing). -2. **Local Development**: - - - App: `cd app && pnpm install && pnpm dev` (Vite dev server). - - Meta processing: `npm run process-meta` or `make process-meta` (uses Makefile targets: `validate`, `check`, `build`). - - Build app: `cd app && pnpm build`—copies blueprints/meta to `dist/` for static hosting. - - Test template: Use PR preview URL or local Dokploy instance; import base64 from template card. - -3. **CI/CD**: +2. **CI/CD**: - `.github/workflows/validate-meta.yml` (if present): Runs validation on push/PR—fails on duplicates, invalid JSON, missing fields. - Integrate processing: Add `npm run process-meta` to build steps; use `--no-backup` in CI. @@ -54,20 +47,22 @@ No tests in repo—focus on manual validation via scripts and Dokploy deploys. D ## Conventions and Patterns +- **Open Source Requirement**: **ALL services MUST be open source**. Only applications with open-source licenses (MIT, Apache, GPL, AGPL, etc.) are allowed. Proprietary or closed-source services are strictly prohibited. - **Template IDs**: Lowercase, kebab-case (e.g., `active-pieces`); unique across repo—enforced by dedupe script. -- **Docker Compose**: Minimal—omit `ports` (Dokploy proxies), `restart: unless-stopped`, persistent volumes (e.g., `- db-data:/var/lib/postgresql/data`). Services named after folder (e.g., `ghost` service). +- **Docker Compose**: Minimal—omit `ports` (Dokploy proxies), persistent volumes (e.g., `- db-data:/var/lib/postgresql/data`). Services named after folder (e.g., `ghost` service). - **template.toml**: - Variables: `[variables] main_domain = "${domain}"`; use helpers for secrets (`${password:64}`, `${base64:32}`). - Domains: `[[config.domains]] serviceName = "" port = 80 host = "${main_domain}"` (path="/" optional). - Env: `[[config.env]]` array of "KEY=VALUE" strings, interpolating vars (e.g., "DB_PASSWORD=${db_pass}"). + - **URL Variables**: When environment variables require URLs (e.g., `WEB_URL`, `NEXTAUTH_URL`, `PUBLIC_URL`), **always use HTTP by default** (e.g., `"http://${main_domain}"`). HTTPS should only be used if explicitly required by the application or when using a reverse proxy with SSL termination. - Mounts: `[[config.mounts]] filePath = "/etc/config" content = """multi-line\ncontent"""`. - JWT helper: `${jwt:secret_var:payload_var}` for auth tokens; payload as JSON string with `exp: ${timestamps:YYYY-MM-DDTHH:mm:ssZ}`. - **Meta.json**: Entries as JSON objects; tags array of lowercase strings (e.g., ["monitoring", "database"]); links object with `github`, `website`, `docs`. - **No Networks**: Rely on Dokploy's isolated deployments—avoid explicit `networks:`. -- **Versions**: Pin images to specific versions in `docker-compose.yml` (e.g., `ghost:5.82.0-alpine`); match in `meta.json.version`. +- **Versions**: Pin images to specific versions in `docker-compose.yml` (e.g., `ghost:5.82.0-alpine`); match in `meta.json.version`. **NEVER use `latest` tag**—it can break templates when upstream images change unexpectedly. **Always verify image exists** using `docker manifest inspect ` before committing. - **Logos**: SVG preferred; size ~128x128; file name in `meta.json.logo` (e.g., "ghost.svg"). -Cross-component: No runtime communication—templates independent. App consumes static blueprints/meta for UI rendering (e.g., search, cards via React components in `app/src/`). +Cross-component: Templates are independent and ship as static blueprints/meta. ## Integration Points diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index a342ac7a..d04171e6 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -5,9 +5,6 @@ on: workflows: [Build Preview Deployment] types: - completed - pull_request: - branches: - - canary permissions: actions: read deployments: write diff --git a/.github/workflows/validate-docker-compose.yml b/.github/workflows/validate-docker-compose.yml index ddd16aab..25d4eaf3 100644 --- a/.github/workflows/validate-docker-compose.yml +++ b/.github/workflows/validate-docker-compose.yml @@ -17,13 +17,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 # Necesitamos el historial completo para comparar con base + fetch-depth: 0 - name: Set up Docker Compose - run: | - echo "🐳 Setting up Docker Compose..." - # Docker Compose V2 viene preinstalado en ubuntu-latest - docker compose version + run: docker compose version - name: Set up Node.js uses: actions/setup-node@v4 @@ -36,282 +33,89 @@ jobs: version: 8 - name: Install dependencies - run: | - echo "📦 Installing Node.js dependencies..." - cd build-scripts && pnpm install + run: cd build-scripts && pnpm install - - name: Get changed files - id: changed-files + - name: Detect changed blueprints + id: changed run: | - echo "🔍 Detecting changed files..." - - # Obtener la rama base BASE_SHA=$(git merge-base HEAD origin/${{ github.base_ref }}) - # Encontrar todos los archivos docker-compose.yml y template.toml modificados/agregados - CHANGED_COMPOSE=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | grep -E 'blueprints/.*/docker-compose\.yml$' || true) - CHANGED_TOML=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | grep -E 'blueprints/.*/template\.toml$' || true) + # Obtener todos los blueprints que tienen cambios (en docker-compose.yml o template.toml) + CHANGED_BLUEPRINTS=$(git diff --name-only --diff-filter=ACMRT $BASE_SHA HEAD | \ + grep -E 'blueprints/[^/]+/(docker-compose\.yml|template\.toml)$' | \ + sed 's|blueprints/\([^/]*\)/.*|\1|' | \ + sort -u) - # Crear lista de directorios únicos que tienen cambios - CHANGED_DIRS=$(echo -e "$CHANGED_COMPOSE\n$CHANGED_TOML" | sed 's|blueprints/\([^/]*\)/.*|\1|' | sort -u) - - echo "Changed compose files:" - echo "$CHANGED_COMPOSE" | while read file; do [ -n "$file" ] && echo " - $file"; done - - echo "Changed TOML files:" - echo "$CHANGED_TOML" | while read file; do [ -n "$file" ] && echo " - $file"; done - - echo "Changed directories:" - echo "$CHANGED_DIRS" | while read dir; do [ -n "$dir" ] && echo " - $dir"; done - - # Guardar para usar en siguientes pasos - echo "compose_files<> $GITHUB_OUTPUT - echo "$CHANGED_COMPOSE" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - echo "toml_files<> $GITHUB_OUTPUT - echo "$CHANGED_TOML" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - echo "directories<> $GITHUB_OUTPUT - echo "$CHANGED_DIRS" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Validate Docker Compose files syntax - id: validate-compose-syntax - run: | - echo "🔍 Validating Docker Compose files syntax..." - - ERROR=0 - COMPOSE_FILES="${{ steps.changed-files.outputs.compose_files }}" - - if [ -z "$COMPOSE_FILES" ]; then - echo "ℹ️ No docker-compose.yml files changed, skipping validation" - exit 0 - fi - - echo "$COMPOSE_FILES" | while read -r compose_file; do - if [ -z "$compose_file" ]; then - continue - fi - - TEMPLATE_DIR=$(dirname "$compose_file") - TEMPLATE_NAME=$(basename "$TEMPLATE_DIR") - - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📦 Validating syntax: $TEMPLATE_NAME" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - # Validar sintaxis de docker-compose.yml usando docker compose - echo "🔍 Validating docker-compose.yml syntax..." - if ! docker compose -f "$compose_file" config > /dev/null 2>&1; then - echo "❌ ERROR: docker-compose.yml syntax is invalid in $TEMPLATE_NAME" - echo "Running docker compose config to show errors:" - docker compose -f "$compose_file" config 2>&1 || true - ERROR=1 - else - echo "✅ docker-compose.yml syntax is valid" - fi - - # Obtener lista de servicios del compose - SERVICES=$(docker compose -f "$compose_file" config --services 2>/dev/null || echo "") - echo "📋 Services found in docker-compose.yml:" - echo "$SERVICES" | while read service; do - [ -n "$service" ] && echo " - $service" - done - - # Guardar servicios para validación posterior - echo "$SERVICES" > "/tmp/${TEMPLATE_NAME}_services.txt" + echo "Changed blueprints:" + echo "$CHANGED_BLUEPRINTS" | while read blueprint; do + [ -n "$blueprint" ] && echo " - $blueprint" done - if [ $ERROR -eq 1 ]; then - echo "" - echo "❌ Docker Compose syntax validation failed" - exit 1 - else - echo "" - echo "✅ All Docker Compose files have valid syntax" - fi + # Guardar lista de blueprints (una por línea) + echo "blueprints<> $GITHUB_OUTPUT + echo "$CHANGED_BLUEPRINTS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - - name: Validate Docker Compose best practices - id: validate-compose-practices + - name: Validate blueprints run: | - echo "🔍 Validating Docker Compose best practices..." + BLUEPRINTS="${{ steps.changed.outputs.blueprints }}" - ERROR=0 - COMPOSE_FILES="${{ steps.changed-files.outputs.compose_files }}" - - if [ -z "$COMPOSE_FILES" ]; then - echo "ℹ️ No docker-compose.yml files changed, skipping validation" + if [ -z "$BLUEPRINTS" ] || [ "$BLUEPRINTS" = "" ]; then + echo "ℹ️ No blueprints changed, skipping validation" exit 0 fi - # Convert to array to avoid subshell issues with pipe - # This ensures ERROR=1 inside the loop propagates to the parent shell - mapfile -t COMPOSE_ARRAY <<< "$COMPOSE_FILES" - - for compose_file in "${COMPOSE_ARRAY[@]}"; do - if [ -z "$compose_file" ]; then - continue - fi - - TEMPLATE_DIR=$(dirname "$compose_file") - TEMPLATE_NAME=$(basename "$TEMPLATE_DIR") - - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📦 Validating best practices: $TEMPLATE_NAME" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - # Validar usando el script de TypeScript - if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts --file "../$compose_file" --verbose); then - ERROR=1 - fi - done - - if [ $ERROR -eq 1 ]; then - echo "" - echo "❌ Docker Compose best practices validation failed" - exit 1 - else - echo "" - echo "✅ All Docker Compose files follow best practices" - fi - - - name: Validate template.toml files - id: validate-toml - run: | - echo "🔍 Validating template.toml files..." - ERROR=0 - DIRECTORIES="${{ steps.changed-files.outputs.directories }}" - if [ -z "$DIRECTORIES" ]; then - echo "ℹ️ No template directories changed, skipping TOML validation" - exit 0 - fi + # Convertir a array para evitar problemas con subshells + mapfile -t BLUEPRINT_ARRAY <<< "$BLUEPRINTS" - # Convert to array to avoid subshell issues with pipe - # This ensures ERROR=1 inside the loop propagates to the parent shell - mapfile -t DIRS_ARRAY <<< "$DIRECTORIES" - - for template_dir in "${DIRS_ARRAY[@]}"; do - if [ -z "$template_dir" ]; then - continue - fi + # Iterar sobre cada blueprint + for blueprint in "${BLUEPRINT_ARRAY[@]}"; do + [ -z "$blueprint" ] && continue - TEMPLATE_PATH="blueprints/$template_dir" - TOML_FILE="$TEMPLATE_PATH/template.toml" - - if [ ! -f "$TOML_FILE" ]; then - echo "⚠️ WARNING: template.toml not found in $template_dir (might be deleted)" - continue - fi + BLUEPRINT_PATH="blueprints/$blueprint" + COMPOSE_FILE="$BLUEPRINT_PATH/docker-compose.yml" + TOML_FILE="$BLUEPRINT_PATH/template.toml" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📝 Validating: $template_dir/template.toml" + echo "📦 Validating: $blueprint" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - # Validar usando el script de TypeScript con tsx - # Ejecutar desde build-scripts para tener acceso a node_modules - if ! (cd build-scripts && pnpm exec tsx validate-template.ts --dir "../$TEMPLATE_PATH" --verbose); then - ERROR=1 - fi - done - - if [ $ERROR -eq 1 ]; then - echo "" - echo "❌ template.toml validation failed" - exit 1 - else - echo "" - echo "✅ All template.toml files are valid" - fi - - - name: Test Docker Compose (dry-run) - id: test-compose - run: | - echo "🧪 Testing Docker Compose files (dry-run)..." - - ERROR=0 - DIRECTORIES="${{ steps.changed-files.outputs.directories }}" - - if [ -z "$DIRECTORIES" ]; then - echo "ℹ️ No template directories changed, skipping dry-run test" - exit 0 - fi - - echo "$DIRECTORIES" | while read -r template_dir; do - if [ -z "$template_dir" ]; then - continue - fi - - COMPOSE_FILE="blueprints/$template_dir/docker-compose.yml" - + # 1. Validar best practices de docker-compose.yml if [ ! -f "$COMPOSE_FILE" ]; then + echo "⚠️ WARNING: docker-compose.yml not found" + ERROR=1 + continue + fi + echo "🔍 Validating docker-compose.yml best practices..." + if ! (cd build-scripts && pnpm exec tsx validate-docker-compose.ts --file "../$COMPOSE_FILE" --verbose); then + ERROR=1 continue fi - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "🧪 Testing: $template_dir" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - # Cambiar al directorio del template para resolver rutas relativas - cd "blueprints/$template_dir" - - # Validar que docker-compose puede parsear el archivo completamente - echo "🔍 Running docker compose config (full validation)..." - if docker compose config > /dev/null 2>&1; then - echo "✅ Docker Compose file is fully valid and can be processed" - - # Mostrar información útil - echo "📋 Configuration summary:" - docker compose config --services | while read service; do - [ -n "$service" ] && echo " Service: $service" - done + # 3. Validar template.toml + if [ -f "$TOML_FILE" ]; then + echo "🔍 Validating template.toml..." + if ! (cd build-scripts && pnpm exec tsx validate-template.ts --dir "../$BLUEPRINT_PATH" --verbose); then + ERROR=1 + continue + fi else - echo "❌ ERROR: Docker Compose file failed full validation" - docker compose config 2>&1 || true + echo "⚠️ WARNING: template.toml not found" ERROR=1 + continue fi - cd - > /dev/null + echo "✅ All validations passed for $blueprint" done if [ $ERROR -eq 1 ]; then echo "" - echo "❌ Docker Compose dry-run test failed" + echo "❌ Validation failed for one or more blueprints" exit 1 else echo "" - echo "✅ All Docker Compose files passed dry-run test" + echo "✅ All blueprints validated successfully" fi - - - name: Summary - if: always() - run: | - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "📊 Validation Summary" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - if [ "${{ steps.validate-compose-syntax.outcome }}" == "success" ] && \ - [ "${{ steps.validate-compose-practices.outcome }}" == "success" ] && \ - [ "${{ steps.validate-toml.outcome }}" == "success" ] && \ - [ "${{ steps.test-compose.outcome }}" == "success" ]; then - echo "✅ All validations passed!" - echo "" - echo "Your Docker Compose and template.toml files are valid and ready to merge." - else - echo "❌ Some validations failed. Please review the errors above." - echo "" - echo "Common issues to check:" - echo " - docker-compose.yml syntax errors" - echo " - template.toml syntax errors" - echo " - serviceName in template.toml must match service names in docker-compose.yml" - echo " - Avoid using container_name, explicit networks, or port mappings" - fi - diff --git a/.gitignore b/.gitignore index b512c09d..2f8606a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +node_modules +package-lock.json +meta.json.backup.* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c34d6beb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,212 @@ +# AGENTS.md: AI Collaboration Guide + +This document provides essential context for AI models interacting with this project. Adhering to these guidelines will ensure consistency and maintain code quality. + +## 1. Project Overview & Purpose + +- **Primary Goal:** This is the official repository for Dokploy Open Source Templates, maintaining Docker Compose templates for deploying 200+ open-source applications via Dokploy (a self-hosted PaaS alternative to Heroku). The project enables rapid, standardized deployment of OSS applications without manual configuration. +- **Business Domain:** Self-hosted Platform as a Service (PaaS), DevOps tooling, containerized application deployment, and open-source software distribution. + +## 2. Core Technologies & Stack + +- **Languages:** JavaScript (Node.js), TypeScript (React frontend), YAML (Docker Compose), TOML (template configuration), JSON (metadata), Shell (build scripts). +- **Frameworks & Runtimes:** Node.js runtime, Vite 5.x (for React 19.x frontend), Docker Compose v3.8+, React Router 7.x. +- **Databases:** No direct database usage in core project (templates may include various databases like PostgreSQL, MySQL, Redis, etc.). +- **Key Libraries/Dependencies:** + - **Core:** `nodemon` for development, custom Node.js scripts for meta processing. + - **Frontend:** `fuse.js` (fuzzy search), `zustand` (state management), `@iarna/toml` (TOML parsing), `shadcn/ui` components, `@radix-ui` primitives, `@codemirror` (code editor), `react-router-dom`, `tailwindcss`. + - **Build:** `vite-plugin-static-copy` for copying blueprints to dist. +- **Platforms:** Linux (primary), Docker containers, Dokploy PaaS, Web browsers (for frontend preview), Cloudflare Pages (for PR previews). +- **Package Manager:** npm for root project, pnpm for frontend (`app/` directory). + +## 3. Architectural Patterns + +- **Overall Architecture:** Template collection and management system with decentralized blueprint architecture. Each blueprint is a self-contained Docker Compose application with Dokploy-specific configuration. +- **Directory Structure Philosophy:** + - `/blueprints`: Contains all deployable application templates, each as a subdirectory with `docker-compose.yml`, `template.toml`, and logo files. Each blueprint is independent with no shared state. + - `/app`: Vite + React + TypeScript frontend for local preview and development. + - `/src/components`: React components (UI components in `/ui` subdirectory using shadcn/ui). + - `/src/hooks`: Custom React hooks (e.g., `useFuseSearch.ts` for fuzzy search). + - `/src/store`: Zustand state management store. + - `/src/lib`: Utility functions and helpers. + - `/build-scripts`: Advanced meta.json processing tools with CLI options and JSON schema validation. + - `/.github/workflows`: CI/CD workflows for validation, preview builds, and deployments. + - Root level: Core processing scripts (`dedupe-and-sort-meta.js`), metadata index (`meta.json`), build configuration (`Makefile`, `package.json`). +- **Module Organization:** Node.js scripts for metadata processing, React components for frontend UI, Docker Compose files for service definitions, TOML files for Dokploy configuration. Frontend uses component-based architecture with hooks for business logic and Zustand for global state. + +## 4. Coding Conventions & Style Guide + +- **Formatting:** JavaScript follows standard conventions with 2-space indentation. TypeScript in frontend uses ESLint configuration. YAML uses 2-space indentation. TOML files use standard formatting. +- **Naming Conventions:** + - Variables, functions: camelCase (`myVariable`, `myFunction`) + - React components: PascalCase (`TemplateGrid`, `SearchBar`) + - Constants: SCREAMING_SNAKE_CASE (`MAX_BUFFER_SIZE`) + - Template IDs: lowercase, kebab-case (`activepieces`, `ghost`) - **MUST** be unique across repository + - Files: snake_case for scripts (`dedupe-and-sort-meta.js`), PascalCase for React components (`TemplateGrid.tsx`), kebab-case for directories (`build-scripts`) + - Docker services: **MUST** match blueprint folder name exactly (e.g., `ghost` service in `blueprints/ghost/`) +- **API Design:** + - **Backend:** Procedural scripting approach with CLI interfaces. Template system uses declarative configuration over imperative code. + - **Frontend:** Component-based React architecture with hooks for logic separation. Zustand for centralized state management. Custom hooks encapsulate complex logic (e.g., `useFuseSearch` for search functionality). +- **Common Patterns & Idioms:** + - **Metaprogramming:** Minimal use of advanced JavaScript features in scripts, focusing on simple, maintainable code. + - **Memory Management:** Relies on Node.js garbage collection and React's automatic memory management. + - **Polymorphism:** Uses JavaScript prototype-based objects and functional programming patterns. React components use composition over inheritance. + - **Type Safety:** + - Backend scripts: JavaScript with JSDoc comments for documentation. + - Frontend: TypeScript with strict type checking enabled. + - **Concurrency:** + - Backend: Synchronous processing model for meta.json operations. + - Frontend: React's concurrent rendering, `useDeferredValue` for debouncing, `useEffect` for async operations. + - **State Management:** Zustand store pattern with selectors for optimal re-renders. URL params synced with search state via React Router. +- **Error Handling:** + - Backend: Node.js error-first callback pattern and try-catch blocks. Scripts validate JSON structure and fail fast on errors. + - Frontend: Error boundaries for React components, try-catch for async operations, user-friendly error messages via toast notifications. + +## 5. Key Files & Entrypoints + +- **Main Entrypoints:** + - **Backend:** `dedupe-and-sort-meta.js` - Primary script for processing meta.json file. + - **Frontend:** `app/src/main.tsx` - React application entry point. +- **Configuration:** + - `package.json` - Root Node.js project configuration and npm scripts. + - `app/package.json` - Frontend dependencies and pnpm scripts. + - `meta.json` - Centralized template registry (200+ entries). + - `Makefile` - Build automation with targets for processing, validation, and cleanup. + - `app/vite.config.ts` - Vite build configuration with static copy plugin. + - `app/tsconfig.json` - TypeScript compiler configuration. +- **CI/CD Pipeline:** + - `.github/workflows/validate-meta.yml` - Validates meta.json structure, duplicates, and sort order. + - `.github/workflows/build-preview.yml` - Builds preview deployments for PRs. + - `.github/workflows/deploy-preview.yml` - Deploys previews to Cloudflare Pages. + +## 6. Development & Testing Workflow + +- **Local Development Environment:** + - **Install dependencies** + + ```bash + # Root project + npm install + + # Frontend (uses pnpm) + cd app && pnpm install + ``` + + - **Process meta.json** (CRITICAL: Run after ANY meta.json edits) + ```bash + npm run process-meta + # or + make process-meta + # or + node dedupe-and-sort-meta.js + ``` + - **Validate without modifying** + ```bash + npm run validate-meta + # or + make validate + ``` + - **Quick check for duplicates/sort status** + ```bash + make check + ``` + - **Clean backup files** + ```bash + make clean + ``` +- **Task Configuration:** + - **NPM Scripts:** Run `npm run` to list available scripts. Key scripts: + - `process-meta`: Remove duplicates and sort meta.json + - `process-meta-verbose`: Process with detailed output + - `validate-meta`: Validate structure without changes + - **Makefile Targets:** Run `make help` to list targets. Key targets: + - `process-meta`: Process meta.json + - `validate`: Validate without modifying + - `check`: Quick duplicate/sort check + - `build`: Full build process + - `clean`: Remove backup files +- **Testing:** No formal unit testing framework. Validation occurs through: + - Script-based validation of meta.json structure and schema + - Manual testing via Dokploy preview deployments (import base64 from PR preview) + - JSON schema validation in `build-scripts/process-meta.js` + - TypeScript compilation errors caught during build + - ESLint for code quality in frontend +- **CI/CD Process:** + - **On meta.json changes:** Validates structure, checks for duplicates, verifies sort order, compares processed vs original. + - **On PR creation:** Builds preview deployment, generates base64 import for testing in Dokploy. + - **Preview testing:** Use PR description link → Search template → Copy base64 → Import in Dokploy instance. + +## 7. Specific Instructions for AI Collaboration + +- **Contribution Guidelines:** + - Follow existing Dokploy template structure strictly. Each blueprint must be independent with no shared state. + - **CRITICAL:** Always run `node dedupe-and-sort-meta.js` or `npm run process-meta` after ANY meta.json edits. + - Test templates in Dokploy preview before submitting PRs (use base64 import from PR preview). + - Add logo file (SVG preferred, ~128x128px) to blueprint folder. + - Ensure template `id` in meta.json exactly matches blueprint folder name (lowercase kebab-case). +- **Docker Compose Conventions (CRITICAL):** + + - **Version:** MUST be `3.8` + - **NEVER include:** `ports` (use `expose` only), `container_name`, `networks` (Dokploy handles isolation) + - **ALWAYS include:** `restart: unless-stopped` or `restart: always`, persistent volumes + - **Service naming:** MUST match blueprint folder name exactly + - **Example:** + ```yaml + version: "3.8" + services: + ghost: + image: ghost:6-alpine + restart: always + volumes: + - ghost:/var/lib/ghost/content + volumes: + ghost: + ``` + +- **template.toml Conventions:** + + - **Variables:** Define in `[variables]` section, use helpers for secrets + - **Domains:** `[[config.domains]]` with `serviceName`, `port`, `host` (path optional) + - **Env:** Array of strings: `env = ["KEY=VALUE", "DB_PASSWORD=${db_pass}"]` + - **Available helpers:** `${domain}`, `${password:length}`, `${base64:length}`, `${hash:length}`, `${uuid}`, `${randomPort}`, `${email}`, `${username}`, `${timestamp}`, `${timestamps:datetime}`, `${timestampms:datetime}`, `${jwt:secret_var:payload_var}` + - **JWT helper example:** `${jwt:mysecret:mypayload}` with payload containing `exp: ${timestamps:2030-01-01T00:00:00Z}` + +- **meta.json Requirements:** + + - **Required fields:** `id`, `name`, `version`, `description`, `links` (with `github`), `logo`, `tags` (array) + - **Tags:** Lowercase strings (e.g., `["monitoring", "database"]`) + - **Version:** MUST match Docker image version in docker-compose.yml + - **Logo:** Filename only (e.g., `"ghost.jpeg"`), file must exist in blueprint folder + +- **Frontend Development:** + + - Use TypeScript with strict type checking + - Follow React hooks patterns, avoid class components + - Use Zustand selectors for state access to optimize re-renders + - Fuse.js searches across `name`, `description`, `tags`, `id` fields + - Use shadcn/ui components for consistency + - Sync URL params with search state via React Router + +- **Security:** + + - Be mindful of security when handling template configurations + - NEVER hardcode secrets in templates - use Dokploy's variable system with helpers + - Pin Docker images to specific versions to avoid supply chain attacks + - Validate user input in frontend before processing + +- **Dependencies:** + + - When adding new templates, ensure Docker images are pinned to specific versions + - Update meta.json with exact version matching Docker Compose image version + - For frontend dependencies: Use `pnpm add ` in `app/` directory + - For root dependencies: Use `npm install ` in root directory + +- **Commit Messages:** Follow conventional commit patterns (e.g., `feat:`, `fix:`, `docs:`, `chore:`). + +- **Common Pitfalls to Avoid:** + 1. Forgetting to process meta.json after editing (CI will fail) + 2. Template ID mismatch between meta.json and folder name + 3. Including `ports`, `container_name`, or `networks` in docker-compose.yml + 4. Using object syntax for env vars in template.toml (must be array of strings) + 5. Logo file missing or filename mismatch in meta.json + 6. Version mismatch between meta.json and docker-compose.yml image tag diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 657ce29d..2671d5af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -148,7 +148,6 @@ Use these in `${}` for dynamic values: - Omit explicit ports; let Dokploy handle exposure. - Use persistent volumes for data (e.g., databases). - - Set `restart: unless-stopped` for services. - **Template.toml**: diff --git a/blueprints/appwrite/docker-compose.yml b/blueprints/appwrite/docker-compose.yml index 130bb5d3..cb622284 100644 --- a/blueprints/appwrite/docker-compose.yml +++ b/blueprints/appwrite/docker-compose.yml @@ -6,31 +6,32 @@ x-logging: &x-logging options: max-file: "5" max-size: "10m" - services: appwrite: - image: appwrite/appwrite:1.6.1 - container_name: appwrite + image: appwrite/appwrite:1.8.0 <<: *x-logging restart: unless-stopped - networks: - - dokploy-network labels: - traefik.enable=true - traefik.constraint-label-stack=appwrite 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: - mariadb - redis + # - clamav environment: - _APP_ENV - _APP_WORKER_PER_CORE - _APP_LOCALE + - _APP_COMPRESSION_MIN_SIZE_BYTES - _APP_CONSOLE_WHITELIST_ROOT - _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_SESSION_ALERTS @@ -43,10 +44,14 @@ services: - _APP_OPTIONS_ABUSE - _APP_OPTIONS_ROUTER_PROTECTION - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS + - _APP_OPTIONS_ROUTER_FORCE_HTTPS - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _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 @@ -73,6 +78,7 @@ services: - _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 @@ -89,21 +95,26 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET - - _APP_FUNCTIONS_SIZE_LIMIT + - _APP_COMPUTE_SIZE_LIMIT - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_BUILD_TIMEOUT - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY + - _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_LOGGING_CONFIG - _APP_MAINTENANCE_INTERVAL - _APP_MAINTENANCE_DELAY + - _APP_MAINTENANCE_START_TIME - _APP_MAINTENANCE_RETENTION_EXECUTION - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_SMS_PROVIDER @@ -120,94 +131,25 @@ services: - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET - _APP_ASSISTANT_OPENAI_API_KEY - appwrite-console: - image: appwrite/console:5.0.12 - container_name: appwrite-console <<: *x-logging + image: appwrite/console:7.4.7 restart: unless-stopped - networks: - - dokploy-network labels: - "traefik.enable=true" - "traefik.constraint-label-stack=appwrite" - environment: - - _APP_ENV - - _APP_WORKER_PER_CORE - - _APP_LOCALE - - _APP_CONSOLE_WHITELIST_ROOT - - _APP_CONSOLE_WHITELIST_EMAILS - - _APP_CONSOLE_SESSION_ALERTS - - _APP_CONSOLE_WHITELIST_IPS - - _APP_CONSOLE_HOSTNAMES - - _APP_SYSTEM_EMAIL_NAME - - _APP_SYSTEM_EMAIL_ADDRESS - - _APP_EMAIL_SECURITY - - _APP_SYSTEM_RESPONSE_FORMAT - - _APP_OPTIONS_ABUSE - - _APP_OPTIONS_ROUTER_PROTECTION - - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS - - _APP_OPENSSL_KEY_V1 - - _APP_DOMAIN - - _APP_DOMAIN_TARGET - - _APP_DOMAIN_FUNCTIONS - - _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_SMTP_HOST - - _APP_SMTP_PORT - - _APP_SMTP_SECURE - - _APP_SMTP_USERNAME - - _APP_SMTP_PASSWORD - - _APP_USAGE_STATS - - _APP_STORAGE_LIMIT - - _APP_STORAGE_PREVIEW_LIMIT - - _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_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 appwrite-realtime: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: realtime - container_name: appwrite-realtime <<: *x-logging restart: unless-stopped - networks: - - dokploy-network + labels: + - "traefik.enable=true" + - "traefik.constraint-label-stack=appwrite" depends_on: - mariadb - redis - labels: - - "traefik.enable=true" - - "traefik.constraint-label-stack=appwrite" environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -227,13 +169,10 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-audits: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-audits <<: *x-logging - container_name: appwrite-worker-audits restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -253,13 +192,10 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-webhooks: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-webhooks <<: *x-logging - container_name: appwrite-worker-webhooks restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -281,13 +217,10 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-deletes: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-deletes <<: *x-logging - container_name: appwrite-worker-deletes restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -295,6 +228,7 @@ services: - 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: @@ -315,6 +249,7 @@ services: - _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 @@ -336,16 +271,16 @@ services: - _APP_EXECUTOR_HOST - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_EXECUTION + - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS + - _APP_EMAIL_CERTIFICATES appwrite-worker-databases: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-databases <<: *x-logging - container_name: appwrite-worker-databases restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -365,19 +300,18 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-builds: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-builds <<: *x-logging - container_name: appwrite-worker-builds restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb volumes: - appwrite-functions:/storage/functions:rw + - appwrite-sites:/storage/sites:rw - appwrite-builds:/storage/builds:rw + - appwrite-uploads:/storage/uploads:rw environment: - _APP_ENV - _APP_WORKER_PER_CORE @@ -398,18 +332,20 @@ services: - _APP_VCS_GITHUB_PRIVATE_KEY - _APP_VCS_GITHUB_APP_ID - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_BUILD_TIMEOUT - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY - - _APP_FUNCTIONS_SIZE_LIMIT + - _APP_SITES_TIMEOUT + - _APP_COMPUTE_BUILD_TIMEOUT + - _APP_COMPUTE_CPUS + - _APP_COMPUTE_MEMORY + - _APP_COMPUTE_SIZE_LIMIT - _APP_OPTIONS_FORCE_HTTPS - - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS + - _APP_OPTIONS_ROUTER_FORCE_HTTPS - _APP_DOMAIN - _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 @@ -426,15 +362,13 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET + - _APP_DOMAIN_SITES appwrite-worker-certificates: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-certificates <<: *x-logging - container_name: appwrite-worker-certificates restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -446,7 +380,11 @@ services: - _APP_WORKER_PER_CORE - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A + - _APP_DOMAIN_TARGET_CAA + - _APP_DNS - _APP_DOMAIN_FUNCTIONS - _APP_EMAIL_CERTIFICATES - _APP_REDIS_HOST @@ -461,13 +399,10 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-functions: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-functions <<: *x-logging - container_name: appwrite-worker-functions restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -488,9 +423,10 @@ services: - _APP_DB_USER - _APP_DB_PASS - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_BUILD_TIMEOUT - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY + - _APP_SITES_TIMEOUT + - _APP_COMPUTE_BUILD_TIMEOUT + - _APP_COMPUTE_CPUS + - _APP_COMPUTE_MEMORY - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST - _APP_USAGE_STATS @@ -499,13 +435,10 @@ services: - _APP_LOGGING_CONFIG appwrite-worker-mails: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-mails <<: *x-logging - container_name: appwrite-worker-mails restart: unless-stopped - networks: - - dokploy-network depends_on: - redis environment: @@ -529,15 +462,14 @@ services: - _APP_SMTP_USERNAME - _APP_SMTP_PASSWORD - _APP_LOGGING_CONFIG + - _APP_DOMAIN + - _APP_OPTIONS_FORCE_HTTPS appwrite-worker-messaging: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-messaging - container_name: appwrite-worker-messaging <<: *x-logging restart: unless-stopped - networks: - - dokploy-network volumes: - appwrite-uploads:/storage/uploads:rw depends_on: @@ -563,6 +495,7 @@ services: - _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 @@ -581,13 +514,12 @@ services: - _APP_STORAGE_WASABI_BUCKET appwrite-worker-migrations: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: worker-migrations <<: *x-logging - container_name: appwrite-worker-migrations restart: unless-stopped - networks: - - dokploy-network + volumes: + - appwrite-imports:/storage/imports:rw depends_on: - mariadb environment: @@ -595,7 +527,11 @@ services: - _APP_WORKER_PER_CORE - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _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 @@ -611,20 +547,21 @@ services: - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET appwrite-task-maintenance: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: maintenance <<: *x-logging - container_name: appwrite-task-maintenance restart: unless-stopped - networks: - - dokploy-network depends_on: - redis environment: - _APP_ENV - _APP_WORKER_PER_CORE - _APP_DOMAIN - - _APP_DOMAIN_TARGET + - _APP_DOMAIN_TARGET_CNAME + - _APP_DOMAIN_TARGET_AAAA + - _APP_DOMAIN_TARGET_A + - _APP_DOMAIN_TARGET_CAA + - _APP_DNS - _APP_DOMAIN_FUNCTIONS - _APP_OPENSSL_KEY_V1 - _APP_REDIS_HOST @@ -641,17 +578,15 @@ services: - _APP_MAINTENANCE_RETENTION_CACHE - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT + - _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - appwrite-worker-usage: - image: appwrite/appwrite:1.6.1 - entrypoint: worker-usage - container_name: appwrite-worker-usage + appwrite-task-stats-resources: + image: appwrite/appwrite:1.8.0 + entrypoint: stats-resources <<: *x-logging restart: unless-stopped - networks: - - dokploy-network depends_on: - redis - mariadb @@ -670,15 +605,39 @@ services: - _APP_REDIS_PASS - _APP_USAGE_STATS - _APP_LOGGING_CONFIG - - _APP_USAGE_AGGREGATION_INTERVAL + - _APP_DATABASE_SHARED_TABLES + - _APP_STATS_RESOURCES_INTERVAL - appwrite-worker-usage-dump: - image: appwrite/appwrite:1.6.1 - entrypoint: worker-usage-dump - container_name: appwrite-worker-usage-dump + appwrite-worker-stats-resources: + image: appwrite/appwrite:1.8.0 + entrypoint: worker-stats-resources <<: *x-logging - networks: - - dokploy-network + restart: unless-stopped + depends_on: + - redis + - mariadb + environment: + - _APP_ENV + - _APP_WORKER_PER_CORE + - _APP_OPENSSL_KEY_V1 + - _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_USAGE_STATS + - _APP_LOGGING_CONFIG + - _APP_STATS_RESOURCES_INTERVAL + + appwrite-worker-stats-usage: + image: appwrite/appwrite:1.8.0 + entrypoint: worker-stats-usage + <<: *x-logging + restart: unless-stopped depends_on: - redis - mariadb @@ -700,13 +659,10 @@ services: - _APP_USAGE_AGGREGATION_INTERVAL appwrite-task-scheduler-functions: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: schedule-functions - container_name: appwrite-task-scheduler-functions <<: *x-logging restart: unless-stopped - networks: - - dokploy-network depends_on: - mariadb - redis @@ -725,13 +681,10 @@ services: - _APP_DB_PASS appwrite-task-scheduler-executions: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: schedule-executions - container_name: appwrite-task-scheduler-executions <<: *x-logging restart: unless-stopped - networks: - - dokploy-network depends_on: - mariadb - redis @@ -750,13 +703,10 @@ services: - _APP_DB_PASS appwrite-task-scheduler-messages: - image: appwrite/appwrite:1.6.1 + image: appwrite/appwrite:1.8.0 entrypoint: schedule-messages - container_name: appwrite-task-scheduler-messages <<: *x-logging restart: unless-stopped - networks: - - dokploy-network depends_on: - mariadb - redis @@ -775,44 +725,48 @@ services: - _APP_DB_PASS appwrite-assistant: - image: appwrite/assistant:0.4.0 - container_name: appwrite-assistant + image: appwrite/assistant:0.8.3 <<: *x-logging restart: unless-stopped - networks: - - dokploy-network environment: - _APP_ASSISTANT_OPENAI_API_KEY + appwrite-browser: + image: appwrite/browser:0.2.4 + <<: *x-logging + restart: unless-stopped + openruntimes-executor: - container_name: openruntimes-executor hostname: exc1 <<: *x-logging restart: unless-stopped stop_signal: SIGINT - image: openruntimes/executor:0.6.11 - networks: - - dokploy-network + image: openruntimes/executor:0.7.22 volumes: - /var/run/docker.sock:/var/run/docker.sock - appwrite-builds:/storage/builds:rw - appwrite-functions:/storage/functions:rw + - appwrite-sites:/storage/sites:rw + # Host mount necessary to share files between executor and runtimes. + # It's not possible to share mount file between 2 containers without host mount (copying is too slow) - /tmp:/tmp:rw environment: - - OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD - - OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL - - OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK + - OPR_EXECUTOR_INACTIVE_TRESHOLD=$_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 + - OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES,$_APP_SITES_RUNTIMES - OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET + - OPR_EXECUTOR_RUNTIME_VERSIONS=v5 - OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG - OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE - OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY - OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET - OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION - OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET + - OPR_EXECUTOR_STORAGE_S3_ENDPOINT=$_APP_STORAGE_S3_ENDPOINT - OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY - OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET - OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION @@ -831,12 +785,9 @@ services: - OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET mariadb: - image: mariadb:10.11 - container_name: appwrite-mariadb + image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p <<: *x-logging restart: unless-stopped - networks: - - dokploy-network volumes: - appwrite-mariadb:/var/lib/mysql:rw environment: @@ -849,39 +800,30 @@ services: redis: image: redis:7.2.4-alpine - container_name: appwrite-redis <<: *x-logging restart: unless-stopped command: > redis-server - --maxmemory 512mb - --maxmemory-policy allkeys-lru - --maxmemory-samples 5 - networks: - - dokploy-network + --maxmemory 512mb + --maxmemory-policy allkeys-lru + --maxmemory-samples 5 volumes: - appwrite-redis:/data:rw -# Uncomment and configure if ClamAV is needed -# clamav: -# image: appwrite/clamav:1.2.0 -# container_name: appwrite-clamav -# restart: unless-stopped -# networks: -# - dokploy-network -# volumes: -# - appwrite-uploads:/storage/uploads + # clamav: + # image: appwrite/clamav:1.2.0 + # restart: unless-stopped + # volumes: + # - appwrite-uploads:/storage/uploads volumes: appwrite-mariadb: appwrite-redis: appwrite-cache: appwrite-uploads: + appwrite-imports: appwrite-certificates: appwrite-functions: + appwrite-sites: appwrite-builds: appwrite-config: - -networks: - dokploy-network: - external: true diff --git a/blueprints/appwrite/template.toml b/blueprints/appwrite/template.toml index 3188ea17..cca2d43d 100644 --- a/blueprints/appwrite/template.toml +++ b/blueprints/appwrite/template.toml @@ -1,5 +1,11 @@ [variables] main_domain = "${domain}" +functions_domain = "functions.${main_domain}" +sites_domain = "sites.${main_domain}" +openssl_key = "${password:32}" +db_root_pw = "${password:32}" +db_user_pw = "${password:32}" +executor_secret = "${password:32}" [config] env = [ @@ -8,11 +14,19 @@ env = [ "_APP_OPTIONS_ABUSE=enabled", "_APP_OPTIONS_FORCE_HTTPS=disabled", "_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled", + "_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled", "_APP_OPTIONS_ROUTER_PROTECTION=disabled", - "_APP_OPENSSL_KEY_V1=your-secret-key", + "_APP_OPENSSL_KEY_V1=${openssl_key}", "_APP_DOMAIN=${main_domain}", - "_APP_DOMAIN_FUNCTIONS=${main_domain}", - "_APP_DOMAIN_TARGET=${main_domain}", + "_APP_CUSTOM_DOMAIN_DENY_LIST=example.com,test.com,app.example.com", + "_APP_DOMAIN_FUNCTIONS=${functions_domain}", + "_APP_DOMAIN_SITES=${sites_domain}", + "_APP_DOMAIN_TARGET=localhost", + "_APP_DOMAIN_TARGET_CNAME=localhost", + "_APP_DOMAIN_TARGET_AAAA=::1", + "_APP_DOMAIN_TARGET_A=127.0.0.1", + "_APP_DOMAIN_TARGET_CAA=", + "_APP_DNS=8.8.8.8", "_APP_CONSOLE_WHITELIST_ROOT=enabled", "_APP_CONSOLE_WHITELIST_EMAILS=", "_APP_CONSOLE_WHITELIST_IPS=", @@ -32,6 +46,8 @@ env = [ "_APP_USAGE_DATABASE_INTERVAL=900", "_APP_WORKER_PER_CORE=6", "_APP_CONSOLE_SESSION_ALERTS=disabled", + "_APP_COMPRESSION_ENABLED=enabled", + "_APP_COMPRESSION_MIN_SIZE_BYTES=1024", "_APP_REDIS_HOST=redis", "_APP_REDIS_PORT=6379", "_APP_REDIS_USER=", @@ -40,8 +56,8 @@ env = [ "_APP_DB_PORT=3306", "_APP_DB_SCHEMA=appwrite", "_APP_DB_USER=user", - "_APP_DB_PASS=password", - "_APP_DB_ROOT_PASS=rootsecretpassword", + "_APP_DB_PASS=${db_user_pw}", + "_APP_DB_ROOT_PASS=${db_root_pw}", "_APP_INFLUXDB_HOST=influxdb", "_APP_INFLUXDB_PORT=8086", "_APP_STATSD_HOST=telegraf", @@ -63,6 +79,7 @@ env = [ "_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", @@ -80,27 +97,36 @@ env = [ "_APP_STORAGE_WASABI_REGION=eu-central-1", "_APP_STORAGE_WASABI_BUCKET=", "_APP_FUNCTIONS_SIZE_LIMIT=30000000", + "_APP_COMPUTE_SIZE_LIMIT=30000000", "_APP_FUNCTIONS_BUILD_SIZE_LIMIT=2000000000", "_APP_FUNCTIONS_TIMEOUT=900", "_APP_FUNCTIONS_BUILD_TIMEOUT=900", + "_APP_COMPUTE_BUILD_TIMEOUT=900", "_APP_FUNCTIONS_CONTAINERS=10", "_APP_FUNCTIONS_CPUS=0", + "_APP_COMPUTE_CPUS=0", "_APP_FUNCTIONS_MEMORY=0", + "_APP_COMPUTE_MEMORY=0", "_APP_FUNCTIONS_MEMORY_SWAP=0", "_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0", - "_APP_EXECUTOR_SECRET=your-secret-key", + "_APP_EXECUTOR_SECRET=${executor_secret}", "_APP_EXECUTOR_HOST=http://exc1/v1", "_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes", "_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0", "_APP_FUNCTIONS_INACTIVE_THRESHOLD=60", + "_APP_COMPUTE_INACTIVE_THRESHOLD=60", "DOCKERHUB_PULL_USERNAME=", "DOCKERHUB_PULL_PASSWORD=", "DOCKERHUB_PULL_EMAIL=", "OPEN_RUNTIMES_NETWORK=appwrite_runtimes", - "_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes", + "_APP_FUNCTIONS_RUNTIMES_NETWORK=dokploy-network", + "_APP_COMPUTE_RUNTIMES_NETWORK=dokploy-network", "_APP_DOCKER_HUB_USERNAME=", "_APP_DOCKER_HUB_PASSWORD=", "_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600", + "_APP_COMPUTE_MAINTENANCE_INTERVAL=3600", + "_APP_SITES_TIMEOUT=900", + "_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.29", "_APP_VCS_GITHUB_APP_NAME=", "_APP_VCS_GITHUB_PRIVATE_KEY=", "_APP_VCS_GITHUB_APP_ID=", @@ -109,9 +135,11 @@ env = [ "_APP_VCS_GITHUB_WEBHOOK_SECRET=", "_APP_MAINTENANCE_INTERVAL=86400", "_APP_MAINTENANCE_DELAY=0", + "_APP_MAINTENANCE_START_TIME=00:00", "_APP_MAINTENANCE_RETENTION_CACHE=2592000", "_APP_MAINTENANCE_RETENTION_EXECUTION=1209600", "_APP_MAINTENANCE_RETENTION_AUDIT=1209600", + "_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800", "_APP_MAINTENANCE_RETENTION_ABUSE=86400", "_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000", "_APP_MAINTENANCE_RETENTION_SCHEDULES=86400", @@ -130,6 +158,18 @@ port = 80 host = "${main_domain}" path = "/" +[[config.domains]] +serviceName = "appwrite" +port = 80 +host = "${sites_domain}" +path = "/" + +[[config.domains]] +serviceName = "appwrite" +port = 80 +host = "${functions_domain}" +path = "/" + [[config.domains]] serviceName = "appwrite-console" port = 80 diff --git a/blueprints/autobase/docker-compose.yml b/blueprints/autobase/docker-compose.yml index 07b99485..13b23d57 100644 --- a/blueprints/autobase/docker-compose.yml +++ b/blueprints/autobase/docker-compose.yml @@ -1,6 +1,6 @@ services: autobase-console: - image: autobase/console:2.3.0 + image: autobase/console:2.5.2 restart: unless-stopped ports: - "80" diff --git a/blueprints/capso/docker-compose.yml b/blueprints/capso/docker-compose.yml index 637b1ded..4714ef0c 100644 --- a/blueprints/capso/docker-compose.yml +++ b/blueprints/capso/docker-compose.yml @@ -17,6 +17,9 @@ services: S3_INTERNAL_ENDPOINT: "http://minio:3902" expose: - 3000 + depends_on: + - ps-mysql + - minio ps-mysql: image: mysql:8.0 @@ -34,11 +37,10 @@ services: - ps-mysql:/var/lib/mysql minio: - image: bitnami/minio:latest + image: quay.io/minio/minio:RELEASE.2025-05-24T17-08-30Z restart: unless-stopped + command: server /bitnami/minio/data --address ":3902" --console-address ":3903" environment: - MINIO_API_PORT_NUMBER: 3902 - MINIO_CONSOLE_PORT_NUMBER: 3903 MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} expose: @@ -47,6 +49,11 @@ services: volumes: - minio-data:/bitnami/minio/data - minio-certs:/certs + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3902/minio/health/ready"] + interval: 30s + timeout: 20s + retries: 3 volumes: ps-mysql: diff --git a/blueprints/cloudreve/cloudreve.png b/blueprints/cloudreve/cloudreve.png new file mode 100644 index 0000000000000000000000000000000000000000..28020b06c9b4d150b9253d4cb4ee6b5fe4b25c04 GIT binary patch literal 6938 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 literal 0 HcmV?d00001 diff --git a/blueprints/cloudreve/docker-compose.yml b/blueprints/cloudreve/docker-compose.yml new file mode 100644 index 00000000..f90f3685 --- /dev/null +++ b/blueprints/cloudreve/docker-compose.yml @@ -0,0 +1,48 @@ +version: "3.8" + +services: + cloudreve: + image: cloudreve/cloudreve:4.10.1 + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_started + restart: unless-stopped + environment: + - CR_CONF_Database.Type=postgres + - CR_CONF_Database.Host=postgresql + - CR_CONF_Database.User=${POSTGRES_USER} + - CR_CONF_Database.Password=${POSTGRES_PASSWORD} + - CR_CONF_Database.Name=${POSTGRES_DB} + - CR_CONF_Database.Port=5432 + - CR_CONF_Redis.Server=redis:6379 + volumes: + - cloudreve_data:/cloudreve/data + + postgresql: + image: postgres:17 + restart: unless-stopped + environment: + - POSTGRES_USER + - POSTGRES_PASSWORD + - POSTGRES_DB + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + redis: + image: redis:8.4.0 + restart: unless-stopped + volumes: + - redis_data:/data + +volumes: + cloudreve_data: + postgres_data: + redis_data: diff --git a/blueprints/cloudreve/template.toml b/blueprints/cloudreve/template.toml new file mode 100644 index 00000000..a5acf6b4 --- /dev/null +++ b/blueprints/cloudreve/template.toml @@ -0,0 +1,16 @@ +[variables] +main_domain = "${domain}" +db_password = "${password:32}" +db_user = "cloudreve" +db_name = "cloudreve" + +[config] +[[config.domains]] +serviceName = "cloudreve" +port = 5212 +host = "${main_domain}" + +[config.env] +POSTGRES_PASSWORD = "${db_password}" +POSTGRES_USER = "${db_user}" +POSTGRES_DB = "${db_name}" diff --git a/blueprints/discourse/docker-compose.yml b/blueprints/discourse/docker-compose.yml index 2b938b85..caafab5b 100644 --- a/blueprints/discourse/docker-compose.yml +++ b/blueprints/discourse/docker-compose.yml @@ -32,7 +32,7 @@ services: restart: unless-stopped discourse-app: - image: docker.io/bitnami/discourse:3.3.2 + image: docker.io/bitnamilegacy/discourse:3.5.0 volumes: - discourse-data:/bitnami/discourse @@ -59,7 +59,7 @@ services: restart: unless-stopped discourse-sidekiq: - image: docker.io/bitnami/discourse:3.3.2 + image: docker.io/bitnamilegacy/discourse:3.5.0 volumes: - discourse-sidekiq-data:/bitnami/discourse diff --git a/blueprints/dolibarr/docker-compose.yml b/blueprints/dolibarr/docker-compose.yml new file mode 100644 index 00000000..9e364c46 --- /dev/null +++ b/blueprints/dolibarr/docker-compose.yml @@ -0,0 +1,40 @@ +services: + dolibarr: + image: dolibarr/dolibarr:21.0.0 + restart: always + environment: + DOLI_DB_HOST: db + DOLI_DB_NAME: $DB_NAME + DOLI_DB_USER: $DB_USER + DOLI_DB_PASSWORD: $DB_PASSWORD + DOLI_URL_ROOT: ${DOLIBARR_HOST} + DOLI_ADMIN_LOGIN: admin + DOLI_ADMIN_PASSWORD: $ADMIN_PASSWORD + volumes: + - dolibarr_documents:/var/www/documents + - dolibarr_html:/var/www/html + depends_on: + db: + condition: service_healthy + + db: + image: mariadb:10.11 + restart: always + environment: + MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD + MYSQL_DATABASE: $DB_NAME + MYSQL_USER: $DB_USER + MYSQL_PASSWORD: $DB_PASSWORD + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + dolibarr_documents: + dolibarr_html: + db_data: diff --git a/blueprints/dolibarr/logo.svg b/blueprints/dolibarr/logo.svg new file mode 100644 index 00000000..94b7c1a6 --- /dev/null +++ b/blueprints/dolibarr/logo.svg @@ -0,0 +1 @@ + diff --git a/blueprints/dolibarr/template.toml b/blueprints/dolibarr/template.toml new file mode 100644 index 00000000..923b0de7 --- /dev/null +++ b/blueprints/dolibarr/template.toml @@ -0,0 +1,23 @@ +[variables] +main_domain = "${domain}" +db_name = "dolibarr" +db_user = "dolibarr" +db_password = "${password:32}" +db_root_password = "${password:32}" +admin_password = "${password:32}" + +[config] +env = [ + "DOLIBARR_HOST=${main_domain}", + "DB_NAME=${db_name}", + "DB_USER=${db_user}", + "DB_PASSWORD=${db_password}", + "DB_ROOT_PASSWORD=${db_root_password}", + "ADMIN_PASSWORD=${admin_password}" +] +mounts = [] + +[[config.domains]] +serviceName = "dolibarr" +port = 80 +host = "${main_domain}" diff --git a/blueprints/easyappointments/docker-compose.yml b/blueprints/easyappointments/docker-compose.yml new file mode 100644 index 00000000..ee2e1edf --- /dev/null +++ b/blueprints/easyappointments/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.8" + +services: + easyappointments: + image: alextselegidis/easyappointments:1.5.0 + restart: unless-stopped + environment: + - BASE_URL=http://${DOMAIN} + - DB_HOST=mysql + - DB_NAME=easyappointments + - DB_USERNAME=root + - DB_PASSWORD=${DB_PASSWORD} + volumes: + - easyappointments:/var/www/html + depends_on: + - mysql + + mysql: + image: mysql:8.0 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=easyappointments + volumes: + - mysql:/var/lib/mysql + +volumes: + easyappointments: + mysql: diff --git a/blueprints/easyappointments/logo.png b/blueprints/easyappointments/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..422670b98f375aeaefd0394f980c2ba044f0c538 GIT binary patch literal 5535 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 literal 0 HcmV?d00001 diff --git a/blueprints/easyappointments/template.toml b/blueprints/easyappointments/template.toml new file mode 100644 index 00000000..997ee0e1 --- /dev/null +++ b/blueprints/easyappointments/template.toml @@ -0,0 +1,13 @@ +[variables] +main_domain = "${domain}" +db_password = "${password:32}" + +[config] +[[config.domains]] +serviceName = "easyappointments" +port = 80 +host = "${main_domain}" + +[config.env] +DOMAIN = "${main_domain}" +DB_PASSWORD = "${db_password}" diff --git a/blueprints/emqx/docker-compose.yml b/blueprints/emqx/docker-compose.yml new file mode 100644 index 00000000..4b11f010 --- /dev/null +++ b/blueprints/emqx/docker-compose.yml @@ -0,0 +1,58 @@ +# This templates requires additional traefik port mapping and entry point +# for port 8883 (mqtts over TCP) +# +# For the full instructions, refer to: +# - https://github.com/Dokploy/dokploy/discussions/3126 +# +# The initial login credentials are: +# - USERNAME: admin +# - PASSWORD: public + +services: + emqx: + image: emqx/emqx-enterprise:6.0.1 + hostname: node1.emqx.com + environment: + EMQX_NODE_NAME: emqx@node1.emqx.com + expose: + - 1883 # MQTT + - 8083 # WS + - 18083 # Dashboard + volumes: + - emqx_data:/opt/emqx/data + - emqx_log:/opt/emqx/log + networks: + dokploy-network: + aliases: + - emqx-service + restart: unless-stopped + healthcheck: + test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"] + interval: 30s + timeout: 5s + retries: 3 + labels: + # MQTT over TLS + - "traefik.tcp.routers.emqx-mqtts.entrypoints=mqtts" + - "traefik.tcp.routers.emqx-mqtts.rule=HostSNI(`broker.yourdomain.com`)" # Change domain + - "traefik.tcp.routers.emqx-mqtts.tls.certresolver=letsencrypt" + - "traefik.tcp.routers.emqx-mqtts.service=emqx-service" + - "traefik.tcp.services.emqx-service.loadbalancer.server.port=1883" + + # + # Optional Web UI: + # - https://github.com/emqx/MQTTX/tree/main/web + # + # mqttx-web: + # image: emqx/mqttx-web:latest + # expose: + # - 80 + # + +volumes: + emqx_data: + emqx_log: + +networks: + dokploy-network: + external: true diff --git a/blueprints/emqx/emqx.svg b/blueprints/emqx/emqx.svg new file mode 100644 index 00000000..ce0c1ec9 --- /dev/null +++ b/blueprints/emqx/emqx.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/blueprints/emqx/template.toml b/blueprints/emqx/template.toml new file mode 100644 index 00000000..5fc03e5f --- /dev/null +++ b/blueprints/emqx/template.toml @@ -0,0 +1,11 @@ +[variables] + +[[config.domains]] +serviceName = "emqx" +port = 18083 +host = "emqx.yourdomain.com" + +[[config.domains]] +serviceName = "emqx" +port = 8083 +host = "broker.yourdomain.com" diff --git a/blueprints/excalidraw/docker-compose.yml b/blueprints/excalidraw/docker-compose.yml index d68065c0..9f2c3091 100644 --- a/blueprints/excalidraw/docker-compose.yml +++ b/blueprints/excalidraw/docker-compose.yml @@ -1,5 +1,8 @@ +version: "3.8" + services: excalidraw: + restart: unless-stopped image: excalidraw/excalidraw:latest healthcheck: test: diff --git a/blueprints/instantdb/docker-compose.yml b/blueprints/instantdb/docker-compose.yml new file mode 100644 index 00000000..b40e043d --- /dev/null +++ b/blueprints/instantdb/docker-compose.yml @@ -0,0 +1,64 @@ +services: + postgres: + image: ghcr.io/instantdb/postgresql:postgresql-16-pg-hint-plan + restart: unless-stopped + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - backend-db:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + command: + - "postgres" + - "-c" + - "wal_level=logical" + - "-c" + - "max_replication_slots=4" + - "-c" + - "max_wal_senders=4" + - "-c" + - "shared_preload_libraries=pg_hint_plan" + - "-c" + - "random_page_cost=1.1" + server: + depends_on: + postgres: + condition: service_healthy + image: hari1367709/instantdb-server:latest + restart: unless-stopped + working_dir: /app + environment: + DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}" + NREPL_BIND_ADDRESS: "0.0.0.0" + PORT: "8888" + HOST: "0.0.0.0" + # AWS Credentials for KMS (required for InstantDB) + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_REGION: ${AWS_REGION:-us-east-1} + # Force unbuffered output for logs + PYTHONUNBUFFERED: "1" + NODE_ENV: "production" + command: ["java", "-Djava.awt.headless=true", "-server", "-jar", "target/instant-standalone.jar"] + ports: + - "8888" + - "6005" + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8888/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s +volumes: + backend-db: + diff --git a/blueprints/instantdb/instant.svg b/blueprints/instantdb/instant.svg new file mode 100644 index 00000000..4e2dc24c --- /dev/null +++ b/blueprints/instantdb/instant.svg @@ -0,0 +1,4 @@ + + + + diff --git a/blueprints/instantdb/template.toml b/blueprints/instantdb/template.toml new file mode 100644 index 00000000..571e6b7a --- /dev/null +++ b/blueprints/instantdb/template.toml @@ -0,0 +1,29 @@ +[variables] +main_domain = "${domain}" +postgres_password = "${password:32}" +postgres_user = "instant" +postgres_db = "instant" +aws_access_key_id = "FILL-YOUR-CREDENTIALS" +aws_secret_access_key = "FILL-YOUR-CREDENTIALS" +aws_region = "us-east-1" + +[config] +env = [ + "POSTGRES_PASSWORD=${postgres_password}", + "POSTGRES_USER=${postgres_user}", + "POSTGRES_DB=${postgres_db}", + "DATABASE_URL=postgresql://${postgres_user}:${postgres_password}@postgres:5432/${postgres_db}", + "NREPL_BIND_ADDRESS=0.0.0.0", + "PORT=8888", + "HOST=0.0.0.0", + "AWS_ACCESS_KEY_ID=${aws_access_key_id}", + "AWS_SECRET_ACCESS_KEY=${aws_secret_access_key}", + "AWS_REGION=${aws_region}", +] +mounts = [] + +[[config.domains]] +serviceName = "server" +port = 8888 +host = "${main_domain}" + diff --git a/blueprints/jenkins/docker-compose.yml b/blueprints/jenkins/docker-compose.yml new file mode 100644 index 00000000..f2b8ad3c --- /dev/null +++ b/blueprints/jenkins/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.8" + +services: + jenkins: + image: jenkins/jenkins:latest + volumes: + - jenkins-home:/var/jenkins_home + - /var/run/docker.sock:/var/run/docker.sock + ports: + - 8080 + +volumes: + jenkins-home: {} \ No newline at end of file diff --git a/blueprints/jenkins/jenkins.svg b/blueprints/jenkins/jenkins.svg new file mode 100644 index 00000000..b511310d --- /dev/null +++ b/blueprints/jenkins/jenkins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/blueprints/jenkins/template.toml b/blueprints/jenkins/template.toml new file mode 100644 index 00000000..46a84921 --- /dev/null +++ b/blueprints/jenkins/template.toml @@ -0,0 +1,12 @@ +[variables] +main_domain = "${domain}" + +[config] +env = {} +mounts = [] + +[[config.domains]] +serviceName = "jenkins" +port = 8080 +host = "${main_domain}" + diff --git a/blueprints/komari-monitor/docker-compose.yml b/blueprints/komari-monitor/docker-compose.yml new file mode 100644 index 00000000..149999b0 --- /dev/null +++ b/blueprints/komari-monitor/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.8" +services: + komari: + image: ghcr.io/komari-monitor/komari:latest + restart: unless-stopped + volumes: + - komari-data:/app/data + environment: + - ADMIN_USERNAME + - ADMIN_PASSWORD + +volumes: + komari-data: {} diff --git a/blueprints/komari-monitor/komari-monitor.ico b/blueprints/komari-monitor/komari-monitor.ico new file mode 100644 index 0000000000000000000000000000000000000000..f314b8f2ac230d50ed23c5d54dea5f7c90531121 GIT binary patch literal 40832 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 + sh -c "mkdir -p /opt/Lavalink/plugins && + chmod -R 0777 /opt/Lavalink/plugins || true && + chown -R 1000:1000 /opt/Lavalink/plugins || true && + echo perms-fixed && sleep 1" + volumes: + - "../files/plugins/:/opt/Lavalink/plugins/" + - "../files/application.yml:/opt/Lavalink/application.yml" + restart: "no" + lavalink: + image: ghcr.io/lavalink-devs/lavalink:4 + depends_on: + - fix-perms + restart: unless-stopped + environment: + _JAVA_OPTIONS: "${_JAVA_OPTIONS:--Xmx6G}" + LAVALINK_SERVER_PASSWORD: "${LAVALINK_SERVER_PASSWORD:-youshallnotpass}" + SERVER_PORT: "${SERVER_PORT:-2333}" + volumes: + - "../files/application.yml:/opt/Lavalink/application.yml:rw" + - "../files/plugins/:/opt/Lavalink/plugins/:rw" + ports: + - ${SERVER_PORT} + + healthcheck: + test: > + sh -c 'wget --header="Authorization: ${LAVALINK_SERVER_PASSWORD}" -qO- http://127.0.0.1:${SERVER_PORT}/v4/info >/dev/null 2>&1 || exit 1' + interval: 100s + timeout: 5s + retries: 5 + start_period: 30s + + entrypoint: > + sh -c 'until [ -w /opt/Lavalink/plugins ] ; do + echo "waiting for /opt/Lavalink/plugins to be writable"; + sleep 1; + done; + exec java -jar /opt/Lavalink/Lavalink.jar' diff --git a/blueprints/lavalink/lavalink.svg b/blueprints/lavalink/lavalink.svg new file mode 100644 index 00000000..0ade923f --- /dev/null +++ b/blueprints/lavalink/lavalink.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/blueprints/lavalink/template.toml b/blueprints/lavalink/template.toml new file mode 100644 index 00000000..6c9a92cd --- /dev/null +++ b/blueprints/lavalink/template.toml @@ -0,0 +1,123 @@ +[variables] +main_domain = "${domain}" +server_port = "2333" +lavalink_server_password = "${password}" + +[config] +[[config.mounts]] +filePath = "./application.yml" +content = """ +server: # REST and WS server + port: 2333 + address: 0.0.0.0 + http2: + enabled: false +plugins: +# name: # Name of the plugin +# some_key: some_value # Some key-value pair for the plugin +# another_key: another_value +lavalink: + plugins: + # - dependency: "com.github.username.pluginName:pluginName-plugin:x.y.z" + # snapshot: false + # pluginsDir: "/opt/Lavalink/plugins" + # defaultPluginRepository: "https://maven.lavalink.dev/releases" + # defaultPluginSnapshotRepository: "https://maven.lavalink.dev/snapshots" + server: + password: "youshallnotpass" + sources: + # The default Youtube source is now deprecated and won't receive further updates. Please use https://github.com/lavalink-devs/youtube-source#plugin instead. + youtube: true + bandcamp: true + soundcloud: true + twitch: true + vimeo: true + nico: true + http: true + local: false + filters: + volume: true + equalizer: true + karaoke: true + timescale: true + tremolo: true + vibrato: true + distortion: true + rotation: true + channelMix: true + lowPass: true + nonAllocatingFrameBuffer: false + bufferDurationMs: 400 + frameBufferDurationMs: 5000 + opusEncodingQuality: 10 + resamplingQuality: LOW + trackStuckThresholdMs: 10000 + useSeekGhosting: true + youtubePlaylistLoadLimit: 6 + playerUpdateInterval: 5 + youtubeSearchEnabled: true + soundcloudSearchEnabled: true + gc-warnings: true + #ratelimit: + #ipBlocks: ["1.0.0.0/8", "..."] # list of ip blocks + #excludedIps: ["...", "..."] # ips which should be explicit excluded from usage by lavalink + #strategy: "RotateOnBan" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch + #searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing + #retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times + #youtubeConfig: # Required for avoiding all age restrictions by YouTube, some restricted videos still can be played without. + #email: "" # Email of Google account + #password: "" # Password of Google account + #httpConfig: # Useful for blocking bad-actors from ip-grabbing your music node and attacking it, this way only the http proxy will be attacked + #proxyHost: "localhost" # Hostname of the proxy, (ip or domain) + #proxyPort: 3128 # Proxy port, 3128 is the default for squidProxy + #proxyUser: "" # Optional user for basic authentication fields, leave blank if you don't use basic auth + #proxyPassword: "" # Password for basic authentication + timeouts: + connectTimeoutMs: 3000 + connectionRequestTimeoutMs: 3000 + socketTimeoutMs: 3000 + +metrics: + prometheus: + enabled: false + endpoint: /metrics + +sentry: + dsn: "" + environment: "" +# tags: +# some_key: some_value +# another_key: another_value + +logging: + file: + path: ./logs/ + + level: + root: INFO + lavalink: INFO + + request: + enabled: true + includeClientInfo: true + includeHeaders: false + includeQueryString: true + includePayload: true + maxPayloadLength: 10000 + + + logback: + rollingpolicy: + max-file-size: 1GB + max-history: 30 +""" + +[[config.domains]] +serviceName = "lavalink" +port = 2_333 +host = "${main_domain}" + +[config.env] +_JAVA_OPTIONS = "-Xmx6G" +LAVALINK_SERVER_PASSWORD = "${lavalink_server_password}" +SERVER_PORT = "${server_port}" diff --git a/blueprints/mage-ai/docker-compose.yml b/blueprints/mage-ai/docker-compose.yml new file mode 100644 index 00000000..7c9bbc23 --- /dev/null +++ b/blueprints/mage-ai/docker-compose.yml @@ -0,0 +1,26 @@ +# https://docs.mage.ai/getting-started/setup#docker-compose +# +# The default credentials are: +# USERNAME: admin@admin.com +# PASSWORD: admin + +services: + mage-ai: + image: mageai/mageai:0.9.78 + command: mage start ${PROJECT_NAME} + environment: + USER_CODE_PATH: /home/src/${PROJECT_NAME} + ENV: ${ENV} + expose: + - 6789 + volumes: + - mageai_data:/home/src/ + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-s", "-f", "-o", "/dev/null", "http://localhost:6789"] + interval: 30s + timeout: 10s + retries: 5 + +volumes: + mageai_data: diff --git a/blueprints/mage-ai/mage-ai.svg b/blueprints/mage-ai/mage-ai.svg new file mode 100644 index 00000000..d45a7d1b --- /dev/null +++ b/blueprints/mage-ai/mage-ai.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/blueprints/mage-ai/template.toml b/blueprints/mage-ai/template.toml new file mode 100644 index 00000000..12cd84e6 --- /dev/null +++ b/blueprints/mage-ai/template.toml @@ -0,0 +1,11 @@ +[variables] +main_domain = "${domain}" + +[[config.domains]] +serviceName = "mage-ai" +port = 6789 +host = "${main_domain}" + +[config.env] +PROJECT_NAME = "mage-ai" +ENV = "production" diff --git a/blueprints/mautic/docker-compose.yml b/blueprints/mautic/docker-compose.yml new file mode 100644 index 00000000..223ea7ee --- /dev/null +++ b/blueprints/mautic/docker-compose.yml @@ -0,0 +1,131 @@ +version: "3.8" + +services: + # ------------------------------------------------------------------------- + # Service 1: Database + # ------------------------------------------------------------------------- + mysql: + image: mysql:8.0 + command: --default-authentication-plugin=mysql_native_password + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MAUTIC_DB_DATABASE} + MYSQL_USER: ${MAUTIC_DB_USER} + MYSQL_PASSWORD: ${MAUTIC_DB_PASSWORD} + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + + # ------------------------------------------------------------------------- + # Service 2: Mautic Web (The Leader) + # ------------------------------------------------------------------------- + mautic: + image: mautic/mautic:5.1.1-apache + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + ports: + - 80 + environment: + - DOCKER_MAUTIC_ROLE=mautic_web + - DOCKER_MAUTIC_RUN_MIGRATIONS=true + - MAUTIC_DB_HOST=${MAUTIC_DB_HOST} + - MAUTIC_DB_PORT=${MAUTIC_DB_PORT} + - MAUTIC_DB_DATABASE=${MAUTIC_DB_DATABASE} + - MAUTIC_DB_USER=${MAUTIC_DB_USER} + - MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD} + - MAUTIC_URL=${MAUTIC_URL} + - MAUTIC_TRUSTED_PROXIES=${MAUTIC_TRUSTED_PROXIES} + - MAUTIC_MESSENGER_DSN_EMAIL=${MAUTIC_MESSENGER_DSN_EMAIL} + - MAUTIC_MESSENGER_DSN_HIT=${MAUTIC_MESSENGER_DSN_HIT} + - PHP_INI_DATE_TIMEZONE=${PHP_INI_DATE_TIMEZONE} + - PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT} + volumes: + - mautic_data:/var/www/html + # AUTOMATION FIX 1: Force permissions to be correct on every start + entrypoint: ["/bin/sh", "-c", "chown -R www-data:www-data /var/www/html && /entrypoint.sh apache2-foreground"] + # AUTOMATION FIX 2: Check if the CONFIG FILE exists. If not, report 'unhealthy'. + # This signals the other containers to keep waiting. + healthcheck: + test: ["CMD-SHELL", "test -f /var/www/html/config/local.php || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 300s # Give you 5 mins to run the installer before marking failed + + # ------------------------------------------------------------------------- + # Service 3: Mautic Cron (Waits for Install) + # ------------------------------------------------------------------------- + mautic-cron: + image: mautic/mautic:5.1.1-apache + restart: unless-stopped + depends_on: + mautic: + condition: service_healthy # AUTOMATION FIX 3: Do not start until config file exists + environment: + - DOCKER_MAUTIC_ROLE=mautic_cron + - MAUTIC_DB_HOST=${MAUTIC_DB_HOST} + - MAUTIC_DB_PORT=${MAUTIC_DB_PORT} + - MAUTIC_DB_DATABASE=${MAUTIC_DB_DATABASE} + - MAUTIC_DB_USER=${MAUTIC_DB_USER} + - MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD} + - MAUTIC_URL=${MAUTIC_URL} + - PHP_INI_DATE_TIMEZONE=${PHP_INI_DATE_TIMEZONE} + volumes: + - mautic_data:/var/www/html + + # ------------------------------------------------------------------------- + # Service 4: Mautic Worker (Waits for Install) + # ------------------------------------------------------------------------- + mautic-worker: + image: mautic/mautic:5.1.1-apache + restart: unless-stopped + depends_on: + mautic: + condition: service_healthy # AUTOMATION FIX 3: Do not start until config file exists + deploy: + resources: + limits: + memory: 512M + environment: + - DOCKER_MAUTIC_ROLE=mautic_worker + - DOCKER_MAUTIC_WORKERS_CONSUME_EMAIL=2 + - DOCKER_MAUTIC_WORKERS_CONSUME_HIT=2 + - DOCKER_MAUTIC_WORKERS_CONSUME_FAILED=2 + - MAUTIC_DB_HOST=${MAUTIC_DB_HOST} + - MAUTIC_DB_PORT=${MAUTIC_DB_PORT} + - MAUTIC_DB_DATABASE=${MAUTIC_DB_DATABASE} + - MAUTIC_DB_USER=${MAUTIC_DB_USER} + - MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD} + - MAUTIC_URL=${MAUTIC_URL} + - MAUTIC_MESSENGER_DSN_EMAIL=${MAUTIC_MESSENGER_DSN_EMAIL} + - MAUTIC_MESSENGER_DSN_HIT=${MAUTIC_MESSENGER_DSN_HIT} + - PHP_INI_DATE_TIMEZONE=${PHP_INI_DATE_TIMEZONE} + volumes: + - mautic_data:/var/www/html + + # ------------------------------------------------------------------------- + # Service 5: phpMyAdmin + # ------------------------------------------------------------------------- + phpmyadmin: + image: phpmyadmin/phpmyadmin + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + environment: + PMA_HOST: mysql + PMA_PORT: 3306 + UPLOAD_LIMIT: 64M + ports: + - 80 + +volumes: + mysql_data: + mautic_data: diff --git a/blueprints/mautic/mautic.svg b/blueprints/mautic/mautic.svg new file mode 100644 index 00000000..3f5229ae --- /dev/null +++ b/blueprints/mautic/mautic.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/blueprints/mautic/template.toml b/blueprints/mautic/template.toml new file mode 100644 index 00000000..eeb79eef --- /dev/null +++ b/blueprints/mautic/template.toml @@ -0,0 +1,52 @@ +[variables] +# Domain 1: For the main Mautic Application +mautic_domain = "${domain}" + +# Domain 2: For phpMyAdmin (Database Manager) +pma_domain = "${domain}" + +# Security: Random passwords +db_password = "${password:32}" +root_password = "${password:32}" + +[config] + +# --- Service 1: Mautic Web --- +[[config.domains]] +serviceName = "mautic" +port = 80 +host = "${mautic_domain}" +path = "/" + +# --- Service 2: phpMyAdmin --- +[[config.domains]] +serviceName = "phpmyadmin" +port = 80 +host = "${pma_domain}" +path = "/" + +# --- Shared Environment Variables --- +[config.env] + +# URL Configuration +MAUTIC_URL = "https://${mautic_domain}" + +# Database Connections +MAUTIC_DB_HOST = "mysql" +MAUTIC_DB_PORT = "3306" +MAUTIC_DB_DATABASE = "mautic" +MAUTIC_DB_USER = "mautic" +MAUTIC_DB_PASSWORD = "${db_password}" +MYSQL_ROOT_PASSWORD = "${root_password}" + +# Security & Proxy (JSON ARRAY FIXED) +# We use single quotes '...' so TOML treats the inner [...] as a string +MAUTIC_TRUSTED_PROXIES = '["0.0.0.0/0"]' + +# Queue Settings +MAUTIC_MESSENGER_DSN_EMAIL = "doctrine://default" +MAUTIC_MESSENGER_DSN_HIT = "doctrine://default" + +# PHP Settings +PHP_INI_DATE_TIMEZONE = "UTC" +PHP_MEMORY_LIMIT = "512M" \ No newline at end of file diff --git a/blueprints/minepanel/docker-compose.yml b/blueprints/minepanel/docker-compose.yml new file mode 100644 index 00000000..e69b75f9 --- /dev/null +++ b/blueprints/minepanel/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.8" +services: + backend: + image: ketbom/minepanel-backend:1.7.1 + restart: unless-stopped + environment: + - NODE_ENV=production + - FRONTEND_URL=${FRONTEND_URL} + - JWT_SECRET=${JWT_SECRET} + - CLIENT_PASSWORD=${CLIENT_PASSWORD} + - CLIENT_USERNAME=${CLIENT_USERNAME} + - BASE_DIR=${BASE_DIR} + volumes: + - minepanel-servers:/app/servers + - minepanel-data:/app/data + - /var/run/docker.sock:/var/run/docker.sock + + frontend: + image: ketbom/minepanel-frontend:1.7.1 + restart: unless-stopped + environment: + - NEXT_PUBLIC_BACKEND_URL=${NEXT_PUBLIC_BACKEND_URL} + - NEXT_PUBLIC_DEFAULT_LANGUAGE=${NEXT_PUBLIC_DEFAULT_LANGUAGE} + depends_on: + - backend + +volumes: + minepanel-servers: + minepanel-data: diff --git a/blueprints/minepanel/minepanel.webp b/blueprints/minepanel/minepanel.webp new file mode 100644 index 0000000000000000000000000000000000000000..21a6f7a46d95120bb863d602ff856ae371e0ef7f GIT binary patch literal 10978 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 ji3An)|K}mM^B(28&3%(3 sh -c " @@ -57,39 +63,28 @@ services: sleep 1 done echo 'PostgreSQL is ready' - + echo 'Waiting for ClickHouse to be ready...' while ! nc -z op-ch 8123; do sleep 1 done echo 'ClickHouse is ready' - + echo 'Running migrations...' - - echo '$DATABASE_URL' - + CI=true pnpm -r run migrate:deploy - + pnpm start " environment: - # Common - NODE_ENV: production - NEXT_PUBLIC_SELF_HOSTED: true - # URLs - SERVICE_FQDN_OPAPI: /api - # Set coolify FQDN domain - NEXT_PUBLIC_API_URL: $SERVICE_FQDN_OPAPI - NEXT_PUBLIC_DASHBOARD_URL: $SERVICE_FQDN_OPDASHBOARD - # Others - COOKIE_SECRET: ${SERVICE_BASE64_COOKIESECRET} - ALLOW_REGISTRATION: ${OPENPANEL_ALLOW_REGISTRATION:-false} - ALLOW_INVITATION: ${OPENPANEL_ALLOW_INVITATION:-true} - EMAIL_SENDER: ${OPENPANEL_EMAIL_SENDER} + COOKIE_SECRET: ${COOKIE_SECRET} + ALLOW_REGISTRATION: ${ALLOW_REGISTRATION} + ALLOW_INVITATION: ${ALLOW_INVITATION} + EMAIL_SENDER: ${EMAIL_SENDER} RESEND_API_KEY: ${RESEND_API_KEY} - <<: *x-database + <<: *x-common healthcheck: - test: [ "CMD-SHELL", "curl -f http://localhost:3000/healthcheck || exit 1" ] + test: ['CMD-SHELL', 'curl -f http://localhost:3000/healthcheck || exit 1'] interval: 10s timeout: 5s retries: 5 @@ -102,55 +97,35 @@ services: condition: service_healthy op-dashboard: - image: lindesvard/openpanel-dashboard:${OP_DASHBOARD_VERSION:-latest} + image: lindesvard/openpanel-dashboard:2 restart: always depends_on: op-api: condition: service_healthy environment: - # Common - NODE_ENV: production - NEXT_PUBLIC_SELF_HOSTED: true - # URLs - SERVICE_FQDN_OPDASHBOARD: - # Set coolify FQDN domain - NEXT_PUBLIC_API_URL: $SERVICE_FQDN_OPAPI - NEXT_PUBLIC_DASHBOARD_URL: $SERVICE_FQDN_OPDASHBOARD - <<: *x-database + <<: *x-common healthcheck: - test: [ 'CMD-SHELL', 'curl -f http://localhost:3000/api/healthcheck || exit 1' ] + test: ['CMD-SHELL', 'curl -f http://localhost:3000/api/healthcheck || exit 1'] interval: 10s timeout: 5s retries: 5 op-worker: - image: lindesvard/openpanel-worker:${OP_WORKER_VERSION:-latest} + image: lindesvard/openpanel-worker:2 restart: always depends_on: op-api: condition: service_healthy environment: - # FQDN - SERVICE_FQDN_OPBULLBOARD: - # Common - NODE_ENV=production: - NEXT_PUBLIC_SELF_HOSTED: true - # Set coolify FQDN domain - NEXT_PUBLIC_API_URL: $SERVICE_FQDN_OPAPI - <<: *x-database + <<: *x-common healthcheck: - test: [ 'CMD-SHELL', 'curl -f http://localhost:3000/healthcheck || exit 1' ] + test: ['CMD-SHELL', 'curl -f http://localhost:3000/healthcheck || exit 1'] interval: 10s timeout: 5s retries: 5 - deploy: - mode: replicated - replicas: $OP_WORKER_REPLICAS volumes: op-db-data: op-kv-data: op-ch-data: op-ch-logs: - op-proxy-data: - op-proxy-config: diff --git a/blueprints/openpanel/template.toml b/blueprints/openpanel/template.toml index 55773508..2a93c33c 100644 --- a/blueprints/openpanel/template.toml +++ b/blueprints/openpanel/template.toml @@ -1,82 +1,91 @@ [variables] main_domain = "${domain}" -api_domain = "${domain}" -db_password = "${password}" +db_password = "${password:32}" cookie_secret = "${base64:32}" -redis_password = "${password}" +redis_password = "${password:32}" [config] +# ClickHouse config files - mounted as directories [[config.mounts]] -filePath = "clickhouse/clickhouse-config.xml" +filePath = "./clickhouse_config/op-config.xml" content = """ - - - warning - true - - 10 - - - - - - - - - - 0.0.0.0 - 0.0.0.0 - opch - - 0 - - - 1 - replica1 - openpanel_cluster - - + + + warning + true + + 10 + + + + + + + + + + 0.0.0.0 + 0.0.0.0 + opch + + 0 + + + 1 + replica1 + openpanel_cluster + + """ [[config.mounts]] -filePath = "clickhouse/clickhouse-user-config.xml" +filePath = "./clickhouse_users/op-user-config.xml" content = """ - - + + 0 0 - + """ [[config.mounts]] -filePath = "clickhouse/init-db.sql" +filePath = "./clickhouse_init/1_init-db.sql" content = """ CREATE DATABASE IF NOT EXISTS openpanel; """ [[config.domains]] serviceName = "op-dashboard" -port = 3_000 +port = 3000 host = "${main_domain}" [[config.domains]] serviceName = "op-api" -port = 3_000 -host = "${api_domain}" +port = 3000 +host = "${main_domain}" +path = "/api" +stripPath = true [config.env] -SERVICE_FQDN_OPDASHBOARD = "http://${main_domain}" -SERVICE_FQDN_OPAPI = "http://${api_domain}" -OPENPANEL_POSTGRES_DB = "openpanel-db" -SERVICE_USER_POSTGRES = "openpanel" -SERVICE_PASSWORD_POSTGRES = "${db_password}" -SERVICE_PASSWORD_REDIS = "${redis_password}" -SERVICE_BASE64_COOKIESECRET = "${cookie_secret}" -OP_WORKER_REPLICAS = "1" +DASHBOARD_URL = "http://${main_domain}" +API_URL = "http://${main_domain}/api" +# Database configuration +POSTGRES_DB = "openpanel" +POSTGRES_USER = "openpanel" +POSTGRES_PASSWORD = "${db_password}" +REDIS_PASSWORD = "${redis_password}" + +# Security +COOKIE_SECRET = "${cookie_secret}" + +# Registration settings +ALLOW_REGISTRATION = "true" +ALLOW_INVITATION = "true" + +# Email configuration (optional - configure for email notifications) +EMAIL_SENDER = "" RESEND_API_KEY = "" -OPENPANEL_ALLOW_REGISTRATION = "true" -OPENPANEL_ALLOW_INVITATION = "true" diff --git a/blueprints/prometheus/docker-compose.yml b/blueprints/prometheus/docker-compose.yml index e1016777..1896c876 100644 --- a/blueprints/prometheus/docker-compose.yml +++ b/blueprints/prometheus/docker-compose.yml @@ -9,6 +9,7 @@ services: - "--web.console.templates=/usr/share/prometheus/consoles" - "--web.enable-lifecycle" volumes: + - ../files/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus volumes: prometheus-data: {} diff --git a/blueprints/prometheus/template.toml b/blueprints/prometheus/template.toml index 535663dc..2415f3fe 100644 --- a/blueprints/prometheus/template.toml +++ b/blueprints/prometheus/template.toml @@ -10,7 +10,10 @@ port = 9_090 host = "${main_domain}" [[config.mounts]] -filePath = "/etc/prometheus/prometheus.yml" +# Note: this relative path is resolved by Dokploy to the file mounted from +# ../files/prometheus.yml in docker-compose, and mapped inside the container +# to /etc/prometheus/prometheus.yml. +filePath = "prometheus.yml" serviceName = "prometheus" content = """ # Prometheus Configuration diff --git a/blueprints/pyrodactyl/docker-compose.yml b/blueprints/pyrodactyl/docker-compose.yml index ad0e42b7..2fde33d7 100644 --- a/blueprints/pyrodactyl/docker-compose.yml +++ b/blueprints/pyrodactyl/docker-compose.yml @@ -1,6 +1,6 @@ services: database: - image: mariadb:10.5 + image: mariadb:11 restart: always command: --default-authentication-plugin=mysql_native_password volumes: @@ -14,7 +14,7 @@ services: image: redis:alpine restart: always panel: - image: ghcr.io/pyrohost/pyrodactyl:main + image: ghcr.io/pyrodactyl-oss/pyrodactyl:latest restart: always links: - database @@ -35,15 +35,8 @@ services: DB_HOST: DB_PORT: DB_PASSWORD: ${MYSQL_PASSWORD} - RECAPTCHA_ENABLED: DB_CONNECTION: "mariadb" -networks: - default: - ipam: - config: - - subnet: 172.20.0.0/16 - volumes: pterodb: pterovar: diff --git a/blueprints/qbitwebui/docker-compose.yml b/blueprints/qbitwebui/docker-compose.yml new file mode 100644 index 00000000..3e805c3c --- /dev/null +++ b/blueprints/qbitwebui/docker-compose.yml @@ -0,0 +1,39 @@ +version: "3.8" + +services: + qbitwebui: + image: ghcr.io/maciejonos/qbitwebui:latest + restart: unless-stopped + environment: + # Required: Encryption key for storing credentials (min 32 chars) + - ENCRYPTION_KEY=${ENCRYPTION_KEY} + # Optional: Server port (default: 3000) + - PORT=${PORT:-3000} + # Optional: Database location (default: ./data/qbitwebui.db) + - DATABASE_PATH=${DATABASE_PATH:-/data/qbitwebui.db} + # Optional: Salt file location (default: ./data/.salt) + - SALT_PATH=${SALT_PATH:-/data/.salt} + # Optional: Allow self-signed certificates for qBittorrent instances (default: false) + # - ALLOW_SELF_SIGNED_CERTS=${ALLOW_SELF_SIGNED_CERTS:-false} + # Optional: Disable authentication/login (single-user mode) (default: false) + # - DISABLE_AUTH=${DISABLE_AUTH:-false} + # Optional: Disable new registrations, creates default admin account (default: false) + # - DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-false} + # Optional: Enable file browser by setting downloads path + # - DOWNLOADS_PATH=/downloads + volumes: + - qbitwebui_data:/data + # Optional: Mount downloads directory for file browser feature + # Read-only mount (browse & download only): + # - /path/to/your/downloads:/downloads:ro + # Or read-write mount (enables delete/move/copy/rename): + # - /path/to/your/downloads:/downloads + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + +volumes: + qbitwebui_data: diff --git a/blueprints/qbitwebui/qbitwebui.png b/blueprints/qbitwebui/qbitwebui.png new file mode 100644 index 0000000000000000000000000000000000000000..48052dffba50a4dc2b560669abaff278d28af9d4 GIT binary patch literal 440710 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