Compare commits

...
Author SHA1 Message Date
Angel S. MorenoandGitHub b40194a4c2 feat(blueprints): add CakePHP 5 template (#1291)
* feat(blueprints): add CakePHP 5 template (themusicdev/php-apache runtime + MariaDB)

* ci: re-trigger build preview (first run hit a transient npm registry failure)

* fix(blueprints/cakephp): env via [config.env] names; pin image to released 1.0.0 (review feedback)
2026-11-05 09:38:03 +01:00
2134338a03 feat: upgrade Seafile template to 14.0 (#1251)
* feat: upgrade Seafile template to 14.0

* fix(seafile): pin seafile-mc 14.0.9 and sdoc-server 2.0.11 instead of moving -latest tags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 09:37:49 +01:00
Kortix SEOandGitHub 868c2f67ed Add kortix deployment to the list of templates (#1294) 2026-11-05 09:31:10 +01:00
Yota HamadaandGitHub 1dd541a61a Bump Dagu to 2.25.4 (#1293)
The template pins 2.10.7, which is fifteen minor releases behind the
current Dagu release. The compose file, basic auth variables, volume,
and port are unchanged and still valid on 2.25.4.
2026-11-05 09:30:50 +01:00
b6797e6967 Add ShopNex (#1284)
* Add ShopNex template

* fix(shopnex): pin v0.13.1 instead of latest

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:22:58 +01:00
NoahandGitHub e0a8e15c57 fix: updates actions runner to 2.338.2 and maintained Node24 (#1255)
* fix: updates actions runner and selects maintained node runtime

* fix: updates actions runner to 2.338.2
2026-11-04 21:22:21 +01:00
Stefan ErnstandGitHub 86f97ab400 chore: update Windshift to v0.9.3 (#1254)
* chore: update Windshift to v0.9.1

* chore: update Windshift to v0.9.3

Point the blueprint at the current release instead of v0.9.1.
2026-11-04 21:21:19 +01:00
19035e2d8d Update NaviMusic to version 4.3.0 (#1285)
* fix(navi-music): update to version 4.3.0

* feat(navi-music): update entrypoint for improved startup behavior

* fix(navi-music): pin yt-cipher by digest (it only publishes a moving master tag)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:19:30 +01:00
63c9ebfd57 feat: add Puppetflow with Postgres template (#1271)
* feat: add Puppetflow with Postgres template

* chore: bump Puppetflow templates to v2026.10.25

* feat: add second Puppetflow queue worker

* fix: send Puppetflow logs to stderr

* fix: harden Puppetflow Dokploy deployments

* fix: keep Dokploy default certificate handling

* fix: use Dokploy default HTTP domains

* chore: bump Puppetflow templates to v2026.10.26

* fix: configure Puppetflow locale and connection timeout

* chore: bump Puppetflow templates to v2026.10.27

* chore: bump Puppetflow templates to v2026.10.28

* chore: bump Puppetflow templates to v2026.10.29

* chore: bump Puppetflow templates to 2026.10.30

* chore: bump Puppetflow templates to 2026.10.32

* fix: align Pinokio production browser config

* chore: bump Puppetflow templates to v2026.11.1

* chore: bump Puppetflow templates to v2026.11.3

* chore: bump Puppetflow templates to v2026.11.4

* chore: bump Puppetflow templates to v2026.11.5

* chore: bump Puppetflow templates to v2026.11.6

* chore: bump Puppetflow templates to v2026.11.7

* fix(puppetflow): use service names instead of container_name

Templates must not set container_name; Compose service names already
resolve inside the project, so REDIS_HOST, PINOKIO_HOST, DB_HOST and the
internal URLs now point at them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(puppetflow): pinokio has no v2026.11.7 image, use v2026.11.1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:15:54 +01:00
e59b9d7123 Update Uptime Kuma image to 'next' version (#1264)
* Update Uptime Kuma image to 'next' version

Uptime Kuma "latest" status is stuck at older version and the official resource recommends to use the "next" tag to get the latest version.

* fix(uptime-kuma): pin 2.5.8 instead of the moving next tag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:15:07 +01:00
Carlos DornelesandGitHub 2500d1da92 feat: update appwrite template to 2.7.0 (#1259)
* feat(appwrite): bump template to 2.6.1

- Update appwrite/appwrite images to 2.6.1 and console to 1.2.97
- Add ExifTool service for image metadata stripping
- Add S3 path-style, VCS Entire, and maintenance queue variables
- Add maintenance scheduler interval and worker pool sizing
- Harden healthcheck with readiness marker and raise PostgreSQL shm
- Bump orchestrator to 2.2.4
- Remove duplicate _APP_CONSOLE_HOSTNAMES entry

* feat(appwrite): bump template to 2.6.2

* fix(appwrite): keep wildcard routers below console

Dokploy generates the console/API routers without a priority, so Traefik
falls back to rule length. A Sites/Functions HostRegexp rooted at the apex
also matches the console host and is longer, so it 404s the console.

The generated router names include a per-domain serial, so the template
cannot set their priority. Pin the template-owned wildcard routers to the
lowest priority instead, for both web and websecure.

* feat(appwrite): bump template to 2.7.0

- appwrite/appwrite 2.6.2 -> 2.7.0 across all services
- Console 1.2.108 -> 1.2.138 (and fix VITE_APPWRITE_VERSION default)
- browser 0.3.8 -> 0.3.10
- orchestrator 2.2.4 -> 4.0.0
- add server key rotation vars (V2, CURRENT, OLDEST)
- add realtime limits, DNS_EXTERNAL, ALLOWED_INTERNAL_ADDRESSES, DB_MAX_CONNECTIONS
- add alert config, APNs push and DOMAIN_FUNCTIONS_FALLBACK
- add the optional appwrite-hansi code review service
- raise worker max coroutines from 79 to 83

* fix(appwrite): ship the reviews worker disabled by default

The self-hosted console profile keeps features.reviews off, so the Reviews
screen only shows up behind the internal debug flag. Running appwrite-hansi
without it means a container that cannot be configured from the UI, so keep
the service and its _APP_REVIEWS_* variables commented out and document the
three steps to opt in.
2026-11-04 21:05:11 +01:00
Florent ClairambaultandGitHub e69493023e Update SolidPing to 0.41.0 (#1256) 2026-11-04 21:04:53 +01:00
Ghazi Ben DahmaneandGitHub 51dfd5694c Add Container Monitoring template (cAdvisor, Prometheus, Grafana) (#1257)
* Add container-monitoring template

* Default container detail dashboard to a single container
2026-11-04 21:04:44 +01:00
d348e0565b chore: update the dossier version and disable germany's BA (#1258)
* chore: update the dossier version and disable germany's BA

* fix(dossier): match meta.json version to the v1.9.0 image

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:04:36 +01:00
Carlos DornelesandGitHub 988ab9a518 feat: update openwa template to 0.25.0 (#1260)
* feat(openwa): bump template to 0.25.0

- Update OpenWA image to 0.25.0
- Add 75s stop grace period for the longer bounded teardown

* feat(openwa): enable the MCP server by default

- Add mcp_enabled and mcp_readonly template variables
- Pass MCP_ENABLED and MCP_READONLY to the container
- Keep the read-only tool tier as the safe default

* feat(openwa): enable Swagger UI by default

Add an enable_swagger variable, wired to ENABLE_SWAGGER, so /api/docs is
served despite the upstream production default.
2026-11-04 20:41:40 +01:00
c91eee410e feat: add Customermates template (#1279)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 20:13:22 +01:00
4cb6a65bb7 Add Calnode template (#1280)
* feat: add calnode template

* feat: add commas to fix template

* feat: replace logo

* fix: add url prefix

* fix: Remove default value for litestream url when unconfigured

* fix(calnode): pin the exact 0.12.0 tag instead of the moving 0.12 tag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 20:12:48 +01:00
WaleedandGitHub 2196ae3834 feat(blueprints): add itsaplan template (#1282) 2026-11-04 20:06:43 +01:00
Raffaele PizzariandGitHub 1de1e92f58 Update OpenTodo to 0.19.0 (#1281) 2026-11-04 20:06:06 +01:00
Yusuf GündoğduandGitHub 159643fdc2 fix(libredb-studio): update the blueprint to 1.2.0 (#1283)
* chore(libredb-studio): bump image to 1.2.0

* fix(libredb-studio): set version to 1.2.0 and update the engine counts

meta.json still pinned 1.0.0 while docker-compose.yml pulls 1.2.0, which the
blueprint rules require to match. The description also carried the 0.18.2
driver and engine counts; 1.2.0 ships 42 drivers reaching 69 named engines.
2026-11-04 20:04:37 +01:00
Ivan FickoandGitHub 098786b840 feat(privatepaste): bump to 0.5.0 (#1286)
* feat(privatepaste): bump to 0.5.0

* feat(privatepaste): bump to 0.5.0

* feat(privatepaste): bump to 0.5.0
2026-11-04 19:56:34 +01:00
LezethoandGitHub dbd79cf7ae feat(umami): bump to 3.4.1 (#1287)
* feat(umami): bump to 3.4.1

* feat(umami): bump to 3.4.1
2026-11-04 19:54:54 +01:00
Perminus GaitaandGitHub a49594ed0a feat(blueprints): add Zeqqat template (#1288) 2026-11-04 19:52:45 +01:00
JRKandGitHub 7ddeb8bc40 fix(docuseal): correct PostgreSQL 18 volume mount (#1290) 2026-11-04 19:50:40 +01:00
Benjamin TouchardandGitHub 1b3c254f89 feat(blueprints): add maintenant template (#1261) 2026-10-31 15:06:01 +01:00
dillonstreatorandGitHub 4c4abb71da Add Reelsaw template (#1262)
Signed-off-by: dillonstreator <dillonstreator@gmail.com>
2026-10-31 15:04:19 +01:00
CardonaandGitHub cdf1babf21 fix(ponos): update template to 0.2.0 and move to the hermez-agency image (#1263) 2026-10-31 15:01:38 +01:00
Raffaele PizzariandGitHub 64dda82a49 feat: add OpenTodo template (#1265)
* feat: add OpenTodo template

* OpenTodo: update to 0.12.0

* OpenTodo 0.13.0
2026-10-31 14:57:24 +01:00
d440767c4d fix(yuvomi): bump to 2.80.0 and generate DB_ENCRYPTION_KEY (#1266)
Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Co-authored-by: Ulas Kalayci <ulas.kalayci@icloud.com>
2026-10-31 14:55:55 +01:00
25c3e3ea38 feat: add PostgreSQL with 80+ extensions (postgres-extensor) template (#1267)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:53:52 +01:00
Web ArtisanandGitHub 9760fe0798 fix(junctio): update image to 0.2.6 (#1269)
* fix(junctio): update image to 0.2.5

* fix(junctio): update image to 0.2.6
2026-10-31 14:51:50 +01:00
KacperandGitHub 576b4e746c feat: add mcpspan template (#1270)
* feat: add mcpspan template

* chore(mcpspan): update to 0.13.0, with the dashboard's address for MCP clients signing in
2026-10-31 14:49:58 +01:00
Théo MeunierandGitHub 06f093336d Add Uptime kotlin template (#1272)
* feat: add Uptime Kotlin Docker Compose setup

* fix: remove default timezone setting from Docker Compose

* fix: update Docker Compose configuration for uptime-kotlin service
2026-10-31 14:42:24 +01:00
08022ee6b3 Legejo: update to 1.33.0 (#1273)
Co-authored-by: Markus Rådberg <3110202+jularboolean@users.noreply.github.com>
2026-10-31 14:40:23 +01:00
aa108ff7e8 fix(phpmyadmin): migrate to the official phpMyAdmin Docker image (#1275)
* fix(phpmyadmin): migrate to the official phpMyAdmin Docker image

See: https://github.com/phpmyadmin/docker/issues/363
Signed-off-by: William Desportes <williamdes@wdes.fr>

* fix(phpmyadmin): update phpMyAdmin to 5.2.3

Pin the official image to the current release instead of a floating reference.

See: https://github.com/phpmyadmin/docker/issues/363
Signed-off-by: William Desportes <williamdes@wdes.fr>

* fix(phpmyadmin): match meta.json version to the 5.2.3 image

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: William Desportes <williamdes@wdes.fr>
Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:39:23 +01:00
e2156415cc feat: add PrivatePaste template (#1276)
* feat: add PrivatePaste template

Self-hosted clipboard sync with an encrypted, searchable history; apps for Windows, macOS, Linux and Android. Image ghcr.io/dynamicsninja/privatepaste:0.2.0 (linux/amd64, linux/arm64).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7qnD4vNtkFtpTzPq1fw5R

* chore(privatepaste): use image 0.3.1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:36:33 +01:00
e873301de1 cachkey: bump version to 0.2.0 (#1277)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:34:47 +01:00
Madhav GuptaandGitHub a6fa179d68 Update Calcom Template to add all env vars (#1278)
* feat: add all env variables for calcom image and health checking for postgres + restart config for containers

* feat: add postgres var generators
2026-10-31 14:32:34 +01:00
4cb26f1575 chore: remove compose version + add checks (#1268)
* fix: reject deprecated Docker Compose version field

* chore: remove deprecated version field in docker compose

* chore(orbit): drop the obsolete compose version key

orbit was merged after this PR was opened and still had version: '3.8' at
the end of the file, which the new validator check rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:26:45 +01:00
hanshs474andGitHub e44418a2fc feat: add Seedance 2.5 Video Generator (emaki) template (#1229)
* add emaki-video-starter docker-compose.yml

* add emaki-video-starter template.toml

* add emaki-video-starter meta.json

* add emaki-video-starter emaki-video-starter.png
2026-10-20 19:58:37 -06:00
9a61776e91 fix(libredb-studio): update image to 1.0.0 (#1230)
* libredb-studio: bump to 1.0.0

* fix(libredb-studio): match meta.json version to the 1.0.0 image

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:58:33 -06:00
f9dd6e28ba feat: add Falco template (#1236)
* feat: add Falco template

* fix: verify Falco template in Dokploy

* fix: make Telegram optional in Falco template

* fix(falco): default MONITORED_HOST so the template deploys out of the box

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:58:31 -06:00
089933f619 feat: add Legejo template (#1233)
* feat: add Legejo template

* feat(legejo): pass the whole environment and list the optional variables

---------

Co-authored-by: Markus Rådberg <3110202+jularboolean@users.noreply.github.com>
2026-10-20 19:55:31 -06:00
Benjamin NussbaumandGitHub 98799bc962 feat(blueprint): update trmnl-larapaper template (#1235) 2026-10-20 19:53:53 -06:00
NoahandGitHub d89d479bb5 feat: adds GitHub Actions runner template (#1234) 2026-10-20 19:53:05 -06:00
Brahim RizqandGitHub 0de3c52420 feat: add Shoutrrr social media scheduler template (#1238)
* feat: add Shoutrrr social media scheduler template

* docs: record Shoutrrr deployment verification

* docs: clarify Shoutrrr health checks and polling units
2026-10-20 19:50:06 -06:00
38caad0c5d feat: add Dossier template (#1240)
* feat: add Dossier template

* docs: note Dossier dashboard timezone limitation

* fix(dossier): derive the password hash at startup so the template deploys

DOSSIER_PASSWORD must be an argon2id hash and the template shipped it empty
behind a :? guard, so the deploy always failed. The template now generates
DOSSIER_ADMIN_PASSWORD and the container hashes it with the bundled
hash-password tool before starting; a user-provided DOSSIER_PASSWORD hash
still takes precedence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:47:55 -06:00
Brahim RizqandGitHub ac9a28da53 feat: add TryPost social media scheduler template (#1239)
* feat: add TryPost social media scheduler template

* docs: record TryPost deployment and integration tests
2026-10-20 19:42:51 -06:00
Brahim RizqandGitHub 4b8900972c feat: add WUD monitoring template (#1242)
* feat: add WUD monitoring template

* feat(wud): exclude Dokploy infrastructure by default
2026-10-20 19:31:33 -06:00
58c610dbbe feat: add 3Dokploy template (#1243)
3Dokploy is a live, read-only, isometric 3D view of the Dokploy it runs
on: projects as buildings, services as racks, deploys on carts.

Claude-Session: https://claude.ai/code/session_01KxDD5rKN6o74v1MFAaGiMe

Co-authored-by: suleyman <24796754+suleyman@users.noreply.github.com>
2026-10-20 19:31:28 -06:00
276fe60979 fix(convex): GHCR image path and bump version (#1245)
* fix(convex): update GHCR image path from get-convex to convex & bump convex version

* fix(convex): set meta version to the pinned image commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:29:41 -06:00
Victor LunaandGitHub 1153a9094d Add kuukura template (self-hosted secrets vault) (#1250)
Signed-off-by: Victor Luna <victor.luna.mullisaca@gmail.com>
2026-10-20 19:29:37 -06:00
Angel S. MorenoandGitHub 9663d868a6 feat: add Cypht template (#1244)
Add Cypht 2.13.0 webmail aggregator with MariaDB for accounts and settings and Redis for sessions and cache.
2026-10-20 19:29:08 -06:00
8ce3d7f453 feat: add Harbor 2.15.4 template with Trivy (#1249)
* feat: add standalone Harbor initialization source

* feat: add Harbor Dokploy blueprint and validation

* docs: record GitHub publication and remote build validation

* chore: scope Harbor contribution for upstream review

* chore(harbor): keep the PR inside blueprints/

Drop the repo-level validate-harbor workflow and the .gitignore entry; the
blueprint's own tools/ and tests/ still document how it was validated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:29:05 -06:00
Khiet Tam NguyenandGitHub 2dbd7aa550 feat(blueprint): add template for eclipse mosquitto MQTT broker (#1247)
* feat(blueprint): add template for eclipse mosquitto MQTT broker

* fix: safely regenerate Mosquitto password file on startup
2026-10-20 19:29:00 -06:00
Khiet Tam NguyenandGitHub a69580f988 feat: add HiveMQ Community Edition broker blueprint (#1248)
* feat: add HiveMQ Community Edition broker blueprint

* refactor: mount HiveMQ CE initialization script

* fix: eliminate HiveMQ CE configuration warnings
2026-10-20 19:22:09 -06:00
eaa6b986a7 fix(wise-mcp): pin image to 1.1.1 instead of latest (#1227)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:26:12 -06:00
Narciso E. Núñez AriasandGitHub 769f9661bc fix(deps): bump source-map-js to 1.2.2 (Dependabot alert #67) (#1228) 2026-10-15 11:25:32 -06:00
208 changed files with 9591 additions and 408 deletions
+5 -5
View File
@@ -1703,8 +1703,8 @@ packages:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
source-map-js@1.2.2:
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
engines: {node: '>=0.10.0'}
style-mod@4.1.2:
@@ -2786,7 +2786,7 @@ snapshots:
jiti: 2.6.1
lightningcss: 1.30.2
magic-string: 0.30.21
source-map-js: 1.2.1
source-map-js: 1.2.2
tailwindcss: 4.1.17
'@tailwindcss/oxide-android-arm64@4.1.17':
@@ -3217,7 +3217,7 @@ snapshots:
dependencies:
nanoid: 3.3.19
picocolors: 1.1.1
source-map-js: 1.2.1
source-map-js: 1.2.2
queue-microtask@1.2.3: {}
@@ -3335,7 +3335,7 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
source-map-js@1.2.1: {}
source-map-js@1.2.2: {}
style-mod@4.1.2: {}
+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#101a3a"/><stop offset="1" stop-color="#050913"/></linearGradient>
<linearGradient id="rim" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#7c6cff"/><stop offset="1" stop-color="#2de2ff"/></linearGradient>
</defs>
<rect x="6" y="6" width="500" height="500" rx="116" fill="url(#bg)" stroke="url(#rim)" stroke-width="12"/>
<polygon points="256.0,181.5 442.0,288.8 256.0,396.2 70.0,288.8" fill="#2de2ff" fill-opacity="0.12" stroke="#2de2ff" stroke-width="9" stroke-linejoin="round"/>
<polygon points="195.5,237.8 256.0,272.7 256.0,197.6 195.5,162.7" fill="#4f3fd0"/>
<polygon points="316.5,237.8 256.0,272.7 256.0,197.6 316.5,162.7" fill="#7c6cff"/>
<polygon points="256.0,127.8 316.5,162.7 256.0,197.6 195.5,162.7" fill="#b3abff"/>
<line x1="208.8" y1="219.2" x2="242.7" y2="238.8" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
<line x1="208.8" y1="194.4" x2="242.7" y2="213.9" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
<polygon points="283.9,288.8 344.3,323.7 344.3,211.0 283.9,176.1" fill="#0f9cbb"/>
<polygon points="404.8,288.8 344.3,323.7 344.3,211.0 404.8,176.1" fill="#2de2ff"/>
<polygon points="344.4,141.2 404.8,176.1 344.3,211.0 283.9,176.1" fill="#b3f6ff"/>
<line x1="297.2" y1="257.1" x2="331.1" y2="276.6" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
<line x1="297.2" y1="219.9" x2="331.1" y2="239.4" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
<polygon points="195.6,339.9 256.0,374.8 256.0,315.7 195.6,280.8" fill="#2856c8"/>
<polygon points="316.4,339.9 256.0,374.8 256.0,315.7 316.4,280.8" fill="#4f8bff"/>
<polygon points="256.0,245.9 316.4,280.8 256.0,315.7 195.6,280.8" fill="#a8c8ff"/>
<line x1="208.8" y1="318.0" x2="242.7" y2="337.5" stroke="#2bff9a" stroke-width="11" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+10
View File
@@ -0,0 +1,10 @@
services:
3dokploy:
image: ghcr.io/suleyman/3dokploy:0.1.1
restart: unless-stopped
environment:
- DOKPLOY_URL=${DOKPLOY_URL}
- DOKPLOY_API_KEY=${DOKPLOY_API_KEY}
- BASIC_AUTH=${BASIC_AUTH}
extra_hosts:
- host.docker.internal:host-gateway
+13
View File
@@ -0,0 +1,13 @@
{
"id": "3dokploy",
"name": "3Dokploy",
"version": "0.1.1",
"description": "A live, isometric 3D view of your Dokploy: projects as buildings, services as racks, deploys rolling in on carts, network traffic as packets. Read-only.",
"logo": "3dokploy.svg",
"links": {
"github": "https://github.com/suleyman/3dokploy",
"website": "",
"docs": "https://github.com/suleyman/3dokploy#readme"
},
"tags": ["monitoring", "dashboard", "devops"]
}
+19
View File
@@ -0,0 +1,19 @@
[variables]
main_domain = "${domain}"
auth_password = "${password:24}"
[config]
mounts = []
[[config.domains]]
serviceName = "3dokploy"
port = 3_000
host = "${main_domain}"
[config.env]
# The Dokploy on this same server. If its port 3000 is closed, use its public URL instead.
DOKPLOY_URL = "http://host.docker.internal:3000"
# Required: an API key from Settings → Profile → API/CLI. It stays on the server.
DOKPLOY_API_KEY = ""
# Site login (user admin).
BASIC_AUTH = "admin:${auth_password}"
+130 -13
View File
@@ -6,22 +6,36 @@ x-logging: &x-logging
max-size: "10m"
services:
appwrite:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
<<: *x-logging
restart: unless-stopped
labels:
- traefik.enable=true
# Dokploy generates the specific routers (console, API, realtime) from the
# domains declared in template.toml, naming them
# "<appName>-<uniqueConfigKey>-<entrypoint>" and never setting a priority.
# Their priority is therefore Traefik's default: the length of the rule.
# The wildcard routers below are owned by this template, and a HostRegexp
# rule is longer than a Host rule, so without an explicit priority they can
# outrank the console/API router whenever the wildcard also matches the
# host (for example a Sites/Functions wildcard rooted at the same apex as
# the console). Pin every wildcard router to the lowest useful priority so
# the specific router always wins; 1 is below any rule-derived priority.
- 'traefik.http.routers.appwrite-sites-wildcard.rule=HostRegexp(`^.+\.${_APP_DOMAIN_SITES}$$`)'
- traefik.http.routers.appwrite-sites-wildcard.priority=1
- traefik.http.routers.appwrite-sites-wildcard.entrypoints=web
- traefik.http.routers.appwrite-sites-wildcard.service=appwrite-wildcard
- 'traefik.http.routers.appwrite-sites-wildcard-secure.rule=HostRegexp(`^.+\.${_APP_DOMAIN_SITES}$$`)'
- traefik.http.routers.appwrite-sites-wildcard-secure.priority=1
- traefik.http.routers.appwrite-sites-wildcard-secure.entrypoints=websecure
- traefik.http.routers.appwrite-sites-wildcard-secure.tls=true
- traefik.http.routers.appwrite-sites-wildcard-secure.service=appwrite-wildcard
- 'traefik.http.routers.appwrite-functions-wildcard.rule=HostRegexp(`^.+\.${_APP_DOMAIN_FUNCTIONS}$$`)'
- traefik.http.routers.appwrite-functions-wildcard.priority=1
- traefik.http.routers.appwrite-functions-wildcard.entrypoints=web
- traefik.http.routers.appwrite-functions-wildcard.service=appwrite-wildcard
- 'traefik.http.routers.appwrite-functions-wildcard-secure.rule=HostRegexp(`^.+\.${_APP_DOMAIN_FUNCTIONS}$$`)'
- traefik.http.routers.appwrite-functions-wildcard-secure.priority=1
- traefik.http.routers.appwrite-functions-wildcard-secure.entrypoints=websecure
- traefik.http.routers.appwrite-functions-wildcard-secure.tls=true
- traefik.http.routers.appwrite-functions-wildcard-secure.service=appwrite-wildcard
@@ -29,7 +43,7 @@ services:
healthcheck:
test:
- CMD-SHELL
- 'curl -fsS -o /dev/null -H "Host: $${_APP_DOMAIN:-localhost}" http://localhost/v1/health/version'
- 'test -f /tmp/.appwrite-ready && curl -fsS -o /dev/null -H "Host: $${_APP_DOMAIN:-localhost}" http://localhost/v1/health/version'
interval: 5s
timeout: 5s
retries: 12
@@ -52,8 +66,13 @@ services:
condition: service_started
appwrite-autogravity:
condition: service_started
appwrite-exiftool:
condition: service_started
environment:
- _APP_DNS_EXTERNAL
- _APP_ALLOWED_INTERNAL_ADDRESSES
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_LOCALE
@@ -86,7 +105,12 @@ services:
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_ROUTER_AUTO_CERTIFICATES
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_NOTIFICATIONS_PUSH_APNS
- _APP_NOTIFICATIONS_PUSH_APNS_KEY
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_URL
@@ -97,6 +121,7 @@ services:
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_FUNCTIONS_FALLBACK
- _APP_DOMAIN_SITES
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
@@ -134,6 +159,7 @@ services:
- _APP_CONNECTIONS_DATABASE_VECTORSDB
- _APP_EMBEDDING
- _APP_USAGE_STATS
- _APP_ALERTS_USAGE_LAG
- _APP_USAGE_PASS
- _APP_CONNECTIONS_DB_USAGE
- _APP_POOL_SIZE_USAGE
@@ -153,6 +179,7 @@ services:
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_AUTOGRAVITY_HOST
- _APP_EXIFTOOL_HOST
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
@@ -162,6 +189,7 @@ services:
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_S3_ENDPOINT
- _APP_STORAGE_S3_PATH_STYLE
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
@@ -194,6 +222,7 @@ services:
- _APP_JOBS_HOST
- _APP_JOBS_SECRET
- _APP_JOBS_ENDPOINT
# - _APP_REVIEWS_SECRET # uncomment with the optional reviews setup
- _APP_GEO_SECRET
- _APP_GEO_ENDPOINT
- _APP_LOGGING_CONFIG
@@ -205,6 +234,7 @@ services:
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_MAINTENANCE_RETENTION_ALERTS
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
@@ -229,6 +259,10 @@ services:
- _APP_VCS_BITBUCKET_WEBHOOK_SECRET
- _APP_VCS_ORIGIN_CLIENT_ID
- _APP_VCS_ORIGIN_PRIVATE_KEY
- _APP_VCS_ENTIRE_PLUGIN
- _APP_VCS_ENTIRE_PRIVATE_KEY
- _APP_VCS_ENTIRE_ISSUER
- _APP_VCS_ENTIRE_ENDPOINT
- _APP_VCS_WEBHOOK_URL
- _APP_MIGRATION_HOST
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
@@ -237,16 +271,17 @@ services:
- _APP_PWNED_PASSWORDS_DSN
appwrite-console:
image: appwrite/new:1.2.74-self-hosted
image: appwrite/new:1.2.138-self-hosted
<<: *x-logging
restart: unless-stopped
environment:
- VITE_CONSOLE_PROFILE=self-hosted
- VITE_APPWRITE_VERSION=${_APP_VERSION:-2.7.0}
- APPWRITE_ENDPOINT_SAME_ORIGIN=true
- VITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
appwrite-realtime:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: realtime
<<: *x-logging
restart: unless-stopped
@@ -257,12 +292,19 @@ services:
condition: service_healthy
environment:
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_CONSOLE_HOSTNAMES
- _APP_REALTIME_TAIL_RATE
- _APP_LIMIT_REALTIME_SUBSCRIPTIONS
- _APP_LIMIT_REALTIME_CHANNELS
- _APP_LIMIT_REALTIME_QUERIES
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -298,7 +340,7 @@ services:
- _APP_LOGGING_FORMAT
appwrite-mqtt:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: mqtt
<<: *x-logging
restart: unless-stopped
@@ -319,11 +361,15 @@ services:
start_period: 20s
environment:
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_WORKER_PER_CORE
- _APP_OPTIONS_ABUSE
- _APP_OPTIONS_ROUTER_PROTECTION
- _APP_REALTIME_TAIL_RATE
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -348,7 +394,7 @@ services:
- _APP_MQTT_MAX_PACKET_SIZE
appwrite-worker:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: worker
<<: *x-logging
restart: unless-stopped
@@ -369,13 +415,21 @@ services:
extra_hosts:
- host.docker.internal:host-gateway
environment:
- _APP_DNS_EXTERNAL
- _APP_ALLOWED_INTERNAL_ADDRESSES
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_WORKERS_NUM=1
# Pool sizing only (sum of per-queue caps). Each queue keeps its own maxCoroutines in PHP; databases stays at 1.
- _APP_WORKER_MAX_COROUTINES=78
# Pool sizing only (sum of per-queue caps). Each queue keeps its own maxCoroutines in PHP; databases and maintenance stay at 1.
- _APP_WORKER_MAX_COROUTINES=83
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_NOTIFICATIONS_PUSH_APNS
- _APP_NOTIFICATIONS_PUSH_APNS_KEY
- _APP_EMAIL_SECURITY
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
@@ -408,6 +462,7 @@ services:
- _APP_CONNECTIONS_DATABASE_VECTORSDB
- _APP_EMBEDDING
- _APP_USAGE_STATS
- _APP_ALERTS_USAGE_LAG
- _APP_USAGE_PASS
- _APP_CONNECTIONS_DB_USAGE
- _APP_POOL_SIZE_USAGE
@@ -416,6 +471,7 @@ services:
- _APP_MAINTENANCE_RETENTION_USAGE_TTL
- _APP_STATS_USAGE_QUEUE_NAME
- _APP_STATS_RESOURCES_QUEUE_NAME
- _APP_MAINTENANCE_QUEUE_NAME
- _APP_STATS_RESOURCES_INTERVAL
- _APP_LOGGING_CONFIG
- _APP_LOGGING_FORMAT
@@ -440,6 +496,7 @@ services:
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_S3_ENDPOINT
- _APP_STORAGE_S3_PATH_STYLE
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
@@ -491,6 +548,10 @@ services:
- _APP_VCS_BITBUCKET_WEBHOOK_SECRET
- _APP_VCS_ORIGIN_CLIENT_ID
- _APP_VCS_ORIGIN_PRIVATE_KEY
- _APP_VCS_ENTIRE_PLUGIN
- _APP_VCS_ENTIRE_PRIVATE_KEY
- _APP_VCS_ENTIRE_ISSUER
- _APP_VCS_ENTIRE_ENDPOINT
- _APP_VCS_WEBHOOK_URL
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
@@ -509,7 +570,7 @@ services:
- _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
appwrite-task-scheduler:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: schedule
<<: *x-logging
restart: unless-stopped
@@ -520,11 +581,15 @@ services:
condition: service_healthy
environment:
- _APP_ENV
- _APP_MEMORY_LIMIT
# Combined mode runs functions/executions/messages timers + enqueue coroutines
# against shared console/cache pools; floor must cover concurrent ticks.
- _APP_WORKER_MAX_COROUTINES=8
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_OPTIONS_FORCE_HTTPS
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
@@ -552,12 +617,16 @@ services:
- _APP_MAINTENANCE_RETENTION_USAGE_TTL
- _APP_STATS_USAGE_QUEUE_NAME
- _APP_STATS_RESOURCES_QUEUE_NAME
- _APP_MAINTENANCE_QUEUE_NAME
- _APP_STATS_RESOURCES_INTERVAL
- _APP_FUNCTIONS_SCHEDULE_SPREAD
- _APP_MAINTENANCE_INACTIVE_INTERVAL
- _APP_MAINTENANCE_INTERVAL
- _APP_ALERTS_INTERVAL
- _APP_LOGGING_FORMAT
appwrite-task-maintenance:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: maintenance
<<: *x-logging
restart: unless-stopped
@@ -568,6 +637,7 @@ services:
condition: service_healthy
environment:
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
@@ -579,6 +649,9 @@ services:
- _APP_DOMAIN_FUNCTIONS
- _APP_ROUTER_AUTO_CERTIFICATES
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -598,10 +671,11 @@ services:
- _APP_MAINTENANCE_RETENTION_AUDIT
- _APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_MAINTENANCE_RETENTION_ALERTS
- _APP_LOGGING_FORMAT
appwrite-task-interval:
image: appwrite/appwrite:2.5.0
image: appwrite/appwrite:2.7.0
entrypoint: interval
<<: *x-logging
restart: unless-stopped
@@ -612,6 +686,7 @@ services:
condition: service_healthy
environment:
- _APP_ENV
- _APP_MEMORY_LIMIT
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -622,6 +697,9 @@ services:
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_OPENSSL_KEY_V1
- _APP_OPENSSL_KEY_V2
- _APP_OPENSSL_KEY_CURRENT
- _APP_OPENSSL_KEY_OLDEST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -638,7 +716,7 @@ services:
- _APP_LOGGING_FORMAT
appwrite-browser:
image: appwrite/browser:0.3.6
image: appwrite/browser:0.3.10
<<: *x-logging
restart: unless-stopped
environment:
@@ -649,6 +727,35 @@ services:
- 1.1.1.1
- 8.8.8.8
# AI code review for GitHub pull requests (optional, shipped off by default).
# The Reviews screen is hidden in the self-hosted console profile, so this
# worker would run with nowhere to configure it from. To opt in:
# 1. uncomment the _APP_REVIEWS_* variables in template.toml (this enables
# the /v1/reviews API and generates the shared secret);
# 2. uncomment this service;
# 3. set _APP_REVIEWS_AI_GATEWAY_KEY, otherwise no review ever runs.
# appwrite-hansi:
# image: ghcr.io/appwrite/hansi:0.4.2
# <<: *x-logging
# restart: unless-stopped
# # A stopped review is not redelivered for about a day, so a running one gets time to finish.
# stop_grace_period: 16m
# depends_on:
# redis:
# condition: service_healthy
# appwrite:
# condition: service_started
# environment:
# - HANSI_APPWRITE_ENDPOINT=http://appwrite/v1
# - HANSI_APPWRITE_SECRET=${_APP_REVIEWS_SECRET}
# - HANSI_QUEUE_URL=redis://${_APP_REDIS_HOST:-redis}:${_APP_REDIS_PORT:-6379}
# - HANSI_QUEUE_USER=${_APP_REDIS_USER}
# - HANSI_QUEUE_PASSWORD=${_APP_REDIS_PASS}
# - HANSI_SHUTDOWN_TIMEOUT=900
# - HANSI_REVIEW_MODEL=${_APP_REVIEWS_MODEL}
# - HANSI_VERIFY_MODEL=${_APP_REVIEWS_VERIFY_MODEL:-${_APP_REVIEWS_MODEL}}
# - AI_GATEWAY_API_KEY=${_APP_REVIEWS_AI_GATEWAY_KEY}
appwrite-autogravity:
image: ghcr.io/appwrite/autogravity:0.0.11
<<: *x-logging
@@ -656,6 +763,11 @@ services:
environment:
- MODEL_BACKEND=focalnet
appwrite-exiftool:
image: ghcr.io/appwrite/exiftool:0.1.1
<<: *x-logging
restart: unless-stopped
appwrite-geo:
image: appwrite/geo:0.3.2
<<: *x-logging
@@ -738,7 +850,7 @@ services:
start_period: 5s
orchestrator:
image: ghcr.io/open-runtimes/orchestrator/orchestrator:2.2.2
image: ghcr.io/open-runtimes/orchestrator/orchestrator:4.0.0
hostname: orchestrator
<<: *x-logging
restart: unless-stopped
@@ -761,6 +873,8 @@ services:
image: appwrite/postgres:0.1.0
<<: *x-logging
restart: unless-stopped
init: true
shm_size: 1g
volumes:
- appwrite-postgresql:/var/lib/postgresql:rw
environment:
@@ -769,6 +883,9 @@ services:
- POSTGRES_PASSWORD=${_APP_DB_PASS}
command:
- postgres
# Every HTTP worker and worker coroutine pools its own connections; the default of 100 runs out under load.
- -c
- max_connections=${_APP_DB_MAX_CONNECTIONS:-500}
healthcheck:
test:
- CMD-SHELL
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "appwrite",
"name": "Appwrite",
"version": "2.5.0",
"version": "2.7.0",
"description": "Appwrite is an end-to-end platform for building Web, Mobile, Native, or Backend apps, packaged as a set of Docker microservices. It includes both a backend server and a fully integrated hosting solution for deploying static and server-side rendered frontends. Appwrite abstracts the complexity and repetitiveness required to build modern apps from scratch and allows you to build secure, full-stack applications faster.\nUsing Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and more services.",
"links": {
"github": "https://github.com/appwrite/appwrite",
+34 -1
View File
@@ -12,6 +12,9 @@ usage_pw = "${password:32}"
executor_secret = "${password:32}"
jobs_secret = "${password:32}"
geo_secret = "${password:32}"
# Optional AI code review of GitHub pull requests. Uncomment together with the
# _APP_REVIEWS_* variables and the appwrite-hansi service in docker-compose.yml.
# reviews_secret = "${password:32}"
console_domain = "console.${main_domain}"
[config]
@@ -20,6 +23,8 @@ console_domain = "console.${main_domain}"
isolated = false
env = [
"_APP_ENV=production",
"_APP_VERSION=2.7.0",
"_APP_MEMORY_LIMIT=512M",
"_APP_EDITION=self-hosted",
"_APP_LOCALE=en",
"_APP_WORKER_PER_CORE=6",
@@ -31,7 +36,12 @@ env = [
"_APP_ROUTER_AUTO_CERTIFICATES=enabled",
"_APP_LOCKING_ENABLED=enabled",
"_APP_OPENSSL_KEY_V1=${openssl_key}",
"_APP_OPENSSL_KEY_V2=",
"_APP_OPENSSL_KEY_CURRENT=1",
"_APP_OPENSSL_KEY_OLDEST=1",
"_APP_NOTIFICATIONS_TRACKING_SECRET=${notifications_tracking_secret}",
"_APP_NOTIFICATIONS_PUSH_APNS=",
"_APP_NOTIFICATIONS_PUSH_APNS_KEY=",
"_APP_DOMAIN=${main_domain}",
"_APP_CONSOLE_DOMAIN=${console_domain}",
"_APP_CONSOLE_URL=https://${console_domain}",
@@ -39,17 +49,19 @@ env = [
"_APP_CONSOLE_TRUSTED_PROJECTS=",
"_APP_CUSTOM_DOMAIN_DENY_LIST=example.com,test.com,app.example.com",
"_APP_DOMAIN_FUNCTIONS=${functions_domain}",
"_APP_DOMAIN_FUNCTIONS_FALLBACK=",
"_APP_DOMAIN_SITES=${sites_domain}",
"_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_DNS_EXTERNAL=",
"_APP_ALLOWED_INTERNAL_ADDRESSES=",
"_APP_TRUSTED_HEADERS=x-forwarded-for",
"_APP_CONSOLE_WHITELIST_ROOT=enabled",
"_APP_CONSOLE_WHITELIST_EMAILS=",
"_APP_CONSOLE_WHITELIST_IPS=",
"_APP_CONSOLE_HOSTNAMES=",
"_APP_CONSOLE_SCHEMA=appwriteio",
"_APP_CONSOLE_SESSION_ALERTS=disabled",
"_APP_CONSOLE_COUNTRIES_DENYLIST=",
@@ -113,9 +125,15 @@ env = [
"_APP_MAINTENANCE_RETENTION_USAGE_TTL=180",
"_APP_STATS_USAGE_QUEUE_NAME=v1-stats-usage",
"_APP_STATS_RESOURCES_QUEUE_NAME=v1-stats-resources",
"_APP_MAINTENANCE_QUEUE_NAME=v1-maintenance",
"_APP_STATS_RESOURCES_INTERVAL=3600",
"_APP_ALERTS_INTERVAL=60",
"_APP_ALERTS_USAGE_LAG=120",
"_APP_MQTT_REPLAY_DEPTH=5",
"_APP_MQTT_MAX_PACKET_SIZE=64000",
"_APP_LIMIT_REALTIME_SUBSCRIPTIONS=100",
"_APP_LIMIT_REALTIME_CHANNELS=100",
"_APP_LIMIT_REALTIME_QUERIES=100",
"_APP_SMTP_HOST=",
"_APP_SMTP_PORT=",
"_APP_SMTP_SECURE=",
@@ -128,6 +146,7 @@ env = [
"_APP_STORAGE_LIMIT=30000000",
"_APP_STORAGE_PREVIEW_LIMIT=20000000",
"_APP_AUTOGRAVITY_HOST=http://appwrite-autogravity:8080",
"_APP_EXIFTOOL_HOST=http://appwrite-exiftool:8080",
"_APP_STORAGE_ANTIVIRUS=disabled",
"_APP_STORAGE_ANTIVIRUS_HOST=clamav",
"_APP_STORAGE_ANTIVIRUS_PORT=3310",
@@ -137,6 +156,7 @@ env = [
"_APP_STORAGE_S3_REGION=us-east-1",
"_APP_STORAGE_S3_BUCKET=",
"_APP_STORAGE_S3_ENDPOINT=",
"_APP_STORAGE_S3_PATH_STYLE=",
"_APP_STORAGE_DO_SPACES_ACCESS_KEY=",
"_APP_STORAGE_DO_SPACES_SECRET=",
"_APP_STORAGE_DO_SPACES_REGION=us-east-1",
@@ -174,11 +194,18 @@ env = [
"_APP_JOBS_HOST=http://orchestrator:8080",
"_APP_JOBS_SECRET=${jobs_secret}",
"_APP_JOBS_ENDPOINT=http://appwrite",
# Reviews are opt-in: the shared secret turns the /v1/reviews API on, and the
# console hides the screen in the self-hosted profile regardless.
# "_APP_REVIEWS_SECRET=${reviews_secret}",
# "_APP_REVIEWS_MODEL=anthropic/claude-sonnet-4.5",
# "_APP_REVIEWS_VERIFY_MODEL=",
# "_APP_REVIEWS_AI_GATEWAY_KEY=",
"_APP_BROWSER_HOST=http://appwrite-browser:3000/v1",
"_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite",
"_APP_DOCKER_HUB_USERNAME=",
"_APP_DOCKER_HUB_PASSWORD=",
"_APP_MAINTENANCE_INTERVAL=86400",
"_APP_MAINTENANCE_INACTIVE_INTERVAL=604800",
"_APP_MAINTENANCE_START_TIME=00:00",
"_APP_MAINTENANCE_RETENTION_CACHE=2592000",
"_APP_MAINTENANCE_RETENTION_EXECUTION=1209600",
@@ -186,6 +213,7 @@ env = [
"_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE=15778800",
"_APP_MAINTENANCE_RETENTION_ABUSE=86400",
"_APP_MAINTENANCE_RETENTION_SCHEDULES=86400",
"_APP_MAINTENANCE_RETENTION_ALERTS=7776000",
"_APP_INTERVAL_DOMAIN_VERIFICATION=120",
"_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300",
"_APP_GRAPHQL_INTROSPECTION=enabled",
@@ -213,12 +241,17 @@ env = [
"_APP_VCS_BITBUCKET_WEBHOOK_SECRET=",
"_APP_VCS_ORIGIN_CLIENT_ID=",
"_APP_VCS_ORIGIN_PRIVATE_KEY=",
"_APP_VCS_ENTIRE_PLUGIN=",
"_APP_VCS_ENTIRE_PRIVATE_KEY=",
"_APP_VCS_ENTIRE_ISSUER=",
"_APP_VCS_ENTIRE_ENDPOINT=",
"_APP_VCS_WEBHOOK_URL=",
"_APP_MIGRATION_HOST=appwrite",
"_APP_MIGRATIONS_FIREBASE_CLIENT_ID=",
"_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=",
"_APP_DB_ROOT_PASS=${db_root_pw}",
"_APP_DB_USER_PASS=${db_user_pw}",
"_APP_DB_MAX_CONNECTIONS=500",
]
[[config.mounts]]
@@ -1,6 +1,4 @@
# dokploy: allow-host-ports — WoW clients connect directly over raw TCP (3724 authentication, 8085 world); Traefik cannot route the game protocol.
version: "3.8"
services:
azerothcore-database:
image: mysql:8.4.11
+2 -2
View File
@@ -1,7 +1,7 @@
services:
# Web app (public): named after the template, as Dokploy requires.
cachkey:
image: ghcr.io/joyboy-dy/cachkey-web:0.1.0
image: ghcr.io/joyboy-dy/cachkey-web:0.2.0
restart: unless-stopped
expose:
- 3000
@@ -16,7 +16,7 @@ services:
# Applies pending database migrations at startup, then serves /api.
cachkey-api:
image: ghcr.io/joyboy-dy/cachkey-api:0.1.0
image: ghcr.io/joyboy-dy/cachkey-api:0.2.0
restart: unless-stopped
expose:
- 3001
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "cachkey",
"name": "Cachkey",
"version": "0.1.0",
"version": "0.2.0",
"description": "Cachkey is an open-source, self-hosted manager for secrets and environment variables: encrypted values, per-environment access for your team, a CLI and a read-only API for servers and CI.",
"logo": "cachkey.svg",
"links": {
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="230px" height="24.673px" viewBox="0 0 230 24.673" enable-background="new 0 0 230 24.673" xml:space="preserve">
<g>
<g>
<path fill="#FFFFFF" d="M133.629,15.422c1.175,0,1.926-0.515,1.926-1.304c0-0.619-0.489-0.957-1.343-0.957h-1.849l-0.601,2.261
H133.629z M133.161,18.398c1.183,0,1.924-0.498,1.924-1.313c0-0.667-0.629-1.005-1.511-1.005h-1.99l-0.619,2.318H133.161z
M131.819,12.495h2.458c1.285,0,2.037,0.601,2.037,1.53c0,0.938-0.714,1.539-1.643,1.726c0.647,0.197,1.163,0.619,1.163,1.361
c0,1.135-1.031,1.952-2.759,1.952h-3.021L131.819,12.495z"/>
<path fill="#FFFFFF" d="M137.138,17.666c0-0.226,0.058-0.498,0.123-0.751l0.713-2.702h0.723l-0.741,2.749
c-0.038,0.16-0.064,0.386-0.064,0.545c0,0.637,0.411,1.013,1.078,1.013c0.751,0,1.511-0.647,1.754-1.577l0.732-2.73h0.723
l-1.295,4.851h-0.724l0.217-0.788c-0.441,0.497-0.957,0.892-1.698,0.892C137.749,19.167,137.138,18.585,137.138,17.666"/>
</g>
<path fill="#FFFFFF" d="M144.419,12.355h0.826l-0.217,0.797h-0.824L144.419,12.355z M143.97,14.212h0.722l-1.305,4.852h-0.722
L143.97,14.212z"/>
<polygon fill="#FFFFFF" points="147.075,12.214 147.797,12.214 145.958,19.064 145.235,19.064 "/>
<g>
<path fill="#FFFFFF" d="M152.339,16.249c0-0.845-0.61-1.501-1.502-1.501c-1.154,0-2.026,1.07-2.026,2.309
c0,0.901,0.562,1.482,1.37,1.482C151.26,18.539,152.339,17.441,152.339,16.249 M148.051,17.122c0-1.718,1.304-3.012,2.722-3.012
c0.994,0,1.558,0.562,1.819,1.201l0.826-3.097h0.723l-1.83,6.85h-0.723l0.216-0.826c-0.46,0.525-1.051,0.938-1.83,0.938
C148.876,19.176,148.051,18.351,148.051,17.122"/>
<path fill="#FFFFFF" d="M158.333,14.851h-0.667l0.171-0.638h0.666l0.159-0.582c0.253-0.976,0.825-1.436,1.661-1.436
c0.375,0,0.712,0.075,0.966,0.207l-0.17,0.619c-0.28-0.132-0.516-0.197-0.796-0.197c-0.46,0-0.808,0.282-0.977,0.92l-0.123,0.469
h1.588l-0.18,0.638h-1.577l-1.134,4.213h-0.724L158.333,14.851z"/>
<path fill="#FFFFFF" d="M164.057,17.159l0.093-0.337c-0.421-0.122-0.976-0.216-1.576-0.216c-0.863,0-1.454,0.469-1.454,1.135
c0,0.517,0.403,0.863,1.032,0.863C162.968,18.604,163.821,18.013,164.057,17.159 M160.388,17.825c0-1.088,0.909-1.755,2.157-1.755
c0.648,0,1.258,0.131,1.727,0.263l0.028-0.112c0.038-0.141,0.066-0.319,0.066-0.45c0-0.629-0.432-0.986-1.313-0.986
c-0.518,0-0.939,0.103-1.37,0.253l-0.123-0.601c0.479-0.178,0.976-0.291,1.605-0.291c1.22,0,1.904,0.563,1.904,1.501
c0,0.207-0.037,0.441-0.103,0.666l-0.732,2.75h-0.704l0.207-0.76c-0.45,0.516-1.041,0.863-1.812,0.863
C161.11,19.167,160.388,18.688,160.388,17.825"/>
<path fill="#FFFFFF" d="M165.651,18.323l0.469-0.469c0.525,0.507,1.106,0.713,1.689,0.713c0.553,0,1.069-0.32,1.069-0.807
c0-0.319-0.217-0.554-0.921-0.826c-0.872-0.338-1.37-0.657-1.37-1.333c0-0.825,0.714-1.492,1.84-1.492
c0.714,0,1.389,0.32,1.792,0.667l-0.422,0.497c-0.403-0.328-0.91-0.563-1.436-0.563c-0.685,0-1.052,0.366-1.052,0.77
c0,0.319,0.198,0.525,0.968,0.826c0.873,0.356,1.322,0.704,1.322,1.333c0,0.901-0.825,1.53-1.857,1.53
C166.992,19.167,166.214,18.886,165.651,18.323"/>
<path fill="#FFFFFF" d="M171.074,18.191c0-0.169,0.027-0.356,0.064-0.507l0.761-2.834h-0.666l0.169-0.638h0.675l0.396-1.464h0.713
l-0.395,1.464h1.529l-0.169,0.638h-1.529l-0.751,2.797c-0.019,0.112-0.037,0.253-0.037,0.356c0,0.337,0.253,0.497,0.665,0.497
c0.217,0,0.441-0.046,0.705-0.149l-0.179,0.656c-0.254,0.103-0.488,0.141-0.77,0.141C171.562,19.148,171.074,18.82,171.074,18.191
"/>
<path fill="#FFFFFF" d="M173.794,19.881c0.545-0.141,0.807-0.414,0.882-0.817h-0.348l0.272-0.985h0.855l-0.227,0.845
c-0.226,0.816-0.61,1.135-1.36,1.276L173.794,19.881z"/>
<path fill="#FFFFFF" d="M184.378,16.118c0-0.713-0.562-1.37-1.492-1.37c-1.032,0-1.998,0.854-1.998,2.018
c0,0.863,0.676,1.304,1.416,1.304C183.401,18.069,184.378,17.112,184.378,16.118 M179.489,19.74l0.45-0.507
c0.516,0.441,1.173,0.732,1.914,0.732c0.61,0,1.069-0.197,1.36-0.498c0.301-0.291,0.47-0.666,0.61-1.163l0.131-0.488
c-0.487,0.497-1.061,0.872-1.857,0.872c-1.013,0-1.971-0.666-1.971-1.83c0-1.614,1.36-2.749,2.722-2.749
c1.041,0,1.511,0.582,1.782,1.163l0.282-1.06h0.723l-1.107,4.157c-0.207,0.75-0.441,1.182-0.789,1.53
c-0.45,0.45-1.051,0.685-1.877,0.685C180.972,20.584,180.09,20.265,179.489,19.74"/>
<path fill="#FFFFFF" d="M187.343,14.212h0.723l-0.33,1.267c0.593-0.845,1.343-1.408,2.31-1.352l-0.207,0.779h-0.047
c-1.06,0-2.12,0.779-2.506,2.243l-0.515,1.914h-0.724L187.343,14.212z"/>
<path fill="#FFFFFF" d="M194.491,16.305c0-0.985-0.619-1.558-1.52-1.558c-1.117,0-2.075,1.107-2.075,2.234
c0,0.985,0.62,1.547,1.521,1.547C193.506,18.529,194.491,17.459,194.491,16.305 M190.166,17c0-1.511,1.303-2.891,2.852-2.891
c1.333,0,2.214,0.911,2.214,2.168c0,1.549-1.342,2.9-2.861,2.9C191.038,19.177,190.166,18.257,190.166,17"/>
</g>
<polygon fill="#FFFFFF" points="196.459,14.212 197.19,14.212 197.444,18.079 199.791,14.194 200.391,14.194 200.663,18.079
202.999,14.212 203.75,14.212 200.728,19.102 200.091,19.102 199.809,15.273 197.473,19.102 196.845,19.102 "/>
<g>
<path fill="#FFFFFF" d="M206.461,18.323l0.469-0.469c0.525,0.507,1.106,0.713,1.688,0.713c0.554,0,1.07-0.32,1.07-0.807
c0-0.319-0.216-0.554-0.92-0.826c-0.873-0.338-1.37-0.657-1.37-1.333c0-0.825,0.713-1.492,1.84-1.492
c0.713,0,1.389,0.32,1.791,0.667l-0.422,0.497c-0.403-0.328-0.91-0.563-1.436-0.563c-0.685,0-1.051,0.366-1.051,0.77
c0,0.319,0.197,0.525,0.967,0.826c0.873,0.356,1.323,0.704,1.323,1.333c0,0.901-0.826,1.53-1.857,1.53
C207.802,19.167,207.022,18.886,206.461,18.323"/>
<path fill="#FFFFFF" d="M216.05,16.305c0-0.985-0.619-1.558-1.521-1.558c-1.116,0-2.073,1.107-2.073,2.234
c0,0.985,0.619,1.547,1.521,1.547C215.065,18.529,216.05,17.459,216.05,16.305 M211.723,17c0-1.511,1.306-2.891,2.854-2.891
c1.333,0,2.216,0.911,2.216,2.168c0,1.549-1.343,2.9-2.862,2.9C212.596,19.177,211.723,18.257,211.723,17"/>
</g>
<polygon fill="#FFFFFF" points="219.408,12.214 220.131,12.214 218.291,19.064 217.568,19.064 "/>
<path fill="#FFFFFF" d="M221.895,12.355h0.826l-0.217,0.797h-0.825L221.895,12.355z M221.443,14.212h0.724l-1.305,4.852h-0.724
L221.443,14.212z"/>
<g>
<path fill="#FFFFFF" d="M227.243,16.249c0-0.845-0.61-1.501-1.503-1.501c-1.154,0-2.026,1.07-2.026,2.309
c0,0.901,0.563,1.482,1.37,1.482C226.163,18.539,227.243,17.441,227.243,16.249 M222.954,17.122c0-1.718,1.304-3.012,2.722-3.012
c0.994,0,1.558,0.562,1.821,1.201l0.825-3.097h0.721l-1.829,6.85h-0.723l0.216-0.826c-0.46,0.525-1.051,0.938-1.83,0.938
C223.779,19.176,222.954,18.351,222.954,17.122"/>
</g>
<polygon fill="#FFFFFF" points="229.146,18.079 228.874,19.064 229.738,19.064 230,18.079 "/>
<g>
<path fill="#FFFFFF" d="M15.806,9.873l12.366,3.07c2.152-0.84,3.445-1.911,3.445-3.07V4.935C31.617,2.213,24.537,0,15.806,0
C7.076,0,0,2.213,0,4.935v4.938c0,2.723,7.076,4.933,15.806,4.933V9.873z"/>
<path fill="#FFFFFF" d="M28.171,17.879l-12.366-3.073v4.934l12.366,3.074c2.153-0.844,3.446-1.911,3.446-3.074v-4.934
C31.618,15.969,30.324,17.036,28.171,17.879"/>
<path fill="#FFFFFF" d="M0,14.806v4.935c0,2.724,7.076,4.933,15.806,4.933v-4.933C7.076,19.741,0,17.53,0,14.806"/>
<path fill="#FFFFFF" d="M45.185,19.29c-3.861,0-6.727-2.98-6.727-6.747v-0.036c0-3.728,2.81-6.784,6.838-6.784
c2.473,0,3.955,0.826,5.172,2.024l-1.836,2.117c-1.012-0.918-2.043-1.48-3.354-1.48c-2.212,0-3.803,1.836-3.803,4.084v0.039
c0,2.248,1.554,4.122,3.803,4.122c1.499,0,2.416-0.599,3.446-1.537l1.836,1.854C49.213,18.39,47.714,19.29,45.185,19.29"/>
<path fill="#FFFFFF" d="M58.506,15.017c-0.486-0.227-1.125-0.375-1.817-0.375c-1.218,0-1.969,0.487-1.969,1.387v0.037
c0,0.767,0.638,1.217,1.555,1.217c1.33,0,2.231-0.728,2.231-1.761V15.017z M58.448,19.064v-1.086
c-0.692,0.768-1.647,1.274-3.035,1.274c-1.891,0-3.447-1.087-3.447-3.073v-0.039c0-2.193,1.667-3.205,4.046-3.205
c1.013,0,1.744,0.169,2.457,0.412V13.18c0-1.179-0.731-1.836-2.156-1.836c-1.086,0-1.854,0.207-2.772,0.543l-0.713-2.173
c1.106-0.489,2.193-0.806,3.897-0.806c3.111,0,4.48,1.611,4.48,4.33v5.826H58.448z"/>
</g>
<polygon fill="#FFFFFF" points="70.123,19.064 67.499,14.961 66.505,16.01 66.505,19.064 63.659,19.064 63.659,5.384 66.505,5.384
66.505,12.676 69.842,9.021 73.251,9.021 69.429,12.973 73.382,19.064 "/>
<g>
<path fill="#FFFFFF" d="M78.757,11.12c-1.18,0-1.95,0.843-2.174,2.135h4.292C80.705,11.98,79.956,11.12,78.757,11.12
M83.61,15.037h-6.989c0.28,1.292,1.18,1.966,2.455,1.966c0.956,0,1.648-0.301,2.435-1.031l1.632,1.444
c-0.939,1.162-2.288,1.874-4.105,1.874c-3.017,0-5.249-2.117-5.249-5.19v-0.039c0-2.867,2.044-5.228,4.967-5.228
c3.354,0,4.891,2.605,4.891,5.454v0.037C83.647,14.605,83.629,14.774,83.61,15.037"/>
<path fill="#FFFFFF" d="M93.521,10.538c0-1.292-0.898-1.985-2.342-1.985H88.95v4.012h2.286c1.442,0,2.285-0.865,2.285-1.987
V10.538z M91.141,15.129h-2.19v3.935h-2.886V5.947h5.359c3.128,0,5.021,1.855,5.021,4.537v0.036
C96.444,13.555,94.083,15.129,91.141,15.129"/>
</g>
<polygon fill="#FFFFFF" points="106.787,19.064 106.787,13.796 101.465,13.796 101.465,19.064 98.58,19.064 98.58,5.947
101.465,5.947 101.465,11.138 106.787,11.138 106.787,5.947 109.672,5.947 109.672,19.064 "/>
<g>
<path fill="#FFFFFF" d="M120.277,10.538c0-1.292-0.899-1.985-2.342-1.985h-2.23v4.012h2.285c1.444,0,2.287-0.865,2.287-1.987
V10.538z M117.899,15.129h-2.194v3.935h-2.886V5.947h5.359c3.128,0,5.022,1.855,5.022,4.537v0.036
C123.201,13.555,120.84,15.129,117.899,15.129"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

+30
View File
@@ -0,0 +1,30 @@
services:
cakephp:
image: ghcr.io/themusicdev/cakephp-hello:1.0.0
restart: unless-stopped
depends_on:
- mariadb
environment:
# CakePHP skeleton reads its app config from these env vars
# (config/app_local.php in the starter repo is env-driven)
- DEBUG=${DEBUG}
- SECURITY_SALT=${SECURITY_SALT}
- DATABASE_URL=mysql://${MARIADB_USER}:${MARIADB_PASSWORD}@mariadb:3306/${MARIADB_DATABASE}?encoding=utf8mb4
expose:
- 80
mariadb:
image: mariadb:11
restart: unless-stopped
environment:
- MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
- MARIADB_DATABASE=${MARIADB_DATABASE}
- MARIADB_USER=${MARIADB_USER}
- MARIADB_PASSWORD=${MARIADB_PASSWORD}
volumes:
- mariadb-data:/var/lib/mysql
expose:
- 3306
volumes:
mariadb-data:
+21
View File
@@ -0,0 +1,21 @@
{
"id": "cakephp",
"name": "CakePHP 5",
"version": "1.0.0",
"description": "CakePHP 5 starter app running on the themusicdev/php-apache 8.5 Apache + PHP runtime image, backed by MariaDB. Fork the starter repo, build your app, and deploy it on Dokploy in a few clicks.",
"logo": "cakephp.svg",
"links": {
"github": "https://github.com/TheMusicDev/cakephp-hello",
"website": "https://cakephp.org/",
"docs": "https://book.cakephp.org/5/en/index.html"
},
"tags": [
"php",
"framework",
"cakephp",
"website"
],
"seoTitle": "CakePHP 5 Docker Compose Setup",
"seoDescription": "Deploy a CakePHP 5 application with MariaDB on Dokploy in minutes — runs on the themusicdev/php-apache Apache + PHP 8.5 runtime image with Traefik proxy awareness built in.",
"newConfig": true
}
+24
View File
@@ -0,0 +1,24 @@
[variables]
main_domain = "${domain}"
root_password = "${password:32}"
database_password = "${password:32}"
database_username = "${username:8}"
database_name = "cakephp"
security_salt = "${base64:32}"
app_debug = "false"
[config]
mounts = []
[[config.domains]]
serviceName = "cakephp"
port = 80
host = "${main_domain}"
[config.env]
DEBUG = "${app_debug}"
SECURITY_SALT = "${security_salt}"
MARIADB_ROOT_PASSWORD = "${root_password}"
MARIADB_DATABASE = "${database_name}"
MARIADB_USER = "${database_username}"
MARIADB_PASSWORD = "${database_password}"
+254 -23
View File
@@ -1,40 +1,271 @@
# If you are on a machine with ARM architecture, please add a "-arm" suffix to the calcom image on line 19.
# If you are on a machine with ARM architecture, please add a "-arm" suffix to the calcom image.
# For example, `calcom/cal.com:v6.2.0` will become `calcom/cal.com:v6.2.0-arm`
# If serving the instance on https, change `NEXT_PUBLIC_WEBAPP_URL` (Line 28) and `NEXTAUTH_URL` (Line 30) to https
#
# If serving the instance over https, change NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_WEBSITE_URL,
# NEXT_PUBLIC_EMBED_LIB_URL and NEXTAUTH_URL in template.toml to use the https scheme.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- calcom-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=db
- DATABASE_URL=postgres://postgres:password@postgres:5432/db
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
calcom:
image: calcom/cal.com:v6.2.0
restart: unless-stopped
depends_on:
- postgres
postgres:
condition: service_healthy
expose:
- 3000
environment:
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- CALENDSO_ENCRYPTION_KEY=${CALENDSO_ENCRYPTION_KEY}
- DATABASE_URL=postgres://postgres:password@postgres:5432/db
- DATABASE_DIRECT_URL=postgres://postgres:password@postgres:5432/db
- DATABASE_HOST=postgres:5432
- NEXT_PUBLIC_WEBAPP_URL=http://${CALCOM_HOST}
- ALLOWED_HOSTNAMES="${CALCOM_HOST}"
- NEXTAUTH_URL=http://${CALCOM_HOST}/api/auth
- NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_SIGNUP}
- EMAIL_SERVER_HOST=${SMTP_SERVER}
- EMAIL_SERVER_PORT=${SMTP_PORT}
- EMAIL_SERVER_USER=${EMAIL_SERVER_USER}
- EMAIL_SERVER_PASSWORD=${SMTP_PASSWORD}
- EMAIL_FROM=${EMAIL_FROM}
# --- DATABASE ---------------------------------------------------------------------------
- DATABASE_URL
- DATABASE_DIRECT_URL
- INSIGHTS_DATABASE_URL
# --- SHARED -----------------------------------------------------------------------------
- NEXT_PUBLIC_WEBAPP_URL
- NEXT_PUBLIC_WEBSITE_URL
- NEXT_PUBLIC_EMBED_LIB_URL
- 'ALLOWED_HOSTNAMES="${ALLOWED_HOSTNAMES}"'
- 'RESERVED_SUBDOMAINS="app","auth","docs","design","console","go","status","api","saml","www","matrix","developer","cal","my","team","support","security","blog","learn","admin"'
- SAML_DATABASE_URL
- SAML_ADMINS
- NEXT_PUBLIC_HOSTED_CAL_FEATURES
- PGSSLMODE
# --- NEXTAUTH ---------------------------------------------------------------------------
- NEXTAUTH_URL
- NEXTAUTH_SECRET
- NEXTAUTH_COOKIE_DOMAIN
- CALCOM_TELEMETRY_DISABLED
- CRON_API_KEY
- CRON_ENABLE_APP_SYNC
- CALENDSO_ENCRYPTION_KEY
# --- SUPPORT / ANALYTICS WIDGETS --------------------------------------------------------
- NEXT_PUBLIC_INTERCOM_APP_ID
- INTERCOM_SECRET
- INTERCOM_API_TOKEN
- NEXT_PUBLIC_POSTHOG_KEY
- NEXT_PUBLIC_POSTHOG_HOST
- NEXT_PUBLIC_ZENDESK_KEY
- NEXT_PUBLIC_HELPSCOUT_KEY
- NEXT_PUBLIC_FRESHCHAT_TOKEN
- NEXT_PUBLIC_FRESHCHAT_HOST
# --- URL SHORTENERS ---------------------------------------------------------------------
- DUB_API_KEY
- NEXT_PUBLIC_DUB_PROGRAM_ID
- DUB_SMS_DOMAIN
- DUB_SMS_FOLDER_ID
- SINK_API_URL
- SINK_API_KEY
# --- OAUTH / CALENDAR INTEGRATIONS ------------------------------------------------------
- OUTLOOK_LOGIN_ENABLED
- GOOGLE_CALENDAR_API_KEY
- GOOGLE_LOGIN_ENABLED
- GOOGLE_API_CREDENTIALS
- GOOGLE_WEBHOOK_TOKEN
- GOOGLE_WEBHOOK_URL
- MICROSOFT_WEBHOOK_TOKEN
- MICROSOFT_WEBHOOK_URL
# --- FEEDBACK / TRANSACTIONAL PROVIDERS -------------------------------------------------
- SEND_FEEDBACK_EMAIL
- SENDGRID_API_KEY
- SENDGRID_EMAIL
- NEXT_PUBLIC_SENDGRID_SENDER_NAME
- SENDGRID_SYNC_API_KEY
- RESEND_API_KEY
# --- SENTRY -----------------------------------------------------------------------------
- SENTRY_ORG
- SENTRY_PROJECT
- SENTRY_AUTH_TOKEN
- NEXT_PUBLIC_SENTRY_DSN
- NEXT_PUBLIC_SENTRY_DSN_CLIENT
- SENTRY_DEBUG
- SENTRY_MAX_SPANS
- SENTRY_SAMPLE_RATE
- SENTRY_TRACES_SAMPLE_RATE
- SENTRY_REPLAYS_SESSION_SAMPLE_RATE
- SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE
# --- FORMBRICKS -------------------------------------------------------------------------
- NEXT_PUBLIC_FORMBRICKS_HOST_URL
- NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID
- FORMBRICKS_FEEDBACK_SURVEY_ID
# --- AVATARAPI --------------------------------------------------------------------------
- AVATARAPI_USERNAME
- AVATARAPI_PASSWORD
# --- TWILIO -----------------------------------------------------------------------------
- NEXT_PUBLIC_SENDER_ID
- TWILIO_VERIFY_SID
- TWILIO_OPT_OUT_ENABLED
# --- STRIPE -----------------------------------------------------------------------------
- NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE
- NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE
- NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN
- NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE
- STRIPE_TEAM_MONTHLY_PRICE_ID
- STRIPE_TEAM_ANNUAL_PRICE_ID
- NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID
- ORG_MONTHLY_CREDITS
- STRIPE_TEAM_PRODUCT_ID
- STRIPE_ORG_MONTHLY_PRICE_ID
- STRIPE_ORG_ANNUAL_PRICE_ID
- STRIPE_ORG_PRODUCT_ID
- STRIPE_ORG_TRIAL_DAYS
- STRIPE_WEBHOOK_SECRET
- STRIPE_WEBHOOK_SECRET_APPS
- STRIPE_WEBHOOK_SECRET_BILLING
- STRIPE_PRIVATE_KEY
- STRIPE_CLIENT_ID
- API_KEY_PREFIX
# --- E-MAIL SETTINGS --------------------------------------------------------------------
- EMAIL_FROM
- EMAIL_FROM_NAME
- EMAIL_SERVER_HOST
- EMAIL_SERVER_PORT
- EMAIL_SERVER_USER
- EMAIL_SERVER_PASSWORD
- AWAITING_PAYMENT_EMAIL_DELAY_MINUTES
# --- BOT PROTECTION ---------------------------------------------------------------------
- NEXT_PUBLIC_CLOUDFLARE_SITEKEY
- NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER
- CLOUDFLARE_TURNSTILE_SECRET
- NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER
# --- CLOSE.COM INTERNAL CRM -------------------------------------------------------------
- CLOSECOM_CLIENT_ID
- CLOSECOM_CLIENT_SECRET
# --- BRANDING ---------------------------------------------------------------------------
- NEXT_PUBLIC_APP_NAME
- NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS
- NEXT_PUBLIC_COMPANY_NAME
- NEXT_PUBLIC_DISABLE_SIGNUP
- NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL
- NEXT_PUBLIC_WEBSITE_TERMS_URL
- NEXT_PUBLIC_HEAD_SCRIPTS
- NEXT_PUBLIC_BODY_SCRIPTS
- CSP_POLICY
- EDGE_CONFIG
# --- BOOKER BEHAVIOUR -------------------------------------------------------------------
- NEXT_PUBLIC_MINUTES_TO_BOOK
- NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD
- NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL
- NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS
- NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS
- NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS
- NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM
- NEXT_PUBLIC_QUICK_AVAILABILITY_ROLLOUT
- BLACKLISTED_GUEST_EMAILS
# --- ORGANIZATIONS ----------------------------------------------------------------------
- ORGANIZATIONS_ENABLED
- NEXT_PUBLIC_ORGANIZATIONS_MIN_SELF_SERVE_SEATS
- NEXT_PUBLIC_ORGANIZATIONS_SELF_SERVE_PRICE_NEW
- ORGANIZATIONS_AUTOLINK
- NEXT_PUBLIC_SINGLE_ORG_SLUG
- PROJECT_ID_VERCEL
- TEAM_ID_VERCEL
- AUTH_BEARER_TOKEN_VERCEL
- LOCAL_TESTING_DOMAIN_VERCEL
- CLOUDFLARE_DNS
- AUTH_BEARER_TOKEN_CLOUDFLARE
- CLOUDFLARE_ZONE_ID
- CLOUDFLARE_VERCEL_CNAME
# --- APP CREDENTIAL SYNC ----------------------------------------------------------------
- CALCOM_CREDENTIAL_SYNC_SECRET
- CALCOM_CREDENTIAL_SYNC_HEADER_NAME
- CALCOM_CREDENTIAL_SYNC_ENDPOINT
- CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY
- CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY
# --- API V2 -----------------------------------------------------------------------------
- NEXT_PUBLIC_API_V2_URL
# --- TASKER / BACKGROUND JOBS -----------------------------------------------------------
- TASKER_ENABLE_WEBHOOKS
- TASKER_ENABLE_EMAILS
- ENABLE_ASYNC_TASKER
- TRIGGER_SECRET_KEY
- TRIGGER_API_URL
- TRIGGER_DEV_PROJECT_REF
# --- RATE LIMITING ----------------------------------------------------------------------
- UNKEY_ROOT_KEY
# --- CAL.AI VOICE AGENTS ----------------------------------------------------------------
- RETELL_AI_KEY
- RETELL_AI_TEST_MODE
- RETELL_AI_TEST_EVENT_TYPE_MAP
- RETELL_AI_TEST_CAL_API_KEY
- STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID
- CAL_AI_CALL_RATE_PER_MINUTE
- NEXT_PUBLIC_CAL_AI_PHONE_NUMBER_MONTHLY_PRICE
# --- VIDEO ------------------------------------------------------------------------------
- HUDDLE01_API_TOKEN
- CAL_VIDEO_BUCKET_NAME
- CAL_VIDEO_BUCKET_REGION
- CAL_VIDEO_ASSUME_ROLE_ARN
- CAL_VIDEO_MEETING_LINK_FOR_TESTING
- CAL_VIDEO_RECORDING_TOKEN_SECRET
# --- WEB PUSH NOTIFICATIONS -------------------------------------------------------------
- NEXT_PUBLIC_VAPID_PUBLIC_KEY
- VAPID_PRIVATE_KEY
# --- MISC -------------------------------------------------------------------------------
- LINGO_DOT_DEV_API_KEY
- IFFY_API_KEY
- DIRECTORY_IDS_TO_LOG
- NEXT_PUBLIC_LOGGER_LEVEL
- DATABASE_CHUNK_SIZE
- SALESFORCE_GRAPHQL_DELAY_MS
- SALESFORCE_GRAPHQL_MAX_DELAY_MS
- SALESFORCE_GRAPHQL_MAX_RETRIES
- TZ
- GOOGLE_ADS_ENABLED
- LINKEDIN_ADS_ENABLED
# --- DEVELOPMENT / E2E ONLY -------------------------------------------------------------
- NEXT_PUBLIC_IS_E2E
- E2E_TEST_MAILHOG_ENABLED
- E2E_TEST_APPLE_CALENDAR_EMAIL
- E2E_TEST_APPLE_CALENDAR_PASSWORD
- E2E_TEST_CALCOM_QA_EMAIL
- E2E_TEST_CALCOM_QA_PASSWORD
- E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS
- E2E_TEST_CALCOM_GCAL_KEYS
- SEED_PLATFORM_OAUTH_CLIENT_ID
- SEED_PLATFORM_OAUTH_CLIENT_SECRET
- SEED_OAUTH2_CLIENT_ID
- SEED_OAUTH2_CLIENT_SECRET_HASHED
volumes:
calcom-data:
+292 -8
View File
@@ -1,19 +1,303 @@
[variables]
main_domain = "${domain}"
calcom_encryption_key = "${base64:32}"
postgres_password = "${password:32}"
database_url = "postgresql://calcom:${postgres_password}@postgres:5432/calendso"
nextauth_secret = "${base64:32}"
calendso_encryption_key = "${base64:24}"
calcom_credential_sync_secret = "${base64:32}"
calcom_app_credential_encryption_key = "${base64:24}"
calcom_service_account_encryption_key = "${base64:24}"
cal_video_recording_token_secret = "${base64:32}"
cron_api_key = "${hash:32}"
[config]
env = [
"CALCOM_HOST=${main_domain}",
# --- POSTGRES (consumed by the `postgres` service) -----------------------------------------
"POSTGRES_USER=calcom",
"POSTGRES_PASSWORD=${postgres_password}",
"POSTGRES_DB=calendso",
# --- DATABASE ------------------------------------------------------------------------------
"DATABASE_URL=${database_url}",
"DATABASE_DIRECT_URL=${database_url}",
# Optional read replica for the Insights dashboard. Falls back to DATABASE_URL when empty.
"INSIGHTS_DATABASE_URL=",
# --- SHARED --------------------------------------------------------------------------------
"NEXT_PUBLIC_WEBAPP_URL=http://${main_domain}",
"NEXT_PUBLIC_WEBSITE_URL=http://${main_domain}",
"NEXT_PUBLIC_EMBED_LIB_URL=http://${main_domain}/embed/embed.js",
"ALLOWED_HOSTNAMES=${main_domain}",
# SAML login needs a separate database and is Enterprise Edition only.
"SAML_DATABASE_URL=",
"SAML_ADMINS=",
"NEXT_PUBLIC_HOSTED_CAL_FEATURES=",
# Set to no-verify only when your Postgres uses a self-signed certificate.
"PGSSLMODE=",
# - NEXTAUTH
"NEXTAUTH_URL=http://${main_domain}",
"NEXTAUTH_SECRET=${nextauth_secret}",
"CALENDSO_ENCRYPTION_KEY=${calcom_encryption_key}",
"DISABLE_SIGNUP=true",
"SMTP_SERVER=",
"SMTP_PORT=",
"SMTP_PASSWORD=",
"NEXTAUTH_COOKIE_DOMAIN=",
# 1 disables anonymous usage collection.
"CALCOM_TELEMETRY_DISABLED=1",
"CRON_API_KEY=${cron_api_key}",
"CRON_ENABLE_APP_SYNC=false",
"CALENDSO_ENCRYPTION_KEY=${calendso_encryption_key}",
# --- SUPPORT / ANALYTICS WIDGETS -----------------------------------------------------------
"NEXT_PUBLIC_INTERCOM_APP_ID=",
"INTERCOM_SECRET=",
"INTERCOM_API_TOKEN=",
"NEXT_PUBLIC_POSTHOG_KEY=",
"NEXT_PUBLIC_POSTHOG_HOST=",
"NEXT_PUBLIC_ZENDESK_KEY=",
"NEXT_PUBLIC_HELPSCOUT_KEY=",
"NEXT_PUBLIC_FRESHCHAT_TOKEN=",
"NEXT_PUBLIC_FRESHCHAT_HOST=",
# --- URL SHORTENERS (SMS workflow links) ---------------------------------------------------
"DUB_API_KEY=",
"NEXT_PUBLIC_DUB_PROGRAM_ID=",
"DUB_SMS_DOMAIN=",
"DUB_SMS_FOLDER_ID=",
"SINK_API_URL=",
"SINK_API_KEY=",
# --- OAUTH / CALENDAR INTEGRATIONS ---------------------------------------------------------
"OUTLOOK_LOGIN_ENABLED=false",
"GOOGLE_CALENDAR_API_KEY=",
"GOOGLE_LOGIN_ENABLED=false",
"GOOGLE_API_CREDENTIALS=",
"GOOGLE_WEBHOOK_TOKEN=",
# Defaults to NEXT_PUBLIC_WEBAPP_URL when empty.
"GOOGLE_WEBHOOK_URL=",
"MICROSOFT_WEBHOOK_TOKEN=",
"MICROSOFT_WEBHOOK_URL=",
# --- FEEDBACK / TRANSACTIONAL PROVIDERS ----------------------------------------------------
"SEND_FEEDBACK_EMAIL=",
"SENDGRID_API_KEY=",
"SENDGRID_EMAIL=",
"NEXT_PUBLIC_SENDGRID_SENDER_NAME=",
"SENDGRID_SYNC_API_KEY=",
"RESEND_API_KEY=",
# --- SENTRY --------------------------------------------------------------------------------
"SENTRY_ORG=",
"SENTRY_PROJECT=",
"SENTRY_AUTH_TOKEN=",
"NEXT_PUBLIC_SENTRY_DSN=",
"NEXT_PUBLIC_SENTRY_DSN_CLIENT=",
"SENTRY_DEBUG=",
"SENTRY_MAX_SPANS=",
"SENTRY_SAMPLE_RATE=",
"SENTRY_TRACES_SAMPLE_RATE=",
"SENTRY_REPLAYS_SESSION_SAMPLE_RATE=",
"SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=",
# --- FORMBRICKS ----------------------------------------------------------------------------
"NEXT_PUBLIC_FORMBRICKS_HOST_URL=https://app.formbricks.com",
"NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=",
"FORMBRICKS_FEEDBACK_SURVEY_ID=",
# --- AVATARAPI (pre-fills avatars during signup) -------------------------------------------
"AVATARAPI_USERNAME=",
"AVATARAPI_PASSWORD=",
# --- TWILIO (SMS workflows) ----------------------------------------------------------------
# NEXT_PUBLIC_SENDER_ID: letters, numbers and spaces only, max 11 characters.
"NEXT_PUBLIC_SENDER_ID=",
"TWILIO_VERIFY_SID=",
"TWILIO_OPT_OUT_ENABLED=",
# --- STRIPE (internal billing, teams and organizations) ------------------------------------
"NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE=",
"NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE=",
"NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0",
"NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=",
"STRIPE_TEAM_MONTHLY_PRICE_ID=",
"STRIPE_TEAM_ANNUAL_PRICE_ID=",
"NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID=",
"ORG_MONTHLY_CREDITS=",
"STRIPE_TEAM_PRODUCT_ID=",
# A price ID belonging to the product in STRIPE_ORG_PRODUCT_ID.
"STRIPE_ORG_MONTHLY_PRICE_ID=",
"STRIPE_ORG_ANNUAL_PRICE_ID=",
"STRIPE_ORG_PRODUCT_ID=",
# Trial days passed to the Stripe checkout session for organizations.
"STRIPE_ORG_TRIAL_DAYS=",
"STRIPE_WEBHOOK_SECRET=",
"STRIPE_WEBHOOK_SECRET_APPS=",
"STRIPE_WEBHOOK_SECRET_BILLING=",
"STRIPE_PRIVATE_KEY=",
"STRIPE_CLIENT_ID=",
# Prefix for public API keys.
"API_KEY_PREFIX=cal_",
# --- E-MAIL SETTINGS -----------------------------------------------------------------------
"EMAIL_FROM=notifications@${main_domain}",
"EMAIL_FROM_NAME=Cal.com",
"EMAIL_SERVER_HOST=",
"EMAIL_SERVER_PORT=587",
"EMAIL_SERVER_USER=",
"EMAIL_FROM=",
"EMAIL_SERVER_PASSWORD=",
"AWAITING_PAYMENT_EMAIL_DELAY_MINUTES=",
# --- BOT PROTECTION ------------------------------------------------------------------------
"NEXT_PUBLIC_CLOUDFLARE_SITEKEY=",
"NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=",
"CLOUDFLARE_TURNSTILE_SECRET=",
"NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER=0",
# --- CLOSE.COM INTERNAL CRM ----------------------------------------------------------------
"CLOSECOM_CLIENT_ID=",
"CLOSECOM_CLIENT_SECRET=",
# --- BRANDING ------------------------------------------------------------------------------
"NEXT_PUBLIC_APP_NAME=Cal.com",
"NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS=help@cal.com",
"NEXT_PUBLIC_COMPANY_NAME=Cal.com, Inc.",
"NEXT_PUBLIC_DISABLE_SIGNUP=false",
"NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL=",
"NEXT_PUBLIC_WEBSITE_TERMS_URL=",
# Deprecated: NEXT_PUBLIC_HEAD_SCRIPTS is injected into the body only, use BODY_SCRIPTS.
"NEXT_PUBLIC_HEAD_SCRIPTS=",
"NEXT_PUBLIC_BODY_SCRIPTS=",
# Set to non-strict to enable a Content Security Policy for support pages.
"CSP_POLICY=",
# Vercel Edge Config, unused outside Vercel.
"EDGE_CONFIG=",
# --- BOOKER BEHAVIOUR ----------------------------------------------------------------------
"NEXT_PUBLIC_MINUTES_TO_BOOK=5",
# Override the booker to only load X days worth of data. 0 = no override.
"NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD=0",
"NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL=",
# Retry interval for stale slot-reservation checks.
"NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS=",
# Must be lower than NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS.
"NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS=",
# Keep high (minutes) — low values put significant load on the instance.
"NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS=",
"NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM=0",
# Percentage of visitors that get quick availability checks.
"NEXT_PUBLIC_QUICK_AVAILABILITY_ROLLOUT=10",
# Emails that may never be added as booking guests.
"BLACKLISTED_GUEST_EMAILS=",
# --- ORGANIZATIONS -------------------------------------------------------------------------
# Organizations need a full domain pointing at the app and must also be enabled at build
# time, so setting this on a prebuilt image is not enough on its own.
"ORGANIZATIONS_ENABLED=",
"NEXT_PUBLIC_ORGANIZATIONS_MIN_SELF_SERVE_SEATS=30",
# $37.00 per seat
"NEXT_PUBLIC_ORGANIZATIONS_SELF_SERVE_PRICE_NEW=37",
# Autolink external provider sign-ups to existing orgs by email domain.
"ORGANIZATIONS_AUTOLINK=",
# Serve only one organization's booking pages. Baked in at build time.
"NEXT_PUBLIC_SINGLE_ORG_SLUG=",
# Vercel/Cloudflare subdomain management for organizations. Only relevant on Vercel.
"PROJECT_ID_VERCEL=",
"TEAM_ID_VERCEL=",
"AUTH_BEARER_TOKEN_VERCEL=",
"LOCAL_TESTING_DOMAIN_VERCEL=",
"CLOUDFLARE_DNS=",
"AUTH_BEARER_TOKEN_CLOUDFLARE=",
"CLOUDFLARE_ZONE_ID=",
"CLOUDFLARE_VERCEL_CNAME=",
# --- APP CREDENTIAL SYNC -------------------------------------------------------------------
# For self-hosters embedding Cal into an app that already holds the integration secrets.
"CALCOM_CREDENTIAL_SYNC_SECRET=${calcom_credential_sync_secret}",
# Header used to verify the webhook secret. Must be lowercase.
"CALCOM_CREDENTIAL_SYNC_HEADER_NAME=calcom-credential-sync-secret",
"CALCOM_CREDENTIAL_SYNC_ENDPOINT=",
# Must match the key configured in your application.
"CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY=${calcom_app_credential_encryption_key}",
"CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY=${calcom_service_account_encryption_key}",
# --- API V2 --------------------------------------------------------------------------------
# This blueprint does not ship the API v2 service (upstream builds it from source and has no
# published image). Point this at your own deployment to enable the v2 API.
"NEXT_PUBLIC_API_V2_URL=",
# --- TASKER / BACKGROUND JOBS --------------------------------------------------------------
"TASKER_ENABLE_WEBHOOKS=0",
"TASKER_ENABLE_EMAILS=0",
"ENABLE_ASYNC_TASKER=false",
"TRIGGER_SECRET_KEY=",
"TRIGGER_API_URL=https://api.trigger.dev",
"TRIGGER_DEV_PROJECT_REF=",
# --- RATE LIMITING (Unkey) -----------------------------------------------------------------
# Optional. Not needed for self-hosting unless you want rate limiting.
"UNKEY_ROOT_KEY=",
# --- CAL.AI VOICE AGENTS (Retell AI) -------------------------------------------------------
"RETELL_AI_KEY=",
"RETELL_AI_TEST_MODE=false",
# JSON map of local to production event type IDs.
"RETELL_AI_TEST_EVENT_TYPE_MAP=",
"RETELL_AI_TEST_CAL_API_KEY=",
"STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=",
"CAL_AI_CALL_RATE_PER_MINUTE=0.29",
# Price for buying a phone number for the Cal.ai voice agent (default 5).
"NEXT_PUBLIC_CAL_AI_PHONE_NUMBER_MONTHLY_PRICE=",
# --- VIDEO ---------------------------------------------------------------------------------
"HUDDLE01_API_TOKEN=",
# S3 storage for Daily Video recordings.
"CAL_VIDEO_BUCKET_NAME=",
"CAL_VIDEO_BUCKET_REGION=",
"CAL_VIDEO_ASSUME_ROLE_ARN=",
"CAL_VIDEO_MEETING_LINK_FOR_TESTING=",
"CAL_VIDEO_RECORDING_TOKEN_SECRET=${cal_video_recording_token_secret}",
# --- WEB PUSH NOTIFICATIONS ----------------------------------------------------------------
# Generate a pair with: npx web-push generate-vapid-keys
"NEXT_PUBLIC_VAPID_PUBLIC_KEY=",
"VAPID_PRIVATE_KEY=",
# --- MISC ----------------------------------------------------------------------------------
# Real-time AI localization via lingo.dev.
"LINGO_DOT_DEV_API_KEY=",
# Content moderation.
"IFFY_API_KEY=",
# Comma-separated DSyncData.directoryId values to log SCIM requests for (debugging only).
"DIRECTORY_IDS_TO_LOG=",
# 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error/fatal, 6: fatal
"NEXT_PUBLIC_LOGGER_LEVEL=",
"DATABASE_CHUNK_SIZE=",
"SALESFORCE_GRAPHQL_DELAY_MS=500",
"SALESFORCE_GRAPHQL_MAX_DELAY_MS=2000",
"SALESFORCE_GRAPHQL_MAX_RETRIES=3",
# Force Node.js to use UTC as the process timezone.
"TZ=UTC",
# Ad click tracking (gclid / li_fat_id). Off by default for self-hosted instances.
"GOOGLE_ADS_ENABLED=0",
"LINKEDIN_ADS_ENABLED=0",
# --- DEVELOPMENT / E2E ONLY ----------------------------------------------------------------
# Leave empty on a real deployment.
"NEXT_PUBLIC_IS_E2E=",
"E2E_TEST_MAILHOG_ENABLED=",
"E2E_TEST_APPLE_CALENDAR_EMAIL=",
"E2E_TEST_APPLE_CALENDAR_PASSWORD=",
"E2E_TEST_CALCOM_QA_EMAIL=",
"E2E_TEST_CALCOM_QA_PASSWORD=",
"E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS=",
"E2E_TEST_CALCOM_GCAL_KEYS=",
"SEED_PLATFORM_OAUTH_CLIENT_ID=",
"SEED_PLATFORM_OAUTH_CLIENT_SECRET=",
"SEED_OAUTH2_CLIENT_ID=",
"SEED_OAUTH2_CLIENT_SECRET_HASHED=",
]
mounts = []
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 31">
<path fill="#60a5fa" d="m 3.043653,30.614292 c -1.04707,-0.32444 -1.94939,-1.09611 -2.51053002,-2.14706 l -0.28493,-0.53364 -0.0338,-10.26902 c -0.0333,-10.1001 -0.0296,-10.28026 0.22157,-10.95165 C 0.93698298,5.3738426 2.254043,4.3160626 3.592343,4.1779326 l 0.66734,-0.069 0.0442,1.54722 c 0.0411,1.4322294 0.069,1.5846294 0.37758,2.0507194 0.43775,0.66128 1.06374,1.03377 1.8697695,1.11256 0.83411,0.0815 1.60889,-0.30288 2.1454309,-1.06449 0.35775,-0.50781 0.37448,-0.59108 0.41656,-2.0715194 l 0.0439,-1.54245 h 4.3226446 4.32263 l 0.0491,1.45985 c 0.0578,1.7152194 0.26715,2.2526994 1.10313,2.8320394 0.44147,0.30594 0.6462,0.36331 1.30282,0.36509 0.6673,0.002 0.85479,-0.0513 1.30717,-0.3706 0.83953,-0.59252 1.03808,-1.10942 1.09117,-2.8410394 l 0.0452,-1.47436 0.61114,0.0588 c 0.88513,0.085 1.92322,0.66148 2.52855,1.40407 0.98071,1.2030494 0.9433,0.7026404 0.90509,12.1120594 l -0.0339,10.12241 -0.34547,0.70339 c -0.37943,0.77247 -1.03179,1.43013 -1.85424,1.86928 l -0.53363,0.28492 -10.31215,0.0218 c -5.6716955,0.0121 -10.451945,-0.0215 -10.622765,-0.0745 z M 5.994413,7.609932 C 5.466163,7.302262 5.404273,6.907952 5.404273,3.8497926 v -2.91273 l 0.36045,-0.38587 c 0.39686,-0.42484 0.9678395,-0.51125 1.5687795,-0.23743 0.6263,0.28536 0.67987,0.58051 0.67987,3.7459 V 6.972402 l -0.36047,0.38586 c -0.29807,0.31911 -0.45393,0.38521 -0.90075,0.3821 -0.29716,-0.002 -0.6381595,-0.0608 -0.7577395,-0.13043 z m 13.312785,-0.25167 -0.36045,-0.38586 V 4.0596626 c 0,-3.08584 0.0628,-3.47128 0.61433,-3.76641 0.31968,-0.17109 0.91477,-0.16181 1.3149,0.0204 0.62629,0.28536 0.67987,0.58051 0.67987,3.74591 V 6.972292 l -0.36046,0.38587 c -0.30429,0.32575 -0.45138,0.38587 -0.9441,0.38587 -0.4927,0 -0.6398,-0.0601 -0.94409,-0.38587 z"/>
<path fill="#ffffff" d="M 13.529898,23.489202 c 0.41676,-0.42858 2.30921,-2.34577 4.20548,-4.26044 3.89443,-3.93222 3.79896,-3.77881 2.93494,-4.71617 -0.86333,-0.9366 -0.70987,-1.03489 -4.7574,3.04728 -2.01816,2.03542 -3.63135,3.56753 -3.70704,3.52074 -0.0737,-0.0455 -0.86549,-0.83379 -1.759495,-1.7516 -1.7365396,-1.78282 -2.1646795,-2.10404 -2.5380305,-1.90423 -0.40259,0.21546 -1.13741,1.12099 -1.13741,1.40162 0,0.18848 0.79327,1.06899 2.5741409,2.85723 3.0861346,3.09893 2.9741146,3.05059 4.1848146,1.80557 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+47
View File
@@ -0,0 +1,47 @@
services:
calnode:
image: ghcr.io/calnode/calnode:0.12.0
restart: unless-stopped
volumes:
- calnode_data:/data
expose:
- "3000"
environment:
PORT: ${PORT}
BASE_URL: ${BASE_URL}
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL}
DATABASE_URL: ${DATABASE_URL}
CALNODE_ENCRYPTION_KEY: ${CALNODE_ENCRYPTION_KEY}
CALNODE_RECOVERY_SECRET: ${CALNODE_RECOVERY_SECRET}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET}
MICROSOFT_TENANT: ${MICROSOFT_TENANT}
EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST}
EMAIL_SMTP_PORT : ${EMAIL_SMTP_PORT}
EMAIL_SMTP_CONNECT_HOST: ${EMAIL_SMTP_CONNECT_HOST}
EMAIL_SMTP_CONNECT_PORT: ${EMAIL_SMTP_CONNECT_PORT}
EMAIL_SMTP_USER: ${EMAIL_SMTP_USER}
EMAIL_SMTP_PASS : ${EMAIL_SMTP_PASS}
EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS}
EMAIL_FROM_NAME: ${EMAIL_FROM_NAME}
EMAIL_SMTP_TLS: ${EMAIL_SMTP_TLS}
EMAIL_SMTP_STARTTLS: ${EMAIL_SMTP_STARTTLS}
LITESTREAM_REPLICA_URL: ${LITESTREAM_REPLICA_URL}
LITESTREAM_ACCESS_KEY_ID: ${LITESTREAM_ACCESS_KEY_ID}
LITESTREAM_SECRET_ACCESS_KEY: ${LITESTREAM_SECRET_ACCESS_KEY}
LITESTREAM_ENDPOINT: ${LITESTREAM_ENDPOINT}
LITESTREAM_REGION: ${LITESTREAM_REGION}
COOKIE_SECURE: ${COOKIE_SECURE}
TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS}
FRAME_ANCESTORS: ${FRAME_ANCESTORS}
LOG_LEVEL: ${LOG_LEVEL}
volumes:
calnode_data:
+20
View File
@@ -0,0 +1,20 @@
{
"id": "calnode",
"name": "Calnode",
"version": "v0.12.0",
"description": "Calnode is a Calendly-style booking app — with first-party video meetings, recording, and AI notetaking built in. It's API-first, webhook-native, and built for a world where agents do the booking. Nothing is paywalled.",
"links": {
"github": "https://github.com/Calnode/calnode",
"website": "https://calnode.com/",
"docs": "https://github.com/Calnode/calnode/tree/main/docs"
},
"logo": "calnode.svg",
"tags": [
"scheduling",
"booking",
"calendar",
"meetings"
],
"seoTitle": "Self-Host Calnode in minutes",
"seoDescription": "Scheduling and booking tool, an alternative to Calendly. Self-host Calnode on Dokploy in minutes – no manual setup needed."
}
+64
View File
@@ -0,0 +1,64 @@
[variables]
main_domain = "${domain}"
calnode_encryption_key = "${base64:32}"
[config]
env = [
"PORT=3000",
"BASE_URL=http:${main_domain}",
"PUBLIC_BASE_URL=",
"DATABASE_URL=sqlite://./data/calnode.db",
"COOKIE_SECURE=",
"CALNODE_ENCRYPTION_KEY=${calnode_encryption_key}",
"CALNODE_RECOVERY_SECRET=",
# Redirect URIs to add:
# http://<domain>/v1/auth/callback
# http://<domain>/v1/calendar/callback
"GOOGLE_CLIENT_ID=",
"GOOGLE_CLIENT_SECRET=",
# Add a Web platform with these redirect URIs:
# http://<domain>/v1/auth/microsoft/callback
# http://<domain>/v1/calendar/callback
"MICROSOFT_CLIENT_ID=",
"MICROSOFT_CLIENT_SECRET=",
"MICROSOFT_TENANT=common",
"EMAIL_SMTP_HOST=",
"EMAIL_SMTP_PORT=587",
"EMAIL_SMTP_CONNECT_HOST=",
"EMAIL_SMTP_CONNECT_PORT=",
"EMAIL_SMTP_USER=",
"EMAIL_SMTP_PASS=",
"EMAIL_FROM_ADDRESS=",
"EMAIL_FROM_NAME=Calnode",
"EMAIL_SMTP_TLS=",
"EMAIL_SMTP_STARTTLS=",
# Object storage (Litestream) — optional, but required for two things: continuous
# SQLite backups, and meeting recordings (Settings → Storage; the "Record" button
# only appears once this is configured AND recordings are turned on there). Both
# share one S3-compatible bucket. No in-app form for this — set as environment
# variables/secrets with your hosting provider and redeploy.
"LITESTREAM_REPLICA_URL=",
"LITESTREAM_ACCESS_KEY_ID=",
"LITESTREAM_SECRET_ACCESS_KEY=",
# LITESTREAM_ENDPOINT is required for any provider other than AWS S3 (Cloudflare
# R2, Backblaze B2, MinIO, etc.) — set it to that provider's S3-compatible endpoint.
"LITESTREAM_ENDPOINT=",
# LITESTREAM_REGION is optional — only some providers need it.
"LITESTREAM_REGION=",
"TRUSTED_PROXY_CIDRS=",
"FRAME_ANCESTORS=",
"LOG_LEVEL=",
]
mounts = []
[[config.domains]]
serviceName = "calnode"
port = 3_000
host = "${main_domain}"
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<rect width="512" height="512" rx="96" fill="#0b1220"/>
<g stroke="#1e2a44" stroke-width="2" opacity="0.7">
<path d="M96 128h320M96 192h320M96 256h320M96 320h320M96 384h320"/>
<path d="M128 96v320M192 96v320M256 96v320M320 96v320M384 96v320"/>
</g>
<g fill="#1f6feb" opacity="0.9">
<rect x="136" y="288" width="40" height="96" rx="6"/>
<rect x="200" y="240" width="40" height="144" rx="6"/>
<rect x="264" y="176" width="40" height="208" rx="6"/>
<rect x="328" y="216" width="40" height="168" rx="6"/>
</g>
<path d="M120 352l64-80 56 24 72-96 80 48" fill="none" stroke="#3fb950" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
<g fill="#3fb950">
<circle cx="120" cy="352" r="12"/>
<circle cx="184" cy="272" r="12"/>
<circle cx="240" cy="296" r="12"/>
<circle cx="312" cy="200" r="12"/>
<circle cx="392" cy="248" r="12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 999 B

@@ -0,0 +1,57 @@
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.55.1
restart: unless-stopped
privileged: true
devices:
- /dev/kmsg
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
command:
- --housekeeping_interval=15s
- --docker_only=true
node-exporter:
image: prom/node-exporter:v1.12.1
restart: unless-stopped
pid: host
volumes:
- /:/host:ro,rslave
command:
- --path.rootfs=/host
prometheus:
image: prom/prometheus:v3.15.1
restart: unless-stopped
volumes:
- ../files/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
grafana:
image: grafana/grafana:13.2.4
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_AUTH_ANONYMOUS_ENABLED: "false"
volumes:
- grafana-data:/var/lib/grafana
- ../files/grafana/datasources.yml:/etc/grafana/provisioning/datasources/prometheus.yml:ro
- ../files/grafana/dashboards.yml:/etc/grafana/provisioning/dashboards/provider.yml:ro
- ../files/grafana/cluster-overview.json:/etc/grafana/provisioning/dashboards/cluster-overview.json:ro
- ../files/grafana/container-detail.json:/etc/grafana/provisioning/dashboards/container-detail.json:ro
depends_on:
- prometheus
volumes:
prometheus-data: {}
grafana-data: {}
+23
View File
@@ -0,0 +1,23 @@
{
"id": "container-monitoring",
"name": "Container Monitoring",
"version": "13.2.4",
"description": "Per-container and host monitoring with cAdvisor, node-exporter, Prometheus and Grafana. Ships two ready dashboards: a cluster overview and a container detail view with project/container filters.",
"logo": "container-monitoring.svg",
"links": {
"github": "https://github.com/google/cadvisor",
"website": "https://prometheus.io/",
"docs": "https://prometheus.io/docs/introduction/overview/"
},
"tags": [
"monitoring",
"observability",
"metrics",
"prometheus",
"grafana",
"docker"
],
"seoTitle": "Container Monitoring with Prometheus and Grafana",
"seoDescription": "Self-host cAdvisor, node-exporter, Prometheus and Grafana with Docker Compose on Dokploy. Includes per-container CPU, memory and network dashboards.",
"newConfig": true
}
@@ -0,0 +1,543 @@
[variables]
main_domain = "${domain}"
grafana_admin_password = "${password:24}"
[config.env]
GRAFANA_ADMIN_PASSWORD = "${grafana_admin_password}"
[[config.domains]]
serviceName = "grafana"
port = 3000
host = "${main_domain}"
[[config.mounts]]
filePath = "/prometheus.yml"
content = '''
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: cadvisor
static_configs:
- targets: ["cadvisor:8080"]
- job_name: node
static_configs:
- targets: ["node-exporter:9100"]
'''
[[config.mounts]]
filePath = "/grafana/datasources.yml"
content = '''
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
'''
[[config.mounts]]
filePath = "/grafana/dashboards.yml"
content = '''
apiVersion: 1
providers:
- name: containers
orgId: 1
folder: ""
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
'''
[[config.mounts]]
filePath = "/grafana/cluster-overview.json"
content = '''
{
"annotations": { "list": [] },
"editable": true,
"graphTooltip": 1,
"links": [],
"panels": [
{
"id": 1,
"type": "stat",
"title": "CPU used",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 0, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 1,
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 70 }, { "color": "red", "value": 90 }] }
},
"overrides": []
},
"targets": [
{ "refId": "A", "expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval])) * 100)" }
]
},
{
"id": 2,
"type": "stat",
"title": "RAM used",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 4, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"decimals": 1,
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 70 }, { "color": "red", "value": 90 }] }
},
"overrides": []
},
"targets": [
{ "refId": "A", "expr": "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes" }
]
},
{
"id": 3,
"type": "stat",
"title": "RAM used %",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 8, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 1,
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 70 }, { "color": "red", "value": 90 }] }
},
"overrides": []
},
"targets": [
{ "refId": "A", "expr": "100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)" }
]
},
{
"id": 4,
"type": "stat",
"title": "RAM available",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 12, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "bytes", "decimals": 1 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "node_memory_MemAvailable_bytes" }
]
},
{
"id": 5,
"type": "stat",
"title": "Containers",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 16, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "none", "decimals": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "count(count by (name) (container_last_seen{name!=\"\"}))" }
]
},
{
"id": 6,
"type": "stat",
"title": "Disk / used",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 20, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 1,
"thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 75 }, { "color": "red", "value": 90 }] }
},
"overrides": []
},
"targets": [
{ "refId": "A", "expr": "100 - (node_filesystem_avail_bytes{mountpoint=\"/\",fstype!~\"tmpfs|overlay\"} / node_filesystem_size_bytes{mountpoint=\"/\",fstype!~\"tmpfs|overlay\"} * 100)" }
]
},
{
"id": 7,
"type": "timeseries",
"title": "Host CPU by mode",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "avg by (mode) (rate(node_cpu_seconds_total{mode!=\"idle\"}[$__rate_interval])) * 100", "legendFormat": "{{mode}}" }
]
},
{
"id": 8,
"type": "timeseries",
"title": "Host memory",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
"fieldConfig": { "defaults": { "unit": "bytes", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes", "legendFormat": "used" },
{ "refId": "B", "expr": "node_memory_MemAvailable_bytes", "legendFormat": "available" },
{ "refId": "C", "expr": "node_memory_MemTotal_bytes", "legendFormat": "total" }
]
},
{
"id": 9,
"type": "timeseries",
"title": "Container CPU (all)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_cpu_usage_seconds_total{name!=\"\", cpu=\"total\"}[$__rate_interval])) * 100", "legendFormat": "all containers" }
]
},
{
"id": 10,
"type": "timeseries",
"title": "Container memory (all)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
"fieldConfig": { "defaults": { "unit": "bytes", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(container_memory_working_set_bytes{name!=\"\"})", "legendFormat": "all containers" }
]
},
{
"id": 11,
"type": "timeseries",
"title": "Container network (all)",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 7, "w": 24, "x": 0, "y": 21 },
"fieldConfig": { "defaults": { "unit": "Bps", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_network_receive_bytes_total{name!=\"\"}[$__rate_interval]))", "legendFormat": "RX" },
{ "refId": "B", "expr": "sum(rate(container_network_transmit_bytes_total{name!=\"\"}[$__rate_interval]))", "legendFormat": "TX" }
]
},
{
"id": 12,
"type": "table",
"title": "Top containers by memory",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 9, "w": 24, "x": 0, "y": 28 },
"fieldConfig": { "defaults": { "unit": "bytes", "custom": { "align": "auto" } }, "overrides": [] },
"options": { "showHeader": true, "sortBy": [{ "displayName": "Value", "desc": true }] },
"targets": [
{
"refId": "A",
"expr": "topk(10, sum by (name, container_label_com_docker_compose_project) (container_memory_working_set_bytes{name!=\"\"}))",
"instant": true,
"format": "table"
}
]
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["cluster"],
"templating": { "list": [] },
"time": { "from": "now-6h", "to": "now" },
"timezone": "browser",
"title": "Cluster Overview",
"uid": "cluster-overview",
"version": 1
}
'''
[[config.mounts]]
filePath = "/grafana/container-detail.json"
content = '''
{
"annotations": { "list": [] },
"editable": true,
"graphTooltip": 1,
"links": [],
"panels": [
{
"id": 1,
"type": "stat",
"title": "CPU",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 0, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "percent", "decimals": 1, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\", cpu=\"total\"}[$__rate_interval])) by (name) * 100", "legendFormat": "{{name}}" }
]
},
{
"id": 2,
"type": "stat",
"title": "Memory",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 4, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "bytes", "decimals": 1, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "sum(container_memory_working_set_bytes{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}) by (name)", "legendFormat": "{{name}}" }
]
},
{
"id": 3,
"type": "stat",
"title": "RAM % of host",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 8, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "percent", "decimals": 2, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "100 * sum(container_memory_working_set_bytes{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}) by (name) / scalar(node_memory_MemTotal_bytes)", "legendFormat": "{{name}}" }
]
},
{
"id": 4,
"type": "stat",
"title": "Network RX",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 12, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "Bps", "decimals": 1, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_network_receive_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "{{name}}" }
]
},
{
"id": 5,
"type": "stat",
"title": "Network TX",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 16, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "Bps", "decimals": 1, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_network_transmit_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "{{name}}" }
]
},
{
"id": 6,
"type": "stat",
"title": "Uptime",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 5, "w": 4, "x": 20, "y": 0 },
"options": {
"reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false },
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"textMode": "auto",
"orientation": "auto"
},
"fieldConfig": { "defaults": { "unit": "s", "decimals": 0, "min": 0 }, "overrides": [] },
"targets": [
{ "refId": "A", "expr": "time() - container_start_time_seconds{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}", "legendFormat": "{{name}}" }
]
},
{
"id": 7,
"type": "timeseries",
"title": "CPU",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\", cpu=\"total\"}[$__rate_interval])) by (name) * 100", "legendFormat": "{{name}}" }
]
},
{
"id": 8,
"type": "timeseries",
"title": "Memory working set",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
"fieldConfig": { "defaults": { "unit": "bytes", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(container_memory_working_set_bytes{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}) by (name)", "legendFormat": "{{name}}" }
]
},
{
"id": 9,
"type": "timeseries",
"title": "Network RX / TX",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 13 },
"fieldConfig": { "defaults": { "unit": "Bps", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_network_receive_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "RX {{name}}" },
{ "refId": "B", "expr": "sum(rate(container_network_transmit_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "TX {{name}}" }
]
},
{
"id": 10,
"type": "timeseries",
"title": "Block I/O",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 },
"fieldConfig": { "defaults": { "unit": "Bps", "min": 0, "custom": { "drawStyle": "line", "lineWidth": 1, "fillOpacity": 10, "showPoints": "never" } }, "overrides": [] },
"options": { "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] }, "tooltip": { "mode": "multi", "sort": "desc" } },
"targets": [
{ "refId": "A", "expr": "sum(rate(container_fs_reads_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "read {{name}}" },
{ "refId": "B", "expr": "sum(rate(container_fs_writes_bytes_total{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"}[$__rate_interval])) by (name)", "legendFormat": "write {{name}}" }
]
},
{
"id": 11,
"type": "table",
"title": "Containers",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 },
"fieldConfig": { "defaults": { "unit": "bytes", "custom": { "align": "auto" } }, "overrides": [] },
"options": { "showHeader": true },
"targets": [
{
"refId": "A",
"expr": "sum by (name, image, container_label_com_docker_compose_project) (container_memory_working_set_bytes{name=~\"$container\", container_label_com_docker_compose_project=~\"$project\"})",
"instant": true,
"format": "table"
}
]
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["containers"],
"templating": {
"list": [
{
"name": "project",
"label": "Project",
"type": "query",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"definition": "label_values(container_last_seen{container_label_com_docker_compose_project!=\"\"}, container_label_com_docker_compose_project)",
"query": {
"query": "label_values(container_last_seen{container_label_com_docker_compose_project!=\"\"}, container_label_com_docker_compose_project)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"multi": true,
"includeAll": true,
"allValue": ".*",
"current": { "selected": true, "text": ["All"], "value": ["$__all"] },
"options": []
},
{
"name": "container",
"label": "Container",
"type": "query",
"datasource": { "type": "prometheus", "uid": "prometheus" },
"definition": "label_values(container_last_seen{container_label_com_docker_compose_project=~\"$project\", name!=\"\"}, name)",
"query": {
"query": "label_values(container_last_seen{container_label_com_docker_compose_project=~\"$project\", name!=\"\"}, name)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"multi": false,
"includeAll": false,
"current": {},
"options": [],
"sort": 1
}
]
},
"time": { "from": "now-6h", "to": "now" },
"timezone": "browser",
"title": "Container Detail",
"uid": "container-detail",
"version": 1
}
'''
+2 -2
View File
@@ -1,6 +1,6 @@
services:
backend:
image: ghcr.io/get-convex/convex-backend:5cdea511cd6527a95dd24152ea0d3c3bb2ab379f
image: ghcr.io/convex/convex-backend:72ce13c379fe6dbdfc305bc8ad699c5663c68c2a
expose:
- "3210"
- "3211"
@@ -24,7 +24,7 @@ services:
start_period: 5s
dashboard:
image: ghcr.io/get-convex/convex-dashboard:5cdea511cd6527a95dd24152ea0d3c3bb2ab379f
image: ghcr.io/convex/convex-dashboard:72ce13c379fe6dbdfc305bc8ad699c5663c68c2a
expose:
- "6791"
environment:
+3 -7
View File
@@ -1,17 +1,13 @@
{
"id": "convex",
"name": "Convex",
"version": "latest",
"version": "72ce13c",
"description": "Convex is an open-source reactive database designed to make life easy for web app developers.",
"logo": "convex.svg",
"links": {
"github": "https://github.com/get-convex/convex-backend",
"github": "https://github.com/convex/convex-backend",
"website": "https://www.convex.dev/",
"docs": "https://www.convex.dev/docs"
},
"tags": [
"backend",
"database",
"api"
]
"tags": ["backend", "database", "api"]
}
@@ -0,0 +1,13 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_10269_2435)">
<path d="M206.222 0H49.7778C22.2863 0 0 22.2863 0 49.7778V206.222C0 233.714 22.2863 256 49.7778 256H206.222C233.714 256 256 233.714 256 206.222V49.7778C256 22.2863 233.714 0 206.222 0Z" fill="white"/>
<path d="M201.697 178.505L175.67 168.424C169.43 166.007 162.039 168.153 156.544 171.971C149.591 176.802 137.646 182.809 118.247 186.566C81.5296 193.677 60.3846 153.139 60.3846 153.139C60.3846 153.139 64.84 196.282 114.909 210.505C164.978 224.727 201.697 178.505 201.697 178.505Z" fill="black"/>
<path d="M161.754 53.5827C161.754 53.5827 98.2194 61.1686 91.5434 110.946C84.8675 160.724 134.937 178.502 134.937 178.502C134.937 178.502 115.366 188.683 95.3394 181.576C75.3116 174.468 64.8398 153.61 64.8398 153.61C64.8398 153.61 41.4738 114.502 74.8537 75.3907C108.233 36.2797 161.754 53.5827 161.754 53.5827Z" fill="black"/>
<path d="M201.672 86.0414L181.054 96.11C172.525 100.275 159.267 88.6796 150.153 86.0293C147.272 85.1916 143.526 84.9392 138.683 85.555C110.719 89.1106 96.0384 103.819 96.0384 103.819C96.0384 103.819 100.233 77.3772 142.179 61.3915C184.124 45.4054 201.672 86.0414 201.672 86.0414Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_10269_2435">
<rect width="256" height="256" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,38 @@
services:
customermates:
image: ghcr.io/customermates/customermates:sha-4f4d61c
restart: unless-stopped
depends_on:
customermates-postgres:
condition: service_healthy
environment:
APP_MODE: self-hosted
NODE_ENV: production
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@customermates-postgres:5432/${POSTGRES_DB}
WORKFLOW_TARGET_WORLD: "@workflow/world-postgres"
WORKFLOW_POSTGRES_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@customermates-postgres:5432/${POSTGRES_DB}
BASE_URL: ${BASE_URL}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
CRON_SECRET: ${CRON_SECRET}
RESEND_API_KEY: ${RESEND_API_KEY}
RESEND_OPERATOR_EMAIL: ${RESEND_OPERATOR_EMAIL}
expose:
- 4000
customermates-postgres:
image: postgres:16.15-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- customermates-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
volumes:
customermates-postgres-data: {}
+17
View File
@@ -0,0 +1,17 @@
{
"id": "customermates",
"name": "Customermates",
"version": "sha-4f4d61c",
"description": "Customermates is an open-source CRM for contacts, organizations, deals, services and tasks, with a REST API, webhooks and an MCP endpoint for external AI clients. Self-hosted, it runs on PostgreSQL 16 and uses the Starter feature set.",
"logo": "customermates.svg",
"links": {
"github": "https://github.com/customermates/customermates",
"website": "https://customermates.com",
"docs": "https://customermates.com/docs/self-hosting"
},
"tags": [
"crm",
"sales",
"business"
]
}
+23
View File
@@ -0,0 +1,23 @@
[variables]
main_domain = "${domain}"
postgres_password = "${hash:16}"
auth_secret = "${hash:32}"
cron_secret = "${hash:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "customermates"
port = 4000
host = "${main_domain}"
[config.env]
BASE_URL = "http://${main_domain}"
BETTER_AUTH_SECRET = "${auth_secret}"
CRON_SECRET = "${cron_secret}"
POSTGRES_USER = "customermates"
POSTGRES_PASSWORD = "${postgres_password}"
POSTGRES_DB = "customermates"
RESEND_API_KEY = ""
RESEND_OPERATOR_EMAIL = "mail@your-company.com"
+1
View File
@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 559.55 246.22"><defs><style>.cls-1{fill:#80b441;}.cls-2{fill:#222;}</style></defs><title>cypht_logo_white</title><path class="cls-1" d="M189.83,391.4q46.63,0,71.35-14.21v-13.7H107Q139.56,391.39,189.83,391.4Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M98.84,355.8H261.18V319q-26.1,17-53.34,17-30.71,0-48.26-17.76c-.15-.16-.29-.32-.44-.47H75.07A106.61,106.61,0,0,0,98.84,355.8Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M67.92,274a132.58,132.58,0,0,0,4.68,36h80.34q-10.29-15.35-10.86-38.11H67.94C67.93,272.6,67.92,273.31,67.92,274Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M68.18,264.2h74q1.07-22.81,12.08-38.12H75A146.51,146.51,0,0,0,68.18,264.2Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M192.22,180.28v22.08a90.15,90.15,0,0,1,17.93-1.7,83.58,83.58,0,0,1,51,17V180.28Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M127.45,180.28h-69A110,110,0,0,0,32.84,218.4h69A110,110,0,0,1,127.45,180.28Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M92,218.4h69a58.88,58.88,0,0,1,14.84-10.47,83.47,83.47,0,0,0-34.56-7.27Q110.23,200.66,92,218.4Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M103.45,180.28A110,110,0,0,0,77.8,218.4H92q18.27-17.73,49.23-17.74a83.47,83.47,0,0,1,34.56,7.27,67.11,67.11,0,0,1,16.47-5.57V180.28Z" transform="translate(-32.84 -145.18)"/><path class="cls-1" d="M261.18,156.19q-18.7-11-60.5-11-53.91,0-88.47,27.42h149Z" transform="translate(-32.84 -145.18)"/><path class="cls-2" d="M354.43,245.57l-30.47,81q-11,29.24-33.11,29.23A42.38,42.38,0,0,1,277,353.89V335.43a19.45,19.45,0,0,0,10,2.71q8.93,0,12.45-8.42l3.95-9.3L273,245.57h25.64l14,45.63a59.21,59.21,0,0,1,2.05,10H315a70.45,70.45,0,0,1,2.42-9.89l14.13-45.77Z" transform="translate(-32.84 -145.18)"/><path class="cls-2" d="M387.17,311.85h-.3v43.21H363.73V245.57h23.14v11.28h.3q8.56-13.11,24.09-13.11,14.58,0,22.53,10t7.94,27.21q0,18.75-9.26,30.11T407.82,322.4Q394.28,322.4,387.17,311.85Zm-.66-30.76v6q0,7.75,4.1,12.67a13.34,13.34,0,0,0,10.77,4.9,14.16,14.16,0,0,0,12.26-6.11q4.37-6.12,4.36-17.32,0-19.78-15.38-19.78A14.44,14.44,0,0,0,391,266.84Q386.51,272.23,386.51,281.09Z" transform="translate(-32.84 -145.18)"/><path class="cls-2" d="M528.74,320.57H505.67V277.94q0-16.49-12-16.48a12.31,12.31,0,0,0-10,4.61,17.82,17.82,0,0,0-3.81,11.72v42.78H456.75v-111h23.14V256.7h.3q8.49-13,23.07-13,25.49,0,25.48,30.76Z" transform="translate(-32.84 -145.18)"/><path class="cls-2" d="M552.48,262.63V297q0,25.42,24.46,25.42,10.32,0,15.45-2.71V302.55a15.63,15.63,0,0,1-7.54,2.12q-9.3,0-9.3-11.72V262.63Z" transform="translate(-32.84 -145.18)"/><polygon class="cls-2" points="552.33 113.96 552.33 104.29 538.27 104.29 519.01 104.29 508.86 104.29 508.86 113.96 519.01 113.96 538.27 113.96 552.33 113.96"/><polygon class="cls-1" points="527.21 92.21 519.66 86.61 519.66 100.38 542.74 100.38 542.74 79.92 527.21 92.21"/><polygon class="cls-1" points="542.74 76.69 542.74 75.96 519.66 81.9 519.66 83.34 527.21 88.9 542.74 76.69"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+80
View File
@@ -0,0 +1,80 @@
services:
cypht:
image: cypht/cypht:2.13.0
restart: unless-stopped
depends_on:
cypht-db:
condition: service_healthy
cypht-redis:
condition: service_healthy
expose:
- 80
environment:
# Initial account (created on first start) and admin
AUTH_USERNAME: ${ADMIN_USERNAME}
AUTH_PASSWORD: ${ADMIN_PASSWORD}
ADMIN_USERS: ${ADMIN_USERNAME}
# Accounts and user settings stored in MariaDB
AUTH_TYPE: DB
USER_CONFIG_TYPE: DB
DB_CONNECTION_TYPE: host
DB_DRIVER: mysql
DB_HOST: cypht-db
DB_PORT: 3306
DB_NAME: cypht
DB_USER: cypht
DB_PASS: ${DB_PASSWORD}
# Sessions and cache in Redis
SESSION_TYPE: REDIS
ENABLE_REDIS: "true"
REDIS_SERVER: cypht-redis
REDIS_PORT: 6379
REDIS_PASS: ${REDIS_PASSWORD}
# The image's baked-in .env sets a unix socket path, which takes precedence over host/port
REDIS_SOCKET: ""
ENABLE_MEMCACHED: "false"
ENCRYPT_LOCAL_STORAGE: "true"
volumes:
- cypht-attachments:/var/lib/hm3/attachments
cypht-db:
image: mariadb:10.11
restart: unless-stopped
environment:
MARIADB_DATABASE: cypht
MARIADB_USER: cypht
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_RANDOM_ROOT_PASSWORD: "yes"
volumes:
- cypht-db:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
cypht-redis:
image: redis:7-alpine
restart: unless-stopped
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
# volatile-lru: sessions and cache keys both carry a TTL, so only they can be evicted
command:
- sh
- -c
- exec redis-server --requirepass "$$REDIS_PASSWORD" --appendonly yes --maxmemory 256mb --maxmemory-policy volatile-lru
volumes:
- cypht-redis:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" --no-auth-warning ping | grep -q PONG"]
interval: 10s
timeout: 5s
retries: 5
volumes:
cypht-attachments: {}
cypht-db: {}
cypht-redis: {}
+17
View File
@@ -0,0 +1,17 @@
{
"id": "cypht",
"name": "Cypht",
"version": "2.13.0",
"description": "Lightweight open source webmail aggregator. Read and send mail from multiple IMAP, SMTP, JMAP and EWS accounts in one place.",
"logo": "cypht.svg",
"links": {
"github": "https://github.com/cypht-org/cypht",
"website": "https://cypht.org/",
"docs": "https://cypht.org/install.html"
},
"tags": [
"self-hosted",
"email",
"webmail"
]
}
+20
View File
@@ -0,0 +1,20 @@
[variables]
main_domain = "${domain}"
admin_username = "admin"
admin_password = "${password:24}"
db_password = "${password:32}"
redis_password = "${password:32}"
[config]
env = [
"ADMIN_USERNAME=${admin_username}",
"ADMIN_PASSWORD=${admin_password}",
"DB_PASSWORD=${db_password}",
"REDIS_PASSWORD=${redis_password}",
]
mounts = []
[[config.domains]]
serviceName = "cypht"
port = 80
host = "${main_domain}"
+1 -1
View File
@@ -4,7 +4,7 @@
services:
dagu:
image: ghcr.io/dagucloud/dagu:2.10.7
image: ghcr.io/dagucloud/dagu:2.25.4
command: dagu start-all
restart: unless-stopped
expose:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "dagu",
"name": "Dagu",
"version": "2.10.7",
"version": "2.25.4",
"description": "Dagu is a self-contained workflow engine with a Web UI. Schedule and orchestrate jobs as DAGs defined in declarative YAML, with no DBMS required.",
"logo": "logo.png",
"links": {
-1
View File
@@ -1,4 +1,3 @@
version: "3.8"
services:
docktail:
image: ghcr.io/marvinvr/docktail:1.6.0
+3 -2
View File
@@ -11,9 +11,9 @@ services:
- DATABASE_URL=postgresql://${POSTGRES_USER:-docuseal}:${POSTGRES_PASSWORD}@docu-postgres:5432/${POSTGRES_DB:-docuseal}
docu-postgres:
image: postgres:latest
image: postgres:18
volumes:
- docuseal-db:/var/lib/postgresql/data
- docuseal-db:/var/lib/postgresql
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
@@ -24,6 +24,7 @@ services:
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
docu-redis:
image: redis:alpine
@@ -0,0 +1,81 @@
services:
dokploy-actions-runner:
# Maintained upstream runner, Ubuntu 24.04; multi-architecture index digest.
image: ghcr.io/myoung34/docker-github-actions-runner:2.338.2-ubuntu-noble@sha256:23e3a4a1da30de5b462ad0ae13d901288e803a688075c3b9b5bb79b7148c07fa
restart: unless-stopped
user: runner
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
stop_grace_period: 2m
# Dokploy's .env supplies interpolation, not automatic container injection.
# Allowlist inputs explicitly. Never replace this with env_file: .env.
environment:
ACCESS_TOKEN: ${ACCESS_TOKEN:?Set ACCESS_TOKEN to a GitHub PAT in Dokploy Environment}
RUNNER_SCOPE: ${RUNNER_SCOPE:?Set RUNNER_SCOPE to repo or org}
REPO_URL: ${REPO_URL:-}
ORG_NAME: ${ORG_NAME:-}
RUNNER_NAME: ${RUNNER_NAME:?Set a unique RUNNER_NAME for each deployment}
LABELS: ${LABELS:?Set comma-separated runner LABELS}
RUNNER_GROUP: ${RUNNER_GROUP:-Default}
# Use the maintained embedded Node24 for shell steps as well as JS actions.
# Keep upstream runtime slots intact; this is not a Node20 binary replacement.
PATH: /actions-runner/externals/node24/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN_AS_ROOT: "false"
START_DOCKER_SERVICE: "false"
DISABLE_AUTOMATIC_DEREGISTRATION: "false"
RUNNER_WORKDIR: /_work
# Keep the PAT in the entrypoint shell for shutdown deregistration.
# Upstream un-exports it before starting the listener. See the linked project security guide.
UNSET_CONFIG_VARS: "false"
# Validates configuration and the runtime floor, then execs upstream lifecycle.
# Inline so Dokploy imports need no custom image or repository file mounts.
# Double dollars defer expansion to the container, not Compose/Dokploy.
entrypoint:
- /bin/bash
- -c
- |
set -euo pipefail
fail() { printf 'Configuration error: %s\n' "$$1" >&2; exit 64; }
[[ "$${ACCESS_TOKEN:-}" =~ ^[A-Za-z0-9_]+$$ ]] || fail 'ACCESS_TOKEN must be a PAT without whitespace, quotes or variable placeholders'
case "$${ACCESS_TOKEN}" in
changeme|CHANGEME|replace_me|your_token) fail 'Replace the ACCESS_TOKEN placeholder with a GitHub PAT' ;;
esac
[[ "$${RUNNER_NAME:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$$ ]] || fail 'RUNNER_NAME must be 1-64 letters, digits, dots, underscores or hyphens; start with a letter or digit'
[[ "$${LABELS:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*(,[A-Za-z0-9][A-Za-z0-9._-]*)*$$ ]] || fail 'LABELS must be comma-separated labels without spaces (letters, digits, dots, underscores or hyphens)'
[[ "$${RUNNER_GROUP:-}" =~ ^[A-Za-z0-9][A-Za-z0-9\ ._-]{0,99}$$ ]] || fail 'RUNNER_GROUP must be 1-100 letters, digits, spaces, dots, underscores or hyphens; start with a letter or digit'
case "$${RUNNER_SCOPE:-}" in
repo)
[[ "$${REPO_URL:-}" =~ ^https://github\.com/[A-Za-z0-9][A-Za-z0-9-]*/[A-Za-z0-9._-]+$$ ]] || fail 'repo scope requires REPO_URL=https://github.com/OWNER/REPO (no trailing slash or .git suffix)'
[[ "$${REPO_URL}" != *.git ]] || fail 'REPO_URL must not have a .git suffix'
[[ -z "$${ORG_NAME:-}" ]] || fail 'Clear ORG_NAME when RUNNER_SCOPE=repo'
[[ "$${RUNNER_GROUP}" == Default ]] || fail 'Custom RUNNER_GROUP is only supported with RUNNER_SCOPE=org'
;;
org)
[[ "$${ORG_NAME:-}" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}$$ ]] || fail 'org scope requires an ORG_NAME slug (not a URL)'
[[ -z "$${REPO_URL:-}" ]] || fail 'Clear REPO_URL when RUNNER_SCOPE=org'
;;
*) fail 'RUNNER_SCOPE must be exactly repo or org' ;;
esac
# Runtime gate: refuse fallback to distro Node after a runner self-update.
node_binary=/actions-runner/externals/node24/bin/node
[[ -x "$${node_binary}" ]] || fail 'The maintained Node24 runtime is missing; update the image'
[[ "$$(command -v node)" == "$${node_binary}" ]] || fail 'Default node must use the maintained Node24 runtime'
node_version="$$("$${node_binary}" --version)"
[[ "$${node_version}" =~ ^v24\.([0-9]+)\.([0-9]+)$$ ]] || fail 'The maintained Node24 runtime is required'
(( 10#$${BASH_REMATCH[1]} > 22 || (10#$${BASH_REMATCH[1]} == 22 && 10#$${BASH_REMATCH[2]} >= 1) )) || fail 'Node24 is below the security baseline; update the image'
# End runtime gate.
exec /entrypoint.sh ./bin/Runner.Listener run --startuptype service
# Local listener liveness only; does not prove GitHub connectivity/readiness.
healthcheck:
test: ["CMD-SHELL", "pgrep -x 'Runner[.]Listener' > /dev/null"]
interval: 30s
timeout: 5s
retries: 3
start_period: 2m
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title">
<title id="title">Actions runner</title>
<rect width="512" height="512" rx="112" fill="#172033"/>
<path d="M120 170h92m88 172h92M256 170v172" fill="none" stroke="#8ba3c7" stroke-width="24" stroke-linecap="round"/>
<circle cx="120" cy="170" r="28" fill="#6ee7b7"/>
<circle cx="392" cy="342" r="28" fill="#6ee7b7"/>
<rect x="192" y="192" width="128" height="128" rx="32" fill="#6ee7b7"/>
<path d="m240 226 44 30-44 30Z" fill="#172033"/>
</svg>

After

Width:  |  Height:  |  Size: 554 B

@@ -0,0 +1,13 @@
{
"id": "dokploy-actions-runner",
"name": "GitHub Actions Runner",
"version": "2.338.2-ubuntu-noble",
"description": "Self-hosted GitHub Actions runner for repositories or organizations, with independent names, labels and runner groups. Runs without privileged mode or Docker socket access.",
"logo": "logo.svg",
"links": {
"github": "https://github.com/Simplarix/dokploy-actions-runner",
"website": "https://docs.github.com/en/actions/concepts/runners/self-hosted-runners",
"docs": "https://github.com/Simplarix/dokploy-actions-runner#readme"
},
"tags": ["development", "automation", "ci"]
}
@@ -0,0 +1,16 @@
[variables]
runner_name = "dokploy-${uuid}"
[config]
domains = []
mounts = []
# Set a GitHub PAT and repository/organization target before deploying.
env = [
"ACCESS_TOKEN=",
"RUNNER_SCOPE=repo",
"REPO_URL=",
"ORG_NAME=",
"RUNNER_NAME=${runner_name}",
"LABELS=dokploy",
"RUNNER_GROUP=Default",
]
+79
View File
@@ -0,0 +1,79 @@
services:
dossier:
image: ghcr.io/aientech/dossier:v1.9.0
restart: unless-stopped
env_file:
- path: .env
required: false
environment:
DATABASE_URL: "postgresql://dossier:${POSTGRES_PASSWORD:?Keep the generated database password}@dossier-postgres:5432/dossier"
# Optional: an argon2id hash from `hash-password`. When empty, it's derived at
# startup from DOSSIER_ADMIN_PASSWORD (generated by the template).
DOSSIER_PASSWORD: "${DOSSIER_PASSWORD:-}"
DOSSIER_ADMIN_PASSWORD: "${DOSSIER_ADMIN_PASSWORD:-}"
DOSSIER_SECRET: "${DOSSIER_SECRET:?Keep the generated DOSSIER_SECRET}"
DOSSIER_DATA_DIR: /data
DOSSIER_TELEMETRY: "${DOSSIER_TELEMETRY:-off}"
DOSSIER_JOBS_ARBEITSAGENTUR: "${DOSSIER_JOBS_ARBEITSAGENTUR:-off}"
DOSSIER_BACKUP_RETENTION_DAYS: "${DOSSIER_BACKUP_RETENTION_DAYS:-14}"
TZ: "${TZ:-UTC}"
command:
- sh
- -c
- |
if [ -z "$$DOSSIER_PASSWORD" ]; then
[ -n "$$DOSSIER_ADMIN_PASSWORD" ] || { echo "Set DOSSIER_ADMIN_PASSWORD or DOSSIER_PASSWORD" >&2; exit 1; }
DOSSIER_PASSWORD="$$(printf '%s\n' "$$DOSSIER_ADMIN_PASSWORD" | hash-password)"
export DOSSIER_PASSWORD
fi
unset DOSSIER_ADMIN_PASSWORD
exec ./start.sh
expose:
- "3000"
volumes:
- dossier-data:/data
depends_on:
dossier-postgres:
condition: service_healthy
healthcheck:
test:
- CMD
- node
- -e
- "fetch('http://127.0.0.1:3000/api/health').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
# The bundled Chromium prints PDFs in this process.
mem_limit: 1g
stop_grace_period: 30s
extra_hosts:
- "host.docker.internal:host-gateway"
logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
dossier-postgres:
image: postgres:18.6-alpine
restart: unless-stopped
environment:
POSTGRES_USER: dossier
POSTGRES_DB: dossier
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?Keep the generated database password}"
volumes:
- dossier-postgres:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dossier -d dossier"]
start_period: 20s
interval: 5s
timeout: 5s
retries: 10
stop_grace_period: 30s
logging: *logging
volumes:
dossier-data:
dossier-postgres:
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14.08" fill="#1d1c1a"/><path fill="#f4f2ed" transform="matrix(0.02844 0 0 -0.02844 10.214 51.200)" d="M1179.8 691.9Q1179.8 974 1032.5 1107.7Q885.3 1241.3 625.7 1241.3H354.9L338.3 1350H674.8Q936.6 1350 1111 1279.8Q1285.4 1209.6 1372.4 1068.2Q1459.5 926.8 1459.5 714.2Q1459.5 486.8 1357.2 327.4Q1254.8 168 1077.9 84Q900.9 0 678.3 0H338.3L354.9 108.7H646.1Q807.3 108.7 927.1 168.3Q1047 227.8 1113.4 356.5Q1179.8 485.1 1179.8 691.9ZM72.3 0V41L223.4 95.9V1254.2L72.3 1308.9V1350H489.4V0Z"/></svg>

After

Width:  |  Height:  |  Size: 585 B

+55
View File
@@ -0,0 +1,55 @@
# Environment variables
These settings were checked against Dossier **v1.5.0**: [environment validation](https://github.com/AienTech/dossier/blob/v1.5.0/src/lib/env.ts), [example environment](https://github.com/AienTech/dossier/blob/v1.5.0/.env.example), and [production Compose](https://github.com/AienTech/dossier/blob/v1.5.0/compose.production.yml).
Add optional values to Dokploy's Environment tab and redeploy. The app loads that environment through an optional `.env` file. Blank optional values count as unset. Required credentials are checked explicitly by Compose; the app validates their format on startup. App settings saved in the database override AI and job search environment defaults.
## Required and generated values
| Variable | Template value | Purpose |
| --- | --- | --- |
| `DOSSIER_PASSWORD` | Empty; you must set it | Login password **hash** from `docker run --rm -it ghcr.io/aientech/dossier:v1.5.0 hash-password`. Paste the complete `argon2id:...` output, never plaintext or a standard PHC hash. |
| `DOSSIER_SECRET` | Generated random hexadecimal string | At least 32 characters. Signs sessions and encrypts saved provider keys. Preserve across redeployments and backups. |
| `POSTGRES_PASSWORD` | Generated random hexadecimal string | Required by both services. Used in the connection URL; a replacement must be URL-safe. |
| `DATABASE_URL` | Constructed by Compose | `postgresql://dossier:<generated password>@dossier-postgres:5432/dossier`. No manual value is needed. |
`POSTGRES_USER=dossier` and `POSTGRES_DB=dossier` are fixed in Compose and match the app's connection URL. Change both service configurations together if customizing them. `POSTGRES_PORT` from upstream's development example is unused: PostgreSQL is private and has no published port.
## Optional application settings
| Variable | Template / upstream default | Purpose |
| --- | --- | --- |
| `DOSSIER_TELEMETRY` | Template `off`; upstream `on` | `on` or `off`. Controls the daily usage ping, independently of update checks. |
| `DOSSIER_BACKUP_RETENTION_DAYS` | `14` | Whole number, at least 1. Retention of nightly backups. |
| `TZ` | `UTC` | Time zone for the app and its 03:00 backup schedule. See the v1.5.0 dashboard caveat in [instructions.md](instructions.md) before changing it. |
| `DOSSIER_DATA_DIR` | `/data`, fixed by Compose | Mounted persistent directory. If customizing, change the volume target and this value together in Compose. |
## Optional AI defaults
No AI variables are required for startup, login, the profile, or base resume PDFs. AI features require a reachable provider and a suitable model. You can configure the same settings in the app instead.
| Variable | Default when unset | Purpose |
| --- | --- | --- |
| `DOSSIER_AI_PROVIDER` | No provider configured | `local`, `openai` (also compatible APIs), or `anthropic`. |
| `DOSSIER_AI_URL` | Provider-specific | HTTP(S) endpoint. Local: `http://localhost:11434`; OpenAI: `https://api.openai.com/v1`; Anthropic: `https://api.anthropic.com`. For host Ollama, use `http://host.docker.internal:11434`. |
| `DOSSIER_AI_MODEL` | No default for local/OpenAI; `claude-sonnet-5` for Anthropic | Provider model identifier. |
| `DOSSIER_AI_KEY` | Unset | Provider credential, if needed. Keep private. |
| `DOSSIER_AI_TIMEOUT_SECONDS` | `300` local; `120` hosted | Whole number of seconds, at least 5, per call. |
## Optional job search defaults
| Variable | Default when unset | Purpose |
| --- | --- | --- |
| `DOSSIER_JOBS_SOURCE` | `off` | `jsearch`, `adzuna`, `arbeitsagentur`, or `off`. |
| `DOSSIER_JSEARCH_KEY` | Unset | Required only for JSearch. |
| `DOSSIER_JSEARCH_HOST` | `openwebninja` | `openwebninja` or `rapidapi`, matching the key issuer. |
| `DOSSIER_ADZUNA_APP_ID` | Unset | Required only for Adzuna. |
| `DOSSIER_ADZUNA_KEY` | Unset | Required only for Adzuna. |
Arbeitsagentur needs no credential. Provider availability, quotas, and billing are managed by the provider.
## Image and test controls
The published image already sets `NODE_ENV=production`, `NEXT_TELEMETRY_DISABLED=1`, `PORT=3000`, and `HOSTNAME=0.0.0.0`. Leave them unchanged so routing and the health check agree. This template uses the prebuilt production image; frontend build inputs and development database port settings are not additional installation requirements.
`DOSSIER_JOBS_TEST_BASE_URL` and `DOSSIER_UPDATES_TEST_BASE_URL` are upstream end-to-end test hooks that redirect job search and release requests to mock endpoints. Keep them unset in normal deployments. They are intentionally absent from the generated Environment tab.
+45
View File
@@ -0,0 +1,45 @@
# Dossier
Dossier is a single-user job search workspace for your profile, resumes, applications, and cover letters. This template runs Dossier v1.5.0 with PostgreSQL 18 and persistent storage. AI and job search providers are optional.
## Before the first deployment
1. Import the template in Dokploy. Keep the generated `POSTGRES_PASSWORD` and `DOSSIER_SECRET` values.
2. Generate a login password hash on a machine with Docker:
```sh
docker run --rm -it ghcr.io/aientech/dossier:v1.5.0 hash-password
```
Enter your chosen password twice. Copy the printed `argon2id:...` string into `DOSSIER_PASSWORD` in Dokploy's Environment tab. Use Dossier's command: it produces a special dollar-free format, rather than a standard `$argon2id$...` hash. Never put your plaintext password in this variable. The empty template value deliberately prevents deployment until you set the hash.
3. Set your domain in Dokploy and enable HTTPS with a certificate. The domain targets service `dossier`, port `3000`. Dokploy manages routing and networks; neither the app nor PostgreSQL needs a published host port.
4. Deploy, wait for both services to become healthy, and open the domain. Sign in with the password you entered in the hashing command.
Allow at least 1 GB of memory for Dossier, plus memory for PostgreSQL and Dokploy. The app's 1 GB container limit accommodates its bundled Chromium PDF renderer. Larger documents or heavier use may need a higher limit. Run exactly one Dossier instance against this database: backups, the application queue, and the browser process run inside the app.
## Optional integrations
You can save your profile and print a base resume without an AI provider. Configure AI later in **Settings → AI provider**, or add the `DOSSIER_AI_*` defaults listed in [environment.md](environment.md). Settings saved in the app take precedence over environment defaults. Hosted services require their own credentials; no provider key is generated or required by this template.
An Ollama service is not installed automatically. To use Ollama already running on the Docker host, set the local provider URL to `http://host.docker.internal:11434` and choose an installed model. The host gateway mapping is included, but Ollama must listen on an address reachable from the container and the host firewall must permit that connection. `localhost` inside Dossier refers to the Dossier container. Alternatively, add your own Ollama service and persistent model volume, then use its service name as the URL. Model downloads and CPU/GPU memory are additional requirements only for that optional service.
Job search is off until you choose a source in **Settings → Job search**. JSearch needs a key; Adzuna needs an app ID and key; Arbeitsagentur needs no key. None is required to deploy or use the application tracker.
The template sets `DOSSIER_TELEMETRY=off`. Upstream defaults to `on`; enabling it sends an install ID and version once per day the app is used. The release update check is separate and can be disabled in Settings → About.
## Persistence, backups, and upgrades
- `dossier-postgres` stores PostgreSQL data at `/var/lib/postgresql`, the parent directory required by PostgreSQL 18 images.
- `dossier-data` stores `/data`, including nightly backups and downloadable PDF/ZIP artifacts. Backups run at 03:00 in `TZ` and are retained for `DOSSIER_BACKUP_RETENTION_DAYS` days, default 14.
- Back up the database, `/data`, and your deployment secrets to another location. A volume on the same server is not an off-server backup. Dossier's Settings → Your data also provides export/import.
- Preserve `DOSSIER_SECRET`: it signs sessions and encrypts saved provider keys. Replacing it signs everyone out and makes previously encrypted keys unreadable. Changing `DOSSIER_PASSWORD` also invalidates existing sessions.
- Keep the generated database password URL-safe if you replace it: it is interpolated into `DATABASE_URL`. Changing it in Environment alone does not change the password in an initialized database.
- The official image applies database migrations before starting the server. Back up first, review upstream release notes, then update the image and metadata together. Keep PostgreSQL on major version 18 unless you perform a planned database migration.
GET `/api/health` is public and checks database connectivity without returning private data. Behind Dokploy's Traefik proxy, Dossier uses `X-Forwarded-Proto` for secure cookies and `X-Forwarded-For` for login rate limiting. Keep the application reachable through the trusted proxy and use HTTPS for regular access.
### Upstream time zone caveat
In v1.5.0, testing `TZ=Europe/Paris` with a UTC browser produced a React hydration warning on the dashboard's weekly activity chart. That chart formats dates in both the server and browser time zones. The template's default `TZ=UTC` passed the tested UTC browser flow. Review the dashboard after changing `TZ`, especially when your browser uses a different zone; this template does not modify upstream application code.
See [environment.md](environment.md) for required and optional variables and [testing.md](testing.md) for a repeatable verification checklist.
+13
View File
@@ -0,0 +1,13 @@
{
"id": "dossier",
"name": "Dossier",
"version": "v1.9.0",
"description": "Dossier is a personal job search app with a career profile, resume and cover letter drafting, PDF export, and an application tracking board. It supports local and hosted AI providers.",
"logo": "dossier.svg",
"links": {
"github": "https://github.com/AienTech/dossier",
"website": "https://github.com/AienTech/dossier",
"docs": "https://github.com/AienTech/dossier#self-hosting"
},
"tags": ["productivity", "job-search", "ai"]
}
+21
View File
@@ -0,0 +1,21 @@
[variables]
main_domain = "${domain}"
postgres_password = "${hash:48}"
dossier_secret = "${hash:64}"
admin_password = "${password:24}"
[config]
env = [
"POSTGRES_PASSWORD=${postgres_password}",
"DOSSIER_SECRET=${dossier_secret}",
# Sign-in password. Its argon2id hash is computed at startup.
"DOSSIER_ADMIN_PASSWORD=${admin_password}",
"DOSSIER_TELEMETRY=off",
"DOSSIER_BACKUP_RETENTION_DAYS=14",
"TZ=UTC"
]
[[config.domains]]
serviceName = "dossier"
port = 3000
host = "${main_domain}"
+16
View File
@@ -0,0 +1,16 @@
# Testing Dossier
Use disposable data and a fresh Dokploy Compose service. Do not run upstream's database-resetting end-to-end suite against an existing installation.
1. Validate metadata, Compose, and TOML with the repository's build scripts, then build the template catalog.
2. Import the Base64 configuration from the PR preview into Dokploy. Generate the password hash using the pinned image's `hash-password` command. Keep both generated secrets, set the hash, and deploy without AI or job search credentials.
3. Confirm both services become healthy and GET `/api/health` returns HTTP 200 with `status: ok`, `db: ok`, and the pinned version. A blank password hash must fail Compose interpolation; a plaintext value must be rejected by the app without logging the value.
4. Open the generated domain. Check that private pages require login, a wrong password is rejected, and the chosen password works. Save a synthetic profile, reload it, and download a base resume PDF. Verify that the downloaded bytes begin with `%PDF-` and contain the expected profile text.
5. For an optional AI integration test, use a local OpenAI-compatible mock with synthetic responses. Configure it in Settings, create an application from a pasted posting, and check the generated application documents. This verifies request handling without paid API calls; it does not validate a live provider's credentials or model quality.
6. Export data, then exercise import/restore only on the disposable installation. Confirm backups appear under `/data`. Recreate the app container and redeploy through Dokploy; profile, applications, database credentials, and backup files must survive.
7. Enable HTTPS on the Dokploy domain, redeploy, and check login, secure cookies, and PDF generation through Traefik. A local test certificate is sufficient for proxy behavior; public certificate issuance requires a real domain.
8. In the disposable fixture, stop PostgreSQL temporarily: `/api/health` should return 503, then recover after PostgreSQL restarts. Inspect app and database logs for migration, permission, connection, and browser errors.
Record the image versions, Dokploy version, checks, and integration limits in the PR. Capture screenshots using synthetic data only. Remove the test service, its volumes, and test images after collecting evidence.
When testing a non-default server time zone, also inspect the populated dashboard for hydration warnings. Dossier v1.5.0 showed a weekly-chart date mismatch with `TZ=Europe/Paris` and a UTC browser; see [instructions.md](instructions.md).
@@ -0,0 +1,16 @@
services:
emaki-video-starter:
image: san8888/emaki-video-starter:1.0.0
restart: unless-stopped
expose:
- 3000
environment:
- PORT=3000
- EMAKI_API_KEY=${EMAKI_API_KEY}
- ACCESS_PASSWORD=${ACCESS_PASSWORD}
- EMAKI_MODEL=${EMAKI_MODEL}
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 30s
timeout: 10s
retries: 3
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+17
View File
@@ -0,0 +1,17 @@
{
"id": "emaki-video-starter",
"name": "Seedance 2.5 Video Generator",
"version": "1.0.0",
"description": "One-page text-to-video and image-to-video web app on the Seedance 2.5 API from emaki. Set EMAKI_API_KEY; an access password is generated so only people you share it with can spend your credits.",
"logo": "emaki-video-starter.png",
"links": {
"github": "https://github.com/hanshs474/emaki-video-starter",
"website": "https://emaki.ai/seedance-2-5",
"docs": "https://emaki.readthedocs.io/"
},
"tags": [
"ai",
"video-generation",
"text-to-video"
]
}
@@ -0,0 +1,16 @@
[variables]
main_domain = "${domain}"
access_password = "${password:16}"
[config]
env = [
"EMAKI_API_KEY=",
"ACCESS_PASSWORD=${access_password}",
"EMAKI_MODEL=seedance_2_5"
]
mounts = []
[[config.domains]]
serviceName = "emaki-video-starter"
port = 3000
host = "${main_domain}"
+148
View File
@@ -0,0 +1,148 @@
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
falco:
image: falcosecurity/falco:0.45.0
hostname: "${MONITORED_HOST:-dokploy}"
restart: unless-stopped
cap_drop:
- ALL
cap_add:
- SYS_ADMIN
- SYS_RESOURCE
- SYS_PTRACE
security_opt:
- apparmor:unconfined
environment:
HOST_ROOT: /host
command:
- /usr/bin/falco
- -o
- engine.kind=modern_ebpf
- -o
- json_output=true
- -o
- json_include_output_property=true
- -o
- stdout_output.enabled=true
- -o
- http_output.enabled=true
- -o
- http_output.url=http://falcosidekick:2801/
- -o
- priority=notice
# Falco needs the real host paths to inspect host and container activity.
volumes:
- type: bind
source: /var/run/docker.sock
target: /host/var/run/docker.sock
read_only: true
bind:
create_host_path: false
- type: bind
source: /proc
target: /host/proc
read_only: true
bind:
create_host_path: false
- type: bind
source: /etc
target: /host/etc
read_only: true
bind:
create_host_path: false
- type: bind
source: /sys/kernel/tracing
target: /sys/kernel/tracing
read_only: true
bind:
create_host_path: false
depends_on:
falcosidekick:
condition: service_started
mem_limit: 1g
stop_grace_period: 30s
logging: *default-logging
falcosidekick:
image: falcosecurity/falcosidekick:2.35.0
restart: unless-stopped
environment:
TELEGRAM_TOKEN: "${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHATID: "${TELEGRAM_CHAT_ID:-}"
TELEGRAM_MINIMUMPRIORITY: "${TELEGRAM_MINIMUM_PRIORITY:-notice}"
WEBUI_URL: http://falco-ui:2802
depends_on:
falco-ui:
condition: service_started
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 256m
stop_grace_period: 30s
logging: *default-logging
falco-ui:
image: falcosecurity/falcosidekick-ui:2.3.1
restart: unless-stopped
environment:
FALCOSIDEKICK_UI_USER: "${FALCO_UI_USER:-admin}:${FALCO_UI_PASSWORD:?Set FALCO_UI_PASSWORD}"
FALCOSIDEKICK_UI_REDIS_URL: falco-redis:6379
FALCOSIDEKICK_UI_REDIS_PASSWORD: "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}"
FALCOSIDEKICK_UI_TTL: "168h"
expose:
- "2802"
depends_on:
falco-redis:
condition: service_healthy
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 256m
stop_grace_period: 30s
logging: *default-logging
falco-redis:
image: redis/redis-stack-server:7.4.0-v8
restart: unless-stopped
# Falcosidekick UI needs RediSearch and RedisJSON, not RedisGears or RedisAI.
command:
- /opt/redis-stack/bin/redis-server
- --dir
- /data
- --loadmodule
- /opt/redis-stack/lib/redisearch.so
- --loadmodule
- /opt/redis-stack/lib/rejson.so
- --requirepass
- "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}"
- --appendonly
- "yes"
- --appendfsync
- everysec
- --maxmemory
- 512mb
- --maxmemory-policy
- noeviction
environment:
REDISCLI_AUTH: "${FALCO_REDIS_PASSWORD:?Set FALCO_REDIS_PASSWORD}"
volumes:
- falco-events:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
mem_limit: 1g
stop_grace_period: 30s
logging: *default-logging
volumes:
falco-events:
+2
View File
@@ -0,0 +1,2 @@
<!-- Source: https://github.com/cncf/artwork/blob/main/projects/falco/icon/color/falco-icon-color.svg -->
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="48.91 -1.09 472.18 576.68"><defs><style>.cls-2{fill:#00b4c8}</style></defs><path fill="none" d="M196.604 498.09a89.682 89.682 0 0 0-13.023.965 40.633 40.633 0 0 0-11.9 3.536 21.616 21.616 0 0 0-8.521 7.237 20.21 20.21 0 0 0-3.215 11.737 31.384 31.384 0 0 0 1.219 9.165 17.022 17.022 0 0 0 7.46 9.96q8.682 5.31 23.477 5.307 18.004 0 27.333-1.93v-43.083a75.905 75.905 0 0 0-9.323-1.93 86.893 86.893 0 0 0-13.507-.965z"/><path d="M449.796 202.845q-59.153 58.926-118.375 117.786L146.075 505.977l-.455-.454c-10.614 10.65-21.158 21.371-31.393 32.377-5.393 5.8-7.384 13.173-1.166 19.87 6.506 7.012 14.346 6.588 21.53 1.266 5.376-3.982 10.062-8.948 14.822-13.705q150.23-150.18 300.194-300.633l60.89-10.613c-9.937-10.087-51.278-40.626-60.7-31.24z" class="cls-2"/><path d="M478.784 292.437a12.923 12.923 0 0 0-9.082-12.55c-9.699-3.506-16.357 2.168-22.599 8.406Q324.865 410.481 202.66 532.702c-7.21 7.214-14.657 15.812-5.816 25.346 9.758 10.524 18.9 2.988 26.64-4.743q123.57-123.452 247.065-246.983c3.915-3.915 7.825-7.863 8.236-13.885zm-162.923-9.911l17.142-17.141c-1.391-4.326-4.908-8.414-8.611-12.12Q206.824 135.632 89.169 18.086c-4.387-4.384-9.075-8.341-15.87-8.073-5.329.275-9.597 2.553-11.62 7.553-3.576 8.834.995 15.298 6.89 21.195q117.585 117.616 235.184 235.215c3.707 3.706 7.796 7.18 12.108 8.55zm154.131 108.506c7.257-7.343 13.7-16.123 3.918-25.36-8.782-8.293-17.17-2.51-24.072 4.354-31.334 31.157-62.48 62.504-93.819 93.655-10.96 10.893-10.454 21.057.401 31.721 18.385 18.067 36.744 36.174 54.585 54.771 10.004 10.428 20.93 16.95 35.042 14.194 3.259.21 5.737.691 8.15.466 10.303-.957 23.307-.281 23.634-14.478.335-14.644-12.332-14.708-22.943-14.312-10.777.402-18.684-4.032-25.845-11.565-10.991-11.561-21.99-23.19-33.87-33.808-10.513-9.4-9.018-16.292.55-25.497 25.204-24.25 49.677-49.261 74.27-74.14zm-209.337-75.967c3.818 3.802 8.394 7.687 13.347 9.321l17.063-17.063c-2.163-6.188-7.316-10.32-11.784-14.798q-72.062-72.226-144.255-144.32c-9.966-9.96-19.873-19.993-30.15-29.63-6.04-5.667-13.213-7.124-19.652-.742-6.13 6.073-5.69 13.22-.859 20.024a66.122 66.122 0 0 0 7.223 8.303q84.46 84.526 169.067 168.905zm-64.009 20.518c9.1 9.076 18.153 18.214 27.585 26.935a17.673 17.673 0 0 0 7.408 4.233l17.85-17.85c-1.172-4.601-4.226-8.242-7.63-11.65q-54.495-54.594-109.11-109.066a33.321 33.321 0 0 0-10.425-7.263c-5.97-2.449-12.126-2.054-16.872 3.173-4.793 5.279-4.73 11.353-1.011 16.908a96.1 96.1 0 0 0 11.722 14.029q40.113 40.402 80.483 80.551z" class="cls-2"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+51
View File
@@ -0,0 +1,51 @@
# Falco
This template runs Falco with the modern eBPF engine and stores events in Redis Stack for Falcosidekick UI. Telegram alerts through Falcosidekick are optional.
## Host requirements
- Use Docker Compose on a Linux server with a kernel that supports modern eBPF (normally Linux 5.8 or later) and kernel BTF at `/sys/kernel/btf/vmlinux`.
- The Docker socket, `/proc`, `/etc`, and `/sys/kernel/tracing` must exist on the deployment server. Falco mounts these host paths read-only to inspect activity. Unlike application data, these paths must refer to the real host directories.
- If tracefs is mounted at `/sys/kernel/debug/tracing`, change only the tracing bind mount's `source` to that path and keep its `target` as `/sys/kernel/tracing`.
- Falco needs `SYS_ADMIN`, `SYS_RESOURCE`, and `SYS_PTRACE`, with an unconfined AppArmor profile. Rootless Docker and hosts that prohibit these capabilities are not supported by this template.
- The service memory limits total 2.5 GiB. Leave additional memory for Dokploy, the operating system, and other applications.
See [Falco's container deployment guide](https://falco.org/docs/setup/container/) for the supported host configuration.
## Before deploying
Set these values in Dokploy's **Environment** tab:
| Variable | Value |
| --- | --- |
| `MONITORED_HOST` | A hostname label for the monitored server, such as `production-01`. This appears in alerts and is not the UI domain. |
| `TELEGRAM_BOT_TOKEN` | Optional. Leave empty to disable Telegram alerts, or set it together with `TELEGRAM_CHAT_ID` to enable them. |
| `TELEGRAM_CHAT_ID` | Optional. The target chat ID, including its leading minus sign for a group chat. Set it together with `TELEGRAM_BOT_TOKEN`. |
| `TELEGRAM_MINIMUM_PRIORITY` | Defaults to `notice`. Filters Telegram alerts when Telegram is configured. |
| `FALCO_UI_USER` | Defaults to `admin`. |
| `FALCO_UI_PASSWORD` | Generated automatically when you import the template. |
| `FALCO_REDIS_PASSWORD` | Generated automatically when you import the template. |
For deployment without Telegram, leave `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` empty. Falco still detects events and sends them to the dashboard. Only the monitored hostname and the UI and Redis passwords are required.
To enable Telegram alerts, provide both credentials, start a conversation with the bot or add it to the target group, and redeploy.
The generated passwords are independent 64-character hexadecimal strings. To replace one, generate a new value with `openssl rand -hex 32`. Keep the Redis password unchanged across redeployments unless you also update all services that use it.
Falco collects events at `notice` or higher. Changing `TELEGRAM_MINIMUM_PRIORITY` only filters Telegram delivery; it does not change Falco's collection threshold or the events sent to the UI.
## Open the dashboard
Deploy with Dokploy's isolated deployments enabled. Open the domain assigned to `falco-ui` on port `2802` and log in with `FALCO_UI_USER` and `FALCO_UI_PASSWORD`. Enable HTTPS for that domain in Dokploy.
Dokploy manages the stack's network and domain routing. No host ports are published. Redis loads the RediSearch and RedisJSON modules required by the UI, requires authentication, and stores data in the `falco-events` volume, with append-only persistence and a seven-day UI event lifetime.
## Verify alerts
On the monitored server, start an interactive shell in a disposable container:
```bash
docker run --rm -it alpine:3.21 sh
```
Exit the shell after it starts. Check the Falco logs and dashboard for the **Terminal shell in container** event. Its hostname should match `MONITORED_HOST`. If Telegram is configured, check the Telegram chat as well.
+13
View File
@@ -0,0 +1,13 @@
{
"id": "falco",
"name": "Falco",
"version": "0.45.0",
"description": "Monitor Linux host and container activity with Falco's modern eBPF engine. Includes Falcosidekick UI, persistent event storage, and optional Telegram alerts.",
"logo": "falco.svg",
"links": {
"github": "https://github.com/falcosecurity/falco",
"website": "https://falco.org/",
"docs": "https://falco.org/docs/"
},
"tags": ["security", "monitoring"]
}
+23
View File
@@ -0,0 +1,23 @@
[variables]
main_domain = "${domain}"
ui_password = "${hash:64}"
redis_password = "${hash:64}"
[config]
env = [
# Name shown for this host in alerts.
"MONITORED_HOST=dokploy",
# Optional Telegram notifications; leave both credentials empty to disable.
"TELEGRAM_BOT_TOKEN=",
"TELEGRAM_CHAT_ID=",
"TELEGRAM_MINIMUM_PRIORITY=notice",
"FALCO_UI_USER=admin",
"FALCO_UI_PASSWORD=${ui_password}",
"FALCO_REDIS_PASSWORD=${redis_password}"
]
mounts = []
[[config.domains]]
serviceName = "falco-ui"
port = 2802
host = "${main_domain}"
+49
View File
@@ -0,0 +1,49 @@
# Falco template validation
Tested on 2026-10-18 with Dokploy `v0.30.8`, Traefik `v3.6.7`, Linux `7.0.0-34-generic`, Docker Engine `29.9.1`, and Docker Compose `5.6.0`, using the exact image tags in this template. Dokploy and the deployment Docker engine ran in a disposable local test environment.
## Checks performed
- Passed `node build-scripts/generate-meta.js --check`, the repository's Compose and TOML validators, and `pnpm build` in `app/`.
- Downloaded the Compose and TOML files from the PR preview and imported their Base64 payload through Dokploy's `compose.import` API. Repeated the import with the corrected template configuration.
- Confirmed that Dokploy generated distinct 64-character hexadecimal UI and Redis passwords and a domain mapped to `falco-ui:2802`. The template uses `${hash:64}` because Dokploy's helper takes a character count; the template repository's local helper has different length semantics.
- Enabled isolated deployments and deployed through Dokploy's `compose.deploy` API. The deployment finished successfully.
- Passed `docker compose config --quiet` with Telegram credentials absent or populated. Empty values for the required monitored hostname and UI and Redis passwords failed with the expected error.
- Started all four services and confirmed Falco opened the syscall source with the modern BPF probe.
- Requested the generated domain through Traefik and received HTTP 200 from the dashboard and health endpoint. The events API returned HTTP 401 without credentials or with an incorrect password, and HTTP 200 with the generated credentials.
- Logged in through Traefik using Chrome and the generated UI credentials, opened the Events tab, and observed a newly triggered container-shell event. No JavaScript errors occurred after authentication.
- Confirmed Redis returned `NOAUTH` without a password and `PONG` with its configured password. Its module list contained exactly RediSearch and RedisJSON; startup no longer logged errors for the unused RedisGears/RedisAI integration.
- Sent synthetic Notice and Informational events to Falcosidekick. Both appeared in the UI; only Notice reached a local mock Telegram API at the default `notice` threshold.
- Confirmed stored events had a seven-day TTL and remained searchable after stopping and starting the stack through Dokploy. The generated credentials and domain routing continued to work.
- Started a disposable container with a terminal shell. Falco detected the real **Terminal shell in container** event, and it reached both the UI and mock Telegram API with the configured hostname.
- Confirmed all four services stayed running with zero automatic restarts, used Dokploy's isolated network, and published no host ports. Falco's host bind mounts remained read-only.
- Checked service logs for unexpected runtime errors. Incorrect-password checks deliberately produced authentication errors in the UI log.
The Dokploy test added a temporary `TELEGRAM_HOST` override to Falcosidekick to reach a local mock Telegram API. This override is not part of the template. The earlier standalone Compose test used temporary localhost port bindings; the Dokploy deployment published no service ports and was accessed through Traefik. No real Telegram credentials or chat were used.
Delivery to the real Telegram API and public HTTPS certificate issuance have not been verified.
In a fresh browser session, the upstream UI probes `/api/v1/auth` before login and receives HTTP 401, which it logs as an Axios error. It also requests a missing `/img/icons/favicon-32x32.png` and receives HTTP 404. These upstream UI behaviors did not prevent login, dashboard rendering, or event display.
## Optional Telegram checks
After making Telegram optional, repeated the following checks directly with Docker Compose and the same pinned images:
| Telegram credentials | Result |
| --- | --- |
| Both variables unset | All four services start; a real container-shell event reaches the UI; no Telegram output or requests. |
| Both values empty | Telegram stays disabled; events reach the UI without errors. |
| Bot token only | Telegram stays disabled; events reach the UI without errors. |
| Chat ID only | Telegram stays disabled; events reach the UI without errors. |
| Both values set | Telegram is enabled; Notice events reach the mock API and Informational events are filtered from Telegram while remaining visible in the UI. |
Removed both credentials after enabling Telegram and confirmed it disabled cleanly. Stopped and started the stack with Telegram absent, then confirmed the real and synthetic events remained visible, Redis was healthy, and all four services were running with zero automatic restarts. Temporary test resources were removed after validation.
## Repeat the deployment checks in Dokploy
1. Import the template from the PR preview, enable isolated deployments, and fill in the required values described in `instructions.md`.
2. Deploy and confirm that all four containers stay running, Redis is healthy, and the Falco logs show the modern BPF probe opening the syscall source.
3. Open the assigned UI domain and log in with the generated credentials.
4. Follow the container-shell check in `instructions.md`. Confirm the event appears in Falco's logs and the dashboard with the configured hostname. Check the Telegram chat if Telegram is configured.
5. Stop and start the Compose service without deleting its volume. Confirm the event remains visible and the generated credentials still work.
6. Optionally configure both Telegram credentials, redeploy, and repeat the event check to verify Telegram delivery.
+9
View File
@@ -0,0 +1,9 @@
*
!Dockerfile
!harbor/
!harbor/*.py
!images.lock.json
!vendor/
!vendor/harbor.yml.tmpl
!tests/
!tests/*.py
+1
View File
@@ -0,0 +1 @@
* text eol=lf
+13
View File
@@ -0,0 +1,13 @@
# syntax=docker/dockerfile:1
ARG PREPARE_IMAGE=goharbor/prepare:v2.15.4@sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317
FROM ${PREPARE_IMAGE} AS runtime
USER root
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY harbor/common.py harbor/initialize.py /app/
COPY images.lock.json /app/images.lock.json
COPY vendor/harbor.yml.tmpl /app/harbor.yml.tmpl
ENTRYPOINT ["python3", "/app/initialize.py"]
FROM runtime AS test
COPY tests /tests
RUN python3 -m unittest discover -s /tests -v
+49
View File
@@ -0,0 +1,49 @@
# Harbor validation record
Date: 2026-10-20. Harbor: 2.15.4. Docker server: Linux, Engine 29.6.2.
Init source: `fc224d78800ffad4cd2a21ecd08f24c6fa122b82`.
## Passed
- All 573 template metadata entries validated with the upstream metadata checker.
- Upstream Compose and template validators accepted the Harbor blueprint.
- YAML and TOML parsing, generated Compose consistency, vendor checksum,
immutable Git source and local source/commit consistency checks passed.
- The Base64 import payload matches the catalogue's `{ compose, config }` format.
- Docker built the init image and ran all 14 Python unit tests successfully.
- After publication, Docker fetched the pinned source commit from the public
GitHub repository and built the runtime init image successfully.
- A fresh isolated Compose deployment completed init and reached healthy state
for all workloads, including Trivy.
- Harbor health, admin login, registry authentication challenge and public token
URL were verified using the deployment's domain as the HTTP Host header.
- A private Harbor project and signing/encryption keys survived restart and
forced container recreation.
- Explicit reconfiguration refused running workloads; changing worker count
with workloads stopped preserved the existing keys and private project.
Integration tests use a unique random `harbor-ci-*` instance and remove only
their own containers, volumes and default network.
## Remaining deployment acceptance checks
- Actual Dokploy Base64 import has not been exercised. Local integration builds
the same pinned support files from disk; remote Git building was verified
separately after publication.
- Public DNS, Let's Encrypt certificate issuance and Dokploy's generated
network/domain configuration require verification on the target server.
- End-to-end Docker login, push/pull over trusted HTTPS and a complete Trivy
vulnerability scan require the deployed domain and vulnerability DB download.
- No production data migration or Harbor version upgrade was attempted.
See [instructions.md](instructions.md) for deployment acceptance steps.
## Reproduce local checks
```sh
node build-scripts/generate-meta.js --check
node blueprints/harbor/tools/check.mjs
docker build --target test -t harbor-template-init-test:local blueprints/harbor
node blueprints/harbor/tools/integration.mjs
```
Install the existing `app` dependencies with pnpm before running `check.mjs`.
+838
View File
@@ -0,0 +1,838 @@
# Generated by tools/render-compose.mjs from images.lock.json.
"services":
"harbor-init":
"build":
"context": "https://github.com/XnCN/harbor-dokploy-template.git#fc224d78800ffad4cd2a21ecd08f24c6fa122b82:blueprints/harbor"
"dockerfile": "Dockerfile"
"target": "runtime"
"args":
"PREPARE_IMAGE": "goharbor/prepare:v2.15.4@sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317"
"platform": "linux/amd64"
"restart": "no"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "init"
"traefik.enable": "false"
"environment":
"HARBOR_INSTANCE_ID": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"HARBOR_HOSTNAME": "${HARBOR_HOSTNAME:?Set HARBOR_HOSTNAME in Dokploy Environment}"
"HARBOR_ADMIN_PASSWORD": "${HARBOR_ADMIN_PASSWORD:?Set HARBOR_ADMIN_PASSWORD in Dokploy Environment}"
"HARBOR_DB_PASSWORD": "${HARBOR_DB_PASSWORD:?Set HARBOR_DB_PASSWORD in Dokploy Environment}"
"DB_MAX_IDLE_CONNS": "${DB_MAX_IDLE_CONNS:-10}"
"DB_MAX_OPEN_CONNS": "${DB_MAX_OPEN_CONNS:-100}"
"POSTGRES_MAX_CONNECTIONS": "${POSTGRES_MAX_CONNECTIONS:-256}"
"JOB_MAX_WORKERS": "${JOB_MAX_WORKERS:-5}"
"HARBOR_LOG_LEVEL": "${HARBOR_LOG_LEVEL:-info}"
"CACHE_ENABLED": "${CACHE_ENABLED:-true}"
"ALLOW_PRIVATE_NETWORK_ACCESS": "${ALLOW_PRIVATE_NETWORK_ACCESS:-false}"
"TRIVY_GITHUB_TOKEN": "${TRIVY_GITHUB_TOKEN:-}"
"TRIVY_DB_REPOSITORY": "${TRIVY_DB_REPOSITORY:-ghcr.io/aquasecurity/trivy-db}"
"TRIVY_JAVA_DB_REPOSITORY": "${TRIVY_JAVA_DB_REPOSITORY:-ghcr.io/aquasecurity/trivy-java-db}"
"HARBOR_HTTP_PROXY": "${HARBOR_HTTP_PROXY:-}"
"HARBOR_HTTPS_PROXY": "${HARBOR_HTTPS_PROXY:-}"
"HARBOR_NO_PROXY": "${HARBOR_NO_PROXY:-}"
"PROJECT_QUOTA_GIB": "${PROJECT_QUOTA_GIB:-20}"
"volumes":
-
"type": "volume"
"source": "config"
"target": "/config"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "data"
"target": "/data"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "database"
"target": "/data/database"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "registry"
"target": "/data/registry"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "redis"
"target": "/data/redis"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "jobs"
"target": "/data/job_logs"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "trivy-cache"
"target": "/data/trivy-adapter/trivy"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "trivy-reports"
"target": "/data/trivy-adapter/reports"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "control"
"target": "/control"
"read_only": false
"volume":
"nocopy": true
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "DAC_OVERRIDE"
- "FOWNER"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${INIT_MEMORY_LIMIT:-768m}"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"core":
"image": "goharbor/harbor-core:v2.15.4@sha256:7d2ba5304a729887cdec7cb222a82b500278423de230ae5150c386a6c332b112"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/etc/core/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/etc/core"
"read_only": true
"volume":
"nocopy": true
"subpath": "core"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "data"
"target": "/data"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"postgresql":
"condition": "service_healthy"
"redis":
"condition": "service_healthy"
"registry":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${CORE_MEMORY_LIMIT:-1g}"
"cpus": "${CORE_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"postgresql":
"image": "goharbor/harbor-db:v2.15.4@sha256:a32d51e636d76303e086a7c5251a2c8415c6f165b2f87bafa914efd866c5403f"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/run/harbor-config/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/run/harbor-config"
"read_only": true
"volume":
"nocopy": true
"subpath": "db"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "database"
"target": "/var/lib/postgresql/data"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
- "DAC_OVERRIDE"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${POSTGRESQL_MEMORY_LIMIT:-1536m}"
"cpus": "${POSTGRESQL_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "120s"
"test":
- "CMD"
- "/bin/sh"
- "/run/harbor-config/health.sh"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"shm_size": "256m"
"environment":
"POSTGRES_MAX_CONNECTIONS": "${POSTGRES_MAX_CONNECTIONS:-256}"
"redis":
"image": "goharbor/valkey-photon:v2.15.4@sha256:bb022e8f236f56f53cee7bb1f609595e497da67d829048e0af2284f97e6eb54f"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/run/harbor-config/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/run/harbor-config"
"read_only": true
"volume":
"nocopy": true
"subpath": "redis"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "redis"
"target": "/var/lib/redis"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${REDIS_MEMORY_LIMIT:-256m}"
"cpus": "${REDIS_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"registry":
"image": "goharbor/registry-photon:v2.15.4@sha256:dc0cb388a6445c69c8a0c406679290d2991c9698833d9e6e86a23bdfaf2f83c3"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/etc/registry/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/etc/registry"
"read_only": true
"volume":
"nocopy": true
"subpath": "registry"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "registry"
"target": "/storage"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"redis":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${REGISTRY_MEMORY_LIMIT:-768m}"
"cpus": "${REGISTRY_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"registryctl":
"image": "goharbor/harbor-registryctl:v2.15.4@sha256:30bd1ace4e3bb390d75b84673f98ddb4a69fcc6d116932ea764391df01943fb7"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/etc/registryctl/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/etc/registryctl"
"read_only": true
"volume":
"nocopy": true
"subpath": "registryctl"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "registry"
"target": "/storage"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/etc/registry"
"read_only": true
"volume":
"nocopy": true
"subpath": "registry"
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"registry":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${REGISTRYCTL_MEMORY_LIMIT:-128m}"
"cpus": "${REGISTRYCTL_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"jobservice":
"image": "goharbor/harbor-jobservice:v2.15.4@sha256:f143578ef30e2be5c48873298257ed0fca31545025958c534e536a06765d2b82"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/etc/jobservice/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/etc/jobservice"
"read_only": true
"volume":
"nocopy": true
"subpath": "jobservice"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "jobs"
"target": "/var/log/jobs"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"core":
"condition": "service_healthy"
"redis":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${JOBSERVICE_MEMORY_LIMIT:-512m}"
"cpus": "${JOBSERVICE_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"portal":
"image": "goharbor/harbor-portal:v2.15.4@sha256:01edf478505b21dba23a83c095f6ecac0c1e705690093edbab41e9a4d6754a75"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/run/harbor-config/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/run/harbor-config"
"read_only": true
"volume":
"nocopy": true
"subpath": "portal"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${PORTAL_MEMORY_LIMIT:-128m}"
"cpus": "${PORTAL_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"proxy":
"image": "goharbor/nginx-photon:v2.15.4@sha256:7eff0287d5ad0f35754e162c8481ced04bbde107153ce586cefb15ee74fa1311"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "true"
"entrypoint":
- "/bin/sh"
- "/etc/nginx/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/etc/nginx"
"read_only": true
"volume":
"nocopy": true
"subpath": "nginx"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"core":
"condition": "service_healthy"
"portal":
"condition": "service_healthy"
"registry":
"condition": "service_healthy"
"exporter":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${PROXY_MEMORY_LIMIT:-256m}"
"cpus": "${PROXY_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"expose":
- "8080"
"trivy-adapter":
"image": "goharbor/trivy-adapter-photon:v2.15.4@sha256:813ca81b4a4ea0f44f5fa81cca2645eeb58d4b1f6c66b428df9d506174bb71d9"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/run/harbor-config/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/run/harbor-config"
"read_only": true
"volume":
"nocopy": true
"subpath": "trivy-adapter"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
-
"type": "volume"
"source": "trivy-cache"
"target": "/home/scanner/.cache/trivy"
"read_only": false
"volume":
"nocopy": true
-
"type": "volume"
"source": "trivy-reports"
"target": "/home/scanner/.cache/reports"
"read_only": false
"volume":
"nocopy": true
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"redis":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${TRIVY_ADAPTER_MEMORY_LIMIT:-1536m}"
"cpus": "${TRIVY_ADAPTER_CPU_LIMIT:-2.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"exporter":
"image": "goharbor/harbor-exporter:v2.15.4@sha256:9ffede33dc519b4be035596a72f00f354754a22a3769077731f81ee7d6852f4f"
"platform": "linux/amd64"
"restart": "unless-stopped"
"labels":
"io.harbor-dokploy.instance": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}"
"io.harbor-dokploy.role": "workload"
"traefik.enable": "false"
"entrypoint":
- "/bin/sh"
- "/run/harbor-config/launch.sh"
"command": []
"volumes":
-
"type": "volume"
"source": "config"
"target": "/run/harbor-config"
"read_only": true
"volume":
"nocopy": true
"subpath": "exporter"
-
"type": "volume"
"source": "control"
"target": "/run/harbor-control"
"read_only": true
"volume":
"nocopy": true
-
"type": "volume"
"source": "config"
"target": "/harbor_cust_cert"
"read_only": true
"volume":
"nocopy": true
"subpath": "shared/trust-certificates"
"depends_on":
"harbor-init":
"condition": "service_completed_successfully"
"core":
"condition": "service_healthy"
"postgresql":
"condition": "service_healthy"
"redis":
"condition": "service_healthy"
"cap_drop":
- "ALL"
"cap_add":
- "CHOWN"
- "SETGID"
- "SETUID"
"security_opt":
- "no-new-privileges:true"
"mem_limit": "${EXPORTER_MEMORY_LIMIT:-128m}"
"cpus": "${EXPORTER_CPU_LIMIT:-1.0}"
"stop_grace_period": "120s"
"healthcheck":
"interval": "15s"
"timeout": "10s"
"retries": 10
"start_period": "60s"
"test":
- "CMD-SHELL"
- "curl --fail -s http://localhost:8080/metrics >/dev/null"
"logging":
"driver": "local"
"options":
"max-size": "${DOCKER_LOG_MAX_SIZE:-20m}"
"max-file": "${DOCKER_LOG_MAX_FILES:-5}"
"volumes":
"data":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-data"
"database":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-database"
"registry":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-registry"
"redis":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-redis"
"jobs":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-jobs"
"config":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-config"
"trivy-cache":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-trivy-cache"
"trivy-reports":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-trivy-reports"
"control":
"name": "${HARBOR_INSTANCE_ID:?Set HARBOR_INSTANCE_ID in Dokploy Environment}-control"
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

+77
View File
@@ -0,0 +1,77 @@
"""Shared validation and persistent state. No Docker or network side effects on import."""
import contextlib
import fcntl
import json
import os
from pathlib import Path
import re
import tempfile
class ConfigError(ValueError):
pass
def boolean(env, name, default=False):
value = env.get(name, str(default)).lower()
if value not in ('true', 'false'):
raise ConfigError(f'{name} must be true or false')
return value == 'true'
def text(env, name, default='', required=False):
value = env.get(name, default)
if any(c in value for c in ('\n', '\r', '\x00')):
raise ConfigError(f'{name} must be a single line')
if required and not value:
raise ConfigError(f'{name} is required')
return value
def integer(env, name, default, minimum=1, maximum=100000):
try:
value = int(env.get(name, str(default)))
except ValueError:
raise ConfigError(f'{name} must be an integer') from None
if not minimum <= value <= maximum:
raise ConfigError(f'{name} must be between {minimum} and {maximum}')
return value
def settings(env):
instance = text(env, 'HARBOR_INSTANCE_ID', required=True)
if not re.fullmatch(r'[a-z][a-z0-9-]{2,47}', instance):
raise ConfigError('HARBOR_INSTANCE_ID: use 3-48 lowercase letters, digits or hyphens')
host = text(env, 'HARBOR_HOSTNAME', required=True)
if (len(host) > 253 or '.' not in host or
not all(re.fullmatch(r'[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', p) for p in host.split('.')) or
re.fullmatch(r'[0-9.]+', host) or host in ('harbor.example.com', 'reg.mydomain.com')):
raise ConfigError('HARBOR_HOSTNAME must be your lowercase FQDN, without scheme, port or path')
return {'instance': instance, 'external_url': f'https://{host}'}
def read_json(path):
return json.loads(Path(path).read_text())
def atomic_json(path, value, mode=0o600):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix='.' + path.name, dir=path.parent)
try:
with os.fdopen(fd, 'w') as stream:
os.fchmod(stream.fileno(), mode)
json.dump(value, stream, sort_keys=True, indent=2)
stream.write('\n')
stream.flush()
os.fsync(stream.fileno())
os.replace(tmp, path)
finally:
Path(tmp).unlink(missing_ok=True)
@contextlib.contextmanager
def operation_lock(directory):
directory = Path(directory)
directory.mkdir(parents=True, exist_ok=True)
with (directory / 'operation.lock').open('a') as stream:
try:
fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise ConfigError('Another initialization is running') from None
try:
yield None
finally:
fcntl.flock(stream, fcntl.LOCK_UN)
+237
View File
@@ -0,0 +1,237 @@
"""Prepare Harbor with upstream tooling; only this container writes runtime configs."""
import hashlib
import json
import os
from pathlib import Path
import platform
import re
import shlex
import socket
import subprocess
import sys
import tempfile
from common import ConfigError, atomic_json, boolean, integer, operation_lock, read_json, settings, text
APP = Path(__file__).resolve().parent
CONTROL = Path('/control')
DATA = Path('/data')
CONFIG = Path('/config')
# Services consume only their own generated config directory.
SERVICES = {
'core': ('core', '/etc/core', 'harbor-core', 10000),
'postgresql': ('db', '/run/harbor-config', 'harbor-db', 999),
'redis': ('redis', '/run/harbor-config', 'valkey-photon', 999),
'registry': ('registry', '/etc/registry', 'registry-photon', 10000),
'registryctl': ('registryctl', '/etc/registryctl', 'harbor-registryctl', 10000),
'jobservice': ('jobservice', '/etc/jobservice', 'harbor-jobservice', 10000),
'portal': ('portal', '/run/harbor-config', 'harbor-portal', 10000),
'proxy': ('nginx', '/etc/nginx', 'nginx-photon', 10000),
'trivy-adapter': ('trivy-adapter', '/run/harbor-config', 'trivy-adapter-photon', 10000),
'exporter': ('exporter', '/run/harbor-config', 'harbor-exporter', 10000),
}
def password(env, name):
value = text(env, name, required=True)
if len(value) < 20 or value in ('Harbor12345', 'root123'):
raise ConfigError(f'{name} must contain at least 20 characters')
return value
def build_config(env, template):
# JSON serialization below is also valid YAML. Never interpolate secrets into YAML text.
conf = json.loads(json.dumps(template))
conf['hostname'] = text(env, 'HARBOR_HOSTNAME', required=True)
conf['external_url'] = 'https://' + conf['hostname']
conf.pop('https', None)
conf['http'] = {'port': 80}
conf['internal_tls'] = {'enabled': False}
conf['data_volume'] = '/data'
conf['harbor_admin_password'] = password(env, 'HARBOR_ADMIN_PASSWORD')
conf['database']['password'] = password(env, 'HARBOR_DB_PASSWORD')
conf['database']['max_idle_conns'] = integer(env, 'DB_MAX_IDLE_CONNS', 10, maximum=100)
conf['database']['max_open_conns'] = integer(env, 'DB_MAX_OPEN_CONNS', 100, maximum=500)
if conf['database']['max_idle_conns'] > conf['database']['max_open_conns']:
raise ConfigError('DB_MAX_IDLE_CONNS must not exceed DB_MAX_OPEN_CONNS')
db_limit = integer(env, 'POSTGRES_MAX_CONNECTIONS', 256, minimum=20, maximum=10000)
if conf['database']['max_open_conns'] * 2 + 10 > db_limit:
raise ConfigError('POSTGRES_MAX_CONNECTIONS must cover core/exporter pools (2 * DB_MAX_OPEN_CONNS + 10)')
conf['x-dokploy-database-connections'] = db_limit
conf['jobservice']['max_job_workers'] = integer(env, 'JOB_MAX_WORKERS', 5, maximum=100)
level = text(env, 'HARBOR_LOG_LEVEL', 'info')
if level not in ('debug', 'info', 'warning', 'error', 'fatal'):
raise ConfigError('Invalid HARBOR_LOG_LEVEL')
conf['log']['level'] = level
conf['log']['local']['location'] = '/data/log'
conf['metric'] = {'enabled': True, 'port': 9090, 'path': '/metrics'}
conf['cache'] = {'enabled': boolean(env, 'CACHE_ENABLED', True), 'expire_hours': 24}
conf['network'] = {'allow_private_network_access': boolean(env, 'ALLOW_PRIVATE_NETWORK_ACCESS', False)}
conf['content_trust'] = {'legacy_signer_pull_enabled': False}
conf['x-dokploy-security'] = {'auth_mode': 'db_auth', 'self_registration': False,
'project_creation_restriction': 'adminonly',
'storage_per_project': integer(env, 'PROJECT_QUOTA_GIB', 20, maximum=1048576) * 1024**3}
conf['trivy'].update({
'insecure': False, 'ignore_unfixed': False, 'skip_update': False,
'skip_java_db_update': False, 'offline_scan': False,
'github_token': text(env, 'TRIVY_GITHUB_TOKEN'),
'db_repository': text(env, 'TRIVY_DB_REPOSITORY', 'ghcr.io/aquasecurity/trivy-db'),
'java_db_repository': text(env, 'TRIVY_JAVA_DB_REPOSITORY', 'ghcr.io/aquasecurity/trivy-java-db'),
})
conf['proxy'] = {
'http_proxy': text(env, 'HARBOR_HTTP_PROXY'), 'https_proxy': text(env, 'HARBOR_HTTPS_PROXY'),
'no_proxy': text(env, 'HARBOR_NO_PROXY'), 'components': ['core', 'jobservice', 'trivy'],
}
return conf
def parse_literal_env(value):
"""Upstream emits raw KEY=value, not shell syntax or Compose interpolation."""
result = {}
for line in value.splitlines():
if not line.strip() or line.lstrip().startswith('#'):
continue
key, sep, item = line.partition('=')
if not sep or not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', key):
raise ConfigError('Invalid environment key generated by upstream prepare')
if '\x00' in item:
raise ConfigError('NUL in generated environment')
result[key] = item
return result
def launcher(environment, command):
if not command:
raise ConfigError('Missing upstream startup command')
lines = ['#!/bin/sh', 'set -eu',
'if [ ! -f /run/harbor-control/ready ]; then',
' echo "Harbor initialization incomplete" >&2; exit 75', 'fi']
for key, value in environment.items():
if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', key):
raise ConfigError('Invalid environment key')
lines.append('export ' + key + '=' + shlex.quote(value))
lines.append('exec ' + ' '.join(shlex.quote(part) for part in command) + ' "$@"')
return '\n'.join(lines) + '\n'
def owned_file(path, content, uid=10000, mode=0o600):
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
os.chown(path, uid, uid)
path.chmod(mode)
def finalize(lock, conf):
# Match upstream's individual file mounts using directory subpaths instead.
for src, dst in (
(DATA / 'secret/core/private_key.pem', CONFIG / 'core/private_key.pem'),
(DATA / 'secret/keys/secretkey', CONFIG / 'core/key'),
(DATA / 'secret/registry/root.crt', CONFIG / 'registry/root.crt'),
):
owned_file(dst, src.read_text())
for service, (folder, target, image, uid) in SERVICES.items():
directory = CONFIG / folder
directory.mkdir(parents=True, exist_ok=True)
env_path = directory / 'env'
environment = parse_literal_env(env_path.read_text()) if env_path.exists() else {}
if service == 'core':
environment['CONFIG_OVERWRITE_JSON'] = json.dumps(conf['x-dokploy-security'])
if service == 'postgresql':
owned_file(directory / 'health.sh', launcher(environment, ['/docker-healthcheck.sh']), uid, 0o500)
image_conf = lock['images'][image]
command = image_conf['entrypoint'] + image_conf['command']
owned_file(directory / 'launch.sh', launcher(environment, command), uid, 0o500)
# All files in one subdirectory are consumed by the same service UID.
for item in [directory, *directory.rglob('*')]:
if item.is_symlink():
raise ConfigError('Unexpected symlink in generated configuration')
os.chown(item, uid, uid)
item.chmod(0o700 if item.is_dir() else (0o500 if item.name in ('launch.sh', 'health.sh') else 0o600))
shared = CONFIG / 'shared/trust-certificates'
shared.mkdir(parents=True, exist_ok=True)
shared.chmod(0o755)
# Portal expects a single config file at this fixed path.
# Compose mounts the generated portal directory under /run/harbor-config.
portal = CONFIG / 'portal/launch.sh'
original = lock['images']['harbor-portal']
owned_file(portal, launcher({}, original['entrypoint'] + ['nginx', '-c', '/run/harbor-config/nginx.conf', '-g', 'daemon off;']), 10000, 0o500)
def fingerprint(conf, lock):
content = json.dumps({'configuration': conf, 'images': lock}, sort_keys=True).encode()
for file in ('initialize.py', 'common.py'):
content += (APP / file).read_bytes()
return hashlib.sha256(content).hexdigest()
def require_stopped_services():
# An explicit reconfiguration must never rewrite configs of running workloads.
for host, port in [('core', 8080), ('postgresql', 5432), ('redis', 6379),
('registry', 5000), ('registryctl', 8080), ('jobservice', 8080),
('portal', 8080), ('proxy', 8080), ('trivy-adapter', 8080), ('exporter', 8080)]:
try:
connection = socket.create_connection((host, port), timeout=2)
except OSError:
continue
connection.close()
raise ConfigError('Stop all Harbor services before --reconfigure; reachable service: ' + host)
def check_persistent_state(old, runtime, digest, generated_complete, database_exists, secrets_complete, reconfigure):
if old and old['instance'] != runtime['instance']:
raise ConfigError('HARBOR_INSTANCE_ID must remain unchanged for existing volumes')
if (old or database_exists) and not secrets_complete:
raise ConfigError('Persistent encryption/signing keys are missing; restore the original data instead of regenerating keys')
if old and (old['fingerprint'] != digest or not generated_complete) and not reconfigure:
raise ConfigError('Configuration changed. Stop Harbor, then run harbor-init --reconfigure (see instructions.md)')
def main(reconfigure=False):
env = dict(os.environ)
runtime = settings(env)
if platform.machine() not in ('x86_64', 'AMD64'):
raise ConfigError('The locked Harbor distribution requires a Linux amd64 host')
lock = read_json(APP / 'images.lock.json')
runtime['harbor_version'] = lock['harbor_version']
CONTROL.mkdir(parents=True, exist_ok=True)
CONTROL.chmod(0o755)
with operation_lock(CONTROL):
if reconfigure:
require_stopped_services()
import yaml
conf = build_config(env, yaml.safe_load((APP / 'harbor.yml.tmpl').read_text()))
digest = fingerprint(conf, lock)
state_file = DATA / 'render-state.json'
old = read_json(state_file) if state_file.exists() else None
required_secrets = [DATA / 'secret/keys/secretkey', DATA / 'secret/core/private_key.pem', DATA / 'secret/registry/root.crt']
database_exists = any((DATA / 'database').iterdir())
generated_complete = all((CONFIG / folder / 'launch.sh').is_file() for folder, _, _, _ in SERVICES.values())
check_persistent_state(old, runtime, digest, generated_complete, database_exists,
all(item.is_file() for item in required_secrets), reconfigure)
if old and old['fingerprint'] == digest and generated_complete:
(CONTROL / 'ready').touch(mode=0o644)
print('Configuration unchanged; persistent secrets preserved')
return
(CONTROL / 'ready').unlink(missing_ok=True)
with tempfile.TemporaryDirectory() as temp:
config_input = Path(temp) / 'harbor.yml'
config_input.write_text(json.dumps(conf, indent=2))
config_input.chmod(0o600)
Path('/compose_location').mkdir(parents=True, exist_ok=True)
# Prepare logs include paths only. Capture errors to avoid dumping secret-bearing configs.
result = subprocess.run(['python3', '/usr/src/app/main.py', 'prepare', '--with-trivy', '--conf', str(config_input)],
cwd='/usr/src/app', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode:
error_log = CONTROL / 'prepare-error.log'
error_log.write_bytes(result.stdout)
error_log.chmod(0o600)
raise ConfigError('Upstream prepare failed; configuration remains unready. Private details: control volume prepare-error.log')
(CONTROL / 'prepare-error.log').unlink(missing_ok=True)
finalize(lock, conf)
atomic_json(state_file, {'fingerprint': digest, 'harbor_version': lock['harbor_version'], 'instance': runtime['instance']})
(CONTROL / 'ready').touch(mode=0o644)
print('Harbor configuration prepared')
if __name__ == '__main__':
try:
if sys.argv[1:] not in ([], ['--reconfigure']):
raise ConfigError('Usage: initialize.py [--reconfigure]')
main(reconfigure=sys.argv[1:] == ['--reconfigure'])
except ConfigError as error:
print(str(error), file=sys.stderr)
sys.exit(1)
+126
View File
@@ -0,0 +1,126 @@
{
"harbor_version": "v2.15.4",
"platform": "linux/amd64",
"verified_at": "2026-10-20",
"images": {
"prepare": {
"repository": "goharbor/prepare",
"tag": "v2.15.4",
"digest": "sha256:79b5614d0d02b5d2c6c9b7b9755f5caaea882797353bfcb6421f77e6d69cc317",
"entrypoint": [
"python3",
"main.py"
],
"command": [],
"user": "root"
},
"harbor-core": {
"repository": "goharbor/harbor-core",
"tag": "v2.15.4",
"digest": "sha256:7d2ba5304a729887cdec7cb222a82b500278423de230ae5150c386a6c332b112",
"entrypoint": [
"/harbor/entrypoint.sh"
],
"command": [],
"user": "harbor"
},
"harbor-db": {
"repository": "goharbor/harbor-db",
"tag": "v2.15.4",
"digest": "sha256:a32d51e636d76303e086a7c5251a2c8415c6f165b2f87bafa914efd866c5403f",
"entrypoint": [
"/docker-entrypoint.sh",
"15",
"18"
],
"command": [],
"user": "postgres"
},
"valkey-photon": {
"repository": "goharbor/valkey-photon",
"tag": "v2.15.4",
"digest": "sha256:bb022e8f236f56f53cee7bb1f609595e497da67d829048e0af2284f97e6eb54f",
"entrypoint": [],
"command": [
"valkey-server",
"/etc/redis.conf"
],
"user": "valkey"
},
"registry-photon": {
"repository": "goharbor/registry-photon",
"tag": "v2.15.4",
"digest": "sha256:dc0cb388a6445c69c8a0c406679290d2991c9698833d9e6e86a23bdfaf2f83c3",
"entrypoint": [
"/home/harbor/entrypoint.sh"
],
"command": [],
"user": "harbor"
},
"harbor-registryctl": {
"repository": "goharbor/harbor-registryctl",
"tag": "v2.15.4",
"digest": "sha256:30bd1ace4e3bb390d75b84673f98ddb4a69fcc6d116932ea764391df01943fb7",
"entrypoint": [
"/home/harbor/start.sh"
],
"command": [],
"user": "harbor"
},
"harbor-jobservice": {
"repository": "goharbor/harbor-jobservice",
"tag": "v2.15.4",
"digest": "sha256:f143578ef30e2be5c48873298257ed0fca31545025958c534e536a06765d2b82",
"entrypoint": [
"/harbor/entrypoint.sh"
],
"command": [],
"user": "harbor"
},
"harbor-portal": {
"repository": "goharbor/harbor-portal",
"tag": "v2.15.4",
"digest": "sha256:01edf478505b21dba23a83c095f6ecac0c1e705690093edbab41e9a4d6754a75",
"entrypoint": [],
"command": [
"nginx",
"-g",
"daemon off;"
],
"user": "nginx"
},
"nginx-photon": {
"repository": "goharbor/nginx-photon",
"tag": "v2.15.4",
"digest": "sha256:7eff0287d5ad0f35754e162c8481ced04bbde107153ce586cefb15ee74fa1311",
"entrypoint": [],
"command": [
"nginx",
"-g",
"daemon off;"
],
"user": "nginx"
},
"trivy-adapter-photon": {
"repository": "goharbor/trivy-adapter-photon",
"tag": "v2.15.4",
"digest": "sha256:813ca81b4a4ea0f44f5fa81cca2645eeb58d4b1f6c66b428df9d506174bb71d9",
"entrypoint": [
"/home/scanner/entrypoint.sh"
],
"command": [],
"user": "scanner"
},
"harbor-exporter": {
"repository": "goharbor/harbor-exporter",
"tag": "v2.15.4",
"digest": "sha256:9ffede33dc519b4be035596a72f00f354754a22a3769077731f81ee7d6852f4f",
"entrypoint": [
"/harbor/entrypoint.sh"
],
"command": [],
"user": "harbor"
}
},
"template_sha256": "0d3a9098005b202c28e5618c0082de271c0e4f726c27b6c475fc45e2a07c560a"
}
+87
View File
@@ -0,0 +1,87 @@
# Harbor 2.15.4 with Trivy
This template installs Harbor on one Linux amd64 Docker host. Start with 4 vCPU,
8 GB RAM and 160 GB SSD for Harbor, plus resources for Dokploy and other apps.
Docker Engine 27+ and Compose 2.35+ are required for named-volume subpaths.
Use Docker Compose mode, not Docker Stack/Swarm mode.
## Before the first deployment
1. Import Harbor from the template catalogue or paste its Base64 configuration
into **Compose → Advanced → Import**.
2. Keep the generated `HARBOR_INSTANCE_ID` and both generated passwords.
The instance ID is the permanent volume prefix; never reuse it for another
deployment. Re-importing generates new credentials and a new volume prefix.
3. Set the domain to a dedicated FQDN pointing to the Dokploy host. Ensure
`HARBOR_HOSTNAME` matches the domain exactly, without a scheme, port or path.
Change both before the first deployment if replacing the generated domain.
4. Confirm domain routing uses service **proxy**, port **8080**, path **/**,
HTTPS and your Let's Encrypt certificate. Enable HTTP-to-HTTPS redirection
in Dokploy. Do not add Basic Auth/ForwardAuth in front of Harbor.
5. Deploy. The init service must finish with exit code 0 before workloads start.
Log in at `https://YOUR_DOMAIN` as **admin**, with `HARBOR_ADMIN_PASSWORD`.
Dokploy supplies network isolation and Traefik routing. Do not add host ports,
container names or a shared `dokploy-network`. Path-based hosting such as
`/harbor` is unsupported. TLS terminates at Dokploy; internal traffic is HTTP.
## Registry and scanning
Create projects and configure quotas, access, scan-on-push and retention in the
Harbor UI. The template does not automatically create projects or apply policies.
Trivy is registered as the default scanner; its initial vulnerability database
download may take time and needs outbound access to GHCR. Set
`TRIVY_GITHUB_TOKEN` if your environment needs an authenticated download.
User self-registration is disabled. Only admins may create projects. Default
quota is 20 GiB per project; set `PROJECT_QUOTA_GIB` before installation to change
it. Advanced resource/configuration defaults are in `docker-compose.yml`.
No automated backup, restore, scheduled maintenance, policy jobs or Docker socket
mounts are included. Arrange an independent backup of all persistent volumes.
## Restart and configuration changes
Regular redeploys preserve data and generated encryption/signing keys. Changing
the domain, init code, Harbor version or generated configuration requires an
explicit reconfiguration. Init fails clearly on unexpected changes.
For a configuration change, use a terminal on the deployment host in the
Dokploy-generated Compose directory, with its existing Compose project name and
Environment values. The commands below assume `docker compose` already resolves
that exact deployment. Keep its volume prefix and volumes unchanged.
1. Back up every persistent volume, then stop the deployment and scheduled redeploys.
2. Save the new Environment values and regenerate the Dokploy Compose file
without starting the workloads.
3. Stop all services, build the new init image, run init alone, then start Harbor:
```sh
docker compose stop
docker compose build harbor-init
docker compose run --rm --no-deps harbor-init --reconfigure
docker compose up -d --wait --wait-timeout 600
```
Do not use `down --volumes`. `--reconfigure` refuses reachable Harbor services
and preserves existing keys. It is not a database-version migration tool.
Do not change `HARBOR_DB_PASSWORD` for an existing database: changing the env
value does not change the database user's password. Similarly, changing
`HARBOR_ADMIN_PASSWORD` does not rotate the existing admin's password; use the UI.
Follow Harbor's official migration procedure before upgrading a populated install.
Missing persistent keys are a fatal error. Restore the original complete data;
do not delete `render-state.json` or regenerate signing keys to bypass the check.
Prepare errors are captured privately as `prepare-error.log` in the control
volume and must be reviewed for sensitive data before sharing.
## Acceptance checks on your Dokploy instance
- Confirm the init service completed, all workloads are healthy and HTTPS is trusted.
- Log in to Harbor, create a private test project and enable scan-on-push.
- Run `docker login YOUR_DOMAIN`, push a small image and pull it back.
- Run a Trivy scan and confirm a report appears after the vulnerability DB downloads.
- Restart/redeploy and verify the project, image, credentials and signing keys persist.
Local Docker tests do not validate a public domain, certificate issuance, a full
Trivy DB download or your Dokploy version's Compose/domain rewriting.
+13
View File
@@ -0,0 +1,13 @@
{
"id": "harbor",
"name": "Harbor",
"version": "2.15.4",
"description": "Harbor is an open source container and OCI artifact registry with access control and Trivy vulnerability scanning.",
"logo": "harbor.svg",
"links": {
"github": "https://github.com/goharbor/harbor",
"website": "https://goharbor.io/",
"docs": "https://goharbor.io/docs/"
},
"tags": ["registry", "containers", "security"]
}
+21
View File
@@ -0,0 +1,21 @@
[variables]
main_domain = "${domain}"
instance_id = "harbor-${uuid}"
admin_password = "${password:32}"
db_password = "${password:32}"
[config]
env = [
"HARBOR_INSTANCE_ID=${instance_id}",
"HARBOR_HOSTNAME=${main_domain}",
"HARBOR_ADMIN_PASSWORD=${admin_password}",
"HARBOR_DB_PASSWORD=${db_password}"
]
[[config.domains]]
serviceName = "proxy"
port = 8080
host = "${main_domain}"
path = "/"
https = true
certificateType = "letsencrypt"
@@ -0,0 +1,112 @@
import shlex
import sys
import tempfile
import unittest
from unittest.mock import patch, Mock
sys.path.insert(0, '/app')
from common import ConfigError, settings, operation_lock
from initialize import (build_config, check_persistent_state, launcher, parse_literal_env,
require_stopped_services)
BASE = {'HARBOR_INSTANCE_ID': 'harbor-test', 'HARBOR_HOSTNAME': 'registry.company.test',
'HARBOR_ADMIN_PASSWORD': 'admin-with-20-characters',
'HARBOR_DB_PASSWORD': 'database-with-20-characters'}
TEMPLATE = {'database': {}, 'jobservice': {}, 'log': {'local': {}}, 'trivy': {}}
class ConfigurationTests(unittest.TestCase):
def test_minimal_configuration_needs_no_backup_credentials(self):
self.assertEqual(settings(BASE), {'instance': 'harbor-test', 'external_url': 'https://registry.company.test'})
def test_invalid_hosts_and_instances(self):
for host in ['localhost', '127.0.0.1', 'https://registry.company.test', 'registry.company.test:443',
'registry.company.test/path', 'a..test', 'HOST.company.test', 'harbor.example.com']:
with self.subTest(host=host), self.assertRaises(ConfigError):
settings({**BASE, 'HARBOR_HOSTNAME': host})
for instance in ['../data', 'harbor_prod', 'Xprod', 'ab']:
with self.subTest(instance=instance), self.assertRaises(ConfigError):
settings({**BASE, 'HARBOR_INSTANCE_ID': instance})
def test_password_validation(self):
for password in ['', 'Harbor12345', 'short', 'long-password-with\nnewline']:
with self.subTest(password=password), self.assertRaises(ConfigError):
build_config({**BASE, 'HARBOR_DB_PASSWORD': password}, TEMPLATE)
def test_security_and_trivy_defaults(self):
conf = build_config(BASE, TEMPLATE)
self.assertEqual(conf['external_url'], 'https://registry.company.test')
self.assertNotIn('https', conf)
self.assertFalse(conf['x-dokploy-security']['self_registration'])
self.assertEqual(conf['x-dokploy-security']['project_creation_restriction'], 'adminonly')
self.assertFalse(conf['trivy']['skip_update'])
self.assertFalse(conf['trivy']['insecure'])
self.assertEqual(TEMPLATE['database'], {})
def test_special_characters_remain_literal(self):
secret = "space $TOKEN $(touch /tmp/injected) # \" ' \\ ="
parsed = parse_literal_env('PASSWORD=' + secret + '\nEMPTY=\nURL=a=b#c\n')
script = launcher(parsed, ['/some executable', 'literal;$HOME'])
line = next(line for line in script.splitlines() if line.startswith('export PASSWORD='))
self.assertEqual(shlex.split(line), ['export', 'PASSWORD=' + secret])
self.assertEqual(parsed['URL'], 'a=b#c')
self.assertNotIn('eval ', script)
def test_invalid_upstream_env_is_rejected(self):
for value in ['BAD-KEY=x', 'export X=x', 'NO_EQUALS', 'KEY=bad\x00value']:
with self.subTest(value=value), self.assertRaises(ConfigError):
parse_literal_env(value)
def test_connection_pool_bounds(self):
for extra in [{'DB_MAX_IDLE_CONNS': '11', 'DB_MAX_OPEN_CONNS': '10'},
{'POSTGRES_MAX_CONNECTIONS': '100'}, {'DB_MAX_OPEN_CONNS': 'NaN'}]:
with self.subTest(extra=extra), self.assertRaises(ConfigError):
build_config({**BASE, **extra}, TEMPLATE)
def test_unchanged_and_first_install_are_allowed(self):
runtime = settings(BASE)
check_persistent_state(None, runtime, 'digest', False, False, False, False)
check_persistent_state({'instance': 'harbor-test', 'fingerprint': 'digest'},
runtime, 'digest', True, True, True, False)
def test_changed_config_requires_explicit_reconfiguration(self):
old = {'instance': 'harbor-test', 'fingerprint': 'old'}
with self.assertRaisesRegex(ConfigError, '--reconfigure'):
check_persistent_state(old, settings(BASE), 'new', True, True, True, False)
check_persistent_state(old, settings(BASE), 'new', True, True, True, True)
def test_missing_keys_never_regenerated_even_when_reconfiguring(self):
old = {'instance': 'harbor-test', 'fingerprint': 'old'}
for state in [None, old]:
for reconfigure in [False, True]:
with self.subTest(state=state, reconfigure=reconfigure), self.assertRaisesRegex(ConfigError, 'keys are missing'):
check_persistent_state(state, settings(BASE), 'new', False, True, False, reconfigure)
def test_existing_volume_cannot_change_instance(self):
with self.assertRaisesRegex(ConfigError, 'INSTANCE_ID'):
check_persistent_state({'instance': 'other', 'fingerprint': 'old'},
settings(BASE), 'new', True, True, True, True)
@patch('initialize.socket.create_connection')
def test_reconfiguration_refuses_live_workloads(self, connect):
connection = Mock()
connect.return_value = connection
with self.assertRaisesRegex(ConfigError, 'Stop all Harbor'):
require_stopped_services()
connection.close.assert_called_once()
@patch('initialize.socket.create_connection', side_effect=ConnectionRefusedError)
def test_stopped_workloads_allow_reconfiguration(self, connect):
require_stopped_services()
self.assertEqual(connect.call_count, 10)
def test_concurrent_initialization_is_rejected(self):
with tempfile.TemporaryDirectory() as folder:
with operation_lock(folder):
with self.assertRaisesRegex(ConfigError, 'Another initialization'):
with operation_lock(folder):
self.fail('Concurrent initialization allowed')
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,4 @@
{
"revision": "fc224d78800ffad4cd2a21ecd08f24c6fa122b82",
"repository": "https://github.com/XnCN/harbor-dokploy-template.git"
}
+58
View File
@@ -0,0 +1,58 @@
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFile, mkdir, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { buildCompose, yaml } from './render-compose.mjs';
const root = new URL('../', import.meta.url);
const require = createRequire(new URL('../../app/package.json', root));
const toml = require('@iarna/toml');
const YAML = require('yaml');
const read = async file => readFile(new URL(file, root), 'utf8');
const lock = JSON.parse(await read('images.lock.json'));
const source = JSON.parse(await read('tools/build-source.json'));
const composeText = await read('docker-compose.yml');
const compose = YAML.parse(composeText);
assert.deepEqual(compose, buildCompose(lock, source));
assert.equal(composeText, '# Generated by tools/render-compose.mjs from images.lock.json.\n' + yaml(compose));
assert.match(source.revision, /^[a-f0-9]{40}$/);
for (const file of ['Dockerfile', '.dockerignore', 'harbor/common.py', 'harbor/initialize.py',
'images.lock.json', 'vendor/harbor.yml.tmpl']) {
const pinned = execFileSync('git', ['show', source.revision + ':blueprints/harbor/' + file],
{ cwd: fileURLToPath(root), encoding: 'utf8' });
assert.equal((await read(file)).replace(/\r\n/g, '\n'), pinned.replace(/\r\n/g, '\n'),
file + ': commit the updated init source and update tools/build-source.json');
}
assert.equal(createHash('sha256').update(await read('vendor/harbor.yml.tmpl')).digest('hex'), lock.template_sha256);
assert.equal(Object.keys(compose.services).length, 11);
assert.ok(!compose.networks);
assert.ok(!compose.services.operations);
for (const [name, service] of Object.entries(compose.services)) {
for (const key of ['ports', 'networks', 'container_name', 'env_file', 'privileged']) assert.ok(!service[key], name + ': ' + key);
assert.ok(!JSON.stringify(service).match(/docker\.sock|BACKUP_|RESTIC_|POLICY_PROJECTS/));
if (name === 'harbor-init') {
assert.equal(service.restart, 'no');
assert.equal(service.build.context, source.repository + '#' + source.revision + ':blueprints/harbor');
} else {
assert.match(service.image, /:v2\.15\.4@sha256:[a-f0-9]{64}$/);
assert.equal(service.depends_on['harbor-init'].condition, 'service_completed_successfully');
}
}
const configText = await read('template.toml');
const config = toml.parse(configText);
assert.equal(config.config.domains[0].serviceName, 'proxy');
assert.equal(config.config.domains[0].port, 8080);
assert.equal(config.config.domains[0].https, true);
assert.ok(config.config.env.includes('HARBOR_HOSTNAME=${main_domain}'));
assert.notEqual(config.variables.admin_password, '${db_password}');
// Match the catalogue's TemplateDialog payload exactly, including Unicode encoding.
const payload = { compose: composeText, config: configText };
const encoded = Buffer.from(JSON.stringify(payload, null, 2)).toString('base64');
assert.deepEqual(JSON.parse(Buffer.from(encoded, 'base64').toString('utf8')), payload);
const artifacts = new URL('../../.harbor-validation/', root);
await mkdir(artifacts, { recursive: true });
await writeFile(new URL('harbor-import.base64.txt', artifacts), encoded + '\n');
console.log('Harbor YAML, TOML, pinned build source, persistence, isolation and catalogue import checks passed.');
console.log('Import artifact: ' + fileURLToPath(new URL('harbor-import.base64.txt', artifacts)));
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import { mkdir, readFile, writeFile, rm } from 'node:fs/promises';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { buildCompose } from './render-compose.mjs';
const root = new URL('../', import.meta.url);
const id = 'harbor-ci-' + randomUUID();
const folder = new URL('../../.harbor-validation/' + id + '/', root);
await mkdir(folder, { recursive: true });
const filename = fileURLToPath(new URL('compose.json', folder));
const lock = JSON.parse(await readFile(new URL('images.lock.json', root), 'utf8'));
const source = JSON.parse(await readFile(new URL('tools/build-source.json', root), 'utf8'));
const config = buildCompose(lock, source);
config.services['harbor-init'].build.context = fileURLToPath(root);
await writeFile(filename, JSON.stringify(config, null, 2));
const env = { ...process.env, HARBOR_INSTANCE_ID: id, HARBOR_HOSTNAME: 'registry.company.test',
HARBOR_ADMIN_PASSWORD: 'test-admin-password-with-entropy', HARBOR_DB_PASSWORD: 'test-database-password-with-entropy' };
const composeArgs = ['compose', '-p', id, '-f', filename];
function run(args, capture = false, overrides = {}) {
const result = spawnSync('docker', [...composeArgs, ...args], {
env: { ...env, ...overrides }, encoding: 'utf8', stdio: capture ? 'pipe' : 'inherit', timeout: 900_000,
});
if (result.status !== 0) throw new Error('Docker ' + args.slice(0, 3).join(' ') + ' failed: ' + (result.stderr || result.error || result.status));
return (result.stdout || '').trim();
}
const workloads = Object.keys(config.services).filter(name => name !== 'harbor-init');
try {
run(['config', '--quiet']);
run(['up', '-d', '--build', '--wait', '--wait-timeout', '600']);
const health = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', 'http://localhost:8080/api/v2.0/health'], true));
assert.equal(health.status, 'healthy');
const challenge = run(['exec', '-T', 'proxy', 'curl', '-sSI', '-H', 'Host: registry.company.test',
'-H', 'X-Forwarded-Proto: https', 'http://localhost:8080/v2/'], true);
assert.match(challenge, /401/);
assert.match(challenge, /https:\/\/registry\.company\.test\/service\/token/);
const user = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD,
'http://localhost:8080/api/v2.0/users/current'], true));
assert.equal(user.username, 'admin');
run(['exec', '-T', 'core', 'curl', '-fsS', 'http://trivy-adapter:8080/probe/healthy'], true);
run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD,
'-H', 'Content-Type: application/json', '-d', '{"project_name":"persistence-test","public":false}',
'http://localhost:8080/api/v2.0/projects'], true);
const hashes = () => run(['exec', '-T', 'core', 'sha256sum', '/etc/core/key', '/etc/core/private_key.pem'], true);
const assertPersistent = before => {
assert.equal(hashes(), before, 'Signing/encryption keys must persist');
const project = JSON.parse(run(['exec', '-T', 'proxy', 'curl', '-fsS', '-u', 'admin:' + env.HARBOR_ADMIN_PASSWORD,
'http://localhost:8080/api/v2.0/projects/persistence-test'], true));
assert.equal(project.name, 'persistence-test', 'Database project must persist');
};
const before = hashes();
const live = spawnSync('docker', [...composeArgs, 'run', '--rm', '--no-deps', 'harbor-init', '--reconfigure'],
{ env, encoding: 'utf8', timeout: 60_000 });
assert.notEqual(live.status, 0, 'Reconfiguration must refuse live services');
assert.match(live.stdout + live.stderr, /Stop all Harbor/);
run(['restart', '--timeout', '20', ...workloads]);
run(['up', '-d', '--wait', '--wait-timeout', '600']);
assertPersistent(before);
run(['up', '-d', '--force-recreate', '--timeout', '20', '--wait', '--wait-timeout', '600']);
assertPersistent(before);
run(['stop', '--timeout', '20', ...workloads]);
run(['run', '--rm', '--no-deps', 'harbor-init', '--reconfigure'], false, { JOB_MAX_WORKERS: '6' });
run(['up', '-d', '--wait', '--wait-timeout', '600'], false, { JOB_MAX_WORKERS: '6' });
assertPersistent(before);
console.log('PASS: prepare, health, admin login, registry token URL, Trivy, restart, redeploy and reconfiguration.');
} catch (error) {
run(['ps', '-a']);
run(['logs', '--no-color', '--tail', '35']);
throw error;
} finally {
// This file and every explicit volume name belong to this freshly generated CI ID only.
try { run(['down', '--timeout', '20', '--volumes', '--remove-orphans']); }
finally { await rm(folder, { recursive: true, force: true }); }
}
+105
View File
@@ -0,0 +1,105 @@
import { readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath, pathToFileURL } from 'node:url';
import path from 'node:path';
const repository = fileURLToPath(new URL('../', import.meta.url));
const env = (name, fallback = '') => '$' + '{' + name + ':-' + fallback + '}';
const required = name => '$' + '{' + name + ':?Set ' + name + ' in Dokploy Environment}';
const mount = (source, target, subpath, readOnly = false) => ({
type: 'volume', source, target, read_only: readOnly,
volume: { nocopy: true, ...(subpath ? { subpath } : {}) },
});
const runtimeConfigs = {
core: ['core', '/etc/core', 'harbor-core'],
postgresql: ['db', '/run/harbor-config', 'harbor-db'],
redis: ['redis', '/run/harbor-config', 'valkey-photon'],
registry: ['registry', '/etc/registry', 'registry-photon'],
registryctl: ['registryctl', '/etc/registryctl', 'harbor-registryctl'],
jobservice: ['jobservice', '/etc/jobservice', 'harbor-jobservice'],
portal: ['portal', '/run/harbor-config', 'harbor-portal'],
proxy: ['nginx', '/etc/nginx', 'nginx-photon'],
'trivy-adapter': ['trivy-adapter', '/run/harbor-config', 'trivy-adapter-photon'],
exporter: ['exporter', '/run/harbor-config', 'harbor-exporter'],
};
const memory = {core:'1g', postgresql:'1536m',redis:'256m',registry:'768m',registryctl:'128m',jobservice:'512m',portal:'128m',proxy:'256m','trivy-adapter':'1536m',exporter:'128m'};
export function buildCompose(lock, buildSource) {
const image = name => { const item = lock.images[name]; return item.repository + ':' + item.tag + '@' + item.digest; };
const labels = role => ({ 'io.harbor-dokploy.instance': required('HARBOR_INSTANCE_ID'), 'io.harbor-dokploy.role': role, 'traefik.enable':'false' });
const logging = { driver:'local', options:{'max-size':env('DOCKER_LOG_MAX_SIZE','20m'),'max-file':env('DOCKER_LOG_MAX_FILES','5')} };
const settingsEnv = { HARBOR_INSTANCE_ID:required('HARBOR_INSTANCE_ID'), HARBOR_HOSTNAME:required('HARBOR_HOSTNAME') };
const configDefaults = { DB_MAX_IDLE_CONNS:'10', DB_MAX_OPEN_CONNS:'100',POSTGRES_MAX_CONNECTIONS:'256',JOB_MAX_WORKERS:'5',HARBOR_LOG_LEVEL:'info',CACHE_ENABLED:'true',
ALLOW_PRIVATE_NETWORK_ACCESS:'false',TRIVY_GITHUB_TOKEN:'',TRIVY_DB_REPOSITORY:'ghcr.io/aquasecurity/trivy-db',
TRIVY_JAVA_DB_REPOSITORY:'ghcr.io/aquasecurity/trivy-java-db',HARBOR_HTTP_PROXY:'',HARBOR_HTTPS_PROXY:'',HARBOR_NO_PROXY:'',PROJECT_QUOTA_GIB:'20' };
const configEnv = { HARBOR_ADMIN_PASSWORD:required('HARBOR_ADMIN_PASSWORD'),HARBOR_DB_PASSWORD:required('HARBOR_DB_PASSWORD'),
...Object.fromEntries(Object.entries(configDefaults).map(([k,v])=>[k,env(k,v)])) };
const volumes = Object.fromEntries(['data','database','registry','redis','jobs','config','trivy-cache','trivy-reports','control'].map(name=>[name,{name:required('HARBOR_INSTANCE_ID')+'-'+name}]));
const initMounts = [mount('config','/config'),mount('data','/data'),mount('database','/data/database'),mount('registry','/data/registry'),
mount('redis','/data/redis'),mount('jobs','/data/job_logs'),mount('trivy-cache','/data/trivy-adapter/trivy'),
mount('trivy-reports','/data/trivy-adapter/reports'),mount('control','/control')];
const services = {
'harbor-init': {
build:{context:buildSource.repository+'#'+buildSource.revision+':blueprints/harbor',dockerfile:'Dockerfile',target:'runtime',args:{PREPARE_IMAGE:image('prepare')}},
platform:lock.platform,restart:'no', labels:labels('init'),
environment:{...settingsEnv,...configEnv},
volumes:initMounts,
cap_drop:['ALL'],cap_add:['CHOWN','DAC_OVERRIDE','FOWNER'],security_opt:['no-new-privileges:true'],
mem_limit:env('INIT_MEMORY_LIMIT','768m'),logging,
},
};
{
for (const [name,[folder,target,imageName]] of Object.entries(runtimeConfigs)) {
const variable = name.replaceAll('-','_').toUpperCase();
services[name] = {
image:image(imageName),platform:lock.platform,restart:'unless-stopped',labels:labels('workload'),
entrypoint:['/bin/sh',target+'/launch.sh'],command:[],
volumes:[mount('config',target,folder,true),mount('control','/run/harbor-control',undefined,true),
mount('config','/harbor_cust_cert','shared/trust-certificates',true)],
depends_on:{'harbor-init':{condition:'service_completed_successfully'}},
cap_drop:['ALL'],cap_add:['CHOWN','SETGID','SETUID'],security_opt:['no-new-privileges:true'],
mem_limit:env(variable+'_MEMORY_LIMIT',memory[name]),cpus:env(variable+'_CPU_LIMIT',name==='trivy-adapter'?'2.0':'1.0'),
stop_grace_period:'120s',healthcheck:{interval:'15s',timeout:'10s',retries:10,start_period:name==='postgresql'?'120s':'60s'},logging,
};
}
const add = (service, ...entries) => services[service].volumes.push(...entries);
const depend = (service, names) => names.forEach(name=>services[service].depends_on[name]={condition:'service_healthy'});
add('postgresql',mount('database','/var/lib/postgresql/data'));
services.postgresql.cap_add.push('DAC_OVERRIDE');
services.postgresql.shm_size='256m';
services.postgresql.environment={POSTGRES_MAX_CONNECTIONS:env('POSTGRES_MAX_CONNECTIONS','256')};
services.postgresql.healthcheck.test=['CMD','/bin/sh','/run/harbor-config/health.sh'];
add('redis',mount('redis','/var/lib/redis'));
add('registry',mount('registry','/storage'));
depend('registry',['redis']);
add('registryctl',mount('registry','/storage'),mount('config','/etc/registry','registry',true));
depend('registryctl',['registry']);
add('core',mount('data','/data'));
depend('core',['postgresql','redis','registry']);
add('jobservice',mount('jobs','/var/log/jobs'));
depend('jobservice',['core','redis']);
add('trivy-adapter',mount('trivy-cache','/home/scanner/.cache/trivy'),mount('trivy-reports','/home/scanner/.cache/reports'));
depend('trivy-adapter',['redis']);
depend('exporter',['core','postgresql','redis']);
services.exporter.healthcheck.test=['CMD-SHELL','curl --fail -s http://localhost:8080/metrics >/dev/null'];
services.proxy.expose=['8080'];
services.proxy.labels['traefik.enable']='true';
depend('proxy',['core','portal','registry','exporter']);
}
return {services,volumes};
}
export function yaml(value, indent=0) {
const pad=' '.repeat(indent);
if (Array.isArray(value)) return value.map(item => typeof item === 'object' && item !== null
? pad+'-\n'+yaml(item,indent+2) : pad+'- '+JSON.stringify(item)+'\n').join('');
return Object.entries(value).map(([key,item])=> {
if (item && typeof item==='object' && Object.keys(item).length) return pad+JSON.stringify(key)+':\n'+yaml(item,indent+2);
return pad+JSON.stringify(key)+': '+JSON.stringify(item)+'\n';
}).join('');
}
export async function render() {
const lock=JSON.parse(await readFile(path.join(repository,'images.lock.json'),'utf8'));
const buildSource=JSON.parse(await readFile(path.join(repository,'tools/build-source.json'),'utf8'));
await writeFile(path.join(repository,'docker-compose.yml'),
'# Generated by tools/render-compose.mjs from images.lock.json.\n'+yaml(buildCompose(lock,buildSource)));
}
if (typeof process !== 'undefined' && process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href===import.meta.url) await render();
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright Project Harbor Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+9
View File
@@ -0,0 +1,9 @@
The harbor.yml.tmpl file is copied without modification from:
https://github.com/goharbor/harbor/blob/v2.15.4/make/harbor.yml.tmpl
Copyright Harbor Authors. Licensed under Apache License 2.0; see HARBOR-LICENSE.
The template checksum and image references are recorded in ../images.lock.json.
Update the vendor template, image metadata/digests and Dockerfile default together,
then rerun unit and integration tests before pinning a new init source commit.
harbor.svg is the official favicon from https://github.com/goharbor/website/blob/main/static/favicon.svg.
+342
View File
@@ -0,0 +1,342 @@
# Configuration file of Harbor
# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: reg.mydomain.com
# http related config
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
port: 80
# https related config
https:
# https port for harbor, default is 443
port: 443
# The path of cert and key files for nginx
certificate: /your/certificate/path
private_key: /your/private/key/path
# enable strong ssl ciphers (default: false)
# strong_ssl_ciphers: false
# # Harbor will set ipv4 enabled only by default if this block is not configured
# # Otherwise, please uncomment this block to configure your own ip_family stacks
# ip_family:
# # ipv6Enabled set to true if ipv6 is enabled in docker network, currently it affected the nginx related component
# ipv6:
# enabled: false
# # ipv4Enabled set to true by default, currently it affected the nginx related component
# ipv4:
# enabled: true
# # Uncomment following will enable tls communication between all harbor components
# internal_tls:
# # set enabled to true means internal tls is enabled
# enabled: true
# # put your cert and key files on dir
# dir: /etc/harbor/tls/internal
# Uncomment external_url if you want to enable external proxy
# And when it enabled the hostname will no longer used
# external_url: https://reg.mydomain.com:8433
# The initial password of Harbor admin
# It only works in first time to install harbor
# Remember Change the admin password from UI after launching Harbor.
harbor_admin_password: Harbor12345
# Harbor DB configuration
database:
# The password for the user('postgres' by default) of Harbor DB. Change this before any production use.
password: root123
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
max_idle_conns: 100
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
# Note: the default number of connections is 1024 for postgres of harbor.
max_open_conns: 900
# The maximum amount of time a connection may be reused. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's age.
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
conn_max_lifetime: 5m
# The maximum amount of time a connection may be idle. Expired connections may be closed lazily before reuse. If it <= 0, connections are not closed due to a connection's idle time.
# The value is a duration string. A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
conn_max_idle_time: 0
# The default data volume
data_volume: /data
# Harbor Storage settings by default is using /data dir on local filesystem
# Uncomment storage_service setting If you want to using external storage
# storage_service:
# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore
# # of registry's containers. This is usually needed when the user hosts a internal storage with self signed certificate.
# ca_bundle:
# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss
# # for more info about this configuration please refer https://distribution.github.io/distribution/about/configuration/
# # and https://distribution.github.io/distribution/storage-drivers/
# filesystem:
# maxthreads: 100
# # set disable to true when you want to disable registry redirect
# redirect:
# disable: false
# Trivy configuration
#
# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases.
# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached
# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it
# should download a newer version from the Internet or use the cached one. Currently, the database is updated every
# 12 hours and published as a new release to GitHub.
trivy:
# ignoreUnfixed The flag to display only fixed vulnerabilities
ignore_unfixed: false
# skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
#
# You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
# If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
# `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
skip_update: false
#
# skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the
# `/home/scanner/.cache/trivy/java-db/trivy-java.db` path
skip_java_db_update: false
#
# OCI repository to retrieve the trivy vulnerability database from.
db_repository: ghcr.io/aquasecurity/trivy-db
# OCI repository to retrieve the Java trivy vulnerability database from.
java_db_repository: ghcr.io/aquasecurity/trivy-java-db
#
# The offline_scan option prevents Trivy from sending API requests to identify dependencies.
# Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it.
# For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't
# exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode.
# It would work if all the dependencies are in local.
# This option doesn't affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment.
offline_scan: false
#
# Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`.
security_check: vuln
#
# insecure The flag to skip verifying registry certificate
insecure: false
#
# timeout The duration to wait for scan completion.
# There is upper bound of 30 minutes defined in scan job. So if this `timeout` is larger than 30m0s, it will also timeout at 30m0s.
timeout: 5m0s
#
# github_token The GitHub access token to download Trivy DB
#
# Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
# for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
# requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
# https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting
#
# You can create a GitHub token by following the instructions in
# https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
#
# github_token: xxx
jobservice:
# Maximum number of job workers in job service
max_job_workers: 10
# Maximum hours of task duration in job service, default 24
max_job_duration_hours: 24
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
job_loggers:
- STD_OUTPUT
- FILE
# - DB
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
logger_sweeper_duration: 1 #days
notification:
# Maximum retry count for webhook job
webhook_job_max_retry: 3
# HTTP client timeout for webhook job
webhook_job_http_client_timeout: 3 #seconds
# Log configurations
log:
# options are debug, info, warning, error, fatal
level: info
# configs for logs in local storage
local:
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
rotate_count: 50
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
# are all valid.
rotate_size: 200M
# The directory on your host that store log
location: /var/log/harbor
# Uncomment following lines to enable external syslog endpoint.
# external_endpoint:
# # protocol used to transmit log to external endpoint, options is tcp or udp
# protocol: tcp
# # The host of external endpoint
# host: localhost
# # Port of external endpoint
# port: 5140
#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
_version: 2.15.0
# Uncomment external_database if using external database.
# external_database:
# harbor:
# host: harbor_db_host
# port: harbor_db_port
# db_name: harbor_db_name
# username: harbor_db_username
# password: harbor_db_password
# ssl_mode: disable
# max_idle_conns: 2
# max_open_conns: 0
# Uncomment redis if need to customize redis db
# redis:
# # db_index 0 is for core, it's unchangeable
# # registry_db_index: 1
# # jobservice_db_index: 2
# # trivy_db_index: 5
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
# # harbor_db_index: 6
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
# # cache_layer_db_index: 7
# Uncomment external_redis if using external Redis server
# external_redis:
# # support redis, redis+sentinel
# # host for redis: <host_redis>:<port_redis>
# # host for redis+sentinel:
# # <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
# host: redis:6379
# password:
# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH <username> <password> form.
# # username:
# # sentinel_master_set must be set to support redis+sentinel
# #sentinel_master_set:
# # tls configuration for redis connection
# # only server-authentication is supported
# # mtls for redis connection is not supported
# # tls connection will be disable by default
# tlsOptions:
# enable: false
# # if it is a self-signed ca, please set the ca path specifically.
# rootCA:
# # db_index 0 is for core, it's unchangeable
# registry_db_index: 1
# jobservice_db_index: 2
# trivy_db_index: 5
# idle_timeout_seconds: 30
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
# # harbor_db_index: 6
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
# # cache_layer_db_index: 7
# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert.
# uaa:
# ca_file: /path/to/ca
# Global proxy
# Config http proxy for components, e.g. http://my.proxy.com:3128
# Components doesn't need to connect to each others via http proxy.
# Remove component from `components` array if want disable proxy
# for it. If you want use proxy for replication, MUST enable proxy
# for core and jobservice, and set `http_proxy` and `https_proxy`.
# Add domain to the `no_proxy` field, when you want disable proxy
# for some special registry.
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy
# Outbound webhook and Slack notification requests may reach private, loopback, link-local and
# cloud-metadata (169.254.169.254) addresses unless this is false. Set it to false to reject them
# (CWE-918) when no webhook receiver runs on an internal network.
network:
allow_private_network_access: true
# Lets a cosign/notation client pull the still-unsigned manifest it is about to sign while the
# project has a content trust policy enabled. The client must already hold push permission on
# the project; its User-Agent only narrows that authorization, and it is spoofable, so set this
# to false if you would rather such a client could not read unsigned manifests.
content_trust:
legacy_signer_pull_enabled: true
# metric:
# enabled: false
# port: 9090
# path: /metrics
# Trace related config
# only can enable one trace provider(jaeger or otel) at the same time,
# and when using jaeger as provider, can only enable it with agent mode or collector mode.
# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed
# if using jaeger agetn mode uncomment agent_host and agent_port
# trace:
# enabled: true
# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth
# sample_rate: 1
# # # namespace used to differentiate different harbor services
# # namespace:
# # # attributes is a key value dict contains user defined attributes used to initialize trace provider
# # attributes:
# # application: harbor
# # # jaeger should be 1.26 or newer.
# # jaeger:
# # endpoint: http://hostname:14268/api/traces
# # username:
# # password:
# # agent_host: hostname
# # # export trace data by jaeger.thrift in compact mode
# # agent_port: 6831
# # otel:
# # endpoint: hostname:4318
# # url_path: /v1/traces
# # compression: false
# # insecure: true
# # # timeout is in seconds
# # timeout: 10
# Enable purge _upload directories
upload_purging:
enabled: true
# remove files in _upload directories which exist for a period of time, default is one week.
age: 168h
# the interval of the purge operations
interval: 24h
dryrun: false
# Cache layer configurations
# If this feature enabled, harbor will cache the resource
# `project/project_metadata/repository/artifact/manifest` in the redis
# which can especially help to improve the performance of high concurrent
# manifest pulling.
# NOTICE
# If you are deploying Harbor in HA mode, make sure that all the harbor
# instances have the same behaviour, all with caching enabled or disabled,
# otherwise it can lead to potential data inconsistency.
cache:
# not enabled by default
enabled: false
# keep cache for one day by default
expire_hours: 24
# Harbor core configurations
# Uncomment to enable the following harbor core related configuration items.
# core:
# # The provider for updating project quota(usage), there are 2 options, redis or db,
# # by default is implemented by db but you can switch the updation via redis which
# # can improve the performance of high concurrent pushing to the same project,
# # and reduce the database connections spike and occupies.
# # By redis will bring up some delay for quota usage updation for display, so only
# # suggest switch provider to redis if you were ran into the db connections spike around
# # the scenario of high concurrent pushing to same project, no improvement for other scenes.
# quota_update_provider: redis # Or db
-2
View File
@@ -1,5 +1,3 @@
version: "3.8"
services:
heyo-docs:
build:
+58
View File
@@ -0,0 +1,58 @@
# Native MQTT over TLS requires a Traefik TCP entrypoint named "mqtts"
# on port 8883 and a corresponding port mapping on the Traefik container.
# WebSocket clients connect using wss://<broker_domain>/mqtt via Dokploy HTTPS.
services:
hivemq-ce-init:
image: curlimages/curl:8.22.0
restart: "no"
user: "0:0"
environment:
- MQTT_USERNAME=${MQTT_USERNAME}
- MQTT_PASSWORD=${MQTT_PASSWORD}
entrypoint: ["/bin/sh", "/tmp/hivemq-ce-init.sh"]
volumes:
- ../files/hivemq-ce-init.sh:/tmp/hivemq-ce-init.sh:ro
- hivemq_extensions:/extensions
hivemq-ce:
image: hivemq/hivemq-ce:2026.5
restart: unless-stopped
depends_on:
hivemq-ce-init:
condition: service_completed_successfully
environment:
- HIVEMQ_ALLOW_ALL_CLIENTS=false
entrypoint:
- /bin/bash
- -ec
- |
test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/hivemq-extension.xml
test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/hivemq-file-rbac-extension-4.6.17.jar
test -f /opt/hivemq/extensions/hivemq-file-rbac-extension/conf/credentials.xml
test ! -e /opt/hivemq/extensions/hivemq-allow-all-extension
exec /opt/docker-entrypoint.sh /opt/hivemq/bin/run.sh
expose:
- 1883 # MQTT over TCP (TLS terminated by Traefik on 8883)
- 8000 # MQTT over WebSockets (WSS via Dokploy HTTPS, path /mqtt)
volumes:
- hivemq_data:/opt/hivemq/data
- hivemq_log:/opt/hivemq/log
- hivemq_extensions:/opt/hivemq/extensions
healthcheck:
test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/1883'"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
labels:
- "traefik.tcp.routers.hivemq-ce-mqtts.entrypoints=mqtts"
- "traefik.tcp.routers.hivemq-ce-mqtts.rule=HostSNI(`${MQTT_DOMAIN}`)"
- "traefik.tcp.routers.hivemq-ce-mqtts.tls=true"
- "traefik.tcp.routers.hivemq-ce-mqtts.tls.certresolver=letsencrypt"
- "traefik.tcp.routers.hivemq-ce-mqtts.service=hivemq-ce-mqtts"
- "traefik.tcp.services.hivemq-ce-mqtts.loadbalancer.server.port=1883"
volumes:
hivemq_data:
hivemq_log:
hivemq_extensions:
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title">
<title id="title">HiveMQ</title>
<g fill="#F5A623">
<path d="m32 3 13 7.5v15L32 33l-13-7.5v-15L32 3Zm0 7-7 4v8l7 4 7-4v-8l-7-4Z" fill-rule="evenodd"/>
<path d="m15 28 13 7.5v15L15 58 2 50.5v-15L15 28Zm0 7-7 4v8l7 4 7-4v-8l-7-4Z" fill-rule="evenodd"/>
<path d="m49 28 13 7.5v15L49 58l-13-7.5v-15L49 28Zm0 7-7 4v8l7 4 7-4v-8l-7-4Z" fill-rule="evenodd"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 478 B

+13
View File
@@ -0,0 +1,13 @@
{
"id": "hivemq-ce",
"name": "HiveMQ Community Edition",
"version": "2026.5",
"description": "Open-source MQTT 3.x and MQTT 5 broker with persistent sessions, WebSocket transport, and file-based client authentication.",
"logo": "hivemq-ce.svg",
"links": {
"github": "https://github.com/hivemq/hivemq-community-edition",
"website": "https://www.hivemq.com/",
"docs": "https://github.com/hivemq/hivemq-community-edition/wiki"
},
"tags": ["broker", "iot", "mqtt"]
}
+88
View File
@@ -0,0 +1,88 @@
[variables]
broker_domain = "${domain}"
mqtt_username = "mqtt"
mqtt_password = "${password:32}"
[config]
env = [
"MQTT_DOMAIN=${broker_domain}",
"MQTT_USERNAME=${mqtt_username}",
"MQTT_PASSWORD=${mqtt_password}"
]
[[config.domains]]
serviceName = "hivemq-ce"
port = 8000
host = "${broker_domain}"
[[config.mounts]]
filePath = "hivemq-ce-init.sh"
content = '''
#!/bin/sh
set -eu
test -n "$MQTT_USERNAME" || { echo "MQTT_USERNAME is required" >&2; exit 1; }
test -n "$MQTT_PASSWORD" || { echo "MQTT_PASSWORD is required" >&2; exit 1; }
case "$MQTT_USERNAME" in
*[!a-zA-Z0-9_.-]*) echo 'Invalid MQTT username' >&2; exit 1 ;;
esac
extension=/extensions/hivemq-file-rbac-extension
work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
if [ ! -f "$extension/hivemq-file-rbac-extension-4.6.17.jar" ]; then
curl --fail --silent --show-error --location --retry 3 \
--proto '=https' --proto-redir '=https' \
--output "$work_dir/rbac.zip" \
https://github.com/hivemq/hivemq-file-rbac-extension/releases/download/4.6.17/hivemq-file-rbac-extension-4.6.17.zip
unzip -oq "$work_dir/rbac.zip" -d "$work_dir"
test -f "$work_dir/hivemq-file-rbac-extension/hivemq-extension.xml"
mkdir -p "$extension"
cp -R "$work_dir/hivemq-file-rbac-extension/." "$extension/"
fi
# The CE image enables anonymous MQTT by default. Never retain that extension.
rm -rf /extensions/hivemq-allow-all-extension
escape_xml() {
printf '%s' "$1" | sed \
-e 's/\&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' \
-e 's/"/\&quot;/g' -e "s/'/\&apos;/g"
}
username=$(escape_xml "$MQTT_USERNAME")
password=$(escape_xml "$MQTT_PASSWORD")
mkdir -p "$extension/conf"
cat > "$extension/conf/config.xml" <<'CONFIG'
<?xml version="1.0" encoding="UTF-8"?>
<extension-configuration>
<password-type>PLAIN</password-type>
<next-extension-instead-of-fail>false</next-extension-instead-of-fail>
</extension-configuration>
CONFIG
credentials=$(mktemp "$extension/conf/.credentials.XXXXXX")
cat > "$credentials" <<CREDENTIALS
<?xml version="1.0" encoding="UTF-8"?>
<file-rbac>
<users>
<user>
<name>$username</name>
<password>$password</password>
<roles><id>mqtt</id></roles>
</user>
</users>
<roles>
<role>
<id>mqtt</id>
<permissions><permission><topic>#</topic></permission></permissions>
</role>
</roles>
</file-rbac>
CREDENTIALS
chown -R 10000:10000 "$extension"
chmod 600 "$credentials"
mv -f "$credentials" "$extension/conf/credentials.xml"
# Remove deprecated paths from existing deployments after the new files are ready.
rm -f "$extension/extension-config.xml" "$extension/credentials.xml"
'''
+182
View File
@@ -0,0 +1,182 @@
# Dokploy template for the Community edition of It's a Plan.
# Dokploy routes the api and web services through its proxy, so this file uses
# expose instead of publishing host ports.
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-itsaplan}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in Dokploy}
POSTGRES_DB: ${POSTGRES_DB:-itsaplan}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-itsaplan} -d ${POSTGRES_DB:-itsaplan}",
]
interval: 5s
timeout: 5s
retries: 10
minio:
image: rustfs/rustfs:1.0.0
restart: unless-stopped
environment:
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY_ID:?set S3_ACCESS_KEY_ID in Dokploy}
RUSTFS_SECRET_KEY: ${S3_SECRET_ACCESS_KEY:?set S3_SECRET_ACCESS_KEY in Dokploy}
volumes:
- minio-data:/data
healthcheck:
test:
[
"CMD",
"curl",
"-fsS",
"-o",
"/dev/null",
"http://127.0.0.1:9000/health",
]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
minio-init:
image: rustfs/rustfs:1.0.0
restart: "no"
depends_on:
minio:
condition: service_healthy
environment:
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY}
S3_BUCKET: ${S3_BUCKET:-planner-attachments}
entrypoint:
- /bin/sh
- -c
- >-
curl -fsS -X PUT --aws-sigv4 aws:amz:us-east-1:s3
--user "$$S3_ACCESS_KEY_ID:$$S3_SECRET_ACCESS_KEY"
"http://minio:9000/$$S3_BUCKET" &&
echo "bucket ready: $$S3_BUCKET"
migrate:
image: ghcr.io/croffasia/itsaplan-api:1.9.0
restart: "no"
command: ["bun", "run", "packages/db/src/migrate.ts"]
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-itsaplan}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-itsaplan}
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-30}
SKIP_PRE_MIGRATION_BACKUP: ${SKIP_PRE_MIGRATION_BACKUP:-}
volumes:
- db-backups:/backups
api:
image: ghcr.io/croffasia/itsaplan-api:1.9.0
restart: unless-stopped
command: ["bun", "run", "apps/api/src/index.ts"]
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
minio-init:
condition: service_completed_successfully
environment:
NODE_ENV: production
API_PORT: 3000
DATABASE_URL: postgres://${POSTGRES_USER:-itsaplan}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-itsaplan}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in Dokploy}
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:?set APP_ENCRYPTION_KEY in Dokploy}
API_URL: ${API_URL:?set API_URL in Dokploy}
APP_URL: ${APP_URL:?set APP_URL in Dokploy}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
CLIENT_IP_HEADER: ${CLIENT_IP_HEADER:-}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
GIT_WEBHOOK_BASE_URL: ${GIT_WEBHOOK_BASE_URL:-}
S3_ENDPOINT: http://minio:9000
S3_BUCKET: ${S3_BUCKET:-planner-attachments}
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:?set S3_ACCESS_KEY_ID in Dokploy}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:?set S3_SECRET_ACCESS_KEY in Dokploy}
S3_REGION: ${S3_REGION:-us-east-1}
S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true}
SSRF_ALLOWED_HOSTS: ${SSRF_ALLOWED_HOSTS:-}
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
expose:
- "3000"
worker:
image: ghcr.io/croffasia/itsaplan-worker:1.9.0
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
environment:
NODE_ENV: production
DATABASE_URL: postgres://${POSTGRES_USER:-itsaplan}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-itsaplan}
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:?set APP_ENCRYPTION_KEY in Dokploy}
TELEMETRY_DISABLED: ${TELEMETRY_DISABLED:-}
DO_NOT_TRACK: ${DO_NOT_TRACK:-}
TELEMETRY_DEBUG: ${TELEMETRY_DEBUG:-}
SSRF_ALLOWED_HOSTS: ${SSRF_ALLOWED_HOSTS:-}
S3_ENDPOINT: http://minio:9000
S3_BUCKET: ${S3_BUCKET:-planner-attachments}
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:?set S3_ACCESS_KEY_ID in Dokploy}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:?set S3_SECRET_ACCESS_KEY in Dokploy}
S3_REGION: ${S3_REGION:-us-east-1}
S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true}
bot:
image: ghcr.io/croffasia/itsaplan-bot:1.9.0
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
environment:
NODE_ENV: production
DATABASE_URL: postgres://${POSTGRES_USER:-itsaplan}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-itsaplan}
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:?set APP_ENCRYPTION_KEY in Dokploy}
web:
image: ghcr.io/croffasia/itsaplan-web:1.9.0
restart: unless-stopped
depends_on:
- api
environment:
NODE_ENV: production
PORT: 3001
HOSTNAME: 0.0.0.0
API_URL: ${API_URL:?set API_URL in Dokploy}
SERVICE_URL_API: http://api:3000
PRIVACY_URL: ${PRIVACY_URL:-}
TERMS_URL: ${TERMS_URL:-}
healthcheck:
test:
["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3001/login"]
interval: 15s
timeout: 5s
retries: 5
start_period: 20s
expose:
- "3001"
volumes:
postgres-data: {}
minio-data: {}
db-backups: {}
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 32 32">
<rect width="32" height="32" rx="7" fill="#08090a"/>
<g transform="translate(4 4) scale(0.75)" fill="none">
<rect x="13" y="14.3" width="6" height="3.4" rx="1.7" fill="#fbfbfa" opacity="0.4"/>
<circle cx="9.5" cy="16" r="6" fill="#fbfbfa"/>
<rect x="17" y="10.5" width="11" height="11" rx="3.4" stroke="#fbfbfa" stroke-width="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 447 B

+15
View File
@@ -0,0 +1,15 @@
{
"id": "itsaplan",
"name": "It's a Plan",
"version": "1.9.0",
"description": "It's a Plan is an open-source project management and issue tracking platform where people and AI agents work together.",
"logo": "itsaplan.svg",
"links": {
"github": "https://github.com/croffasia/itsaplan",
"website": "https://itsaplan.dev",
"docs": "https://github.com/croffasia/itsaplan/blob/main/docs/self-hosting.md"
},
"tags": ["project-management", "issue-tracking", "ai"],
"seoTitle": "Self-host It's a Plan with Dokploy",
"seoDescription": "Deploy the open-source project management and issue tracking platform It's a Plan with Dokploy."
}
+36
View File
@@ -0,0 +1,36 @@
[variables]
main_domain = "${domain}"
api_domain = "api.${main_domain}"
postgres_password = "${password:32}"
auth_secret = "${base64:32}"
encryption_key = "${base64:32}"
s3_access_key = "${username}"
s3_secret_key = "${password:32}"
[config]
env = [
"POSTGRES_USER=itsaplan",
"POSTGRES_PASSWORD=${postgres_password}",
"POSTGRES_DB=itsaplan",
"BETTER_AUTH_SECRET=${auth_secret}",
"APP_ENCRYPTION_KEY=${encryption_key}",
"API_URL=https://${api_domain}",
"APP_URL=https://${main_domain}",
"S3_BUCKET=planner-attachments",
"S3_ACCESS_KEY_ID=${s3_access_key}",
"S3_SECRET_ACCESS_KEY=${s3_secret_key}",
"S3_REGION=us-east-1",
"S3_FORCE_PATH_STYLE=true",
"BACKUP_RETENTION_DAYS=30",
]
mounts = []
[[config.domains]]
serviceName = "api"
port = 3000
host = "${api_domain}"
[[config.domains]]
serviceName = "web"
port = 3001
host = "${main_domain}"
+1 -1
View File
@@ -1,6 +1,6 @@
services:
junctio:
image: ghcr.io/k2so-dev/junctio:0.2.1
image: ghcr.io/k2so-dev/junctio:0.2.6
restart: unless-stopped
expose:
- 3000
+3 -3
View File
@@ -1,13 +1,13 @@
{
"id": "junctio",
"name": "Junctio",
"version": "0.2.1",
"version": "0.2.6",
"description": "Self-hosted MCP gateway that puts stdio, Docker and remote MCP servers behind one endpoint per client, with API key or OAuth downstream auth and upstream tokens refreshed before they expire.",
"logo": "logo.png",
"links": {
"github": "https://github.com/k2so-dev/junctio",
"website": "https://junctio.pages.dev",
"docs": "https://junctio.pages.dev/docs/"
"website": "https://junctio.org",
"docs": "https://junctio.org/docs/"
},
"tags": [
"ai",
File diff suppressed because one or more lines are too long
+18
View File
@@ -0,0 +1,18 @@
# Kortix Setup Instructions
## Deploy
1. Create the service from the **Kortix** template. Dokploy generates the database password, JWT secret and secret seed, and three domains (app, API, Supabase gateway).
2. In the service's **Environment** tab, replace each `REPLACE_ME`:
- `PLATINUM_API_KEY`: Platinum API key. Kortix agents run in Platinum cloud sandboxes; get a key at https://platinum.dev.
- `KORTIX_ADMIN_EMAIL`: Email of the first admin. Sign up with this address right after the deploy finishes.
3. In the **Domains** tab, edit each of the three domains (frontend, api, kong), turn on **HTTPS** and choose **Let's Encrypt**. The template's addresses use `https://`; if you swap in your own domains, update `PUBLIC_URL`, `API_PUBLIC_URL` and `SUPABASE_PUBLIC_URL` to match.
4. Deploy and wait until every container is healthy. The first deploy takes a few minutes while the images download and Postgres initializes.
## After the first deploy
1. Open the Kortix app's address and **sign up with the admin email** right away.
2. Signed in as the admin, open **`/admin/git`** and connect GitHub. Install the GitHub App on an organization so Kortix can create project repos. On a personal account, create each repo on GitHub first, then import it.
3. Add your model key in **Settings → Model**.
4. Create a project and give an agent its first task.
5. To stop others from signing up, set `DISABLE_SIGNUP=true` and redeploy.
+3
View File
@@ -0,0 +1,3 @@
<svg width="30" height="25" viewBox="0 0 30 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.5614 24.916H29.8268C29.8268 19.6306 26.9378 15.0039 22.6171 12.4587C26.9377 9.91355 29.8267 5.28685 29.8267 0.00146484H25.5613C25.5613 5.00287 21.8906 9.18692 17.0654 10.1679V0.00146484H12.8005V10.1679C7.9526 9.20401 4.3046 5.0186 4.3046 0.00146484H0.0391572C0.0391572 5.28685 2.92822 9.91355 7.24884 12.4587C2.92818 15.0039 0.0390625 19.6306 0.0390625 24.916H4.30451C4.30451 19.8989 7.95259 15.7135 12.8005 14.7496V24.9206H17.0654V14.7496C21.9133 15.7134 25.5614 19.8989 25.5614 24.916Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 620 B

+20
View File
@@ -0,0 +1,20 @@
{
"id": "kortix",
"name": "Kortix",
"version": "0.14.19",
"description": "Kortix is an open source AI Operating System: AI agents that do real work for your team, with your own models and tools. Self-host it on your own server with Dokploy in a few clicks.",
"logo": "kortix.svg",
"links": {
"github": "https://github.com/kortix-ai/suna",
"website": "https://kortix.com",
"docs": "https://kortix.com/docs/developers#self-hosting"
},
"tags": [
"ai",
"agents",
"automation",
"self-hosted"
],
"seoTitle": "Kortix: Self-Hosted AI Agents & Docker Compose",
"seoDescription": "Open source AI agents for your team. Self-host Kortix via Docker Compose on Dokploy: your data, your model keys."
}
+38
View File
@@ -0,0 +1,38 @@
[variables]
main_domain = "${domain}"
api_domain = "${domain}"
supabase_domain = "${domain}"
postgres_password = "${password:64}"
jwt_secret = "${password:128}"
kortix_secret_seed = "${password:64}"
[config]
env = [
"POSTGRES_PASSWORD=${postgres_password}",
"JWT_SECRET=${jwt_secret}",
"KORTIX_SECRET_SEED=${kortix_secret_seed}",
"PUBLIC_URL=https://${main_domain}",
"API_PUBLIC_URL=https://${api_domain}",
"SUPABASE_PUBLIC_URL=https://${supabase_domain}",
"PLATINUM_API_KEY=REPLACE_ME",
"KORTIX_ADMIN_EMAIL=REPLACE_ME",
"DISABLE_SIGNUP=false",
"EMAIL_URL=",
"EMAIL_FROM=",
"COMPOSIO_API_KEY=",
]
[[config.domains]]
serviceName = "frontend"
port = 3000
host = "${main_domain}"
[[config.domains]]
serviceName = "api"
port = 8008
host = "${api_domain}"
[[config.domains]]
serviceName = "kong"
port = 8000
host = "${supabase_domain}"
+32
View File
@@ -0,0 +1,32 @@
# The master key is generated on the first start and kept in the kuukura-keys volume, apart from the database in
# kuukura-data. After the first start, open the service terminal and run `/ku-server key show` once to back it up.
services:
kuukura:
image: ghcr.io/victorlunam/kuukura:2026.10.13
restart: unless-stopped
environment:
VAULT_DOMAIN: ${VAULT_DOMAIN}
VAULT_STORAGE: sqlite
VAULT_SQLITE_PATH: /data/kuukura.db
VAULT_KEY_PROVIDER: manual
VAULT_MASTER_KEY_FILE: /keys/master_key
VAULT_MASTER_KEY_GENERATE: "true"
VAULT_OIDC_CLIENT_ID: ${VAULT_OIDC_CLIENT_ID}
VAULT_OIDC_CLIENT_SECRET: ${VAULT_OIDC_CLIENT_SECRET}
# set to `cloudflare` when Cloudflare's proxy is in front, so the audit log sees the real client IP
VAULT_TRUSTED_PROXIES: ${VAULT_TRUSTED_PROXIES:-}
volumes:
- kuukura-data:/data
- kuukura-keys:/keys
expose:
- 8080
healthcheck:
test: ["CMD", "/ku-server", "healthcheck"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes:
kuukura-data: {}
kuukura-keys: {}
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="kuukura">
<rect width="64" height="64" rx="14" fill="#071519"/>
<rect x=".5" y=".5" width="63" height="63" rx="13.5" stroke="#EAF4F2" stroke-opacity=".14"/>
<circle cx="32" cy="32" r="20" stroke="#EAF4F2" stroke-width="4.5"/>
<path d="M32 20.5h0M32 43.5h0M20.5 32h0M43.5 32h0M23.87 23.87h0M40.13 23.87h0M23.87 40.13h0M40.13 40.13h0" stroke="#EAF4F2" stroke-width="4.2" stroke-linecap="round"/>
<circle cx="32" cy="32" r="4.6" fill="#F5A524"/>
</svg>

After

Width:  |  Height:  |  Size: 555 B

+13
View File
@@ -0,0 +1,13 @@
{
"id": "kuukura",
"name": "kuukura",
"version": "2026.10.13",
"description": "kuukura is a self-hosted secrets vault for small teams: values are decrypted only on your server, reveals require a passkey, and every access is audit-logged. Requires a Google OAuth client ID and secret, set in the Environment tab before deploying.",
"logo": "kuukura.svg",
"links": {
"github": "https://github.com/victorlunam/kuukura",
"website": "https://kuukura-main-landing.neodev-f72.workers.dev",
"docs": "https://github.com/victorlunam/kuukura/blob/main/docs/deploy.md"
},
"tags": ["security", "secrets", "self-hosted"]
}
+22
View File
@@ -0,0 +1,22 @@
# VAULT_OIDC_CLIENT_ID and VAULT_OIDC_CLIENT_SECRET are a Google OAuth client (type "Web application", redirect URI
# https://<your domain>/auth/callback). Fill them in the Environment tab before deploying: while empty, the server
# exits at startup with a message saying which setting is missing.
# Set your real domain in the Domains tab, turn HTTPS on, and set VAULT_DOMAIN to the same domain.
[variables]
main_domain = "${domain}"
[config]
mounts = []
env = [
"VAULT_DOMAIN=${main_domain}",
# Required: a Google OAuth client (see the comment at the top).
"VAULT_OIDC_CLIENT_ID=",
"VAULT_OIDC_CLIENT_SECRET=",
# Optional: `cloudflare` when Cloudflare's proxy is in front, so the audit log sees the real client IP.
"VAULT_TRUSTED_PROXIES=",
]
[[config.domains]]
serviceName = "kuukura"
port = 8080
host = "${main_domain}"

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