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
107 changed files with 485 additions and 735 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

-32
View File
@@ -1,32 +0,0 @@
version: "3.8"
services:
9router:
image: decolua/9router:0.5.40
restart: unless-stopped
environment:
DATA_DIR: /app/data
PORT: "20128"
HOSTNAME: "0.0.0.0"
NODE_ENV: production
JWT_SECRET: ${JWT_SECRET}
INITIAL_PASSWORD: ${INITIAL_PASSWORD}
API_KEY_SECRET: ${API_KEY_SECRET}
MACHINE_ID_SALT: ${MACHINE_ID_SALT}
BASE_URL: http://${ROUTER_HOST}
NEXT_PUBLIC_BASE_URL: http://${ROUTER_HOST}
HEADROOM_URL: http://headroom:8787
AUTH_COOKIE_SECURE: "false"
volumes:
- 9router-data:/app/data
depends_on:
- headroom
# Optional token-reduction proxy that 9Router calls through HEADROOM_URL.
# Shipped as a sidecar because the 9Router image does not bundle it.
headroom:
image: ghcr.io/chopratejas/headroom:0.6.7
restart: unless-stopped
volumes:
9router-data:
-17
View File
@@ -1,17 +0,0 @@
{
"id": "9router",
"name": "9Router",
"version": "0.5.40",
"description": "Self-hosted AI model router that rotates requests across 40+ providers with automatic fallback, exposing an OpenAI-compatible endpoint for coding agents.",
"logo": "9router.png",
"links": {
"github": "https://github.com/decolua/9router",
"website": "https://9router.com",
"docs": "https://github.com/decolua/9router/blob/master/DOCKER.md"
},
"tags": [
"ai",
"proxy",
"developer-tools"
]
}
-21
View File
@@ -1,21 +0,0 @@
[variables]
main_domain = "${domain}"
jwt_secret = "${password:64}"
initial_password = "${password:32}"
api_key_secret = "${password:64}"
machine_id_salt = "${password:32}"
[config]
env = [
"ROUTER_HOST=${main_domain}",
"JWT_SECRET=${jwt_secret}",
"INITIAL_PASSWORD=${initial_password}",
"API_KEY_SECRET=${api_key_secret}",
"MACHINE_ID_SALT=${machine_id_salt}",
]
mounts = []
[[config.domains]]
serviceName = "9router"
port = 20128
host = "${main_domain}"
+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 -1
View File
@@ -2,7 +2,7 @@ version: "3.8"
services:
chatto:
image: ghcr.io/chattocorp/chatto:0.4
image: ghcr.io/chattocorp/chatto:0.3.8
restart: unless-stopped
expose:
- "4000"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "chatto",
"name": "Chatto (single binary)",
"version": "0.4.x",
"version": "0.3.8",
"description": "A fully-featured real-time chat application for teams and communities. This template deploys the single Chatto binary with embedded NATS. Note: email/password registration requires SMTP to be configured after deployment; voice/video calls are not included in the single-binary setup.",
"logo": "logo.png",
"links": {
+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 -1
View File
@@ -1,6 +1,6 @@
services:
fmd-server:
image: registry.gitlab.com/fmd-foss/fmd-server:0.16.0
image: registry.gitlab.com/fmd-foss/fmd-server:v0.11.0
expose:
- 8080
environment:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "fmd-server",
"name": "FMD Server",
"version": "0.16.0",
"version": "0.11.0",
"description": "A server to communicate with the FMD Android app, to locate and control your devices.",
"logo": "fmd-server.svg",
"links": {
+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]]
-19
View File
@@ -1,19 +0,0 @@
# Open Design
Local-first, open-source design tool with native desktop apps, a large library of
design systems, and an extensible plugin ecosystem.
## Notes
- **Access / security.** This template sets `OD_DISABLE_API_AUTH=1`. The Open Design
daemon token-gates its API when bound to a non-loopback address, and a browser
cannot supply that bearer token — so disabling it is required for the web UI to
load. As a result the instance is **reachable by anyone who has the URL**. If it is
internet-facing, put an authenticating layer in front (for example Dokploy/Traefik
basic-auth) and enable an HTTPS certificate on the domain.
- **AI features.** The image intentionally does not bundle an AI agent CLI. To
generate designs, either **Sign in to Open Design** (cloud) or choose **Bring your
own key** and provide a provider API key (e.g. `ANTHROPIC_API_KEY`,
`OPENAI_API_KEY`). A "vela binary not found" notice on the sign-in screen is
expected and only affects the cloud-agent path.
-29
View File
@@ -1,29 +0,0 @@
version: "3.8"
services:
open-design:
image: ghcr.io/nexu-io/od:0.16.1
restart: unless-stopped
environment:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=192
OD_BIND_HOST: 0.0.0.0
OD_PORT: 7456
OD_API_TOKEN: ${OD_API_TOKEN}
OD_ALLOWED_ORIGINS: ${OD_ALLOWED_ORIGINS}
# The daemon token-gates its API when bound to 0.0.0.0, and a browser
# cannot present that token, so the web UI needs API auth disabled to
# load. The instance is then reachable by anyone with the URL — front it
# with auth/HTTPS if it is internet-facing (see README.md).
OD_DISABLE_API_AUTH: "1"
volumes:
- open_design_data:/app/.od
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:7456/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
volumes:
open_design_data: {}
-16
View File
@@ -1,16 +0,0 @@
{
"id": "open-design",
"name": "Open Design",
"version": "0.16.1",
"description": "Open Design is a local-first, open-source design tool with native desktop apps, a large library of design systems, and an extensible plugin ecosystem that runs across many coding agents.",
"logo": "open-design.svg",
"links": {
"github": "https://github.com/nexu-io/open-design",
"website": "https://open-design.ai",
"docs": "https://open-design.ai"
},
"tags": [
"design",
"self-hosted"
]
}
-3
View File
@@ -1,3 +0,0 @@
<svg width="82" height="82" viewBox="0 0.726562 82 82" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M41 0.726562C76.5753 0.726562 82 6.15121 82 41.7266C82 77.3019 76.5753 82.7266 41 82.7266C5.42465 82.7266 0 77.3019 0 41.7266C0 6.15121 5.42465 0.726562 41 0.726562ZM40.8906 16.4258C26.9164 16.4258 15.5879 27.7543 15.5879 41.7285C15.5879 46.7757 15.5879 58.0219 15.5879 63.665C15.588 65.5281 17.091 67.0312 18.9541 67.0312H40.8906C54.8647 67.0311 66.1932 55.7026 66.1934 41.7285C66.1934 27.7544 54.8647 16.4259 40.8906 16.4258ZM40.8906 21.4863C52.0699 21.4864 61.1328 30.5492 61.1328 41.7285C61.1327 52.9078 52.0699 61.9706 40.8906 61.9707C29.7113 61.9707 20.6485 52.9078 20.6484 41.7285C20.6484 30.5491 29.7113 21.4863 40.8906 21.4863ZM32.6445 32.2549C31.9921 32.0027 31.3503 32.6469 31.5996 33.3037L39.3145 53.6045C39.6345 54.4468 40.877 54.2162 40.877 53.3145V41.6836H52.665C53.5605 41.6836 53.7908 40.4368 52.9551 40.1133L32.6445 32.2549Z" fill="#26251E"/>
</svg>

Before

Width:  |  Height:  |  Size: 980 B

-15
View File
@@ -1,15 +0,0 @@
[variables]
main_domain = "${domain}"
api_token = "${password:32}"
[config]
env = [
"OD_API_TOKEN=${api_token}",
"OD_ALLOWED_ORIGINS=https://${main_domain},http://${main_domain}",
]
mounts = []
[[config.domains]]
serviceName = "open-design"
port = 7456
host = "${main_domain}"
+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"
+3 -1
View File
@@ -27,7 +27,7 @@ services:
retries: 5
senddock:
image: ghcr.io/arkhe-systems/senddock:latest
image: ghcr.io/arkhe-systems/senddock:0.6.5.1
restart: unless-stopped
depends_on:
postgres:
@@ -40,6 +40,8 @@ services:
- JWT_SECRET=${JWT_SECRET}
- PUBLIC_URL=${PUBLIC_URL}
- FRONTEND_URL=${PUBLIC_URL}
- DEPLOYMENT_MODE=self-hosted
- SENDDOCK_LICENSE_KEY=${SENDDOCK_LICENSE_KEY}
- PORT=8080
expose:
- 8080
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "senddock",
"name": "SendDock",
"version": "0.8.0",
"version": "0.6.5.1",
"description": "Open-source, self-hosted email marketing platform with BYO SMTP. Manage subscribers, templates, broadcasts, and scheduled campaigns from one place.",
"logo": "senddock.svg",
"links": {
+2 -1
View File
@@ -7,7 +7,8 @@ jwt_secret = "${password:64}"
env = [
"POSTGRES_PASSWORD=${postgres_password}",
"JWT_SECRET=${jwt_secret}",
"PUBLIC_URL=https://${main_domain}"
"PUBLIC_URL=https://${main_domain}",
"SENDDOCK_LICENSE_KEY="
]
[[config.domains]]
+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 -3
View File
@@ -13,10 +13,8 @@ services:
- "110" # POP3
- "995" # POP3S
volumes:
- stalwart_etc:/etc/stalwart
- stalwart_data:/var/lib/stalwart
- stalwart_data:/opt/stalwart-mail
restart: unless-stopped
volumes:
stalwart_etc:
stalwart_data:
+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]]
+81 -21
View File
@@ -11,7 +11,7 @@ name: supabase
services:
studio:
image: supabase/studio:2026.08.03-sha-022b374
image: supabase/studio:2026.06.03-sha-0bca601
restart: unless-stopped
healthcheck:
test:
@@ -23,6 +23,9 @@ services:
interval: 5s
retries: 5
start_period: 30s
depends_on:
analytics:
condition: service_healthy
environment:
HOSTNAME: "0.0.0.0"
@@ -32,9 +35,6 @@ services:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# See: https://supabase.com/docs/guides/self-hosting/remove-superuser-access
POSTGRES_USER_READ_WRITE: postgres
PG_META_CRYPTO_KEY: ${PG_META_CRYPTO_KEY}
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
PGRST_DB_MAX_ROWS: ${PGRST_DB_MAX_ROWS:-1000}
@@ -49,12 +49,14 @@ services:
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
AUTH_JWT_SECRET: ${JWT_SECRET}
SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_PUBLISHABLE_KEY:-}
SUPABASE_SECRET_KEY: ${SUPABASE_SECRET_KEY:-}
# Analytics (Logflare) was removed from the upstream self-hosted stack
ENABLED_FEATURES_LOGS_ALL: "false"
LOGFLARE_API_KEY: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
LOGFLARE_PUBLIC_ACCESS_TOKEN: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
LOGFLARE_PRIVATE_ACCESS_TOKEN: ${LOGFLARE_PRIVATE_ACCESS_TOKEN}
LOGFLARE_URL: http://analytics:4000
NEXT_PUBLIC_ENABLE_LOGS: "true"
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
SNIPPETS_MANAGEMENT_FOLDER: /app/snippets
EDGE_FUNCTIONS_MANAGEMENT_FOLDER: /app/edge-functions
volumes:
@@ -62,7 +64,7 @@ services:
- ../files/volumes/functions:/app/edge-functions:Z
kong:
image: kong/kong:3.9.3
image: kong/kong:3.9.1
restart: unless-stopped
healthcheck:
test: ["CMD", "kong", "health"]
@@ -136,7 +138,6 @@ services:
GOTRUE_JWT_EXP: ${JWT_EXPIRY}
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_JWT_KEYS: ${JWT_KEYS:-[]}
GOTRUE_JWT_ISSUER: ${API_EXTERNAL_URL}
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
@@ -162,19 +163,12 @@ services:
depends_on:
db:
condition: service_healthy
healthcheck:
test: [ "CMD", "postgrest", "--ready" ]
interval: 5s
timeout: 5s
retries: 3
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
PGRST_DB_MAX_ROWS: ${PGRST_DB_MAX_ROWS:-1000}
PGRST_DB_EXTRA_SEARCH_PATH: ${PGRST_DB_EXTRA_SEARCH_PATH:-public}
PGRST_DB_ANON_ROLE: anon
PGRST_ADMIN_SERVER_PORT: 3001
PGRST_ADMIN_SERVER_HOST: localhost
PGRST_JWT_SECRET: ${JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: "false"
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
@@ -298,8 +292,7 @@ services:
PG_META_DB_HOST: ${POSTGRES_HOST}
PG_META_DB_PORT: ${POSTGRES_PORT}
PG_META_DB_NAME: ${POSTGRES_DB}
# See: https://supabase.com/docs/guides/self-hosting/remove-superuser-access
PG_META_DB_USER: postgres
PG_META_DB_USER: supabase_admin
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
CRYPTO_KEY: ${PG_META_CRYPTO_KEY}
@@ -329,9 +322,43 @@ services:
"/home/deno/functions/main"
]
analytics:
image: supabase/logflare:1.36.1
restart: unless-stopped
expose:
- 4000
# First boot: Logflare seeds _analytics in Postgres; allow extra time before marking unhealthy.
healthcheck:
test:
[
"CMD-SHELL",
"curl -sSfL -o /dev/null http://localhost:4000/health"
]
timeout: 10s
interval: 5s
retries: 15
start_period: 90s
depends_on:
db:
condition: service_healthy
environment:
LOGFLARE_NODE_HOST: 127.0.0.1
DB_USERNAME: supabase_admin
DB_DATABASE: _supabase
DB_HOSTNAME: ${POSTGRES_HOST}
DB_PORT: ${POSTGRES_PORT}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_SCHEMA: _analytics
LOGFLARE_PUBLIC_ACCESS_TOKEN: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
LOGFLARE_PRIVATE_ACCESS_TOKEN: ${LOGFLARE_PRIVATE_ACCESS_TOKEN}
LOGFLARE_SINGLE_TENANT: "true"
LOGFLARE_SUPABASE_MODE: "true"
LOGFLARE_MIN_CLUSTER_SIZE: 1
POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/_supabase
POSTGRES_BACKEND_SCHEMA: _analytics
LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
db:
# Major upgrade from Postgres 15 requires a dump/restore for existing installs.
# See: https://github.com/orgs/supabase/discussions/46080
image: supabase/postgres:17.6.1.136
restart: unless-stopped
volumes:
@@ -358,6 +385,9 @@ services:
timeout: 5s
retries: 15
start_period: 60s
depends_on:
vector:
condition: service_healthy
environment:
POSTGRES_HOST: /var/run/postgresql
PGPORT: ${POSTGRES_PORT}
@@ -377,6 +407,36 @@ services:
"log_min_messages=fatal"
]
vector:
image: timberio/vector:0.53.0-alpine
restart: unless-stopped
volumes:
- ../files/volumes/logs/vector.yml:/etc/vector/vector.yml:ro,z
- ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro,z
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://vector:9001/health"
]
timeout: 5s
interval: 5s
retries: 5
start_period: 10s
environment:
LOGFLARE_PUBLIC_ACCESS_TOKEN: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
command:
[
"--config",
"/etc/vector/vector.yml"
]
security_opt:
- "label=disable"
supavisor:
image: supabase/supavisor:2.9.5
restart: unless-stopped
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "supabase",
"name": "SupaBase",
"version": "2026.08.03 / dokploy >= 0.22.5",
"version": "2026.06.03 / dokploy >= 0.22.5",
"description": "The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. This require at least version 0.22.5 of dokploy.",
"links": {
"github": "https://github.com/supabase/supabase",
+289 -1
View File
@@ -2,6 +2,8 @@
main_domain = "${domain}"
postgres_password = "${password:32}"
dashboard_password = "${password:32}"
logflare_public_access_token = "${password:32}"
logflare_private_access_token = "${password:32}"
pg_meta_crypto_key = "${password:32}"
s3_protocol_access_key_id = "${password:24}"
s3_protocol_access_key_secret = "${password:48}"
@@ -57,6 +59,8 @@ env = [
'SECRET_KEY_BASE=${secret_key_base}',
'VAULT_ENC_KEY=${vault_enc_key}',
'PG_META_CRYPTO_KEY=${pg_meta_crypto_key}',
'LOGFLARE_PUBLIC_ACCESS_TOKEN=${logflare_public_access_token}',
'LOGFLARE_PRIVATE_ACCESS_TOKEN=${logflare_private_access_token}',
'',
'',
'############',
@@ -160,7 +164,19 @@ env = [
'# Functions - Configuration for Functions',
'############',
'# NOTE: VERIFY_JWT applies to all functions. Per-function VERIFY_JWT is not supported yet.',
'FUNCTIONS_VERIFY_JWT=false']
'FUNCTIONS_VERIFY_JWT=false',
'',
'',
'############',
'# Logs - Configuration for Logflare',
'############',
'',
'# Docker socket location - this value will differ depending on your OS',
'DOCKER_SOCKET_LOCATION=/var/run/docker.sock',
'',
'# Google Cloud Project details',
'GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID',
'GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER']
[[config.mounts]]
filePath = "/volumes/api/kong-entrypoint.sh"
@@ -991,6 +1007,278 @@ Deno.serve(async (req: Request) => {
})
"""
[[config.mounts]]
filePath = "/volumes/logs/vector.yml"
content = """api:
enabled: true
address: 0.0.0.0:9001
sources:
docker_host:
type: docker_logs
transforms:
project_logs:
type: remap
inputs:
- docker_host
source: |-
.project = "default"
.event_message = del(.message)
compose_service, label_err = get(.label, ["com.docker.compose.service"])
if label_err != null || compose_service == null {
abort
}
compose_service = to_string!(compose_service)
if compose_service == "vector" {
abort
}
.appname = "supabase-" + compose_service
del(.container_created_at)
del(.container_id)
del(.source_type)
del(.stream)
del(.label)
del(.image)
del(.host)
del(.stream)
router:
type: route
inputs:
- project_logs
route:
kong: '.appname == "supabase-kong" || .appname == "supabase-envoy"'
auth: '.appname == "supabase-auth"'
rest: '.appname == "supabase-rest"'
realtime: '.appname == "supabase-realtime"'
storage: '.appname == "supabase-storage"'
functions: '.appname == "supabase-functions"'
db: '.appname == "supabase-db"'
# Ignores non nginx errors since they are related with kong booting up
kong_logs:
type: remap
inputs:
- router.kong
source: |-
req, err = parse_nginx_log(.event_message, "combined")
if err == null {
.timestamp = req.timestamp
.metadata.request.headers.referer = req.referer
.metadata.request.headers.user_agent = req.agent
.metadata.request.headers.cf_connecting_ip = req.client
.metadata.response.status_code = req.status
url, split_err = split(req.request, " ")
if split_err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
}
if err != null {
abort
}
kong_err:
type: remap
inputs:
- router.kong
source: |-
.metadata.request.method = "GET"
.metadata.response.status_code = 200
parsed, err = parse_nginx_log(.event_message, "error")
if err == null {
.timestamp = parsed.timestamp
.severity = parsed.severity
.metadata.request.host = parsed.host
.metadata.request.headers.cf_connecting_ip = parsed.client
url, err = split(parsed.request, " ")
if err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
}
if err != null {
abort
}
# Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
auth_logs:
type: remap
inputs:
- router.auth
source: |-
parsed, err = parse_json(.event_message)
if err == null {
.metadata.timestamp = parsed.time
.metadata = merge!(.metadata, parsed)
}
# PostgREST logs are structured so we separate timestamp from message using regex
rest_logs:
type: remap
inputs:
- router.rest
source: |-
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.timestamp = parse_timestamp!(value: parsed.time,format: "%d/%b/%Y:%H:%M:%S %z")
.metadata.host = .project
}
realtime_logs_filtered:
type: filter
inputs:
- router.realtime
condition: '!contains(string!(.event_message), "/health")'
realtime_logs:
type: remap
inputs:
- realtime_logs_filtered
source: |-
.metadata.project = del(.project)
.metadata.external_id = .metadata.project
parsed, err = parse_regex(.event_message, r'^(?P<time>\\d+:\\d+:\\d+\\.\\d+) \\[(?P<level>\\w+)\\] (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
}
functions_logs:
type: remap
inputs:
- router.functions
source: |-
.metadata.project_ref = del(.project)
# Storage logs may contain json objects so we parse them for completeness
storage_logs:
type: remap
inputs:
- router.storage
source: |-
.metadata.project = del(.project)
.metadata.tenantId = .metadata.project
parsed, err = parse_json(.event_message)
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
.metadata.timestamp = parsed.time
.metadata.context[0].host = parsed.hostname
.metadata.context[0].pid = parsed.pid
}
# Postgres logs some messages to stderr which we map to warning severity level
db_logs:
type: remap
inputs:
- router.db
source: |-
.metadata.host = "db-default"
.metadata.parsed.timestamp = .timestamp
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
if err != null || parsed == null {
.metadata.parsed.error_severity = "info"
}
if parsed.level != null {
.metadata.parsed.error_severity = parsed.level
}
if .metadata.parsed.error_severity == "info" {
.metadata.parsed.error_severity = "log"
}
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
sinks:
logflare_auth:
type: 'http'
inputs:
- auth_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod'
logflare_realtime:
type: 'http'
inputs:
- realtime_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod'
logflare_rest:
type: 'http'
inputs:
- rest_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod'
logflare_db:
type: 'http'
inputs:
- db_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=postgres.logs'
logflare_functions:
type: 'http'
inputs:
- functions_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs'
logflare_storage:
type: 'http'
inputs:
- storage_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2'
logflare_kong:
type: 'http'
inputs:
- kong_logs
- kong_err
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 30
retry_initial_backoff_secs: 1
headers:
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN?LOGFLARE_PUBLIC_ACCESS_TOKEN is required}
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod'
"""
[[config.mounts]]
filePath = "/volumes/pooler/pooler.exs"
content = """{:ok, _} = Application.ensure_all_started(:supavisor)
+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]]
-46
View File
@@ -1,46 +0,0 @@
services:
whatomate:
image: shridh0r/whatomate:0.1.1-beta-0.16.1
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
- TZ=Etc/UTC
volumes:
- ../files/config.toml:/app/config.toml:ro
- whatomate-uploads:/app/uploads
command: ["server", "-migrate", "-config", "/app/config.toml"]
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=whatomate
- POSTGRES_PASSWORD=whatomate
- POSTGRES_DB=whatomate
healthcheck:
test: ["CMD-SHELL", "pg_isready -U whatomate -d whatomate"]
interval: 10s
timeout: 5s
retries: 6
volumes:
- whatomate-postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- whatomate-redis-data:/data
volumes:
whatomate-uploads:
whatomate-postgres-data:
whatomate-redis-data:

Some files were not shown because too many files have changed in this diff Show More