Compare commits

..
18 Commits
Author SHA1 Message Date
145076b142 feat: add template for Tera Router AI gateway (#1319)
* feat: add template for Tera Router AI gateway

* fix(terarouter): run healthcheck through a shell so '|| exit 1' works

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-13 07:47:55 +01:00
570815f14b feat: add Life Console template (#1321)
* feat: add Life Console template

* fix(life-console): use env array and plain port in template.toml

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-13 07:47:47 +01:00
Maciej ZygmuntandGitHub 82b0cd130e feat: add Excaliself template (#1322) 2026-11-13 07:47:16 +01:00
Yusuf GündoğduandGitHub 4d0ddf0741 libredb-studio: bump to 1.3.0 (#1318) 2026-11-13 07:47:03 +01:00
Tiziano ZontaandGitHub d88aa7f6ea feat(blueprints): add OpenBao template (#1320)
* feat(blueprints): add OpenBao template

* fix(openbao): use https cluster_addr for raft
2026-11-13 07:46:55 +01:00
4f0ddfb507 chore(senddock): update repository and image references to SendDock org (#1314)
* chore(senddock): update repository and image references to SendDock org

* fix(senddock): keep the image pinned (0.8.3) after the registry move

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-12 02:02:25 +01:00
AJ Taylor, CSM, PMI-ACPandGitHub 3ea0dd6d22 feat(blueprints): add Frameleaf template (FL-1221) (#1298) 2026-11-12 01:57:58 +01:00
KacperandGitHub 3b2a9d9d4e chore(mcpspan): bump to 0.14.0 (#1300) 2026-11-12 01:57:40 +01:00
37b5cc3267 fix(itsaplan): use configurable v1.10.0 image version (#1296)
* fix(itsaplan): use configurable v1.10.0 image version

* Fix version format in meta.json

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-11-12 01:57:06 +01:00
Murtaza ShajapurwalaandGitHub abdb388392 feat: add KiviDB template (#1299) 2026-11-12 01:52:35 +01:00
da527f5c9c feat(blueprints): add Ferret (#1301)
Co-authored-by: Wade Benson <wade@cryptogeek.org>
2026-11-12 01:49:31 +01:00
+29 e4f1a2d954 feat: add Homelable template (#1303)
* New Version (#1295)

* 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>

* 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

* cachkey: bump version to 0.2.0 (#1277)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 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>

* 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>

* Legejo: update to 1.33.0 (#1273)

Co-authored-by: Markus Rådberg <3110202+jularboolean@users.noreply.github.com>

* 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

* 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

* 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

* feat: add PostgreSQL with 80+ extensions (postgres-extensor) template (#1267)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 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>

* feat: add OpenTodo template (#1265)

* feat: add OpenTodo template

* OpenTodo: update to 0.12.0

* OpenTodo 0.13.0

* fix(ponos): update template to 0.2.0 and move to the hermez-agency image (#1263)

* Add Reelsaw template (#1262)

Signed-off-by: dillonstreator <dillonstreator@gmail.com>

* feat(blueprints): add maintenant template (#1261)

* fix(docuseal): correct PostgreSQL 18 volume mount (#1290)

* feat(blueprints): add Zeqqat template (#1288)

* feat(umami): bump to 3.4.1 (#1287)

* feat(umami): bump to 3.4.1

* feat(umami): bump to 3.4.1

* 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

* 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.

* Update OpenTodo to 0.19.0 (#1281)

* feat(blueprints): add itsaplan template (#1282)

* 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>

* feat: add Customermates template (#1279)

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* 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.

* 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>

* Add Container Monitoring template (cAdvisor, Prometheus, Grafana) (#1257)

* Add container-monitoring template

* Default container detail dashboard to a single container

* Update SolidPing to 0.41.0 (#1256)

* 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.

* 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>

* 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>

* 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>

* 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.

* 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

* 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>

* 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.

* Add kortix deployment to the list of templates (#1294)

* 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>

* 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)

---------

Signed-off-by: William Desportes <williamdes@wdes.fr>
Signed-off-by: dillonstreator <dillonstreator@gmail.com>
Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Madhav Gupta <114633095+M4dhav@users.noreply.github.com>
Co-authored-by: Félicio de SOUZA <feliciodsa.dev@gmail.com>
Co-authored-by: Ivan Ficko <ivan.ficko@outlook.com>
Co-authored-by: William Desportes <williamdes@wdes.fr>
Co-authored-by: Jularbool <markus@radberg.org>
Co-authored-by: Markus Rådberg <3110202+jularboolean@users.noreply.github.com>
Co-authored-by: Théo Meunier <62860058+TheoMeunier@users.noreply.github.com>
Co-authored-by: Kacper <kcprzaton@gmail.com>
Co-authored-by: Web Artisan <ad4ptec@gmail.com>
Co-authored-by: Jose Garcia <47431411+ruxwez@users.noreply.github.com>
Co-authored-by: Ulaş Kalaycı <108589275+ulsklyc@users.noreply.github.com>
Co-authored-by: Ulas Kalayci <ulas.kalayci@icloud.com>
Co-authored-by: Raffaele Pizzari <raffaele.pizzari@gmail.com>
Co-authored-by: Cardona <18017015+SalvadorCardona@users.noreply.github.com>
Co-authored-by: dillonstreator <dillonstreator@gmail.com>
Co-authored-by: Benjamin Touchard <benjamin@kolapsis.com>
Co-authored-by: JRK <jessym.reziga@gmail.com>
Co-authored-by: Perminus Gaita <35493600+Perminus-Gaita@users.noreply.github.com>
Co-authored-by: Lezetho <126505858+lezetho@users.noreply.github.com>
Co-authored-by: Yusuf Gündoğdu <yusufgundogdu555@gmail.com>
Co-authored-by: Waleed <waleedi825@gmail.com>
Co-authored-by: Benjamin Wagner <72468969+benjiwagner@users.noreply.github.com>
Co-authored-by: Carlos Dorneles <duducp2013@gmail.com>
Co-authored-by: Alien <hi@aien.me>
Co-authored-by: Ghazi Ben Dahmane <ghazi.ben.dahmane@gmail.com>
Co-authored-by: Florent Clairambault <florent.clairambault@gmail.com>
Co-authored-by: Thet Hmuu <thethmuueinsoe@gmail.com>
Co-authored-by: Andre Carbajal Vargas <carbajalvargasandre@gmail.com>
Co-authored-by: Stefan Ernst <ernst.stefan@icloud.com>
Co-authored-by: Noah <70114451+zFl4wless@users.noreply.github.com>
Co-authored-by: ArseniyDev <41956571+ArseniyX@users.noreply.github.com>
Co-authored-by: Yota Hamada <yohamta@gmail.com>
Co-authored-by: Kortix SEO <347261315+Kortix-seo@users.noreply.github.com>
Co-authored-by: Angel S. Moreno <angelxmoreno@gmail.com>

* feat: add Homelable template

---------

Signed-off-by: William Desportes <williamdes@wdes.fr>
Signed-off-by: dillonstreator <dillonstreator@gmail.com>
Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Madhav Gupta <114633095+M4dhav@users.noreply.github.com>
Co-authored-by: Félicio de SOUZA <feliciodsa.dev@gmail.com>
Co-authored-by: Ivan Ficko <ivan.ficko@outlook.com>
Co-authored-by: William Desportes <williamdes@wdes.fr>
Co-authored-by: Jularbool <markus@radberg.org>
Co-authored-by: Markus Rådberg <3110202+jularboolean@users.noreply.github.com>
Co-authored-by: Théo Meunier <62860058+TheoMeunier@users.noreply.github.com>
Co-authored-by: Kacper <kcprzaton@gmail.com>
Co-authored-by: Web Artisan <ad4ptec@gmail.com>
Co-authored-by: Jose Garcia <47431411+ruxwez@users.noreply.github.com>
Co-authored-by: Ulaş Kalaycı <108589275+ulsklyc@users.noreply.github.com>
Co-authored-by: Ulas Kalayci <ulas.kalayci@icloud.com>
Co-authored-by: Raffaele Pizzari <raffaele.pizzari@gmail.com>
Co-authored-by: Cardona <18017015+SalvadorCardona@users.noreply.github.com>
Co-authored-by: dillonstreator <dillonstreator@gmail.com>
Co-authored-by: Benjamin Touchard <benjamin@kolapsis.com>
Co-authored-by: JRK <jessym.reziga@gmail.com>
Co-authored-by: Perminus Gaita <35493600+Perminus-Gaita@users.noreply.github.com>
Co-authored-by: Lezetho <126505858+lezetho@users.noreply.github.com>
Co-authored-by: Yusuf Gündoğdu <yusufgundogdu555@gmail.com>
Co-authored-by: Benjamin Wagner <72468969+benjiwagner@users.noreply.github.com>
Co-authored-by: Carlos Dorneles <duducp2013@gmail.com>
Co-authored-by: Alien <hi@aien.me>
Co-authored-by: Ghazi Ben Dahmane <ghazi.ben.dahmane@gmail.com>
Co-authored-by: Florent Clairambault <florent.clairambault@gmail.com>
Co-authored-by: Thet Hmuu <thethmuueinsoe@gmail.com>
Co-authored-by: Andre Carbajal Vargas <carbajalvargasandre@gmail.com>
Co-authored-by: Stefan Ernst <ernst.stefan@icloud.com>
Co-authored-by: Noah <70114451+zFl4wless@users.noreply.github.com>
Co-authored-by: ArseniyDev <41956571+ArseniyX@users.noreply.github.com>
Co-authored-by: Yota Hamada <yohamta@gmail.com>
Co-authored-by: Kortix SEO <347261315+Kortix-seo@users.noreply.github.com>
Co-authored-by: Angel S. Moreno <angelxmoreno@gmail.com>
Co-authored-by: Mauricio Siu <siumauricio@icloud.com>
2026-11-12 01:49:25 +01:00
Benjamin NussbaumandGitHub 56db97bed7 feat(blueprint): update trmnl-larapaper template (#1302)
* feat(blueprint): update trmnl-larapaper template

* feat(blueprint): update trmnl-larapaper template
2026-11-12 01:48:54 +01:00
Brahim RizqandGitHub ce8bc7d253 feat: add EmDash CMS template (#1308) 2026-11-12 01:45:43 +01:00
zaadevofcandGitHub 98b927efc9 feat: add Satgas template (#1306)
* feat: add Satgas template

* chore: bump Satgas to 0.1.12

* chore: bump Satgas to 0.1.13
2026-11-12 01:41:56 +01:00
Karthik KamalakannanandGitHub 38fa59288b Add OpenFault template (#1310) 2026-11-12 01:41:15 +01:00
ripabakandGitHub a6459ace66 feat: add freeframe template (#1307)
* feat(blueprint): add freeframe

* fix(blueprint): support performance adjustment in env

* fix(blueprints/freeframe): ship tuning settings as active env

Dokploy parses template.toml into key/value pairs only, so a commented knob
never reaches the Environment tab and cannot be uncommented there. Set the six
tuning variables to FreeFrame's own defaults instead (TRANSCODING_CONCURRENCY,
TRANSCODER_CPU_LIMIT, TRANSCODER_QUALITIES, TRANSCODER_SOURCE_COPY, API_WORKERS,
UPLOAD_CONCURRENCY), so they arrive editable after import; docker-compose.yml
already defaults each one, so deleting a variable falls back to the same value.
The comments stay as documentation for repo readers, and instructions/testing
now describe editing the Environment tab rather than uncommenting a line.
2026-11-12 01:41:08 +01:00
N. FajriandGitHub 997f0fd9e9 feat: add template for nakama ai agent (#1309) 2026-11-12 01:41:06 +01:00
72 changed files with 1703 additions and 20 deletions
+36
View File
@@ -0,0 +1,36 @@
services:
emdash:
build:
context: https://github.com/emdash-cms/emdash.git#10beb8c5a99e41c3a8554b09ac5123c4dddc8d11
restart: unless-stopped
environment:
EMDASH_SITE_URL: "${EMDASH_SITE_URL:?Set the public HTTPS origin}"
EMDASH_ENCRYPTION_KEY: "${EMDASH_ENCRYPTION_KEY:-}"
EMDASH_TRUSTED_PROXY_HEADERS: x-forwarded-for
HOST: 0.0.0.0
PORT: "4321"
expose:
- "4321"
volumes:
- emdash-data:/app/data
- emdash-uploads:/app/uploads
healthcheck:
test:
- CMD
- node
- -e
- "fetch('http://127.0.0.1:4321/_emdash/api/setup/status').then(r => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
start_period: 60s
interval: 30s
timeout: 10s
retries: 3
stop_grace_period: 30s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
emdash-data:
emdash-uploads:
+12
View File
@@ -0,0 +1,12 @@
<svg width="75" height="75" viewBox="0 0 75 75" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="3" width="69" height="69" rx="10.518" stroke="url(#border)" stroke-width="6"/>
<rect x="18" y="34" width="39.3661" height="6.56101" fill="url(#dash)"/>
<defs>
<linearGradient id="border" x1="-43" y1="124" x2="92" y2="-42" gradientUnits="userSpaceOnUse">
<stop stop-color="#0F006B"/><stop offset=".25" stop-color="#911475"/><stop offset=".5" stop-color="#F6821F"/><stop offset=".75" stop-color="#FFE9CB"/><stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="dash" x1="91" y1="27" x2="28" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/><stop offset=".35" stop-color="#FFF8EE"/><stop offset=".7" stop-color="#FBAD41"/><stop offset="1" stop-color="#FF6633"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 859 B

+38
View File
@@ -0,0 +1,38 @@
# Environment variables
Checked against [EmDash 1.4.0](https://github.com/emdash-cms/emdash/tree/10beb8c5a99e41c3a8554b09ac5123c4dddc8d11), its root Dockerfile, Blog Astro configuration, and runtime environment readers. Compose passes the variables below into the process; editing an unused variable in Dokploy does not change the compiled Astro configuration.
## Operator settings
| Variable | Template value | Meaning |
| --- | --- | --- |
| `EMDASH_SITE_URL` | `https://<generated-domain>` | Required public HTTP(S) origin, without a path. Use the final HTTPS origin for production setup, passkeys, CSRF, and absolute URLs. Keep it aligned with the Dokploy domain. |
| `EMDASH_ENCRYPTION_KEY` | Empty | Optional for the stock site. Required for encrypted plugin-secret settings. Use `npx --yes emdash@1.4.0 secrets generate`; the format is `emdash_enc_v1_` plus 43 canonical base64url characters encoding 32 random bytes. For rotation, list the new key first followed by older keys separated by commas. Preserve every key still used by saved secrets. |
No administrator password, database password, or provider key is generated by this template. The setup wizard creates the administrator's passkey. EmDash generates its preview HMAC secret and commenter-IP salt into SQLite on first use. Sessions and API tokens are generated by the application.
## Fixed Compose and build values
| Setting | Value | Meaning |
| --- | --- | --- |
| `HOST` / `PORT` | `0.0.0.0` / `4321` | Node listener, matching routing and the health check. |
| `EMDASH_TRUSTED_PROXY_HEADERS` | `x-forwarded-for` | Trust the controlled Traefik proxy for client IPs. Keep the app isolated from direct/untrusted connections. Change Compose if another proxy policy is needed. |
| Database | `file:./data/data.db` | Fixed by the upstream Dockerfile's Blog configuration. `DATABASE_URL` and `DATABASE_PATH` do not switch this build's database. |
| Media | `./uploads` | Fixed local adapter with public route `/_emdash/api/media/file`. S3 variables alone do not switch storage. |
| `NODE_OPTIONS` | Build-stage `--max-old-space-size=4096` | Upstream compiler heap allowance; not a runtime memory reservation. No secrets are passed as build arguments. |
The upstream Dockerfile has no argument to supply the public origin during compilation. `EMDASH_SITE_URL` is supplied at runtime; local media therefore uses original files rather than build-configured image optimization. Change the upstream project/build if you need that optimization or another theme.
## Optional upstream controls
These are not generated or forwarded by this template. To use one, add an explicit Compose environment mapping and redeploy after reading its upstream contract:
- `EMDASH_PREVIEW_SECRET`: overrides the database-generated preview secret. Changing it invalidates existing preview links.
- `EMDASH_IP_SALT`: overrides the database-generated commenter-IP salt. Changing it resets rate-limit continuity. Legacy `EMDASH_AUTH_SECRET` is only a salt fallback, not a login requirement.
- `EMDASH_MIGRATIONS_MODE`: defaults to `auto`; `check` refuses pending migrations, and `manual` leaves migration management to the operator. Do not change it without an explicit migration procedure.
- `EMDASH_ALLOWED_ORIGINS`: comma-separated HTTP(S) origins for advanced authentication deployments, subject to the passkey relying-party domain rules. The single-domain template needs none.
- `EMDASH_TURNSTILE_SECRET_KEY`: optional Cloudflare Turnstile secret for public comments. Unset means no Turnstile enforcement. Also configure the matching public widget/site key in your theme before enabling it; a secret alone rejects comments that have no valid token.
Email, OAuth, AI, remote database/storage credentials, and plugin settings are optional integrations, not installation requirements. Many also require installing/configuring the corresponding adapter or plugin and rebuilding. See [instructions.md](instructions.md) before adding them. Cloudflare bindings and Wrangler settings do not apply to this Node build.
`EMDASH_QUERY_LOG=1` enables diagnostic query logging; `EMDASH_PSEUDO_LOCALE=1` is a development UI test option. Both are unset here. Development bypass/reset/email endpoints are compiled out of production and are not setup mechanisms. `EMDASH_URL`, `EMDASH_TOKEN`, and `EMDASH_HEADERS` are CLI client inputs, not server deployment settings.
+37
View File
@@ -0,0 +1,37 @@
# EmDash
This template builds EmDash **1.4.0** from its [release commit](https://github.com/emdash-cms/emdash/tree/10beb8c5a99e41c3a8554b09ac5123c4dddc8d11), using the upstream Dockerfile. It packages the official Blog theme, SQLite, and local media storage in one Node.js service. The scheduler runs inside that service; a separate database or worker is unnecessary.
## First deployment
1. Import the template. In Dokploy, assign your final domain to service `emdash`, port `4321`, and enable HTTPS with a valid certificate.
2. Set `EMDASH_SITE_URL` to that exact public origin, for example `https://blog.example.com`, without a path. The generated value uses the initial Dokploy domain. Update it if you change the domain.
3. Deploy. The first build compiles the upstream monorepo and downloads dependencies and fonts. Allow several minutes and at least 4 GB of build memory, plus memory for Dokploy and other services. Subsequent builds can reuse Docker's cache.
4. Open `https://<your-domain>/_emdash/admin/` promptly, complete the site setup, and register your administrator passkey. The first visitor can claim an unconfigured site; keep access restricted until setup is complete. Use your final HTTPS domain from the start: passkeys are bound to it. No default password or email provider is required.
5. Create and publish a post, then open its public page. Upload a media file and verify its URL before adding real content.
Use Dokploy's isolated deployment option. Only Traefik should reach the app: the template trusts its `X-Forwarded-For` header for client-IP rate limiting. No host port or custom network is needed. Run one replica with these local SQLite and filesystem volumes.
## Optional features
The stock Blog build works without cloud accounts, AI keys, or email credentials. Email delivery, magic links, and email recovery require a provider plugin; no production console email provider exists. Register another passkey and keep recovery access before relying on the site.
Plugins with encrypted secret settings require `EMDASH_ENCRYPTION_KEY`. Generate it with the official command on Node.js 22.23.4 or later:
```sh
npx --yes emdash@1.4.0 secrets generate
```
Copy the complete `emdash_enc_v1_...` value into Dokploy's Environment tab and redeploy. Keep it private and back it up separately. The template leaves it empty because Dokploy's generic password/base64 helpers do not produce this application's required key format.
Adding email or authentication providers, changing the theme, using PostgreSQL/S3, or enabling sandboxed plugins requires a custom Astro project/build. Setting provider credentials alone does not add those integrations to the stock Blog build. Its configuration includes no plugin sandbox runner. See [Node.js deployment](https://docs.emdashcms.com/deployment/nodejs/) and [plugin sandbox](https://docs.emdashcms.com/deployment/plugin-sandbox/).
## Persistence, backups, and upgrades
- `emdash-data` stores `/app/data/data.db`, including content, users, passkeys, settings, and automatically generated preview/IP secrets.
- `emdash-uploads` stores `/app/uploads`, including media and application-generated backup artifacts.
- Node's default filesystem session store is outside these volumes. Container recreation can sign users out; register a passkey so you can sign in again.
- Back up both volumes and any encryption key off-server. For a consistent offline SQLite backup, stop writes and copy the database together with any `-wal` and `-shm` files. Restore into an isolated installation and verify content, login, and media. The app's JSON archives are not a full restorable database backup; see [upstream recovery guidance](https://docs.emdashcms.com/guides/backups/).
- Before upgrading, take a verified recovery point and read the release notes. Change the pinned Git commit and metadata together, then rebuild. Pending core migrations run on the first request; a code rollback alone does not undo them. Keep the matching old build, database, media, and keys for recovery.
The upstream Dockerfile runs as root. Named volumes are writable without a separate ownership initializer; changing the user requires testing permissions for the database, uploads, and session directory. The health check queries setup state through the database, but does not prove media writes, passkey login, scheduled publishing, or optional integrations work. The build has no public origin at build time, so local media is served without Astro image optimization; the runtime origin still configures URLs and authentication.
+13
View File
@@ -0,0 +1,13 @@
{
"id": "emdash",
"name": "EmDash",
"version": "1.4.0",
"description": "EmDash is an Astro-native CMS with a visual editor, structured content, media library, and passkey authentication. This template builds the official blog site with persistent SQLite and local media storage.",
"logo": "emdash.svg",
"links": {
"github": "https://github.com/emdash-cms/emdash",
"website": "https://emdashcms.com/",
"docs": "https://docs.emdashcms.com/"
},
"tags": ["cms", "blog", "publishing"]
}
+13
View File
@@ -0,0 +1,13 @@
[variables]
main_domain = "${domain}"
[config]
env = [
"EMDASH_SITE_URL=https://${main_domain}",
"EMDASH_ENCRYPTION_KEY=",
]
[[config.domains]]
serviceName = "emdash"
port = 4321
host = "${main_domain}"
+15
View File
@@ -0,0 +1,15 @@
# Verification checklist
Use disposable data and your own final HTTPS domain. Never run development reset endpoints against an existing installation.
1. Run metadata, Compose, and TOML validation and build the template catalog. Confirm that the main service, metadata ID, domain target, and logo agree.
2. Import into a fresh Dokploy Compose service with isolated deployment enabled. Verify the pinned Git build, port `4321`, both named volumes, and the generated public-origin value. Enable HTTPS and update `EMDASH_SITE_URL` if the domain changes.
3. Deploy without optional keys or providers. Wait for a new successful deployment and a healthy container. Check setup status, the version endpoint, logs, and the public site through Traefik.
4. In a fresh browser context, complete setup and register a passkey over HTTPS. Verify the setup nonce cookie is Secure, HttpOnly, and SameSite=Strict. Sign out and sign in again with that passkey. Confirm anonymous access to private API routes is refused and production dev-bypass/reset routes cannot create sessions or reset data.
5. Create a post, save a draft, reload it, publish, and open the public page. Verify draft content remains private. Upload a disposable image, open its media URL, and compare its bytes. Schedule another post and await actual publication without manual intervention.
6. Remove only the application container and redeploy through Dokploy. Sign in again if needed. Verify the administrator/passkey, published and draft content, uploaded media, and settings survived.
7. Verify the health check fails when Node cannot serve requests and recovers afterward. Check database/media operations separately; an HTTP health response alone is insufficient.
8. Copy the Base64 from the PR preview catalog, decode it, and compare raw Compose/TOML to the committed files. Import that exact payload into a second fresh Dokploy service and repeat setup, publish, media, and persistence checks. Ensure its volumes and administrator are independent.
9. Before using plugins, generate a valid encryption key with the official CLI. Verify the integration with its own provider. This stock build includes neither an email provider nor a sandbox runner; do not claim external delivery or sandbox execution from basic CMS tests.
Record the tested platform, checks, screenshots, and untested boundaries in the PR. Keep credentials, passkey private material, session state, and local fixture details out of public evidence.
+42
View File
@@ -0,0 +1,42 @@
services:
excaliself:
image: ghcr.io/maciekzygmunt/excaliself:0.11.1
restart: unless-stopped
expose:
- 3000
environment:
DATABASE_URL: postgresql://excaliself:${POSTGRES_PASSWORD}@db:5432/excaliself
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
BASE_URL: ${BASE_URL}
# Optional single sign-on, see https://excaliself.com/docs/single-sign-on/
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_ID: ${MICROSOFT_TENANT_ID:-}
OIDC_ISSUER: ${OIDC_ISSUER:-}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
OIDC_NAME: ${OIDC_NAME:-}
SSO_ALLOWED_DOMAINS: ${SSO_ALLOWED_DOMAINS:-}
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: excaliself
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: excaliself
volumes:
- excaliself-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U excaliself -d excaliself"]
interval: 5s
timeout: 5s
retries: 10
volumes:
excaliself-db:
+8
View File
@@ -0,0 +1,8 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="113" fill="#6F66C9"/>
<g transform="translate(129.28 112) scale(11.52)" fill="#FFFFFF">
<path d="M1.10614 16.3305C0.382001 16.5374 -0.342137 16.0202 0.175104 14.7788L1.83028 7.33052C2.45097 5.67535 4.62338 6.19259 4.20959 7.84776L2.03717 15.5029C1.93373 16.0202 1.51993 16.3305 1.10614 16.3305Z"/>
<path d="M6.79573 19.3305C5.76125 20.1581 4.31297 19.0202 5.14056 17.7788L17.1406 0.813272C18.5888 -1.0488 21.0716 0.606376 19.9337 2.57189L6.79573 19.3305Z"/>
<path d="M10.4165 24.0891C9.27854 23.9857 8.7613 22.5374 10.0027 22.0202L19.1061 17.6753C21.0716 16.8478 22.2096 19.3305 20.3475 20.0547L10.4165 24.0891Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 762 B

+18
View File
@@ -0,0 +1,18 @@
{
"id": "excaliself",
"name": "Excaliself",
"version": "0.11.1",
"description": "Self-hosted, open-source alternative to Excalidraw+ for teams. Shared drawings, folders, live collaboration, comments and SSO, stored in Postgres.",
"logo": "excaliself.svg",
"links": {
"github": "https://github.com/maciekzygmunt/excaliself",
"website": "https://excaliself.com/",
"docs": "https://excaliself.com/docs/"
},
"tags": [
"whiteboard",
"diagrams",
"collaboration",
"self-hosted"
]
}
+18
View File
@@ -0,0 +1,18 @@
[variables]
main_domain = "${domain}"
postgres_password = "${password:32}"
auth_secret = "${password:64}"
[config]
# Use https:// in BASE_URL once the domain has HTTPS. Sign-in only works from this URL.
env = [
"BASE_URL=http://${main_domain}",
"BETTER_AUTH_SECRET=${auth_secret}",
"POSTGRES_PASSWORD=${postgres_password}",
]
mounts = []
[[config.domains]]
serviceName = "excaliself"
port = 3000
host = "${main_domain}"
+18
View File
@@ -0,0 +1,18 @@
services:
ferret:
image: ghcr.io/ferret-project/ferret:2026.11.4
restart: unless-stopped
environment:
# The address people (and AI assistants over MCP) use: Dokploy's domain for this app.
PUBLIC_URL: ${PUBLIC_URL}
# Dokploy's Traefik is the proxy in front: believe its X-Forwarded-For.
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
# AUTH_SECRET, FILES_KEY and the push keys are made by Ferret on first start and kept in /data.
# Optional settings (email, AI, backups, single sign-on): https://github.com/ferret-project/ferret/blob/main/apps/docs/hosting/configuration.md
volumes:
- ferret-data:/data
expose:
- 8787
volumes:
ferret-data:
+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" fill="#7c4a2d"/><g fill="#fdf9f4"><ellipse cx="32" cy="42.5" rx="12.5" ry="10"/><ellipse cx="16.5" cy="29.5" rx="4.6" ry="6.2" transform="rotate(-22 16.5 29.5)"/><ellipse cx="26" cy="19.5" rx="5" ry="6.6" transform="rotate(-8 26 19.5)"/><ellipse cx="38" cy="19.5" rx="5" ry="6.6" transform="rotate(8 38 19.5)"/><ellipse cx="47.5" cy="29.5" rx="4.6" ry="6.2" transform="rotate(22 47.5 29.5)"/></g></svg>

After

Width:  |  Height:  |  Size: 500 B

+18
View File
@@ -0,0 +1,18 @@
{
"id": "ferret",
"name": "Ferret",
"version": "2026.11.4",
"description": "Self-hosted home inventory: find anything you own. Photos, barcodes, QR labels, warranties, maintenance and an insurance report, with a REST API, an MCP server and optional AI on your own models.",
"logo": "ferret.svg",
"links": {
"github": "https://github.com/ferret-project/ferret",
"website": "https://github.com/ferret-project/ferret",
"docs": "https://github.com/ferret-project/ferret/tree/main/apps/docs/hosting"
},
"tags": [
"inventory",
"home",
"self-hosted",
"mcp"
]
}
+14
View File
@@ -0,0 +1,14 @@
[variables]
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "ferret"
port = 8787
host = "${main_domain}"
[config.env]
PUBLIC_URL = "http://${main_domain}"
TRUSTED_PROXIES = "172.16.0.0/12"
+63
View File
@@ -0,0 +1,63 @@
services:
frameleaf:
image: ghcr.io/frameleaf/frameleaf-server:frameleaf-v1.2.3
restart: unless-stopped
expose:
- 2283
volumes:
# Originals, thumbnails and encoded video
- frameleaf-library:/data
# The Studio render worker enrols from here (read-only on machine learning)
- frameleaf-studio-worker:/run/frameleaf/studio-worker
environment:
DB_HOSTNAME: frameleaf-database
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE_NAME: ${DB_DATABASE_NAME}
FRAMELEAF_MACHINE_LEARNING_URL: http://frameleaf-machine-learning:3003
TZ: ${TZ}
depends_on:
frameleaf-database:
condition: service_healthy
# Encrypted Locked files are decrypted into /dev/shm for thumbnails, video and ML
shm_size: 2gb
stop_grace_period: 10s
healthcheck:
disable: false
frameleaf-machine-learning:
image: ghcr.io/frameleaf/frameleaf-machine-learning:frameleaf-v1.2.3
restart: unless-stopped
volumes:
- frameleaf-model-cache:/cache
- frameleaf-studio-worker:/run/frameleaf/studio-worker:ro
# No database credentials here: the inference worker only needs to reach the server
environment:
FRAMELEAF_STUDIO_WORKER_SERVER_URL: http://frameleaf:2283
TZ: ${TZ}
healthcheck:
disable: false
frameleaf-database:
image: ghcr.io/frameleaf/frameleaf-postgres:19beta4-pgvector0.8.7@sha256:7b155ed85302ab66c1022309f1f70f0b79f98f70bd52af8d7d3611a0cbf08cf0
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: "--data-checksums"
volumes:
# PostgreSQL 18+ layout: mount /var/lib/postgresql, not /var/lib/postgresql/data
- frameleaf-postgres:/var/lib/postgresql
shm_size: 128mb
healthcheck:
test: ["CMD-SHELL", "pg_isready --host=127.0.0.1 --dbname=\"$${POSTGRES_DB}\" --username=\"$${POSTGRES_USER}\" && /usr/local/bin/healthcheck.sh"]
interval: 30s
start_period: 5m
start_interval: 5s
volumes:
frameleaf-library:
frameleaf-model-cache:
frameleaf-postgres:
frameleaf-studio-worker:
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+23
View File
@@ -0,0 +1,23 @@
{
"id": "frameleaf",
"name": "Frameleaf",
"version": "1.2.3",
"description": "Frameleaf is a self-hosted photo and video library with native apps, private face and smart search, photo and video editing, and optional cloud backup. It is an open-source (AGPLv3) fork of Immich and is currently in beta.",
"logo": "frameleaf.png",
"links": {
"github": "https://github.com/Frameleaf/frameleaf-app",
"website": "https://frameleaf.app/",
"docs": "https://frameleaf.app/docs/start/introduction/"
},
"tags": [
"photos",
"videos",
"media",
"backup",
"photo-management",
"self-hosted"
],
"seoTitle": "Frameleaf Docker Compose Setup",
"seoDescription": "Self-hosted photo and video library (beta, fork of Immich) with face and smart search, photo and video editing. Deploy with Docker Compose on Dokploy in minutes.",
"newConfig": true
}
+17
View File
@@ -0,0 +1,17 @@
[variables]
main_domain = "${domain}"
db_password = "${password:32}"
[config]
env = [
"DB_USERNAME=frameleaf",
"DB_PASSWORD=${db_password}",
"DB_DATABASE_NAME=frameleaf",
"TZ=Etc/UTC",
]
mounts = []
[[config.domains]]
serviceName = "frameleaf"
port = 2283
host = "${main_domain}"
+301
View File
@@ -0,0 +1,301 @@
# FreeFrame — collaborative media review and approval for images, audio and
# video (a self-hosted Frame.io alternative).
#
# Adapted from the upstream production compose files:
# docker-compose.prod.yml, docker-compose.images.yml and
# docker-compose.storage.yml in https://github.com/Techiebutler/freeframe
#
# Three things differ from upstream, all Dokploy-specific:
# * Dokploy's Traefik is the only reverse proxy: no `traefik` service here.
# Domains are declared in template.toml, not in this file.
# * The API registers its routes WITHOUT a prefix while the web image calls
# /api/*, so the API domain needs `path = "/api"` + `stripPath = true` in
# template.toml — that is what makes both services share one domain.
# * Storage (Silo, a MinIO fork) is exposed on a second domain, because the
# browser fetches presigned S3 URLs directly and cannot resolve the
# internal `freeframe-silo` hostname.
#
# No GPU is involved anywhere: the transcoding worker detects none and encodes
# in software (x264/x265) on the CPU. Size TRANSCODING_CONCURRENCY to the cores.
#
# The API and the four Celery containers are the same image and read the same
# settings at import, so the same environment block repeats in each of them.
services:
freeframe-postgres:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- freeframe-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-freeframe}"]
interval: 10s
timeout: 5s
retries: 5
freeframe-redis:
image: redis:7-alpine
restart: unless-stopped
# Password-protected and AOF-persisted: this holds the Celery queues (video
# transcodes survive a restart) and the login magic codes.
command: >
sh -c "redis-server --requirepass \"$$REDIS_PASSWORD\" --appendonly yes"
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
volumes:
- freeframe-redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" --no-auth-warning ping"]
interval: 10s
timeout: 5s
retries: 5
# Object storage. FreeFrame has no local-filesystem backend: uploads, the
# HLS renditions, posters and avatars all live in a bucket.
freeframe-silo:
image: pgsty/silo:RELEASE.2026-09-16T00-00-00Z
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${S3_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY}
volumes:
- freeframe-silo-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
freeframe-api:
image: ghcr.io/techiebutler/freeframe-api:1.22.0
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
S3_STORAGE: ${S3_STORAGE}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION}
MAIL_PROVIDER: ${MAIL_PROVIDER}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_USE_TLS: ${SMTP_USE_TLS}
API_WORKERS: ${API_WORKERS:-4}
TRANSCODING_CONCURRENCY: ${TRANSCODING_CONCURRENCY:-2}
TRANSCODER_CPU_LIMIT: ${TRANSCODER_CPU_LIMIT:-}
TRANSCODER_QUALITIES: ${TRANSCODER_QUALITIES:-1080p,720p,360p}
TRANSCODER_SOURCE_COPY: ${TRANSCODER_SOURCE_COPY:-false}
DISABLE_DOCS: "true"
depends_on:
freeframe-postgres:
condition: service_healthy
freeframe-redis:
condition: service_healthy
freeframe-silo:
condition: service_healthy
# The schema is created on first start and upgraded on every deploy. The
# bucket and its CORS rules are created by the API itself, from
# FRONTEND_URL, so no init container is needed.
command:
- sh
- -c
- >-
cd /workspace/apps/api && alembic upgrade head && cd /workspace &&
gunicorn apps.api.main:app
-w $${API_WORKERS:-4}
-k uvicorn.workers.UvicornWorker
--bind 0.0.0.0:8000
--timeout 120
--graceful-timeout 30
--access-logfile -
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
start_period: 40s
retries: 3
# Video transcoding. CPU-bound, and the reason a 4+ core box is recommended.
freeframe-worker:
image: ghcr.io/techiebutler/freeframe-api:1.22.0
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
S3_STORAGE: ${S3_STORAGE}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION}
MAIL_PROVIDER: ${MAIL_PROVIDER}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_USE_TLS: ${SMTP_USE_TLS}
API_WORKERS: ${API_WORKERS:-4}
TRANSCODING_CONCURRENCY: ${TRANSCODING_CONCURRENCY:-2}
TRANSCODER_CPU_LIMIT: ${TRANSCODER_CPU_LIMIT:-}
TRANSCODER_QUALITIES: ${TRANSCODER_QUALITIES:-1080p,720p,360p}
TRANSCODER_SOURCE_COPY: ${TRANSCODER_SOURCE_COPY:-false}
depends_on:
freeframe-postgres:
condition: service_healthy
freeframe-redis:
condition: service_healthy
freeframe-silo:
condition: service_healthy
command: sh -c "celery -A apps.api.tasks.celery_app worker -Q transcoding -c $${TRANSCODING_CONCURRENCY:-2} --loglevel=INFO"
freeframe-email-worker:
image: ghcr.io/techiebutler/freeframe-api:1.22.0
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
S3_STORAGE: ${S3_STORAGE}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION}
MAIL_PROVIDER: ${MAIL_PROVIDER}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_USE_TLS: ${SMTP_USE_TLS}
API_WORKERS: ${API_WORKERS:-4}
TRANSCODING_CONCURRENCY: ${TRANSCODING_CONCURRENCY:-2}
TRANSCODER_CPU_LIMIT: ${TRANSCODER_CPU_LIMIT:-}
TRANSCODER_QUALITIES: ${TRANSCODER_QUALITIES:-1080p,720p,360p}
TRANSCODER_SOURCE_COPY: ${TRANSCODER_SOURCE_COPY:-false}
depends_on:
freeframe-redis:
condition: service_healthy
command: sh -c "celery -A apps.api.tasks.celery_app worker -Q email_high,email_low -c 2 --loglevel=INFO"
# Housekeeping: retention GC, stale-upload reaper, orphan sweep. Kept off the
# transcoding worker (these hold a slot for minutes) and off the email worker
# (a magic code queued behind a bucket listing is a login someone waits on).
# These tasks hard-delete past their retention windows.
freeframe-maintenance-worker:
image: ghcr.io/techiebutler/freeframe-api:1.22.0
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
S3_STORAGE: ${S3_STORAGE}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION}
MAIL_PROVIDER: ${MAIL_PROVIDER}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_USE_TLS: ${SMTP_USE_TLS}
API_WORKERS: ${API_WORKERS:-4}
TRANSCODING_CONCURRENCY: ${TRANSCODING_CONCURRENCY:-2}
TRANSCODER_CPU_LIMIT: ${TRANSCODER_CPU_LIMIT:-}
TRANSCODER_QUALITIES: ${TRANSCODER_QUALITIES:-1080p,720p,360p}
TRANSCODER_SOURCE_COPY: ${TRANSCODER_SOURCE_COPY:-false}
depends_on:
freeframe-postgres:
condition: service_healthy
freeframe-redis:
condition: service_healthy
freeframe-silo:
condition: service_healthy
command: sh -c "celery -A apps.api.tasks.celery_app worker -Q maintenance -c 1 --loglevel=INFO"
freeframe-beat:
image: ghcr.io/techiebutler/freeframe-api:1.22.0
restart: unless-stopped
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
JWT_SECRET: ${JWT_SECRET}
FRONTEND_URL: ${FRONTEND_URL}
S3_STORAGE: ${S3_STORAGE}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_BUCKET: ${S3_BUCKET}
S3_REGION: ${S3_REGION}
MAIL_PROVIDER: ${MAIL_PROVIDER}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}
SMTP_USE_TLS: ${SMTP_USE_TLS}
API_WORKERS: ${API_WORKERS:-4}
TRANSCODING_CONCURRENCY: ${TRANSCODING_CONCURRENCY:-2}
TRANSCODER_CPU_LIMIT: ${TRANSCODER_CPU_LIMIT:-}
TRANSCODER_QUALITIES: ${TRANSCODER_QUALITIES:-1080p,720p,360p}
TRANSCODER_SOURCE_COPY: ${TRANSCODER_SOURCE_COPY:-false}
depends_on:
freeframe-redis:
condition: service_healthy
command: sh -c "celery -A apps.api.tasks.celery_app beat --loglevel=INFO -s /tmp/celerybeat-schedule"
freeframe:
image: ghcr.io/techiebutler/freeframe-web:1.22.0
restart: unless-stopped
environment:
# Server-to-server, for the branding Next renders into the first byte. It
# cannot reuse NEXT_PUBLIC_API_URL, which is the `/api` path the browser
# resolves against this origin: a fetch inside this container has no
# origin to resolve it against. The browser-facing URL is baked into the
# published image and is always /api.
API_INTERNAL_URL: http://freeframe-api:8000
UPLOAD_CONCURRENCY: ${UPLOAD_CONCURRENCY:-5}
depends_on:
freeframe-api:
condition: service_started
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000/login"]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
volumes:
freeframe-postgres-data:
freeframe-redis-data:
freeframe-silo-data:
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+94
View File
@@ -0,0 +1,94 @@
# FreeFrame
FreeFrame is a self-hostable, open-source alternative to Frame.io: collaborative
review, annotation and approval for images, audio and video, with frame-accurate
comments, version comparison, share links and an approval workflow.
## Two domains
- The **main domain** serves the web app (service `freeframe`, port 3000). Its
`/api` path is routed to the API (service `freeframe-api`, port 8000) with the
prefix stripped, so both live on one hostname — that is what the web image
expects, and why the API domain entry has `path = "/api"` and
`stripPath = true`.
- The **second domain** exposes the bundled object store (service
`freeframe-silo`, port 9000). FreeFrame has no local-filesystem storage: media
is uploaded straight to the bucket with presigned URLs that the *browser*
fetches, so this endpoint has to be publicly reachable. It is not an internal
detail.
## First start
`freeframe-api` waits for Postgres, Redis and the object store to report healthy,
then runs `alembic upgrade head` and starts. It also creates the bucket and its
CORS rules on startup from `FRONTEND_URL`, so there is nothing to set up by hand.
Open the main domain and complete the setup wizard: **the first account becomes
the super admin**, and it is created with a password, so no mail server is needed
to sign in the first time.
Uploads take a moment before they are reviewable: a background worker transcodes
each video into the review streams. There is no GPU in this template, so encoding
is software x264/x265 on the CPU.
## HTTP by default, HTTPS later
`FRONTEND_URL` and `S3_PUBLIC_ENDPOINT` default to **`http://`**, so the stack works
immediately on a host without a valid certificate (including the `*.traefik.me` names
Dokploy generates when no domain is configured — those can never get a trusted
certificate, so HTTPS there means every upload fails with
`ERR_CERT_AUTHORITY_INVALID` before a single byte is sent).
To serve FreeFrame over HTTPS, with a real domain pointing at the server:
1. Enable HTTPS (Let's Encrypt) on **all three** domains in Dokploy. Do not enable it on
one and leave another on HTTP: an HTTPS page cannot upload to an HTTP storage endpoint
(the browser blocks it as mixed content), and an HTTP page cannot use an HTTPS storage
endpoint without a trusted certificate.
2. Change both variables to `https://` and redeploy:
```
FRONTEND_URL=https://<main domain>
S3_PUBLIC_ENDPOINT=https://<storage domain>
```
3. **Recreate the API** after changing them. The bucket's CORS rules are written from
`FRONTEND_URL` when the API starts, so a mismatch shows up as uploads stuck at 0% while
the UI itself still loads.
If uploads stop working after a scheme change, that pair of variables is the first thing
to check, and the second is whether the storage domain redirects HTTP to HTTPS.
## Sizing
Video transcoding is the only heavy operation, and it runs on the CPU here. 2 cores
/ 4 GB is the documented minimum; 4 cores / 8 GB is the recommended starting point.
`template.toml` sets the tuning settings to the values FreeFrame would use anyway,
so they are visible and editable in Dokploy's **Environment** tab after import (TOML
comments are not carried over — only key/value pairs are). `docker-compose.yml` also
defaults each one, so deleting a variable falls back to the same value. They are:
| Setting | Default | What it does |
|---|---|---|
| `TRANSCODING_CONCURRENCY` | `2` | Videos encoded at once. Size it to the cores (1 on 2 cores, 2-3 on 4, 4-6 on 8+). |
| `TRANSCODER_CPU_LIMIT` | *(unbounded)* | Cores one ffmpeg may use: `4` or `40%`. Unbounded takes about 80% of a 10-core host for one three-rung ladder, so set this when the UI must stay responsive while encoding. Machine ceiling is this times `TRANSCODING_CONCURRENCY`. |
| `TRANSCODER_QUALITIES` | `1080p,720p,360p` | Dropping `360p` saves CPU and storage but leaves viewers on slow connections without a lower rendition. |
| `TRANSCODER_SOURCE_COPY` | `false` | Remux a browser-safe H.264 8-bit master instead of re-encoding it, when the ladder resolves to one rendition at the source's own size. |
| `API_WORKERS` | `4` | Gunicorn workers in the API container. |
| `UPLOAD_CONCURRENCY` | `5` | Upload parts in flight per tab; 6 is the useful maximum over HTTP/1.1. |
`max(1, round(cores × share))` rounds a percentage up, and a value at or above the
available core count is treated as unbounded and reported in the worker log.
## Backups
Three volumes hold state: `freeframe-postgres-data` (metadata), `freeframe-silo-data`
(originals and every rendition) and `freeframe-redis-data` (queues). Back up the
database and the bucket together. Housekeeping runs on a schedule and hard-deletes
soft-deleted rows and their objects after `SOFT_DELETE_RETENTION_DAYS` (30 by
default); set it to `0` to switch that off.
## Versioning
Images are pinned to the release this template ships (`1.22.0`). To move to another
release, change the three `ghcr.io/techiebutler/freeframe-*` tags together — the API,
the four Celery containers and the web app are published per release — and run
`alembic upgrade head` again by redeploying.
+13
View File
@@ -0,0 +1,13 @@
{
"id": "freeframe",
"name": "FreeFrame",
"version": "1.22.0",
"description": "FreeFrame is an open-source, self-hostable alternative to Frame.io: frame-accurate video, image and audio review with annotations, version comparison, share links and approvals.",
"logo": "freeframe.png",
"links": {
"github": "https://github.com/Techiebutler/freeframe",
"website": "https://github.com/Techiebutler/freeframe",
"docs": "https://github.com/Techiebutler/freeframe/blob/main/docs/deployment.md"
},
"tags": ["media", "video", "collaboration", "file-sharing", "self-hosted"]
}
+111
View File
@@ -0,0 +1,111 @@
[variables]
main_domain = "${domain}"
storage_domain = "${domain}"
postgres_password = "${password:32}"
redis_password = "${password:32}"
jwt_secret = "${hash:64}"
s3_access_key = "freeframe"
s3_secret_key = "${password:32}"
frontend_url = "http://${main_domain}"
database_url = "postgresql://freeframe:${postgres_password}@freeframe-postgres:5432/freeframe"
redis_url = "redis://:${redis_password}@freeframe-redis:6379/0"
s3_endpoint = "http://freeframe-silo:9000"
s3_public_endpoint = "http://${storage_domain}"
[config]
[[config.domains]]
serviceName = "freeframe"
port = 3000
host = "${main_domain}"
# The API's routes have no prefix, but the browser calls /api/*, so the path is
# stripped before the request reaches the container.
[[config.domains]]
serviceName = "freeframe-api"
port = 8000
host = "${main_domain}"
path = "/api"
stripPath = true
# Presigned S3 URLs are handed to the browser, so the store needs its own
# publicly reachable hostname.
[[config.domains]]
serviceName = "freeframe-silo"
port = 9000
host = "${storage_domain}"
[config.env]
POSTGRES_USER = "freeframe"
POSTGRES_PASSWORD = "${postgres_password}"
POSTGRES_DB = "freeframe"
REDIS_PASSWORD = "${redis_password}"
DATABASE_URL = "${database_url}"
REDIS_URL = "${redis_url}"
JWT_SECRET = "${jwt_secret}"
# Exact origin people browse to. The API derives its CORS policy and the
# bucket's CORS rules from it, so it must match the scheme and host actually
# served: http by default (works without a certificate), https once HTTPS is
# enabled on the domains — then change this AND S3_PUBLIC_ENDPOINT together and
# recreate the API, see instructions.md.
FRONTEND_URL = "${frontend_url}"
S3_STORAGE = "minio"
S3_ENDPOINT = "${s3_endpoint}"
S3_PUBLIC_ENDPOINT = "${s3_public_endpoint}"
S3_ACCESS_KEY = "${s3_access_key}"
S3_SECRET_KEY = "${s3_secret_key}"
S3_BUCKET = "freeframe"
S3_REGION = "us-east-1"
# ── Tuning ───────────────────────────────────────────────────────────────────
# These are set to FreeFrame's own defaults, so a fresh deploy behaves exactly
# like upstream. Change a value in Dokploy's Environment tab and redeploy the
# container it affects. (TOML comments like this one are not carried into the
# Environment tab — Dokploy keeps key/value pairs only — so there is nothing to
# uncomment here: the lines below are already active.)
#
# Parallel video encodes (Celery workers on the transcoding queue). Size it to
# the cores: 1 on 2 cores, 2-3 on 4, 4-6 on 8+. There is no GPU here, so
# encoding is software x264/x265 and each job saturates the machine unless it
# is capped below.
# TRANSCODING_CONCURRENCY (below)
#
# Cores ONE ffmpeg process may use: a count ("4") or a share of what the
# container may use ("40%"). Empty = unbounded, which on a 10-core host is
# about 8 cores for a single three-rung ladder. The machine-wide ceiling is
# this times TRANSCODING_CONCURRENCY; it does not cover decoding, so a heavy
# master can run ~25% over.
# TRANSCODER_CPU_LIMIT (below; empty by default = unbounded)
#
# Review stream ladder. Dropping 360p saves CPU and storage, at the cost of a
# lower rendition for viewers on slow connections.
# TRANSCODER_QUALITIES (below)
#
# Remux a browser-safe H.264 8-bit master instead of re-encoding it: minutes
# become seconds. Only applies when the ladder resolves to ONE rendition at the
# source's own size, e.g. TRANSCODER_QUALITIES = "1080p".
# TRANSCODER_SOURCE_COPY (below)
#
# API gunicorn workers, and upload parts in flight per browser tab (6 is the
# useful maximum over HTTP/1.1; more only helps with HTTP/2).
# API_WORKERS, UPLOAD_CONCURRENCY (below)
TRANSCODING_CONCURRENCY = "2"
TRANSCODER_CPU_LIMIT = ""
TRANSCODER_QUALITIES = "1080p,720p,360p"
TRANSCODER_SOURCE_COPY = "false"
API_WORKERS = "4"
UPLOAD_CONCURRENCY = "5"
# Optional. The first account is created in the setup wizard with a password,
# so the app is usable without a mail server; magic-code login, invitations and
# notification emails need one.
MAIL_PROVIDER = "smtp"
MAIL_FROM_ADDRESS = "noreply@${main_domain}"
MAIL_FROM_NAME = "FreeFrame"
SMTP_HOST = ""
SMTP_PORT = "587"
SMTP_USER = ""
SMTP_PASSWORD = ""
SMTP_USE_TLS = "true"
+83
View File
@@ -0,0 +1,83 @@
# Testing FreeFrame
Use a disposable Dokploy project and test accounts. Upload only media you own, and do not
send invitation or notification mail to addresses you do not control.
## Repository checks
From the repository root:
```sh
node build-scripts/generate-meta.js --check
cd build-scripts
pnpm exec tsx validate-docker-compose.ts --file ../blueprints/freeframe/docker-compose.yml --verbose
pnpm exec tsx validate-template.ts --dir ../blueprints/freeframe --verbose
cd ../app
pnpm install --frozen-lockfile
pnpm run build
```
Confirm that `meta.json`'s `version` (1.22.0) matches the three `ghcr.io/techiebutler/freeframe-*`
tags. With a temporary `.env` holding the 24 variables `template.toml` generates, run
`docker compose config --quiet` in the blueprint directory: it must pass, and it must fail if
`DATABASE_URL`, `REDIS_URL` or `JWT_SECRET` is removed (the API reads `Settings` at import, so
those three are not optional).
## Dokploy integration
1. Open the PR preview, search for **FreeFrame**, and copy the Base64 import configuration.
Import it into a new Compose service and deploy with the generated environment. No
variable needs editing for a first run.
2. **Domains.** Three must exist after import: the main domain on `freeframe:3000`, the same
main domain with path `/api` and **Strip Path** on `freeframe-api:8000`, and a second
domain on `freeframe-silo:9000`. Verify `https://<main-domain>/api/health` answers through
the strip-path router, and that the web app itself loads at `https://<main-domain>/`.
3. **First start.** Wait for `freeframe-postgres`, `freeframe-redis` and `freeframe-silo` to
report healthy; the API then runs `alembic upgrade head` before gunicorn. Check the deploy
log for a completed migration and no restart loop, then complete the setup wizard — the
first account becomes the super admin, with a password, without any mail server.
4. **Storage.** The API creates the bucket and its CORS rules on startup from `FRONTEND_URL`.
Confirm `https://<storage-domain>` is reachable in a browser (a 404/`AccessDenied` XML is
fine, a connection error or certificate warning is not). Never expose the console (9001).
5. **A real upload.** Create a project, upload a short 1080p clip. The browser PUTs parts
straight to the storage domain (presigned, so no proxy limit applies) and the
`freeframe-worker` transcodes it on the CPU: the deploy log shows the ffmpeg backend as
`cpu`, and the asset becomes playable at 1080p/720p/360p. Check that the poster, the HLS
streams and the download rendition appear, and that a second version can be compared with
the first frame by frame.
6. **Collaboration.** Add a comment at a timecode and a drawing annotation, then create a
share link and open it in a private window: the reviewer sees the player and comments but
no admin UI. Verify an approval decision is recorded.
7. **Restart and recreation.** Force a redeploy (recreate the containers). Confirm the super
admin login, the project, the uploaded media and every rendition survive, and that the
bucket still holds the objects — `freeframe-postgres-data`, `freeframe-redis-data` and
`freeframe-silo-data` are the only state.
8. **HTTPS.** With a real domain, enable it on all three domains and change
`FRONTEND_URL` and `S3_PUBLIC_ENDPOINT` to `https://` in the same redeploy, then recreate
the API so the bucket CORS is rewritten. Verify uploads and playback still work (a
scheme mismatch shows up here and nowhere else). Leaving everything on `http://` is the
supported default and needs nothing. A `*.traefik.me` host cannot serve a trusted
certificate: over HTTPS there, every part `PUT` fails with
`ERR_CERT_AUTHORITY_INVALID` and the upload sits at 0%.
9. **Optional settings.** Set `SMTP_*` only in Dokploy's Environment, redeploy, and check
magic-code login and an invitation email against a local capture server. Confirm the six
tuning variables arrived in the Environment tab (TOML comments do not survive the import,
so only key/value pairs do), set `TRANSCODER_CPU_LIMIT` to `40%`, redeploy the transcoding
worker, and check that the log reports the budget while a second upload waits behind the
first instead of both encoding at once.
## Scope
These checks validate the template and core workflows. Real SMTP providers, an external S3
bucket (R2/B2/AWS), HEVC 10-bit and HDR sources, large 4K ladders, Garage or other S3
implementations, and long-running retention sweeps each need their own setup and are not
implied by a healthy container. Intel (VAAPI) transcoding is not part of this template: the
published images ship NVENC only, and there is no GPU here.
## Verified configuration
Fill this in from your own run — do not copy it from a template author's report.
- Tested on: `<date>`, Dokploy `<version>`, Traefik `<version>`, `<host arch>`.
- Images: `ghcr.io/techiebutler/freeframe-api:1.22.0`, `ghcr.io/techiebutler/freeframe-web:1.22.0`.
- Result: `<what passed>`, `<what could not be exercised, and why>`.
+58
View File
@@ -0,0 +1,58 @@
services:
homelable-backend:
image: ghcr.io/pouzor/homelable-backend:3.6.0
restart: unless-stopped
environment:
SECRET_KEY: ${SECRET_KEY}
SQLITE_PATH: ${SQLITE_PATH}
CORS_ORIGINS: ${CORS_ORIGINS}
AUTH_MODE: ${AUTH_MODE}
AUTH_USERNAME: ${AUTH_USERNAME}
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD}
SCANNER_RANGES: ${SCANNER_RANGES}
STATUS_CHECKER_INTERVAL: ${STATUS_CHECKER_INTERVAL}
STATUS_CHECKER_ENABLED: ${STATUS_CHECKER_ENABLED}
# The upstream image accepts a bcrypt hash, while Dokploy can generate a
# password but cannot generate a bcrypt hash. Hash the generated password
# before starting uvicorn, then remove the plaintext from the app process.
entrypoint:
- /bin/sh
- -c
- |
export AUTH_PASSWORD_HASH="$$(python -c 'import bcrypt, os; print(bcrypt.hashpw(os.environ["INITIAL_ADMIN_PASSWORD"].encode(), bcrypt.gensalt()).decode())')"
unset INITIAL_ADMIN_PASSWORD
exec "$$@"
- --
command:
- uvicorn
- app.main:app
- --host
- 0.0.0.0
- --port
- "8000"
expose:
- "8000"
cap_add:
- NET_RAW
volumes:
- homelable-data:/app/data
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/api/v1/health"]
interval: 30s
timeout: 5s
retries: 5
start_period: 15s
homelable:
image: ghcr.io/pouzor/homelable-frontend:3.6.0
restart: unless-stopped
depends_on:
homelable-backend:
condition: service_healthy
environment:
BACKEND_UPSTREAM: homelable-backend:8000
expose:
- "80"
volumes:
homelable-data:
+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 82" width="64" height="82" fill="none">
<defs>
<radialGradient id="bg-glow" cx="50%" cy="35%" r="55%">
<stop offset="0%" stop-color="#00d4ff" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#0d1117" stop-opacity="0"/>
</radialGradient>
<filter id="node-glow">
<feGaussianBlur stdDeviation="1.5" result="blur"/>
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<circle cx="32" cy="32" r="32" fill="#0d1117"/>
<circle cx="32" cy="32" r="32" fill="url(#bg-glow)"/>
<path d="M32 11 L53 30 L48 30 L48 53 L16 53 L16 30 L11 30 Z"
fill="#161b22" stroke="#00d4ff" stroke-width="1.5" stroke-linejoin="round"/>
<line x1="16" y1="30" x2="48" y2="30" stroke="#00d4ff" stroke-width="0.5" opacity="0.25"/>
<rect x="27" y="40" width="10" height="13" rx="1.5"
fill="#0d1117" stroke="#00d4ff" stroke-width="1" opacity="0.9"/>
<line x1="32" y1="23" x2="32" y2="30" stroke="#a855f7" stroke-width="1.2" opacity="0.7" stroke-linecap="round"/>
<line x1="21" y1="38" x2="29" y2="33" stroke="#39d353" stroke-width="1.2" opacity="0.7" stroke-linecap="round"/>
<line x1="43" y1="38" x2="35" y2="33" stroke="#39d353" stroke-width="1.2" opacity="0.7" stroke-linecap="round"/>
<circle cx="32" cy="33" r="5" fill="#00d4ff" opacity="0.12"/>
<circle cx="32" cy="33" r="3" fill="#00d4ff" filter="url(#node-glow)"/>
<circle cx="21" cy="38" r="2.5" fill="#39d353" filter="url(#node-glow)"/>
<circle cx="43" cy="38" r="2.5" fill="#39d353" filter="url(#node-glow)"/>
<circle cx="32" cy="23" r="2" fill="#a855f7" filter="url(#node-glow)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"id": "homelable",
"name": "Homelable",
"version": "3.6.0",
"description": "Self-hosted homelab infrastructure visualisation, documentation, network scanning and live monitoring.",
"logo": "homelable.svg",
"links": {
"github": "https://github.com/Pouzor/homelable",
"website": "https://homelable.net",
"docs": "https://github.com/Pouzor/homelable/blob/main/INSTALLATION.md"
},
"tags": [
"homelab",
"network",
"monitoring",
"documentation",
"self-hosted"
]
}
+22
View File
@@ -0,0 +1,22 @@
[variables]
main_domain = "${domain}"
secret_key = "${base64:32}"
admin_password = "${password:32}"
[config]
[[config.domains]]
serviceName = "homelable"
port = 80
host = "${main_domain}"
path = "/"
[config.env]
SECRET_KEY = "${secret_key}"
SQLITE_PATH = "/app/data/homelab.db"
CORS_ORIGINS = '["https://${main_domain}", "http://${main_domain}"]'
AUTH_MODE = "local"
AUTH_USERNAME = "admin"
INITIAL_ADMIN_PASSWORD = "${admin_password}"
SCANNER_RANGES = '["192.168.1.0/24"]'
STATUS_CHECKER_INTERVAL = "60"
STATUS_CHECKER_ENABLED = "true"
+5 -5
View File
@@ -65,7 +65,7 @@ services:
echo "bucket ready: $$S3_BUCKET"
migrate:
image: ghcr.io/croffasia/itsaplan-api:1.9.0
image: ghcr.io/croffasia/itsaplan-api:${VERSION:-1.10.0}
restart: "no"
command: ["bun", "run", "packages/db/src/migrate.ts"]
depends_on:
@@ -79,7 +79,7 @@ services:
- db-backups:/backups
api:
image: ghcr.io/croffasia/itsaplan-api:1.9.0
image: ghcr.io/croffasia/itsaplan-api:${VERSION:-1.10.0}
restart: unless-stopped
command: ["bun", "run", "apps/api/src/index.ts"]
depends_on:
@@ -118,7 +118,7 @@ services:
- "3000"
worker:
image: ghcr.io/croffasia/itsaplan-worker:1.9.0
image: ghcr.io/croffasia/itsaplan-worker:${VERSION:-1.10.0}
restart: unless-stopped
depends_on:
postgres:
@@ -141,7 +141,7 @@ services:
S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true}
bot:
image: ghcr.io/croffasia/itsaplan-bot:1.9.0
image: ghcr.io/croffasia/itsaplan-bot:${VERSION:-1.10.0}
restart: unless-stopped
depends_on:
postgres:
@@ -154,7 +154,7 @@ services:
APP_ENCRYPTION_KEY: ${APP_ENCRYPTION_KEY:?set APP_ENCRYPTION_KEY in Dokploy}
web:
image: ghcr.io/croffasia/itsaplan-web:1.9.0
image: ghcr.io/croffasia/itsaplan-web:${VERSION:-1.10.0}
restart: unless-stopped
depends_on:
- api
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "itsaplan",
"name": "It's a Plan",
"version": "1.9.0",
"version": "1.10.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": {
+1
View File
@@ -10,6 +10,7 @@ s3_secret_key = "${password:32}"
[config]
env = [
"POSTGRES_USER=itsaplan",
"VERSION=1.10.0",
"POSTGRES_PASSWORD=${postgres_password}",
"POSTGRES_DB=itsaplan",
"BETTER_AUTH_SECRET=${auth_secret}",
+12
View File
@@ -0,0 +1,12 @@
services:
kividb:
image: kividb/kividb:v1.2.0
restart: unless-stopped
command: ["--requirepass", "${KIVIDB_PASSWORD}", "--aof"]
expose:
- 6380
volumes:
- kividb-data:/home/kividb
volumes:
kividb-data: {}
+6
View File
@@ -0,0 +1,6 @@
1. KiviDB speaks the Redis protocol (RESP2/RESP3) on port `6380`, not 6379. It has no web interface, so this template has no domain.
2. Inside this compose project, connect to `kividb:6380`, e.g. `redis://:<password>@kividb:6380`. Use any Redis client or `redis-cli -h kividb -p 6380`.
3. The password is the generated `KIVIDB_PASSWORD` in the Dokploy environment settings.
4. Data is kept in the `kividb-data` volume (`/home/kividb`), with append-only persistence turned on (`--aof`).
5. The image has no shell and no `redis-cli`, so there is no container healthcheck and `docker exec` into it will not work; check it from a client instead.
6. Keep port `6380` private unless remote clients need direct access.
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"id": "kividb",
"name": "KiviDB",
"version": "1.2.0",
"description": "KiviDB is a Redis-compatible in-memory database written in Rust, with full-text and vector search (FT.*) built in.",
"logo": "kividb.png",
"links": {
"github": "https://github.com/kividbio/kividb",
"website": "https://kividb.io/",
"docs": "https://kividb.io/docs"
},
"tags": [
"database",
"cache",
"redis",
"in-memory",
"vector-database"
]
}
+7
View File
@@ -0,0 +1,7 @@
[variables]
kividb_password = "${password:32}"
[config]
env = [
"KIVIDB_PASSWORD=${kividb_password}"
]
+1 -1
View File
@@ -1,6 +1,6 @@
services:
libredb-studio:
image: ghcr.io/libredb/libredb-studio:1.2.0
image: ghcr.io/libredb/libredb-studio:1.3.0
restart: unless-stopped
environment:
# Since 0.18.0 ADMIN_PASSWORD and USER_PASSWORD seed the account table only
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "libredb-studio",
"name": "LibreDB Studio",
"version": "1.2.0",
"version": "1.3.0",
"description": "The open-source SQL IDE you host yourself. Query PostgreSQL, MySQL, Oracle, SQL Server, MongoDB, Redis, ClickHouse, Cassandra and more from your browser, forty-two drivers reaching sixty-nine named engines.",
"logo": "libredb-studio.svg",
"links": {
@@ -0,0 +1,38 @@
services:
app:
image: ghcr.io/tj-coding/life-console:0.1.0
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://lifeconsole:${DB_PASSWORD}@db:5432/lifeconsole
APP_URL: ${APP_URL}
TRUST_PROXY: "1"
SIGNUPS: open
AUTH_PASSWORD: "on"
UPDATE_CHECK: "off"
BACKUP_DIR: /backups
expose:
- 8080
volumes:
- backups:/backups
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: lifeconsole
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: lifeconsole
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 3s
retries: 20
volumes:
- db:/var/lib/postgresql/data
volumes:
db:
backups:
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

+18
View File
@@ -0,0 +1,18 @@
{
"id": "life-console",
"name": "Life Console",
"version": "0.1.0",
"description": "Life Console is a keyboard-first planner that shows what to do right now: time-block your week from a backlog, count down deadlines and link each task to the goal it serves. Multi-user, works offline and installs on phones.",
"logo": "life-console.png",
"links": {
"github": "https://github.com/TJ-coding/life-console",
"website": "https://life-console.dev",
"docs": "https://life-console.dev/docs/self-hosting"
},
"tags": [
"productivity",
"planner",
"time-blocking",
"tasks"
]
}
+16
View File
@@ -0,0 +1,16 @@
[variables]
main_domain = "${domain}"
db_password = "${password:32}"
[config]
# APP_URL uses http to match Dokploy's generated domain; change to https:// once HTTPS is on for the domain
env = [
"DB_PASSWORD=${db_password}",
"APP_URL=http://${main_domain}",
]
mounts = []
[[config.domains]]
serviceName = "app"
port = 8080
host = "${main_domain}"
+3 -3
View File
@@ -15,7 +15,7 @@ services:
retries: 10
migrate:
image: ghcr.io/mcpspan/core-api:0.13.0
image: ghcr.io/mcpspan/core-api:0.14.0
restart: "no"
depends_on:
db:
@@ -25,7 +25,7 @@ services:
command: ["./node_modules/.bin/node-pg-migrate", "up"]
api:
image: ghcr.io/mcpspan/core-api:0.13.0
image: ghcr.io/mcpspan/core-api:0.14.0
restart: unless-stopped
depends_on:
migrate:
@@ -45,7 +45,7 @@ services:
start_period: 10s
dashboard:
image: ghcr.io/mcpspan/core-dashboard:0.13.0
image: ghcr.io/mcpspan/core-dashboard:0.14.0
restart: unless-stopped
depends_on:
api:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "mcpspan",
"name": "mcpspan",
"version": "0.13.0",
"version": "0.14.0",
"description": "mcpspan is open-source analytics for MCP servers: which tools agents call, how often they fail and why, which clients call them, and how fast they answer. Add one line with an SDK in any of eight languages, or none at all behind its proxy, and see it in the dashboard. Parameter values never leave your server.",
"logo": "mcpspan.svg",
"links": {
+47
View File
@@ -0,0 +1,47 @@
services:
nakama:
image: ghcr.io/ahmadrosid/nakama:0.4.71
restart: unless-stopped
expose:
- 4310
environment:
NAKAMA_CONFIG_DIR: /nakama/data
# Daily encrypted export, newest 7 kept. Must stay outside the data volume.
NAKAMA_BACKUP_DIR: /nakama/backups
# Where the Update button leaves requests for the updater service.
NAKAMA_UPDATER_DIR: /nakama/updates
NAKAMA_SECRETS_KEY: ${NAKAMA_SECRETS_KEY}
# Public URL for OAuth callbacks and shared links (Dokploy reverse proxy).
NAKAMA_WEB_PUBLIC_URL: https://${NAKAMA_HOST}
# Required behind Dokploy's Traefik proxy for correct login rate limiting.
NAKAMA_TRUST_PROXY: "true"
volumes:
- nakama-data:/nakama/data
- nakama-backups:/nakama/backups
- nakama-updates:/nakama/updates
# Optional: the Update button in Settings. Turn it on with
# COMPOSE_PROFILES=updater in .env. It holds the Docker socket, which is root
# on this host, so it opens no port and only upgrades the nakama service.
updater:
image: docker:29-cli
profiles: ["updater"]
environment:
# The docker:cli image defaults to tcp://docker:2375.
- DOCKER_HOST=unix:///var/run/docker.sock
- NAKAMA_IMAGE=ghcr.io/ahmadrosid/nakama:${NAKAMA_VERSION:-latest}
# The script ships inside the Nakama image, so it updates with Nakama.
entrypoint:
- sh
- -c
- docker run --rm --entrypoint cat "$$NAKAMA_IMAGE" /app/scripts/nakama-updater.sh > /tmp/updater.sh && exec sh /tmp/updater.sh
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .:/compose
- nakama-updates:/updates
restart: unless-stopped
volumes:
nakama-data:
nakama-backups:
nakama-updates:
+17
View File
@@ -0,0 +1,17 @@
{
"id": "nakama",
"name": "Nakama",
"version": "0.4.71",
"description": "AI agent platform for teams: a shared workspace where agents get their own tools, skills, memory and channels (Telegram, WhatsApp, Discord, Slack) with orgs, invites and roles built in.",
"logo": "nakama.svg",
"links": {
"github": "https://github.com/ahmadrosid/nakama",
"website": "https://getnakama.cloud",
"docs": "https://ahmadrosid.github.io/nakama/"
},
"tags": [
"ai",
"agents",
"automation"
]
}
+11
View File
@@ -0,0 +1,11 @@
<svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path
d="M24 10.58L8.5 37.42L39.5 37.42Z"
stroke="#0A0A0A"
stroke-linejoin="round"
stroke-width="4.2"
/>
<circle cx="24" cy="10.58" fill="#0A0A0A" r="7.5" />
<circle cx="8.5" cy="37.42" fill="#0A0A0A" r="7.5" />
<circle cx="39.5" cy="37.42" fill="#0A0A0A" r="7.5" />
</svg>

After

Width:  |  Height:  |  Size: 373 B

+15
View File
@@ -0,0 +1,15 @@
[variables]
main_domain = "${domain}"
nakama_secrets_key = "${base64:32}"
[config]
env = [
"NAKAMA_HOST=${main_domain}",
"NAKAMA_SECRETS_KEY=${nakama_secrets_key}",
]
mounts = []
[[config.domains]]
serviceName = "nakama"
port = 4310
host = "${main_domain}"
+22
View File
@@ -0,0 +1,22 @@
services:
openbao:
image: openbao/openbao:2.7.2
restart: unless-stopped
command: server
environment:
BAO_ADDR: "http://127.0.0.1:8200"
expose:
- "8200"
volumes:
- ../files/config/openbao.hcl:/openbao/config/openbao.hcl:ro
- openbao-data:/openbao/file
healthcheck:
# exit code 1 = server unreachable; 2 = sealed (still healthy, so the UI stays reachable for init/unseal)
test: ["CMD-SHELL", "bao status >/dev/null 2>&1; [ $$? -ne 1 ]"]
interval: 30s
timeout: 10s
retries: 5
start_period: 15s
volumes:
openbao-data:
+21
View File
@@ -0,0 +1,21 @@
{
"id": "openbao",
"name": "OpenBao",
"version": "2.7.2",
"description": "OpenBao is an open-source, community-driven fork of HashiCorp Vault under the Linux Foundation for managing secrets, encryption keys and certificates. This template runs a single node with persistent Raft storage and the web UI. After deploy, open the UI to initialize OpenBao, store the unseal keys and root token safely, then unseal it (unseal again after every restart).",
"logo": "openbao.svg",
"links": {
"github": "https://github.com/openbao/openbao",
"website": "https://openbao.org/",
"docs": "https://openbao.org/docs/"
},
"tags": [
"security",
"secrets",
"devops",
"infrastructure"
],
"seoTitle": "OpenBao Docker Compose Setup",
"seoDescription": "Open-source secrets manager forked from Vault. Deploy OpenBao with Docker Compose on Dokploy – persistent Raft storage, web UI, manual init and unseal.",
"newConfig": true
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

+31
View File
@@ -0,0 +1,31 @@
[variables]
main_domain = "${domain}"
[config]
[[config.domains]]
serviceName = "openbao"
port = 8200
host = "${main_domain}"
[[config.mounts]]
filePath = "./config/openbao.hcl"
content = """
ui = true
disable_mlock = true
api_addr = "https://${main_domain}"
cluster_addr = "https://openbao:8201"
storage "raft" {
path = "/openbao/file"
node_id = "openbao-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
# TLS is terminated by Traefik (Dokploy)
tls_disable = true
}
"""
+13
View File
@@ -0,0 +1,13 @@
services:
openfault:
image: ghcr.io/openfault/openfault:0.1.0
restart: unless-stopped
environment:
- OPENFAULT_BASE_URL=https://${OPENFAULT_HOST}
- OPENFAULT_TRUST_PROXY=1
- OPENFAULT_SETUP_TOKEN=${OPENFAULT_SETUP_TOKEN}
volumes:
- openfault-data:/data
volumes:
openfault-data: {}
+13
View File
@@ -0,0 +1,13 @@
{
"id": "openfault",
"name": "OpenFault",
"version": "0.1.0",
"description": "Self-hosted, Sentry-compatible error tracker. One container, SQLite inside, built for coding agents.",
"logo": "openfault.svg",
"links": {
"github": "https://github.com/OpenFault/openfault",
"website": "https://openfault.dev",
"docs": "https://openfault.dev/docs/install"
},
"tags": ["monitoring"]
}
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="#000">
<path d="M36.95 6H20A14 14 0 0 0 6 20V44A14 14 0 0 0 20 58H22.91Z"/>
<path d="M41.09 6H44A14 14 0 0 1 58 20V44A14 14 0 0 1 44 58H27.05L33.53 34H42.5L44.12 28H35.15L37.04 21H48.04L49.66 15H38.66Z"/>
</svg>

After

Width:  |  Height:  |  Size: 278 B

+15
View File
@@ -0,0 +1,15 @@
[variables]
main_domain = "${domain}"
setup_token = "${password:32}"
[config]
env = [
"OPENFAULT_HOST=${main_domain}",
"OPENFAULT_SETUP_TOKEN=${setup_token}",
]
mounts = []
[[config.domains]]
serviceName = "openfault"
port = 8080
host = "${main_domain}"
+15
View File
@@ -0,0 +1,15 @@
services:
satgas:
image: ghcr.io/zeative/satgas:0.1.13
restart: unless-stopped
environment:
SATGAS_PUBLIC_URL: ${SATGAS_PUBLIC_URL}
SATGAS_TOKEN: ${SATGAS_TOKEN}
volumes:
- satgas-data:/data
- satgas-home:/home/node
stop_grace_period: 15s
volumes:
satgas-data: {}
satgas-home: {}
+13
View File
@@ -0,0 +1,13 @@
{
"id": "satgas",
"name": "Satgas",
"version": "0.1.13",
"description": "Your own AI team, self-hosted. Describe what you want, and a lead hires the right agents, runs them in parallel on the model you choose, and puts the results on a shared canvas you can watch and edit.",
"logo": "satgas.svg",
"links": {
"github": "https://github.com/zeative/satgas",
"website": "https://github.com/zeative/satgas",
"docs": "https://github.com/zeative/satgas#self-hosting"
},
"tags": ["ai", "agents", "developer-tools", "self-hosted"]
}
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#141414"/>
<g transform="translate(8 8) scale(2)">
<path d="M8.5 12c3.5 0 3.5-6 7-6M8.5 12c3.5 0 3.5 6 7 6" fill="none" stroke="#f5f5f5" stroke-width="1.75" stroke-linecap="round"/>
<circle cx="6" cy="12" r="2.5" fill="#f5f5f5"/>
<circle cx="18" cy="6" r="2.5" fill="#ffb90e"/>
<circle cx="18" cy="18" r="2.5" fill="#f5f5f5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 465 B

+16
View File
@@ -0,0 +1,16 @@
[variables]
main_domain = "${domain}"
sign_in_token = "${password:32}"
[config]
# Must match how the domain is served: Dokploy's generated domain is http, so switch this to https with your own domain.
env = [
"SATGAS_PUBLIC_URL=http://${main_domain}",
"SATGAS_TOKEN=${sign_in_token}",
]
mounts = []
[[config.domains]]
serviceName = "satgas"
port = 4317
host = "${main_domain}"
+1 -1
View File
@@ -26,7 +26,7 @@ services:
retries: 5
senddock:
image: ghcr.io/arkhe-systems/senddock:0.8.3
image: ghcr.io/senddock/senddock:0.8.3
restart: unless-stopped
depends_on:
postgres:
+1 -1
View File
@@ -5,7 +5,7 @@
"description": "Open-source, self-hosted email marketing platform with BYO SMTP. Manage subscribers, templates, broadcasts, and scheduled campaigns from one place.",
"logo": "senddock.svg",
"links": {
"github": "https://github.com/Arkhe-Systems/senddock",
"github": "https://github.com/SendDock/senddock",
"website": "https://senddock.dev",
"docs": "https://docs.senddock.dev"
},
+2 -2
View File
@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="112" fill="#000000"/>
<g stroke="#ffffff" stroke-width="8" stroke-linejoin="round" stroke-linecap="round" fill="#000000">
<rect width="512" height="512" rx="112" fill="#111113"/>
<g stroke="#00af7b" stroke-width="14" stroke-linejoin="round" stroke-linecap="round" fill="#111113">
<polyline points="106,236 206,176 306,236" fill="none"/>
<polygon points="106,236 66,216 166,156 206,176"/>
<polygon points="206,176 246,156 346,216 306,236"/>

Before

Width:  |  Height:  |  Size: 855 B

After

Width:  |  Height:  |  Size: 856 B

+60
View File
@@ -0,0 +1,60 @@
services:
terarouter:
image: ghcr.io/montara-project/tera-router:v1.23.0
restart: unless-stopped
expose:
- 8080
environment:
ENV: production
DEBUG: "false"
MACHINE_ID: ${MACHINE_ID}
APP_SECRET: ${APP_SECRET}
APP_NAME: tera-router-server
# For Default Super User
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
# No published port: every request arrives via Dokploy's proxy on the
# internal network, so private ranges are safe to trust for X-Forwarded-For, this forward Real IP using DNS Cloudflare
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
CORS_ALLOWED_ORIGINS: https://${TERA_HOST}
API_URL: https://${TERA_HOST}
# For Database
DATABASE_PATH: /app/data/terarouter.db
MIGRATE_ON_BOOT: "true"
SEED_ON_BOOT: "true"
# Optional for token reduction proxy
HEADROOM_URL: http://headroom:8787
HEADROOM_PROXY_TOKEN: ${HEADROOM_PROXY_TOKEN}
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes:
- terarouter-data:/app/data
depends_on:
- headroom
# Token-reduction proxy the gateway calls through HEADROOM_URL; switch it on
# under Settings > Token Saving. HEADROOM_PROXY_TOKEN is required because the
# sidecar binds beyond loopback on the internal network.
headroom:
image: ghcr.io/headroomlabs-ai/headroom:0.41.0
restart: unless-stopped
environment:
HEADROOM_HOST: "0.0.0.0"
HEADROOM_PORT: "8787"
HEADROOM_COMPRESS_ALLOW_REMOTE: "1"
HEADROOM_PROXY_TOKEN: ${HEADROOM_PROXY_TOKEN}
# Kompress (ModernBERT on CPU) outruns the gateway's 5s Headroom timeout
# on large tool results; the structural compressors stay on.
HEADROOM_DISABLE_KOMPRESS: "1"
HEADROOM_DISABLE_KOMPRESS_FALLBACK: "1"
volumes:
terarouter-data:
+17
View File
@@ -0,0 +1,17 @@
{
"id": "terarouter",
"name": "Tera Router",
"version": "v1.23.0",
"description": "Self-hosted AI router: a single OpenAI/Anthropic-compatible gateway in front of all your providers, with a dashboard for accounts, routing, model catalogs, and cost analytics.",
"logo": "tera.png",
"links": {
"github": "https://github.com/montara-project/tera-router",
"website": "https://terarouter.xyz",
"docs": "https://docs.terarouter.xyz"
},
"tags": [
"ai",
"proxy",
"developer-tools"
]
}
+30
View File
@@ -0,0 +1,30 @@
[variables]
main_domain = "${domain}"
machine_id = "${randomPort}"
app_secret = "${base64:48}"
admin_email = "${email}"
admin_password = "${password:32}"
headroom_proxy_token = "${base64:32}"
[config]
env = [
"TERA_HOST=${main_domain}",
"MACHINE_ID=${machine_id}",
"APP_SECRET=${app_secret}",
"ADMIN_EMAIL=${admin_email}",
"ADMIN_PASSWORD=${admin_password}",
"TRUSTED_PROXIES=10.0.1.0/24,173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32",
"SENTRY_DSN=",
"HEADROOM_PROXY_TOKEN=${headroom_proxy_token}",
"OIDC_ISSUER=",
"OIDC_CLIENT_ID=",
"OIDC_CLIENT_SECRET=",
"OIDC_PROVIDER_NAME=",
"OIDC_REDIRECT_URL=",
]
mounts = []
[[config.domains]]
serviceName = "terarouter"
port = 8080
host = "${main_domain}"
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,6 +1,6 @@
services:
trmnl-larapaper:
image: ghcr.io/usetrmnl/larapaper:0.45.0
image: ghcr.io/usetrmnl/larapaper:0.46.0
environment:
- APP_URL=${APP_URL}
- PHP_OPCACHE_ENABLE=${PHP_OPCACHE_ENABLE}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "trmnl-larapaper",
"name": "TRMNL LaraPaper",
"version": "0.45.0",
"version": "0.46.0",
"description": "LaraPaper is a self-hosted application (BYOS) to manage TRMNL e-ink devices.",
"logo": "larapaper.svg",
"links": {
+4 -1
View File
@@ -1,6 +1,9 @@
services:
zeqqat:
image: ghcr.io/zeqqat/zeqqat:${ZEQQAT_VERSION:-0.3.40}
image: ghcr.io/zeqqat/zeqqat:${ZEQQAT_VERSION:-latest}
# Pull on every deploy, so redeploying picks up a new "latest" instead of
# reusing an older image Docker already has.
pull_policy: always
restart: unless-stopped
depends_on:
postgres:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "zeqqat",
"name": "Zeqqat",
"version": "0.3.40",
"version": "latest",
"description": "Give AI agents access to your Dokploy without handing them your API key. Per-tool permissions, team roles and a full log of every call.",
"logo": "zeqqat.svg",
"links": {