Compare commits

...
Author SHA1 Message Date
Mauricio SiuandClaude Fable 5 21c1fec5d8 feat: validator opt-out marker for intentional host ports + annotate rustdesk
The validate-docker-compose CI on this PR was failing on rustdesk's
hbbs/hbbr port mappings (21115-21119) — intentional host-published ports
for the RustDesk relay/rendezvous protocol, which Traefik cannot route.

Adds a '# dokploy: allow-host-ports' top-of-file marker that downgrades
the port-mapping errors to warnings for such protocol templates
(mail/game/streaming/VPN/remote-desktop), and annotates rustdesk.
Other exception templates (poste.io, mailu, oryx, fivem...) can be
annotated case-by-case as the weekly audit (#1049) surfaces them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 01:22:34 -06:00
Mauricio Siu f8a75dba2b Merge remote-tracking branch 'origin/canary' into fix/normalize-empty-mounts 2026-08-06 01:21:15 -06:00
Mauricio SiuandClaude Fable 5 b9c4365e9f fix: normalize template.toml mounts across all blueprints (closes #1047)
- 79 blueprints declared a bare [[config.mounts]] header (one empty array
  element in TOML) -> replaced with the repo idiom 'mounts = []'
- 38 blueprints carried doc-style mount blocks (name/mountPath/description)
  that don't match the schema (filePath/content) and are inert in Dokploy's
  template processor -> removed; their named volumes already live in each
  docker-compose.yml
- penpot: duplicate 'expose' map key made its docker-compose.yml invalid
  YAML -> merged into one expose list

After this, validate-template.ts passes for all 502 blueprints. The
remaining validate-docker-compose.ts flags are the intentional host-port
templates (mail/game/streaming protocols: poste.io, mailu, oryx, fivem,
lodestone, etc.) which the validator does not yet model as exceptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 04:28:49 -06:00
82 changed files with 103 additions and 401 deletions
+1 -2
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
timezone = "America/New_York"
[config]
mounts = []
[[config.domains]]
serviceName = "agentdvr"
port = 8090
@@ -10,5 +11,3 @@ host = "${main_domain}"
[config.env]
TIMEZONE = "${timezone}"
[[config.mounts]]
+2 -2
View File
@@ -24,6 +24,7 @@ port = 80
host = "${main_domain}"
[config]
mounts = []
env = [
# App
"APP_URL=${main_domain}",
@@ -50,5 +51,4 @@ env = [
"MYSQL_RANDOM_ROOT_PASSWORD=yes"
]
# No custom mounts needed; volumes are already defined in compose.
[[config.mounts]]
# No custom mounts needed; volumes are already defined in compose.
+1 -2
View File
@@ -4,6 +4,7 @@ mysql_root_password = "${password:32}"
mysql_password = "${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "azuracast"
port = 80
@@ -27,5 +28,3 @@ NGINX_RADIO_PORTS = "8000,8010,8020,8030,8040,8050"
PREFER_RELEASE_BUILDS = "true"
COMPOSER_PLUGIN_MODE = "false"
ADDITIONAL_MEDIA_SYNC_WORKER_COUNT = "0"
[[config.mounts]]
+1 -4
View File
@@ -72,7 +72,7 @@ s3_endpoint = ""
s3_bucket = ""
[config]
mounts = []
[[config.domains]]
serviceName = "webapp"
port = 80
@@ -165,6 +165,3 @@ MYSQL_DATABASE = "${system_db_name}"
MYSQL_USER = "${db_user}"
MYSQL_PASSWORD = "${db_password}"
MYSQL_ROOT_PASSWORD = "${db_root_password}"
[[config.mounts]]
+1 -2
View File
@@ -4,6 +4,7 @@ app_password = "${password:32}"
db_root_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "booklore"
port = 6060
@@ -20,5 +21,3 @@ MYSQL_USER = "booklore"
MYSQL_PASSWORD = "${app_password}"
# API Key
MYSQL_ROOT_PASSWORD = "${db_root_password}"
[[config.mounts]]
+1 -15
View File
@@ -2,24 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "borgitory"
port = 8000
host = "${main_domain}"
[config.env]
[[config.mounts]]
name = "borgitory-data"
mountPath = "/app/data"
description = "Database and encryption key storage"
[[config.mounts]]
name = "borgitory-sources"
mountPath = "/mnt/sources"
description = "Sources to back up (read-only)"
[[config.mounts]]
name = "borgitory-repos"
mountPath = "/mnt/repos"
description = "Borg repositories (read-only)"
+1 -7
View File
@@ -3,18 +3,12 @@ main_domain = "${domain}"
postgres_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "budget-board-client"
port = 6253
host = "${main_domain}"
path = "/"
[config.env]
POSTGRES_PASSWORD = "${postgres_password}"
[[config.mounts]]
source = "../files/db-data"
target = "/var/lib/postgresql/data"
type = "bind"
+1 -4
View File
@@ -2,13 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "checkcle"
port = 8090
host = "${main_domain}"
[config.env]
[[config.mounts]]
source = "checkcle-data"
target = "/mnt/pb_data"
+1 -5
View File
@@ -11,6 +11,7 @@ hide_history = "false"
language = "en"
[config]
mounts = []
[[config.domains]]
serviceName = "convertx"
port = 3000
@@ -26,8 +27,3 @@ WEBROOT = "${webroot}"
FFMPEG_ARGS = "${ffmpeg_args}"
HIDE_HISTORY = "${hide_history}"
LANGUAGE = "${language}"
[[config.mounts]]
source = "../files/data"
target = "/app/data"
type = "bind"
+1 -3
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "cookiecloud"
port = 8088
@@ -9,6 +10,3 @@ host = "${main_domain}"
[config.env]
# No environment variables required by default
[[config.mounts]]
# Data volume mount is defined in docker-compose.yml
+1 -7
View File
@@ -2,16 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "cup"
port = 8000
host = "${main_domain}"
[config.env]
[[config.mounts]]
serviceName = "cup"
source = "/var/run/docker.sock"
target = "/var/run/docker.sock"
type = "bind"
+1 -2
View File
@@ -2,6 +2,7 @@
app_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "directory-lister" # Must match the service name in docker-compose.yml
port = 80
@@ -15,5 +16,3 @@ READMES_FIRST = "false"
ZIP_DOWNLOADS = "true"
TIMEZONE = "UTC"
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "docling-serve"
port = 5001
@@ -11,5 +12,3 @@ host = "${main_domain}"
DOCLING_SERVE_ENABLE_UI = "1"
DOCLING_SERVE_HOST = "0.0.0.0"
DOCLING_SERVE_PORT = "5001"
[[config.mounts]]
+1 -5
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
pg_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "app"
port = 3000
@@ -26,8 +27,3 @@ DL_PG_NAME = "domain_locker"
# DL_DOMAIN_INFO_API = ""
# DL_DOMAIN_SUBS_API = ""
# DL_DISABLE_WRITE_METHODS = "false"
[[config.mounts]]
# Example mount for PostgreSQL persistence (already defined in compose volumes)
# filePath = "/var/lib/postgresql/data"
# content = ""
+1 -5
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
docker_socket = "/var/run/docker.sock"
[config]
mounts = []
[[config.domains]]
serviceName = "dozzle"
port = 8080
@@ -11,8 +12,3 @@ host = "${main_domain}"
[config.env]
DOZZLE_USERNAME = "${username}"
DOZZLE_PASSWORD = "${password:16}"
[[config.mounts]]
source = "${docker_socket}"
target = "/var/run/docker.sock"
read_only = true
+1 -6
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
default_pin = "${password:4}"
[config]
mounts = []
[[config.domains]]
serviceName = "dumbassets"
port = 3000
@@ -19,9 +20,3 @@ DEMO_MODE = "false"
APPRISE_URL = ""
CURRENCY_CODE = "USD"
CURRENCY_LOCALE = "en-US"
[[config.mounts]]
serviceName = "dumbassets"
type = "volume"
source = "dumbassets-data"
target = "/app/data"
+2 -2
View File
@@ -4,6 +4,7 @@ main_domain = "${domain}"
dumbbudget_pin = "${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "dumbbudget"
port = 3000
@@ -20,5 +21,4 @@ ALLOWED_ORIGINS = "${main_domain}"
# The named volume 'dumbbudget-data' is defined in the docker-compose.yml.
# According to Dokploy's template examples, volumes declared in the compose
# file are automatically managed and do not require a separate entry here.
[[config.mounts]]
# file are automatically managed and do not require a separate entry here.
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "https://${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "dumbdrop"
port = 3000
@@ -20,5 +21,3 @@ APPRISE_URL = ""
APPRISE_MESSAGE = "New file uploaded {filename} ({size}), Storage used {storage}"
APPRISE_SIZE_UNIT = "Auto"
ALLOWED_EXTENSIONS = ""
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "dumbpad"
port = 3000
@@ -17,5 +18,3 @@ LOCKOUT_TIME = "15"
MAX_ATTEMPTS = "5"
COOKIE_MAX_AGE = "24"
PAGE_HISTORY_COOKIE_AGE = "365"
[[config.mounts]]
+1 -2
View File
@@ -7,6 +7,7 @@ root_pass = "${password:32}"
secret_key = "${password:64}"
[config]
mounts = []
[[config.domains]]
serviceName = "ezbookkeeping"
port = 8080
@@ -27,5 +28,3 @@ EBK_DATABASE_PASSWD = "${db_pass}"
EBK_LOG_MODE = "file"
# Security secret key used for application protection
EBK_SECURITY_SECRET_KEY = "${secret_key}"
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "flaresolverr"
port = 8191
@@ -12,5 +13,3 @@ LOG_LEVEL = "info"
LOG_HTML = "false"
CAPTCHA_SOLVER = "none"
TZ = "Europe/London"
[[config.mounts]]
+1 -4
View File
@@ -6,6 +6,7 @@ secret_key = "${password:32}"
totp_key = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "flatnotes"
port = 8080
@@ -22,7 +23,3 @@ FLATNOTES_SECRET_KEY = "${secret_key}"
FLATNOTES_TOTP_KEY = "${totp_key}"
FLATNOTES_SESSION_EXPIRY_DAYS = "30"
FLATNOTES_PATH_PREFIX = ""
[[config.mounts]]
name = "flatnotes-data"
mountPath = "/data"
+1 -4
View File
@@ -5,6 +5,7 @@ password = "${password:16}"
secret_key = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "flatnotes"
port = 8080
@@ -26,7 +27,3 @@ FLATNOTES_QUICK_ACCESS_TITLE = "RECENTLY MODIFIED"
FLATNOTES_QUICK_ACCESS_TERM = "*"
FLATNOTES_QUICK_ACCESS_SORT = "lastModified"
FLATNOTES_QUICK_ACCESS_LIMIT = "4"
[[config.mounts]]
name = "flatnotes-data"
mountPath = "/data"
+1 -8
View File
@@ -5,6 +5,7 @@ jwt_refresh_secret = "${password:32}"
express_secret = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "flowise"
port = 3000
@@ -14,11 +15,3 @@ host = "${main_domain}"
JWT_AUTH_TOKEN_SECRET = "${jwt_secret}"
JWT_REFRESH_TOKEN_SECRET = "${jwt_refresh_secret}"
EXPRESS_SESSION_SECRET = "${express_secret}"
[[config.mounts]]
name = "flowise_data"
mountPath = "/root/.flowise"
[[config.mounts]]
name = "redis_data"
mountPath = "/data"
+1 -2
View File
@@ -4,6 +4,7 @@ db_password = "${password:24}"
db_root_password = "${password:24}"
[config]
mounts = []
[[config.domains]]
serviceName = "gitea"
port = 3000
@@ -19,5 +20,3 @@ GITEA__database__USER = "gitea"
GITEA__database__PASSWD = "${db_password}"
GITEA_DB_PASSWORD = "${db_password}"
MYSQL_ROOT_PASSWORD = "${db_root_password}"
[[config.mounts]]
+1 -2
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
db_password = "${password:24}"
[config]
mounts = []
[[config.domains]]
serviceName = "gitea"
port = 3000
@@ -17,5 +18,3 @@ GITEA__database__NAME = "gitea"
GITEA__database__USER = "gitea"
GITEA__database__PASSWD = "${db_password}"
GITEA_DB_PASSWORD = "${db_password}"
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "gitea"
port = 3000
@@ -19,5 +20,3 @@ USER_GID = "1000"
# GITEA__mailer__SMTP_PORT = "465"
# GITEA__mailer__USER = "apikey"
# GITEA__mailer__PASSWD = "${password:32}"
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "grimoire"
port = 5173
@@ -12,5 +13,3 @@ PORT = "5173"
PUBLIC_ORIGIN = "https://${main_domain}"
PUBLIC_HTTPS_ONLY = "true"
PUBLIC_SIGNUP_DISABLED = "false"
[[config.mounts]]
+1 -3
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
default_email = "${email}"
[config]
mounts = []
[[config.domains]]
serviceName = "grist"
port = 8484
@@ -10,6 +11,3 @@ host = "${main_domain}"
[config.env]
GRIST_DEFAULT_EMAIL = "${default_email}"
[[config.mounts]]
+1 -6
View File
@@ -6,6 +6,7 @@ mongo_admin_password = "${password}"
mongo_habitica_password = "${password}"
[config]
mounts = []
[[config.domains]]
serviceName = "client"
port = 80
@@ -24,9 +25,3 @@ MONGO_ADMIN_PASSWORD = "${mongo_admin_password}"
MONGO_HABITICA_USER = "habitica"
MONGO_HABITICA_PASSWORD = "${mongo_habitica_password}"
ADMIN_EMAIL = "no-reply@${main_domain}"
[[config.mounts]]
serviceName = "mongo"
type = "volume"
source = "habitica-mongo-data"
target = "/data/db"
+1 -3
View File
@@ -4,7 +4,7 @@ db_password = "${password:32}"
encryption_key = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "hoppscotch"
port = 80
@@ -37,5 +37,3 @@ VITE_APP_PRIVACY_POLICY_LINK = "https://docs.hoppscotch.io/support/privacy"
# Subpath Access
ENABLE_SUBPATH_BASED_ACCESS = "true"
[[config.mounts]]
+1 -25
View File
@@ -10,7 +10,7 @@ DB_PASSWORD = "${password:20}"
MARIADB_ROOT_PASSWORD = "${password:24}"
[config]
mounts = []
[[config.domains]]
serviceName = "app"
port = 80
@@ -24,27 +24,3 @@ DB_DATABASE = "${DB_DATABASE}"
DB_USERNAME = "${DB_USERNAME}"
DB_PASSWORD = "${DB_PASSWORD}"
MARIADB_ROOT_PASSWORD = "${MARIADB_ROOT_PASSWORD}"
[[config.mounts]]
name = "app_images"
mountPath = "/var/www/html/public/img"
[[config.mounts]]
name = "app_logs"
mountPath = "/var/www/html/app/logs"
[[config.mounts]]
name = "app_backup"
mountPath = "/var/www/html/public/backup"
[[config.mounts]]
name = "app_themes"
mountPath = "/var/www/html/public/themes"
[[config.mounts]]
name = "app_migrate"
mountPath = "/var/www/html/app/migrations"
[[config.mounts]]
name = "db_data"
mountPath = "/var/lib/mysql"
+1 -5
View File
@@ -4,7 +4,7 @@ jwt_secret = "${password:32}" # secure default
recovery_mode = "false"
[config]
mounts = []
[[config.domains]]
serviceName = "kuno"
port = 80
@@ -18,7 +18,3 @@ GIN_MODE = "release"
NODE_ENV = "production"
JWT_SECRET = "${jwt_secret}"
RECOVERY_MODE = "${recovery_mode}"
[[config.mounts]]
name = "kuno-data"
mountPath = "/app/data"
+1 -4
View File
@@ -6,6 +6,7 @@ MAIL_PASSWORD = "${password:32}"
SECRET_KEY = "${password:64}"
[config]
mounts = []
[[config.domains]]
serviceName = "ihatemoney"
port = 8000
@@ -37,7 +38,3 @@ LEGAL_LINK = ""
PORT = "8000"
PUID = "0"
PGID = "0"
[[config.mounts]]
volumeName = "sqlite-db"
mountPath = "/database"
+1 -10
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
karakeep_version = "release"
[config]
mounts = []
[[config.domains]]
serviceName = "web"
port = 3000
@@ -106,13 +107,3 @@ host = "${main_domain}"
# "CRAWLER_HTTP_PROXY" = ""
# "CRAWLER_HTTPS_PROXY" = ""
# "CRAWLER_NO_PROXY" = ""
[[config.mounts]]
# Persistent data directory for Karakeep
volumeName = "data"
mountPath = "/data"
[[config.mounts]]
# Meilisearch data directory
volumeName = "meilisearch"
mountPath = "/meili_data"
+1 -15
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
KENER_SECRET_KEY = "${password:64}"
[config]
mounts = []
[[config.domains]]
serviceName = "kener"
port = 3000
@@ -16,18 +17,3 @@ REDIS_URL = "redis://redis:6379"
DATABASE_URL = "sqlite://./database/kener.sqlite.db"
RESEND_API_KEY = ""
RESEND_SENDER_EMAIL = ""
[[config.mounts]]
type = "volume"
source = "data"
target = "/app/database"
[[config.mounts]]
type = "volume"
source = "redis_data"
target = "/data"
[[config.mounts]]
type = "bind"
source = "../files/uploads"
target = "/app/uploads"
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "kestra"
port = 8080
@@ -9,5 +10,3 @@ host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -2
View File
@@ -8,6 +8,7 @@ KEYCLOAK_ADMIN_PASSWORD = "${password:32}"
KC_HOSTNAME = "${main_domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "keycloak"
port = 8080
@@ -20,5 +21,3 @@ POSTGRES_PASSWORD = "${POSTGRES_PASSWORD}"
KEYCLOAK_ADMIN = "${KEYCLOAK_ADMIN}"
KEYCLOAK_ADMIN_PASSWORD = "${KEYCLOAK_ADMIN_PASSWORD}"
KC_HOSTNAME = "${KC_HOSTNAME}"
[[config.mounts]]
+2 -12
View File
@@ -6,7 +6,7 @@ db_user = "kitchenowl"
db_name = "kitchenowl"
[config]
mounts = []
[[config.domains]]
serviceName = "web"
port = 8080
@@ -20,14 +20,4 @@ DB_NAME = "${db_name}"
DB_USER = "${db_user}"
DB_PASSWORD = "${db_password}"
# Persist uploads/attachments
[[config.mounts]]
serviceName = "web"
volumeName = "kitchenowl_files"
mountPath = "/data"
# Persist Postgres data
[[config.mounts]]
serviceName = "db"
volumeName = "kitchenowl_db"
mountPath = "/var/lib/postgresql/data"
# Persist uploads/attachments
+1 -5
View File
@@ -4,7 +4,7 @@ secret_key = "${password:64}"
auth_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "frontend"
port = 3000
@@ -28,7 +28,3 @@ LETTERFEED_AUTO_ADD_NEW_SENDERS = "false"
LETTERFEED_SECRET_KEY = "${secret_key}"
LETTERFEED_AUTH_USERNAME = "admin"
LETTERFEED_AUTH_PASSWORD = "${auth_password}"
[[config.mounts]]
name = "letterfeed_data"
mountPath = "/data"
+1 -8
View File
@@ -4,6 +4,7 @@ admin_email = "${email}"
mysql_root_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "linkstack"
port = 80
@@ -18,11 +19,3 @@ LOG_LEVEL = "info"
PHP_MEMORY_LIMIT = "256M"
UPLOAD_MAX_FILESIZE = "8M"
MYSQL_ROOT_PASSWORD = "${mysql_root_password}"
[[config.mounts]]
volume = "linkstack-data"
target = "/htdocs"
[[config.mounts]]
volume = "mysql-data"
target = "/var/lib/mysql"
+1 -2
View File
@@ -29,6 +29,7 @@ novita_api_key = ""
infinity_api_key = ""
[config]
mounts = []
[[config.domains]]
serviceName = "litellm"
port = 4000
@@ -63,5 +64,3 @@ ANTHROPIC_API_KEY = "${anthropic_api_key}"
INFISICAL_TOKEN = "${infisical_token}"
NOVITA_API_KEY = "${novita_api_key}"
INFINITY_API_KEY = "${infinity_api_key}"
[[config.mounts]]
+1 -1
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "memos"
port = 5230
@@ -11,4 +12,3 @@ host = "${main_domain}"
[config.env]
MEMOS_MODE = "prod"
MEMOS_PORT = "5230"
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "metube"
port = 8081
@@ -39,5 +40,3 @@ DOWNLOAD_MODE = "limited"
MAX_CONCURRENT_DOWNLOADS = "3"
LOGLEVEL = "INFO"
ENABLE_ACCESSLOG = "false"
[[config.mounts]]
+1 -2
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
mx_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "mixpost"
port = 80
@@ -19,5 +20,3 @@ DB_DATABASE="mixpost_db"
DB_USERNAME="mixpost_user"
DB_PASSWORD="${mx_password}"
[[config.mounts]]
+1 -6
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "morphos-server"
port = 8080
@@ -11,9 +12,3 @@ certResolver = "letsencrypt"
[config.env]
# No environment variables required based on provided information
[[config.mounts]]
source = "../files/morphos-upload"
target = "/upload"
type = "bind"
readOnly = false
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "navidrome"
port = 4533
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -2
View File
@@ -4,6 +4,7 @@ admin_password = "${password:16}"
user_password = "${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "neko"
port = 8080
@@ -18,5 +19,3 @@ NEKO_MEMBER_MULTIUSER_USER_PASSWORD = "${user_password}"
NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD = "${admin_password}"
NEKO_WEBRTC_EPR = "52000-52100"
NEKO_WEBRTC_ICELITE = "1"
[[config.mounts]]
+1 -10
View File
@@ -5,6 +5,7 @@ postgres_password = "${password:32}"
postgres_db = "root_db"
[config]
mounts = []
[[config.domains]]
serviceName = "nocodb"
port = 8080
@@ -14,13 +15,3 @@ host = "${main_domain}"
POSTGRES_USER = "${postgres_user}"
POSTGRES_PASSWORD = "${postgres_password}"
POSTGRES_DB = "${postgres_db}"
[[config.mounts]]
serviceName = "nocodb"
volumeName = "nc_data"
mountPath = "/usr/app/data"
[[config.mounts]]
serviceName = "root_db"
volumeName = "db_data"
mountPath = "/var/lib/postgresql/data"
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "omni-tools"
port = 80
@@ -10,5 +11,3 @@ host = "${main_domain}"
[config.env]
# API Key
LOCIZE_API_KEY = ""
[[config.mounts]]
+1 -14
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
webui_secret_key = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "open-webui"
port = 8080
@@ -12,17 +13,3 @@ host = "${main_domain}"
# Secret key for signing JWTs for authentication.
# A random key is generated by default.
webui_secret_key = "${webui_secret_key}" # API Key
[[config.mounts]]
serviceName = "open-webui"
type = "volume"
source = "open-webui"
target = "/app/backend/data"
# Mount for the optional 'ollama' service.
# This will be used if you uncomment the 'ollama' service in the docker-compose file.
[[config.mounts]]
serviceName = "ollama"
type = "volume"
source = "ollama"
target = "/root/.ollama"
+1 -4
View File
@@ -3,6 +3,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "opengist"
port = 6157
@@ -12,7 +13,3 @@ host = "${main_domain}"
UID = "1001"
GID = "1001"
OG_LOG_LEVEL = "info"
[[config.mounts]]
# This template uses a named volume defined in the docker-compose.yml,
# so no file mounts need to be configured here.
+1 -20
View File
@@ -7,6 +7,7 @@ allowed_origin = "http://${main_domain}"
next_public_api_base_url = "/api"
[config]
mounts = []
[[config.domains]]
serviceName = "openinary"
port = 3000
@@ -18,23 +19,3 @@ BETTER_AUTH_SECRET = "${better_auth_secret}"
BETTER_AUTH_URL = "${better_auth_url}"
ALLOWED_ORIGIN = "${allowed_origin}"
NEXT_PUBLIC_API_BASE_URL = "${next_public_api_base_url}"
[[config.mounts]]
serviceName = "openinary"
volumeName = "cache-data"
mountPath = "/app/apps/api/cache"
[[config.mounts]]
serviceName = "openinary"
volumeName = "public-files"
mountPath = "/app/apps/api/public"
[[config.mounts]]
serviceName = "openinary"
volumeName = "db-data"
mountPath = "/app/data"
[[config.mounts]]
serviceName = "openinary"
volumeName = "db-data"
mountPath = "/app/web-standalone/data"
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "openspeedtest"
port = 3000
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "owncast"
port = 8080
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "palmr"
port = 5487
@@ -9,5 +10,3 @@ host = "${main_domain}"
[config.env]
ENCRYPTION_KEY = ""
[[config.mounts]]
+1 -12
View File
@@ -7,6 +7,7 @@ email_user = "noreply@example.com"
email_pass = "${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "passbolt"
port = 80
@@ -27,15 +28,3 @@ PASSBOLT_EMAIL_PORT = "587"
PASSBOLT_EMAIL_USER = "${email_user}"
PASSBOLT_EMAIL_PASS = "${email_pass}"
PASSBOLT_EMAIL_TLS = "true"
[[config.mounts]]
volume = "gpg_volume"
target = "/etc/passbolt/gpg"
[[config.mounts]]
volume = "jwt_volume"
target = "/etc/passbolt/jwt"
[[config.mounts]]
volume = "passbolt_mariadb_data"
target = "/var/lib/mysql"
+1 -2
View File
@@ -5,6 +5,7 @@ mysql_password = "${password:16}"
mysql_root_password = "${password:20}"
[config]
mounts = []
[[config.domains]]
serviceName = "paymenter"
port = 80
@@ -18,5 +19,3 @@ MYSQL_PASSWORD = "${mysql_password}"
MYSQL_ROOT_PASSWORD = "${mysql_root_password}"
MYSQL_DATABASE = "paymenter"
MYSQL_USER = "paymenter"
[[config.mounts]]
+1 -2
View File
@@ -180,5 +180,4 @@ services:
restart: always
expose:
- '1025'
expose:
- 1080
- '1080'
+1 -6
View File
@@ -5,7 +5,7 @@ admin_password = "${password:32}"
jwt_secret = "${jwt:jwt_secret}"
[config]
mounts = []
[[config.domains]]
serviceName = "picsur"
port = 8080
@@ -16,8 +16,3 @@ host = "${main_domain}"
"PICSUR_ADMIN_PASSWORD" = "${admin_password}"
"PICSUR_JWT_SECRET" = "${jwt_secret}"
"POSTGRES_PASSWORD" = "${postgres_password}"
[[config.mounts]]
name = "picsur-data"
serviceName = "picsur_postgres"
mountPath = "/var/lib/postgresql/data"
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "plark"
port = 80
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -4
View File
@@ -4,6 +4,7 @@ admin_email = "${email}"
admin_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "pocketbase"
port = 8090
@@ -12,7 +13,3 @@ host = "${main_domain}"
[config.env]
ADMIN_EMAIL = "${admin_email}"
ADMIN_PASSWORD = "${admin_password}"
[[config.mounts]]
name = "pocketbase-data"
mountPath = "/pocketbase"
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "portainer"
port = 9000
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1 -2
View File
@@ -5,6 +5,7 @@ postgres_password = "${password:32}"
image_tag = "latest"
[config]
mounts = []
[[config.domains]]
serviceName = "rote-frontend"
port = 80
@@ -19,5 +20,3 @@ host = "${backend_domain}"
POSTGRES_PASSWORD = "${postgres_password}"
IMAGE_TAG = "${image_tag}"
VITE_API_BASE = "http://${backend_domain}"
[[config.mounts]]
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "rss-bridge"
port = 80
host = "${main_domain}"
[config.env]
[[config.mounts]]
+1
View File
@@ -1,3 +1,4 @@
# dokploy: allow-host-ports — RustDesk relay/rendezvous protocol ports (21115-21119) must be host-published; Traefik cannot route raw TCP/UDP.
services:
hbbs:
image: rustdesk/rustdesk-server:latest
+1 -3
View File
@@ -3,12 +3,10 @@ server_domain = "${domain}"
encryption_key = "${password:32}"
[config]
mounts = []
[config.env]
RELAY_HOST = "${server_domain}"
RUSTDESK_RELAY_SERVER = "${server_domain}:21117"
RUSTDESK_API_SERVER = "http://${server_domain}:21118"
RUSTDESK_ID_SERVER = "${server_domain}:21116"
ENCRYPTION_KEY = "${encryption_key}"
[[config.mounts]]
+1 -15
View File
@@ -6,6 +6,7 @@ incoming_port = "${INCOMING_PORT:-50000}"
dht_port = "${DHT_PORT:-6881}"
[config]
mounts = []
# Dokploy will route this domain to the container's internal port 8080
[[config.domains]]
serviceName = "rutorrent"
@@ -17,18 +18,3 @@ PUID = "${puid}"
PGID = "${pgid}"
INCOMING_PORT = "${incoming_port}"
DHT_PORT = "${dht_port}"
[[config.mounts]]
type = "volume"
source = "rutorrent-data"
target = "/data"
[[config.mounts]]
type = "volume"
source = "rutorrent-downloads"
target = "/downloads"
[[config.mounts]]
type = "volume"
source = "rutorrent-passwd"
target = "/passwd"
+1 -4
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
sb_user = "admin:${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "silverbullet"
port = 3000
@@ -10,7 +11,3 @@ host = "${main_domain}"
[config.env]
SB_USER = "${sb_user}"
[[config.mounts]]
name = "silverbullet-space"
mountPath = "/space"
+1 -4
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "statping-ng"
port = 8080
@@ -10,7 +11,3 @@ host = "${main_domain}"
[config.env]
TZ = "UTC"
DB_CONN = "sqlite"
[[config.mounts]]
source = "../files/statping-ng"
target = "/app"
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "storyden"
port = 8000
@@ -9,5 +10,3 @@ host = "${main_domain}"
[config.env]
STORYDEN_FQDN = "http://${main_domain}"
[[config.mounts]]
+1 -2
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
session_secret = "${password:64}"
[config]
mounts = []
[[config.domains]]
serviceName = "streamflow"
port = 7575
@@ -11,5 +12,3 @@ host = "${main_domain}"
[config.env]
SESSION_SECRET = "${session_secret}"
TIMEZONE = "Asia/Jakarta"
[[config.mounts]]
+1 -5
View File
@@ -4,6 +4,7 @@ surrealdb_user = "${username}"
surrealdb_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "surrealdb"
port = 8000
@@ -12,8 +13,3 @@ host = "${main_domain}"
[config.env]
SURREAL_USER = "${surrealdb_user}"
SURREAL_PASS = "${surrealdb_password}"
[[config.mounts]]
serviceName = "surrealdb"
volumeName = "db_data"
mountPath = "/usr/app/data"
+1 -6
View File
@@ -4,13 +4,8 @@ main_domain = "${domain}"
jwt_secret = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "tianji"
port = 12345
host = "${main_domain}"
[[config.mounts]]
serviceName = "postgres"
type = "volume"
source = "tianji-db-data"
target = "/var/lib/postgresql/data"
+1 -2
View File
@@ -6,6 +6,7 @@ keep_app_running = "1"
timezone = "Europe/Vienna"
[config]
mounts = []
[[config.domains]]
serviceName = "tor"
port = 5800
@@ -16,5 +17,3 @@ DISPLAY_WIDTH = "${display_width}"
DISPLAY_HEIGHT = "${display_height}"
KEEP_APP_RUNNING = "${keep_app_running}"
TZ = "${timezone}"
[[config.mounts]]
+1 -5
View File
@@ -3,7 +3,7 @@ main_domain = "${domain}"
upsnap_password = "${password:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "upsnap"
port = 8090
@@ -17,7 +17,3 @@ UPSNAP_SCAN_RANGE = "192.168.1.0/24"
UPSNAP_SCAN_TIMEOUT = "500ms"
UPSNAP_PING_PRIVILEGED = "true"
UPSNAP_WEBSITE_TITLE = "Upsnap Network Monitor"
[[config.mounts]]
source = "upsnap-data"
target = "/app/pb_data"
+1 -10
View File
@@ -4,6 +4,7 @@ db_password = "${password:32}"
jwt_secret = "${jwt:db_password}"
[config]
mounts = []
[[config.domains]]
serviceName = "vikunja"
port = 3456
@@ -17,13 +18,3 @@ VIKUNJA_DATABASE_TYPE = "postgres"
VIKUNJA_DATABASE_USER = "vikunja"
VIKUNJA_DATABASE_DATABASE = "vikunja"
VIKUNJA_SERVICE_JWTSECRET = "${jwt_secret}"
[[config.mounts]]
serviceName = "vikunja"
volumeName = "vikunja-files"
mountPath = "/app/vikunja/files"
[[config.mounts]]
serviceName = "db"
volumeName = "vikunja-db"
mountPath = "/var/lib/postgresql/data"
+1 -17
View File
@@ -6,7 +6,7 @@ upload_user = "${username}"
upload_password = "${password:16}"
[config]
mounts = []
[[config.domains]]
serviceName = "web"
port = 3000
@@ -28,19 +28,3 @@ POCKETBASE_ENCRYPTION_KEY = "${pocketbase_key}"
UPLOAD_USER = "${upload_user}"
UPLOAD_PASSWORD = "${upload_password}"
ORIGIN = "http://${main_domain}"
[[config.mounts]]
serviceName = "search"
volumeName = "search-data"
mountPath = "/meili_data/data.ms"
[[config.mounts]]
serviceName = "db"
volumeName = "db-data"
mountPath = "/pb_data"
[[config.mounts]]
serviceName = "web"
volumeName = "uploads"
mountPath = "/app/uploads"
+1 -2
View File
@@ -2,6 +2,7 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "web-check"
port = 3000
@@ -27,5 +28,3 @@ API_TIMEOUT_LIMIT = "10000"
API_CORS_ORIGIN = "*"
API_ENABLE_RATE_LIMIT = "true"
ENABLE_ANALYTICS = "false"
[[config.mounts]]
+1 -5
View File
@@ -3,6 +3,7 @@ main_domain = "${domain}"
SECRET = "${password:64}"
[config]
mounts = []
[[config.domains]]
serviceName = "yamtrack"
port = 8000
@@ -12,8 +13,3 @@ host = "${main_domain}"
SECRET = "${SECRET}"
TZ = "Europe/Berlin"
REDIS_URL = "redis://redis:6379"
[[config.mounts]]
serviceName = "yamtrack"
volumeName = "yamtrack-db"
mountPath = "/yamtrack/db"
+1 -2
View File
@@ -2,11 +2,10 @@
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "yt-dlp-webui"
port = 3033
host = "${main_domain}"
[config.env]
[[config.mounts]]
+19 -3
View File
@@ -25,6 +25,7 @@ interface ValidationResult {
type LogLevel = "info" | "success" | "warning" | "error" | "debug";
class DockerComposeValidator {
private allowHostPorts = false;
private options: Required<DockerComposeValidatorOptions>;
private errors: string[] = [];
private warnings: string[] = [];
@@ -73,6 +74,9 @@ class DockerComposeValidator {
}
const content = fs.readFileSync(composePath, "utf8");
// Opt-out marker for templates whose protocols require host-published ports
// (mail/SMTP, game servers, streaming, VPN, remote-desktop relays...).
this.allowHostPorts = /^#\s*dokploy:\s*allow-host-ports\b/m.test(content);
const compose = yaml.parse(content) as ComposeSpecification;
if (!compose || typeof compose !== "object") {
@@ -172,13 +176,25 @@ class DockerComposeValidator {
if (typeof port === "string") {
// Check for port mapping format (e.g., "3000:3000" or "8080:80")
if (/^\d+:\d+/.test(port)) {
this.error(
`Service '${serviceName}': ports[${index}] uses port mapping format '${port}'. According to README, use only port number (e.g., '3000') instead of '3000:3000'. Dokploy handles port routing.`
);
if (this.allowHostPorts) {
this.warning(
`Service '${serviceName}': ports[${index}] publishes host port '${port}' (allowed by '# dokploy: allow-host-ports' marker)`
);
} else {
this.error(
`Service '${serviceName}': ports[${index}] uses port mapping format '${port}'. According to README, use only port number (e.g., '3000') instead of '3000:3000'. Dokploy handles port routing.`
);
}
}
} else if (typeof port === "object" && port !== null) {
// Check for published port mapping
if (port.published && port.target) {
if (this.allowHostPorts) {
this.warning(
`Service '${serviceName}': ports[${index}] publishes host port (allowed by '# dokploy: allow-host-ports' marker)`
);
return;
}
this.error(
`Service '${serviceName}': ports[${index}] uses port mapping (published: ${port.published}, target: ${port.target}). According to README, use only port number. Dokploy handles port routing.`
);