New release (#1038)
* fix: meta.json of Poke blueprint The current github/docs links was broken before: https://codeberg.org/ashley/poke after: https://codeberg.org/ashleyirispuppy/poke * fix: broken links in some blueprints * Add openhabittracker blueprint * fix(affinepro): update to current AFFiNE images The template pinned ghcr.io/toeverything/affine-graphql:stable-780dd83, an ephemeral per-commit tag on the old image name that has since been removed from GHCR, so the template could no longer pull and deploy. - Switch both app and migration services to ghcr.io/toeverything/affine:stable, the rolling stable tag recommended by the official self-host compose, so the template no longer rots when per-commit tags are garbage-collected - Move postgres to pgvector/pgvector:pg16 and set AFFINE_INDEXER_ENABLED=false, matching the current upstream self-host compose requirements - Add postgres/redis healthchecks and proper depends_on conditions so the self-host-predeploy migration job runs only once the database is ready, and the server starts only after migrations complete successfully Closes #777 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cockpit): persist config and storage volumes per upstream docs The template mounted `html:/var/www/html` (freezing the app code from the first deploy) and `data:/var/www/html/storage/data`, which misses uploaded assets (stored under `storage/uploads`) and cache. Per the official Cockpit persistent-storage docs, mount `config:/var/www/html/config` and `storage:/var/www/html/storage` instead. Closes #549 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(kener): define REDIS_URL and drop unused databases Kener v4 requires Redis for its BullMQ queues, caching and scheduler, so the app crashed on startup with "REDIS_URL is not defined in environment variables" right after printing that it was running (issue #976). The template also spun up both a postgres and a mariadb container while DATABASE_URL pointed at SQLite, so neither database was ever used (issue #282). Changes, aligned with the upstream docker-compose.yml (rajnandan1/kener): - Add a redis:7-alpine service with a healthcheck and persistent volume, wire REDIS_URL=redis://redis:6379 and make kener depend on redis being healthy - Remove the unused postgres and mysql services plus their env vars and generated passwords; keep SQLite (upstream default) persisted in the kener_db volume - Set ORIGIN to https://<domain> instead of http://localhost:3000 so CSRF protection works behind the generated domain - Drop the stale /app/uploads bind mount (kener v4 only persists /app/database) and the obsolete compose version key Deploy-tested on a Dokploy instance: migrations and seed complete, schedulers start, "Kener version 4.1.2 is running!", HTTP 200 on the generated domain, both containers stable. Closes #976 Closes #282 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(homarr): use named volume for /appdata and hex encryption key - Replace the relative bind mount `../homarr/appdata:/appdata` with a named volume `homarr_appdata:/appdata` so the data survives cleanups and can be backed up through Dokploy volume backups. - Generate SECRET_ENCRYPTION_KEY with `${hash:64}` instead of `${password:64}`: homarr requires a 64-character hex string (`openssl rand -hex 32`) and the password helper emits non-hex characters (g-z). Closes #738 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(logto): fix postgres role error and update to 1.41.0 The postgres healthcheck ran pg_isready without -U while POSTGRES_USER was set to logto, so every probe attempted to connect as role "postgres" and spammed FATAL: role "postgres" does not exist. Pass -U logto -d logto explicitly and set POSTGRES_DB. Also fix LOGTO_ENDPOINT pointing at the admin domain instead of the main domain, update Logto 1.27.0 -> 1.41.0, use ${password:32} for the generated password, drop the ephemeral host port publishing and add restart policies. Closes #130 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(gitlab-ce): serve on the generated domain (remove removed unicorn config, pin omnibus) The template set unicorn['worker_processes'] / unicorn['worker_timeout'] in GITLAB_OMNIBUS_CONFIG. Unicorn was removed in GitLab 14.0, so on current gitlab-ce images `gitlab-ctl reconfigure` aborts with "Removed configurations found in gitlab.rb" and GitLab never starts serving — the generated domain returned 502 Bad Gateway (issue #380). Rewrite the template as the official all-in-one Omnibus setup: - pin gitlab/gitlab-ce:19.1.2-ce.0 instead of :latest - external_url http://<domain> with nginx listen_port 80 / listen_https false (TLS terminates at Traefik) - set gitlab_rails['initial_root_password'] from a generated template password - puma single mode + prometheus monitoring off to keep memory reasonable - drop the external postgres/redis containers (Omnibus bundles and manages its own) and the broken ports entries ("2224" published a random host port while sshd listens on 22) Verified on a live Dokploy instance: domain serves the GitLab sign-in page and root login with the generated password works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(zitadel): wire EXTERNALDOMAIN/PORT/SECURE to the generated domain Zitadel resolves its instance from the external domain/port/scheme, so the template's ZITADEL_EXTERNALPORT=8080 made every generated URL (OIDC issuer, login redirects) point at http://<domain>:8080 instead of the domain served by Traefik, ending in {"code":5,"message":"Not Found"} (#516). - Expose ZITADEL_EXTERNALDOMAIN / ZITADEL_EXTERNALPORT / ZITADEL_EXTERNALSECURE as template env vars, defaulting to the generated domain over HTTP (port 80, secure=false) so the routed domain and Zitadel's external config always match; HTTPS users flip them to 443/true in the Env tab. - Pin image to v4.16.0 (was latest) and keep the built-in login v1 via ZITADEL_DEFAULTINSTANCE_FEATURES_LOGINV2_REQUIRED=false so no separate login-v2 container is needed. - Add the official readiness healthcheck, drop the unused /app/data volume, bogus SMTP env keys, published port and obsolete compose version key. Closes #516 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(immich): update to v3.0.2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(automatisch): switch to official image and wire up DOMAIN to fix Bad Gateway - Replace unofficial dockeriddonuts/automatisch:2.0 mirror (stale since Nov 2024) with the official automatischio/automatisch:0.15.0 image - Define DOMAIN in template.toml [config.env]: the compose referenced ${DOMAIN} but it was never provided, so HOST resolved empty and the app generated localhost URLs for the web app and webhooks - Set API_URL/WEB_APP_URL so generated URLs don't carry the internal :3000 port behind Traefik - Add postgres healthcheck and gate the app on service_healthy to avoid the boot-time migration race; worker now starts after main (official compose order) - Drop unnecessary published port and no-op REDIS_* env on the redis service - Bump meta.json version to 0.15.0 Closes #97 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(signoz): fix ClickHouse hostname resolution and IPv6 binding - Override the ClickHouse image's config.d/docker_related_config.xml, which listens on the IPv6 wildcard address ([::]) first and fails to bind on hosts without IPv6 support; listen on 0.0.0.0 (IPv4) only and disable the internal DNS cache so transient startup DNS failures for zookeeper-1/clickhouse are not cached (issue #534). - Set hostname: clickhouse (instead of container_name) so the server identifies itself as the cluster host declared in cluster.xml. - Remove the explicit signoz-net network: Dokploy attaches its own networks automatically and the repo validator rejects explicit networks; a fixed-name shared network also breaks service DNS across deployments. - Fix mount mismatch: template.toml created files/signoz/prometheus.xml while the compose mounts ../files/signoz/prometheus.yml, so Docker silently mounted a directory as the signoz prometheus config. - Give the otel-collector domain its own generated host: both domains previously shared the same host and path "/", creating two conflicting Traefik routers where one service randomly won. Closes #534 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(infisical): update to v0.162.6 - Bump image from v0.135.0-postgres to v0.162.6 (upstream dropped the -postgres tag suffix after v0.146.0; plain tags are the postgres build) - Remove the db-migration service: the standalone image runs database migrations automatically on boot (auto-start-migrations), matching the official docker-compose.prod.yml which no longer ships that service - Generate ENCRYPTION_KEY (${hash:32}) and AUTH_SECRET (${base64:32}) per deployment instead of shipping hardcoded example secrets - Fix SITE_URL to http://${main_domain} (no :8080; the domain is proxied) - Refresh SMTP env names (SMTP_FROM_ADDRESS/SMTP_FROM_NAME; SMTP_SECURE was removed upstream) and drop unused CLIENT_* env placeholders that were never passed to the container Closes #215 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: add databasus (postgresus renamed upstream) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(evolutionapi): enable integration flags and update version Adds the integration enable env flags introduced in Evolution API v2.3.x (N8N_ENABLED, OPENAI_ENABLED, EVOAI_ENABLED, DIFY_ENABLED, TYPEBOT_ENABLED, CHATWOOT_ENABLED) so integrations no longer fail with 'N8n is disabled', and pins the image to v2.3.7 instead of latest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(rybbit): update to v2.7.0 The template shipped rybbit v1.5.1, which is no longer supported and is vulnerable to known React2Shell exploits (see #659). Update to the latest stable release v2.7.0 and align the stack with the upstream docker-compose: - Pin ghcr.io/rybbit-io/rybbit-backend and rybbit-client to v2.7.0 - Add the redis service now required by the backend (session tracking and BullMQ queues), with password auth, AOF persistence and noeviction policy per upstream - Drop the DOMAIN_NAME env var (only used by the upstream Caddy webserver, which Dokploy replaces with Traefik) - Add DISABLE_TELEMETRY env and generate BETTER_AUTH_SECRET as base64 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(phpmyadmin): raise PHP upload limit so SQL imports over 2MB work Without UPLOAD_LIMIT the phpMyAdmin image keeps PHP's default 2M post_max_size/upload_max_filesize, so importing any SQL file larger than 2MB fails with 'POST Content-Length exceeds the limit of 2097152 bytes' followed by a session_start error page. - Add UPLOAD_LIMIT (512M) and MAX_EXECUTION_TIME (600) env vars, configurable from the template env - Wire the previously unused MYSQL_DATABASE env var into the compose file, replacing the hardcoded 'tu_base_de_datos' placeholder, and default it to 'phpmyadmin' instead of the system 'mysql' schema Closes #236 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(openpanel): pin verified 2.2.1 images and fix ClickHouse interserver host - Pin lindesvard/openpanel-api|dashboard|worker to 2.2.1 (verified working end-to-end on a Dokploy instance) instead of the mutable :2 tag, so a future broken 2.x release cannot silently break the template again — which is how #615 originated when 2.0.0 shipped. - Fix interserver_http_host to match the actual service name (op-ch), aligning with upstream self-hosting config. - Set meta.json version to the pinned 2.2.1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(wordpress): route loopback requests through Traefik to fix cURL error 28 WordPress resolves its own public domain to the server's public IP from inside the container, so REST API / WP-Cron / Site Health loopback requests depend on hairpin NAT, which times out (cURL error 28) on many hosts. Pin the site domain to the dokploy-traefik container IP in /etc/hosts at startup (with retries, since Traefik joins the compose network shortly after the container starts) so loopback traffic stays inside the Docker network for both HTTP and HTTPS. No-op fallback when dokploy-traefik is not resolvable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(minio): migrate to pgsty/minio community fork (upstream is source-only and archived) MinIO Inc. stopped publishing Docker images and pre-built binaries in October 2025 and archived the minio/minio repository in February 2026. The official minio/minio image is frozen at RELEASE.2025-09-07T16-13-09Z and no longer receives security fixes (the fix for CVE-2025-62506 was never published as an image). - Switch image to pgsty/minio:RELEASE.2026-06-18T00-00-00Z, the actively maintained community fork (drop-in replacement, AGPLv3, same env vars, same /data on-disk format, restores the full web console) - Route the S3 API (port 9000) through its own generated domain instead of publishing host port 9000, per repo conventions - Update meta.json links/description to point at the maintained fork Closes #469 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(nextcloud-aio): drop AIO branding, keep template id, pin nextcloud 33.0.6 The template ships plain nextcloud + mariadb + redis + cron, not the nextcloud/all-in-one mastercontainer. Display name and description no longer claim AIO; folder id is kept as nextcloud-aio to avoid breaking existing installs. Image pinned to the current stable tag (33.0.6). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add MailCatcher NG template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: add per-template setup instructions (supabase, trigger.dev) Adds instructions.md files for the Supabase and Trigger.dev templates and renders them in the template browser as a new Instructions tab in the template dialog (fetched from blueprints/<id>/instructions.md, rendered with a small dependency-free markdown component). Also removes the placeholder blueprints/ackee/instructions.md so it does not surface as an empty Instructions tab. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Weblate template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Synapse (Matrix homeserver) template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Dify template Dify 1.15.0 (open-source LLM app development platform): api + worker + worker_beat + web + pgvector PostgreSQL (also used as the vector store) + redis + sandbox + ssrf_proxy (squid) + plugin_daemon, fronted by an internal nginx gateway that mirrors the upstream path routing. Closes #88 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Sim Studio template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Steedos template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Silex template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add SpacetimeDB template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add CertMate template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add PostHog template Based on the official hobby deployment, trimmed to run on a single server: Django web + Celery worker, Node plugin-server (CDP, ingestion, session replay, recording API), Rust capture/replay-capture/ feature-flags/hypercache, livestream, Caddy path router, Postgres, Redis, Redpanda (Kafka), ZooKeeper, ClickHouse and MinIO. Images are pinned to a tested master commit (PostHog no longer publishes tagged releases); an init container fetches the matching ClickHouse configuration, Kafka table schemas, funnel UDF binaries and the GeoIP database at the same pinned ref. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Markup template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Frappe LMS template Adds a Frappe LMS (Frappe Learning) template based on the erpnext-v16 blueprint pattern (configurator + create-site + workers + scheduler + websocket + mariadb + 3x redis). Pins ghcr.io/frappe/lms:v2.52.0, the latest upstream image that ships the lms app (v2.52.1+ images are currently published without any apps baked in). The image does not bundle the payments app, so create-site clears lms's required_apps before install; paid-course checkout is unavailable, everything else works. The backend is gated on a create-site completion marker so gunicorn workers never serve (and cache) a half-installed site during the 3-6 minute first boot. Closes #517 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add TREK template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Oryx (SRS) media streaming template Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: stop plugins service crash loop (logs-redis TLS default) In production images LOGS_REDIS_TLS/TRACES_REDIS_TLS default to true, so the logs-ingestion consumer (part of the plugin-server default mode) kept attempting a TLS handshake against the plaintext Redis, timed out every ~11s and killed the process after 10 errors (restart loop every ~2.5 min). Constrain the plugins service to the capability groups this template ships (cdp_workflows, realtime_cohorts, feature_flags) and pin the redis TLS flags to false as belt-and-braces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add n8n queue mode template n8n running in queue mode (EXECUTIONS_MODE=queue): main instance for the editor/webhooks, a scalable pool of workers (deploy.replicas driven by the N8N_WORKER_REPLICAS env var), Redis (Bull) as the queue broker and PostgreSQL as the database. Pinned to the current stable n8nio/n8n:2.30.4. Closes #455 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add bonfire template Bonfire (social flavour) — federated social networking toolkit. App + PostGIS + Meilisearch, mirroring upstream's reference deployments. Closes #125 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add dagu blueprint Dagu is a self-contained workflow engine with a Web UI for scheduling and orchestrating jobs as DAGs defined in declarative YAML. - Pin ghcr.io/dagucloud/dagu:2.10.7 and expose the Web UI on port 8080 - Set DAGU_HOST=0.0.0.0 so the service is reachable through the proxy - Enable basic auth with a generated per-deploy admin password - Persist /var/lib/dagu (DAGs, logs, run history) in a named volume Claude-Session: https://claude.ai/code/session_01CRNT6sa2q2P9rUTokeoGhQ * feat: add OneUptime template Adds OneUptime (open-source observability platform: uptime monitoring, status pages, incident management, on-call, logs/traces/metrics) as a template, following upstream's current single-app architecture (app + nginx ingress + postgres + redis + clickhouse + probe). Closes #514 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: add Mailu template Mailu 2024.06 mail server: front (nginx), admin, imap (dovecot), smtp (postfix), antispam (rspamd), Roundcube webmail and redis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(templates): add systemprompt (#1032) * feat(templates): add systemprompt * fix header comment: accurate in both repos * chore: normalize template tags to kebab-case (#987) * feat: adds `check-links.js` to find broken links (#983) * feat: add struxa template (#1037) * feat: add struxa template Adds a Dokploy blueprint for Struxa (github.com/struxadotcloud/struxa), a self-hosted, open-source server management panel. Deploys the published GHCR images (dashboard, watchkeeper, migrate) plus MySQL and MinIO sidecars. The web service generates its RS256 JWT keypair at startup via Node's built-in crypto module, since no Dokploy template helper covers RSA key generation. * fix: pin struxa images to latest floating tag struxa's release workflow pushes a latest floating tag alongside version tags on every stable release, matching the convention used by ~44% of existing blueprints (version: "latest" in meta.json). * fix: generate a 64-char DATABASE_ENCRYPTION_KEY Dokploy's runtime \${hash:N} helper produces N hex characters, not N bytes (the templates repo's local preview helpers.ts models it as byte count, which is inconsistent with the platform). struxa's env schema requires DATABASE_ENCRYPTION_KEY to be >=64 chars (it's hex decoded to a 32-byte AES-256 key), so \${hash:32} only produced half the required length. Confirmed against other blueprints (sim, chatwoot, outline) which all use \${hash:64} for 64-char secrets. * fix: address Copilot review comments - Add explicit \`version: "3.8"\` to docker-compose.yml, matching the documented template format. - Pin minio/minio to a specific RELEASE tag (used elsewhere in the repo: plane, posthog) instead of the floating latest tag. - Default BETTER_AUTH_URL/APP_URL/CORS_ORIGIN to http://, matching repo convention for Dokploy-fronted apps (TLS terminates at the proxy). * fix: rename web service to struxa AGENTS.md convention: the primary Docker service name should match the blueprint folder name. Renamed the web/dashboard service from "web" to "struxa" in docker-compose.yml and updated template.toml's config.domains.serviceName to match. * Add PodFetch template (#1035) * Add PodFetch template * Use env array syntax in template.toml * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Address review: match version to image tag, expose admin_username variable, dark-mode friendly logo --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(docker): share plugins and themes volumes across containers (#990) * feat(directus): upgrade template to Directus 12.1.1 (#986) * feat(directus): upgrade template to Directus 12.1.1 * feat(directus): add commented S3 env variables to template.toml --------- Co-authored-by: Zakher Masri <46135573+zaaakher@users.noreply.github.com> Co-authored-by: zakher <zakhermasri@gmail.com> Co-authored-by: Jinjinov <dzindzinovic@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Stephan Fitzpatrick <stephan@knowsuchagency.com> Co-authored-by: Mauricio Siu <mauricio@dokploy.com> Co-authored-by: Edward Burton <ejb503@gmail.com> Co-authored-by: Marko Kostich <markokosticdev@gmail.com> Co-authored-by: Jakub Krzyżanowski <jakubkrzyz.kontakt@gmail.com> Co-authored-by: SamTV12345 <40429738+SamTV12345@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Mafly <110502739+Nekoplii@users.noreply.github.com>
@@ -14,6 +14,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import { Label } from "./ui/label";
|
||||
import { Clipboard } from "lucide-react";
|
||||
import { Input } from "./ui/input";
|
||||
import Markdown from "./ui/markdown";
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
@@ -32,6 +33,7 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateDialogProps {
|
||||
@@ -169,8 +171,23 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs defaultValue="docker-compose" className="w-full">
|
||||
<Tabs
|
||||
defaultValue={
|
||||
templateFiles?.instructions
|
||||
? "instructions"
|
||||
: "docker-compose"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="w-full justify-start">
|
||||
{templateFiles?.instructions && (
|
||||
<TabsTrigger
|
||||
value="instructions"
|
||||
className="data-[state=active]:font-bold"
|
||||
>
|
||||
Instructions
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger
|
||||
value="docker-compose"
|
||||
className="data-[state=active]:font-bold"
|
||||
@@ -184,6 +201,24 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
Configuration
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{templateFiles?.instructions && (
|
||||
<TabsContent value="instructions" className="mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="flex flex-col items-start w-fit justify-start gap-1">
|
||||
<span className="leading-tight text-xl font-semibold">
|
||||
Setup Instructions
|
||||
</span>
|
||||
<span className="leading-tight text-sm text-gray-500">
|
||||
instructions.md
|
||||
</span>
|
||||
</Label>
|
||||
<div className="rounded-md border p-4">
|
||||
<Markdown content={templateFiles.instructions} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="docker-compose" className="mt-4">
|
||||
{templateFiles?.dockerCompose && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateGridProps {
|
||||
@@ -80,20 +81,34 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
const fetchTemplateFiles = async (templateId: string) => {
|
||||
setModalLoading(true);
|
||||
try {
|
||||
const [dockerComposeRes, configRes] = await Promise.all([
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
]);
|
||||
const [dockerComposeRes, configRes, instructionsRes] = await Promise.all(
|
||||
[
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
fetch(`/blueprints/${templateId}/instructions.md`),
|
||||
]
|
||||
);
|
||||
|
||||
const dockerCompose = dockerComposeRes.ok
|
||||
? await dockerComposeRes.text()
|
||||
: null;
|
||||
const config = configRes.ok ? await configRes.text() : null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config });
|
||||
// Guard against SPA fallbacks that return index.html with a 200 status
|
||||
// for templates that don't have an instructions.md file.
|
||||
const instructionsIsMarkdown =
|
||||
instructionsRes.ok &&
|
||||
!(instructionsRes.headers.get("content-type") || "").includes(
|
||||
"text/html"
|
||||
);
|
||||
const instructions = instructionsIsMarkdown
|
||||
? await instructionsRes.text()
|
||||
: null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config, instructions });
|
||||
} catch (err) {
|
||||
console.error("Error fetching template files:", err);
|
||||
setTemplateFiles({ dockerCompose: null, config: null });
|
||||
setTemplateFiles({ dockerCompose: null, config: null, instructions: null });
|
||||
} finally {
|
||||
setModalLoading(false);
|
||||
}
|
||||
|
||||
177
app/src/components/ui/markdown.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import React from "react";
|
||||
|
||||
// Minimal markdown renderer (no external dependencies) for the per-template
|
||||
// instructions.md files. Supports headings, paragraphs, fenced code blocks,
|
||||
// ordered/unordered lists, and inline code / bold / links.
|
||||
|
||||
const INLINE_PATTERN =
|
||||
/(`[^`]+`)|(\*\*[^*]+\*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
|
||||
|
||||
const renderInline = (text: string): React.ReactNode[] => {
|
||||
const nodes: React.ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let key = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
INLINE_PATTERN.lastIndex = 0;
|
||||
|
||||
while ((match = INLINE_PATTERN.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
nodes.push(text.slice(lastIndex, match.index));
|
||||
}
|
||||
const token = match[0];
|
||||
if (token.startsWith("`")) {
|
||||
nodes.push(
|
||||
<code
|
||||
key={key++}
|
||||
className="px-1.5 py-0.5 rounded bg-muted font-mono text-[0.85em]"
|
||||
>
|
||||
{token.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
} else if (token.startsWith("**")) {
|
||||
nodes.push(<strong key={key++}>{token.slice(2, -2)}</strong>);
|
||||
} else {
|
||||
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token);
|
||||
if (link) {
|
||||
nodes.push(
|
||||
<a
|
||||
key={key++}
|
||||
href={link[2]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 dark:text-blue-400 underline underline-offset-2"
|
||||
>
|
||||
{link[1]}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
nodes.push(token);
|
||||
}
|
||||
}
|
||||
lastIndex = match.index + token.length;
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(text.slice(lastIndex));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
const HEADING_CLASSES: Record<number, string> = {
|
||||
1: "text-2xl font-bold mt-2",
|
||||
2: "text-xl font-semibold mt-6 border-b pb-1",
|
||||
3: "text-lg font-semibold mt-4",
|
||||
4: "text-base font-semibold mt-3",
|
||||
};
|
||||
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Markdown: React.FC<MarkdownProps> = ({ content }) => {
|
||||
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
||||
const blocks: React.ReactNode[] = [];
|
||||
let key = 0;
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
|
||||
// Blank line
|
||||
if (line.trim() === "") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fenced code block
|
||||
if (line.trim().startsWith("```")) {
|
||||
const code: string[] = [];
|
||||
i++;
|
||||
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
||||
code.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
i++; // skip closing fence
|
||||
blocks.push(
|
||||
<pre
|
||||
key={key++}
|
||||
className="bg-muted rounded-md p-4 overflow-x-auto text-sm font-mono whitespace-pre"
|
||||
>
|
||||
<code>{code.join("\n")}</code>
|
||||
</pre>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Heading
|
||||
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
||||
if (heading) {
|
||||
const level = heading[1].length;
|
||||
const Tag = `h${Math.min(level + 1, 6)}` as keyof React.JSX.IntrinsicElements;
|
||||
blocks.push(
|
||||
<Tag key={key++} className={HEADING_CLASSES[level]}>
|
||||
{renderInline(heading[2])}
|
||||
</Tag>
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unordered list
|
||||
if (/^[-*]\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^[-*]\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ul key={key++} className="list-disc pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ordered list
|
||||
if (/^\d+\.\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^\d+\.\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ol key={key++} className="list-decimal pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Paragraph: consume consecutive plain lines
|
||||
const paragraph: string[] = [line];
|
||||
i++;
|
||||
while (
|
||||
i < lines.length &&
|
||||
lines[i].trim() !== "" &&
|
||||
!/^(#{1,4})\s+/.test(lines[i]) &&
|
||||
!/^[-*]\s+/.test(lines[i]) &&
|
||||
!/^\d+\.\s+/.test(lines[i]) &&
|
||||
!lines[i].trim().startsWith("```")
|
||||
) {
|
||||
paragraph.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<p key={key++} className="leading-relaxed">
|
||||
{renderInline(paragraph.join(" "))}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="space-y-3 text-sm">{blocks}</div>;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
## Instructions
|
||||
|
||||
We don't have nothing to show here....
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/seanmorley15/adventurelog",
|
||||
"website": "https://adventurelog.app/",
|
||||
"docs": "https://adventurelog.app/docs/"
|
||||
"docs": "https://adventurelog.app/docs/intro/adventurelog_overview.html"
|
||||
},
|
||||
"tags": [
|
||||
"activity",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
affinepro:
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3010
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -19,16 +20,21 @@ services:
|
||||
- MAILER_PASSWORD=${MAILER_PASSWORD}
|
||||
- MAILER_SENDER=${MAILER_SENDER}
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
|
||||
migration:
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
command: node ./scripts/self-host-predeploy.js
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
command: ["sh", "-c", "node ./scripts/self-host-predeploy.js"]
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -39,17 +45,25 @@ services:
|
||||
- affine-storage:/root/.affine/storage
|
||||
- affine-config:/root/.affine/config
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
image: pgvector/pgvector:pg16
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=affinepro
|
||||
- POSTGRES_INITDB_ARGS=--data-checksums
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "affinepro"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
@@ -57,9 +71,14 @@ services:
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD}' ping | grep PONG"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
affine-storage: {}
|
||||
affine-config: {}
|
||||
postgres-data: {}
|
||||
redis-data: {}
|
||||
redis-data: {}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "affinepro",
|
||||
"name": "Affine Pro",
|
||||
"version": "stable-780dd83",
|
||||
"version": "stable",
|
||||
"description": "Affine Pro is a modern, self-hosted platform designed for collaborative content creation and project management. It offers an intuitive interface, seamless real-time collaboration, and powerful tools for organizing tasks, notes, and ideas.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/toeverything/Affine",
|
||||
"website": "https://affine.pro/",
|
||||
"docs": "https://affine.pro/docs"
|
||||
"docs": "https://docs.affine.pro/"
|
||||
},
|
||||
"tags": [
|
||||
"collaboration",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"docs": "https://answer.apache.org/docs"
|
||||
},
|
||||
"tags": [
|
||||
"q&a",
|
||||
"q-and-a",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/Mintplex-Labs/anything-llm",
|
||||
"website": "https://useanything.com",
|
||||
"docs": "https://github.com/Mintplex-Labs/anything-llm/tree/master/docs"
|
||||
"website": "https://anythingllm.com/",
|
||||
"docs": "https://docs.anythingllm.com/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
automatisch:
|
||||
image: dockeriddonuts/automatisch:2.0
|
||||
image: automatischio/automatisch:0.15.0
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3000
|
||||
environment:
|
||||
- HOST=${DOMAIN}
|
||||
- PROTOCOL=http
|
||||
- PORT=3000
|
||||
- API_URL=http://${DOMAIN}
|
||||
- WEB_APP_URL=http://${DOMAIN}
|
||||
- APP_ENV=production
|
||||
- REDIS_HOST=automatisch-redis
|
||||
- REDIS_USERNAME=default
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- POSTGRES_HOST=automatisch-postgres
|
||||
- POSTGRES_DATABASE=automatisch
|
||||
@@ -23,11 +21,13 @@ services:
|
||||
volumes:
|
||||
- storage:/automatisch/storage
|
||||
depends_on:
|
||||
- automatisch-postgres
|
||||
- automatisch-redis
|
||||
automatisch-postgres:
|
||||
condition: service_healthy
|
||||
automatisch-redis:
|
||||
condition: service_started
|
||||
|
||||
automatisch-worker:
|
||||
image: dockeriddonuts/automatisch:2.0
|
||||
image: automatischio/automatisch:0.15.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
@@ -44,8 +44,7 @@ services:
|
||||
volumes:
|
||||
- storage:/automatisch/storage
|
||||
depends_on:
|
||||
- automatisch-postgres
|
||||
- automatisch-redis
|
||||
- automatisch
|
||||
|
||||
automatisch-postgres:
|
||||
image: postgres:15-alpine
|
||||
@@ -56,18 +55,20 @@ services:
|
||||
- POSTGRES_DB=automatisch
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d automatisch"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
automatisch-redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
environment:
|
||||
- REDIS_USERNAME=default
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
storage: {}
|
||||
postgres_data: {}
|
||||
redis_data: {}
|
||||
redis_data: {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "automatisch",
|
||||
"name": "Automatisch",
|
||||
"version": "2.0",
|
||||
"version": "0.15.0",
|
||||
"description": "Automatisch is a powerful, self-hosted workflow automation tool designed for connecting your apps and automating repetitive tasks. With Automatisch, you can create workflows to sync data, send notifications, and perform various actions seamlessly across different services.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
|
||||
@@ -13,6 +13,7 @@ port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
DOMAIN = "${main_domain}"
|
||||
DB_PASSWORD = "${db_password}"
|
||||
REDIS_PASSWORD = "${redis_password}"
|
||||
ENCRYPTION_KEY = "${encryption_key}"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/babybuddy/babybuddy",
|
||||
"website": "https://babybuddy.app",
|
||||
"docs": "https://docs.babybuddy.app"
|
||||
"docs": "https://docs.baby-buddy.net/"
|
||||
},
|
||||
"tags": [
|
||||
"parenting",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/morpheus65535/bazarr",
|
||||
"website": "https://www.bazarr.media/",
|
||||
"docs": "https://www.bazarr.media/docs"
|
||||
"docs": "https://wiki.bazarr.media/"
|
||||
},
|
||||
"tags": [
|
||||
"subtitles",
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"description": "BentoPDF is a lightweight PDF conversion microservice that exposes a simple HTTP API for generating PDFs.",
|
||||
"logo": "image.png",
|
||||
"links": {
|
||||
"github": "https://github.com/bentopdf/bentopdf",
|
||||
"github": "https://github.com/alam00000/bentopdf/",
|
||||
"website": "https://bentopdf.com/",
|
||||
"docs": "https://github.com/bentopdf/bentopdf#readme"
|
||||
"docs": "https://www.bentopdf.com/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"pdf",
|
||||
|
||||
@@ -28,6 +28,9 @@ services:
|
||||
- AUTORUN_LARAVEL_STORAGE_LINK=true
|
||||
volumes:
|
||||
- billmora-storage:/var/www/html/storage
|
||||
- billmora-plugins:/var/www/html/plugin
|
||||
- billmora-themes-resources:/var/www/html/resources/themes
|
||||
- billmora-themes-public:/var/www/html/public/themes
|
||||
depends_on:
|
||||
billmora-db:
|
||||
condition: service_healthy
|
||||
@@ -58,6 +61,9 @@ services:
|
||||
- AUTORUN_ENABLED=false
|
||||
volumes:
|
||||
- billmora-storage:/var/www/html/storage
|
||||
- billmora-plugins:/var/www/html/plugin
|
||||
- billmora-themes-resources:/var/www/html/resources/themes
|
||||
- billmora-themes-public:/var/www/html/public/themes
|
||||
depends_on:
|
||||
billmora-db:
|
||||
condition: service_healthy
|
||||
@@ -88,6 +94,9 @@ services:
|
||||
- AUTORUN_ENABLED=false
|
||||
volumes:
|
||||
- billmora-storage:/var/www/html/storage
|
||||
- billmora-plugins:/var/www/html/plugin
|
||||
- billmora-themes-resources:/var/www/html/resources/themes
|
||||
- billmora-themes-public:/var/www/html/public/themes
|
||||
depends_on:
|
||||
billmora-db:
|
||||
condition: service_healthy
|
||||
@@ -126,5 +135,8 @@ services:
|
||||
|
||||
volumes:
|
||||
billmora-storage:
|
||||
billmora-plugins:
|
||||
billmora-themes-resources:
|
||||
billmora-themes-public:
|
||||
billmora-db-data:
|
||||
billmora-redis-data:
|
||||
|
||||
1
blueprints/bonfire/bonfire.svg
Normal file
|
After Width: | Height: | Size: 72 KiB |
84
blueprints/bonfire/docker-compose.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
bonfire:
|
||||
image: bonfirenetworks/bonfire:1.0.5-social-amd64
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
search:
|
||||
condition: service_started
|
||||
environment:
|
||||
# Instance
|
||||
- HOSTNAME=${BONFIRE_HOSTNAME}
|
||||
- PUBLIC_PORT=${PUBLIC_PORT:-80}
|
||||
- SERVER_PORT=4000
|
||||
- APP_NAME=${APP_NAME:-Bonfire}
|
||||
- LANG=en_US.UTF-8
|
||||
# Secrets
|
||||
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||
- SIGNING_SALT=${SIGNING_SALT}
|
||||
- ENCRYPTION_SALT=${ENCRYPTION_SALT}
|
||||
- RELEASE_COOKIE=${RELEASE_COOKIE}
|
||||
# Database (migrations run automatically on startup)
|
||||
- POSTGRES_HOST=db
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=bonfire_db
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
# Must stay false for the initial migrations; can be set to true
|
||||
# after the first deployment to speed up future upgrades
|
||||
- DB_MIGRATE_INDEXES_CONCURRENTLY=${DB_MIGRATE_INDEXES_CONCURRENTLY:-false}
|
||||
# Search (Meilisearch)
|
||||
- SEARCH_ADAPTER=meili
|
||||
- SEARCH_MEILI_INSTANCE=http://search:7700
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
# Email (the first account to sign up becomes admin and needs no
|
||||
# confirmation email; configure a mail backend for further signups)
|
||||
- MAIL_BACKEND=${MAIL_BACKEND:-none}
|
||||
- MAIL_SERVER=${MAIL_SERVER:-}
|
||||
- MAIL_PORT=${MAIL_PORT:-}
|
||||
- MAIL_USER=${MAIL_USER:-}
|
||||
- MAIL_PASSWORD=${MAIL_PASSWORD:-}
|
||||
- MAIL_DOMAIN=${MAIL_DOMAIN:-}
|
||||
- MAIL_FROM=${MAIL_FROM:-}
|
||||
- MAIL_KEY=${MAIL_KEY:-}
|
||||
# Max upload size in megabytes
|
||||
- UPLOAD_LIMIT=${UPLOAD_LIMIT:-20}
|
||||
volumes:
|
||||
- bonfire-uploads:/opt/app/data/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
|
||||
db:
|
||||
image: postgis/postgis:17-3.5-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=bonfire_db
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d bonfire_db"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
search:
|
||||
image: getmeili/meilisearch:v1.11
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
- MEILI_ENV=production
|
||||
- MEILI_NO_ANALYTICS=true
|
||||
volumes:
|
||||
- search-data:/meili_data
|
||||
|
||||
volumes:
|
||||
bonfire-uploads:
|
||||
db-data:
|
||||
search-data:
|
||||
17
blueprints/bonfire/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "bonfire",
|
||||
"name": "Bonfire",
|
||||
"version": "1.0.5",
|
||||
"description": "Federated social networking toolkit for communities: customizable digital spaces with ActivityPub federation, flexible boundaries and moderation tools.",
|
||||
"logo": "bonfire.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/bonfire-networks/bonfire-app",
|
||||
"website": "https://bonfirenetworks.org",
|
||||
"docs": "https://docs.bonfirenetworks.org"
|
||||
},
|
||||
"tags": [
|
||||
"social",
|
||||
"fediverse",
|
||||
"activitypub"
|
||||
]
|
||||
}
|
||||
55
blueprints/bonfire/template.toml
Normal file
@@ -0,0 +1,55 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key_base = "${password:64}"
|
||||
signing_salt = "${password:32}"
|
||||
encryption_salt = "${password:32}"
|
||||
release_cookie = "${password:32}"
|
||||
postgres_password = "${password:32}"
|
||||
meili_master_key = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "bonfire"
|
||||
port = 4000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
# Domain of your Bonfire instance (must match the domain above)
|
||||
"BONFIRE_HOSTNAME" = "${main_domain}"
|
||||
# Port used to build public URLs: keep 80 while the domain is plain HTTP,
|
||||
# and change to 443 once you enable HTTPS on the domain
|
||||
# (HTTPS is required for federation with other fediverse instances)
|
||||
"PUBLIC_PORT" = "80"
|
||||
# Display name of your instance
|
||||
"APP_NAME" = "Bonfire"
|
||||
# Max upload size in megabytes
|
||||
"UPLOAD_LIMIT" = "20"
|
||||
|
||||
# Secrets (auto-generated)
|
||||
"SECRET_KEY_BASE" = "${secret_key_base}"
|
||||
"SIGNING_SALT" = "${signing_salt}"
|
||||
"ENCRYPTION_SALT" = "${encryption_salt}"
|
||||
"RELEASE_COOKIE" = "${release_cookie}"
|
||||
"POSTGRES_PASSWORD" = "${postgres_password}"
|
||||
"MEILI_MASTER_KEY" = "${meili_master_key}"
|
||||
|
||||
# Optional: set to true only AFTER the first deployment finished its initial
|
||||
# database migrations (speeds up index creation during future upgrades)
|
||||
# "DB_MIGRATE_INDEXES_CONCURRENTLY" = "true"
|
||||
|
||||
# --- Email ---
|
||||
# The first account to sign up becomes the instance admin and does not need
|
||||
# a confirmation email. Further signups are invite-only by default (the admin
|
||||
# can create invite links or open up signups in the instance settings), and
|
||||
# require a working mail backend to confirm their email address.
|
||||
# Supported backends include smtp, mailgun, brevo, postmark, ses and more:
|
||||
# https://docs.bonfirenetworks.org/deploy.html
|
||||
# "MAIL_BACKEND" = "smtp"
|
||||
# "MAIL_SERVER" = "smtp.example.com"
|
||||
# "MAIL_PORT" = "587"
|
||||
# "MAIL_USER" = "postmaster@example.com"
|
||||
# "MAIL_PASSWORD" = ""
|
||||
# "MAIL_DOMAIN" = "example.com"
|
||||
# "MAIL_FROM" = "bonfire@example.com"
|
||||
# For API-based backends (e.g. mailgun) set the API key instead:
|
||||
# "MAIL_KEY" = ""
|
||||
@@ -10,7 +10,7 @@
|
||||
"docs": "https://manual.calibre-ebook.com/"
|
||||
},
|
||||
"tags": [
|
||||
"Documents",
|
||||
"E-Commerce"
|
||||
"documents",
|
||||
"e-commerce"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
"docs": "https://carbone.io/documentation/design/overview/getting-started.html"
|
||||
},
|
||||
"tags": [
|
||||
"Document Generation",
|
||||
"Automation",
|
||||
"Reporting",
|
||||
"Productivity"
|
||||
"document-generation",
|
||||
"automation",
|
||||
"reporting",
|
||||
"productivity"
|
||||
]
|
||||
}
|
||||
|
||||
33
blueprints/certmate/docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
certmate:
|
||||
image: fabriziosalmi/certmate:2.21.3
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- SECRET_KEY=${SECRET_KEY} # 🔐 Flask session secret
|
||||
- API_BEARER_TOKEN=${API_BEARER_TOKEN} # 🔐 API / web UI token
|
||||
- BEHIND_PROXY=${BEHIND_PROXY}
|
||||
- CLOUDFLARE_TOKEN=${CLOUDFLARE_TOKEN}
|
||||
- LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
ports:
|
||||
- 8000
|
||||
volumes:
|
||||
- certmate_certificates:/app/certificates
|
||||
- certmate_data:/app/data
|
||||
- certmate_logs:/app/logs
|
||||
- certmate_backups:/app/backups
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
volumes:
|
||||
certmate_certificates:
|
||||
certmate_data:
|
||||
certmate_logs:
|
||||
certmate_backups:
|
||||
13
blueprints/certmate/instructions.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# CertMate
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Deploy the template and open the app domain.
|
||||
2. Log in with the **API bearer token**: it is auto-generated by the template and stored in the `API_BEARER_TOKEN` environment variable (Dokploy → your service → Environment). The same token authenticates REST API requests (`Authorization: Bearer <token>`).
|
||||
3. In **Settings**, set your Let's Encrypt email and add credentials for your DNS provider (Cloudflare, Route53, etc.) to issue certificates through DNS-01 challenges.
|
||||
|
||||
## Notes
|
||||
|
||||
- `CLOUDFLARE_TOKEN` can optionally be set as an environment variable instead of configuring it in the UI.
|
||||
- Certificates, application data and backups are persisted in the `certmate_certificates`, `certmate_data` and `certmate_backups` volumes.
|
||||
- API documentation is available at `/docs/` (Swagger) and `/redoc/` on your domain.
|
||||
BIN
blueprints/certmate/logo.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
18
blueprints/certmate/meta.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "certmate",
|
||||
"name": "CertMate",
|
||||
"version": "2.21.3",
|
||||
"description": "CertMate is an SSL certificate management system with a web UI and REST API. It automates issuing and renewing Let's Encrypt certificates via DNS-01 challenges across 20+ DNS providers, with unified backups and multi-account support.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/fabriziosalmi/certmate",
|
||||
"website": "https://www.certmate.org/",
|
||||
"docs": "https://www.certmate.org/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"ssl",
|
||||
"certificates",
|
||||
"security",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
20
blueprints/certmate/template.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
SECRET_KEY = "${password:32}"
|
||||
API_BEARER_TOKEN = "${password:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "certmate"
|
||||
port = 8000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SECRET_KEY = "${SECRET_KEY}" # 🔐 Flask session secret
|
||||
API_BEARER_TOKEN = "${API_BEARER_TOKEN}" # 🔐 API / web UI token
|
||||
BEHIND_PROXY = "true"
|
||||
CLOUDFLARE_TOKEN = ""
|
||||
LETSENCRYPT_EMAIL = ""
|
||||
LOG_LEVEL = "INFO"
|
||||
@@ -10,8 +10,8 @@
|
||||
"docs": "https://github.com/dgtlmoon/changedetection.io/wiki"
|
||||
},
|
||||
"tags": [
|
||||
"Monitoring",
|
||||
"Data",
|
||||
"Notifications"
|
||||
"monitoring",
|
||||
"data",
|
||||
"notifications"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/bluewave-labs/checkmate",
|
||||
"website": "https://checkmate.so/",
|
||||
"docs": "https://docs.checkmate.so"
|
||||
"docs": "https://checkmate.so/docs"
|
||||
},
|
||||
"tags": [
|
||||
"self-hosted",
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"docs": "https://v4-docs.chevereto.com/"
|
||||
},
|
||||
"tags": [
|
||||
"Image Hosting",
|
||||
"File Management",
|
||||
"Open Source",
|
||||
"Multi-User",
|
||||
"Private Albums"
|
||||
"image-hosting",
|
||||
"file-management",
|
||||
"open-source",
|
||||
"multi-user",
|
||||
"private-albums"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"docs": "https://chibisafe.app/docs/intro"
|
||||
},
|
||||
"tags": [
|
||||
"media system",
|
||||
"media-system",
|
||||
"storage",
|
||||
"file-sharing"
|
||||
]
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"docs": "https://docs.chiefonboarding.com/"
|
||||
},
|
||||
"tags": [
|
||||
"Employee Onboarding",
|
||||
"HR Management",
|
||||
"Task Tracking",
|
||||
"Role-Based Access",
|
||||
"Document Management"
|
||||
"employee-onboarding",
|
||||
"hr-management",
|
||||
"task-tracking",
|
||||
"role-based-access",
|
||||
"document-management"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ services:
|
||||
- COCKPIT_DATABASE_SERVER=mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongo:27017
|
||||
- COCKPIT_DATABASE_NAME=cockpit
|
||||
volumes:
|
||||
- html:/var/www/html
|
||||
- data:/var/www/html/storage/data
|
||||
- config:/var/www/html/config
|
||||
- storage:/var/www/html/storage
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
@@ -25,6 +25,6 @@ services:
|
||||
- mongo-data:/data/db
|
||||
|
||||
volumes:
|
||||
html:
|
||||
data:
|
||||
config:
|
||||
storage:
|
||||
mongo-data:
|
||||
@@ -12,8 +12,8 @@
|
||||
"tags": [
|
||||
"crawler",
|
||||
"scraping",
|
||||
"AI",
|
||||
"LLM",
|
||||
"API"
|
||||
"ai",
|
||||
"llm",
|
||||
"api"
|
||||
]
|
||||
}
|
||||
|
||||
25
blueprints/dagu/docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
# Access the Dagu Web UI at the assigned domain.
|
||||
# Sign in with the basic auth credentials from your .env file (default user: admin).
|
||||
# Place your DAG definitions (YAML) in the dags directory via the Web UI or the API.
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
dagu:
|
||||
image: ghcr.io/dagucloud/dagu:2.10.7
|
||||
command: dagu start-all
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
volumes:
|
||||
- dagu-data:/var/lib/dagu
|
||||
environment:
|
||||
# Dagu binds to 127.0.0.1 by default; 0.0.0.0 is required to reach it
|
||||
- DAGU_HOST=0.0.0.0
|
||||
- DAGU_PORT=8080
|
||||
- DAGU_AUTH_MODE=basic
|
||||
- DAGU_AUTH_BASIC_USERNAME=${ADMIN_USERNAME}
|
||||
- DAGU_AUTH_BASIC_PASSWORD=${ADMIN_PASSWORD}
|
||||
|
||||
volumes:
|
||||
dagu-data: {}
|
||||
BIN
blueprints/dagu/logo.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
19
blueprints/dagu/meta.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "dagu",
|
||||
"name": "Dagu",
|
||||
"version": "2.10.7",
|
||||
"description": "Dagu is a self-contained workflow engine with a Web UI. Schedule and orchestrate jobs as DAGs defined in declarative YAML, with no DBMS required.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/dagucloud/dagu",
|
||||
"website": "https://dagu.sh",
|
||||
"docs": "https://docs.dagu.sh"
|
||||
},
|
||||
"tags": [
|
||||
"workflow",
|
||||
"automation",
|
||||
"scheduler",
|
||||
"cron",
|
||||
"devops"
|
||||
]
|
||||
}
|
||||
15
blueprints/dagu/template.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "dagu"
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
ADMIN_USERNAME = "admin"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
12
blueprints/databasus/databasus.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_287_1020)">
|
||||
<path d="M50.1522 115.189C50.1522 121.189 57.1564 121.193 59 118C60.1547 116 61 114 61 108C61 102 58.1044 96.9536 55.3194 91.5175C54.6026 90.1184 53.8323 88.6149 53.0128 86.9234C51.6073 84.0225 49.8868 81.3469 47.3885 79.2139C47.0053 78.8867 46.8935 78.0093 46.9624 77.422C47.2351 75.1036 47.5317 72.7876 47.8283 70.4718C48.3186 66.6436 48.8088 62.8156 49.1909 58.9766C49.459 56.2872 49.4542 53.5119 49.1156 50.8329C48.3833 45.0344 45.1292 40.7783 40.1351 37.9114C38.6818 37.0771 38.2533 36.1455 38.4347 34.5853C38.9402 30.2473 40.6551 26.3306 42.8342 22.6642C44.8356 19.297 47.1037 16.0858 49.3676 12.8804C49.6576 12.4699 49.9475 12.0594 50.2367 11.6488C50.6069 11.1231 51.5231 10.7245 52.1971 10.7075C60.4129 10.5017 68.6303 10.3648 76.8477 10.2636C77.4123 10.2563 78.1584 10.5196 78.5221 10.9246C83.6483 16.634 88.2284 22.712 90.9778 29.9784C91.1658 30.4758 91.3221 30.9869 91.4655 31.4997C92.4976 35.1683 92.4804 35.1803 89.5401 37.2499L89.4071 37.3436C83.8702 41.2433 81.8458 46.8198 82.0921 53.349C82.374 60.8552 84.0622 68.1313 85.9869 75.3539C86.3782 76.8218 86.6318 77.9073 85.2206 79.2609C82.3951 81.9698 81.2196 85.6872 80.6575 89.4687C80.0724 93.4081 79.599 97.3637 79.1254 101.32C78.8627 103.515 78.8497 105.368 78.318 107.904C76.2819 117.611 71 128 63 128H50.1522C45 128 41 123.189 41 115.189H50.1522Z" fill="#155DFC"/>
|
||||
<path d="M46.2429 6.56033C43.3387 11.1 40.3642 15.4031 37.7614 19.9209C35.413 23.9964 33.8487 28.4226 33.0913 33.1211C32.0998 39.2728 33.694 44.7189 38.0765 48.9775C41.6846 52.4835 42.6153 56.4472 42.152 61.1675C41.1426 71.4587 39.1174 81.5401 36.2052 91.4522C36.1769 91.5477 36.0886 91.6255 35.8974 91.8977C34.1517 91.3525 32.3161 90.8446 30.5266 90.2095C5.53011 81.3376 -12.7225 64.953 -24.1842 41.0298C-25.175 38.9625 -26.079 36.8498 -26.9263 34.7202C-27.0875 34.3151 -26.9749 33.5294 -26.6785 33.2531C-17.1479 24.3723 -7.64007 15.4647 2.00468 6.70938C8.64568 0.681612 16.5812 -1.21558 25.2457 0.739942C31.9378 2.24992 38.5131 4.27834 45.1363 6.09048C45.5843 6.2128 45.9998 6.45502 46.2429 6.56033Z" fill="#155DFC"/>
|
||||
<path d="M96.9586 89.3257C95.5888 84.7456 94.0796 80.4011 93.0111 75.9514C91.6065 70.0978 90.4683 64.1753 89.3739 58.2529C88.755 54.9056 89.3998 51.8176 91.89 49.2108C98.2669 42.5358 98.3933 34.7971 95.3312 26.7037C92.7471 19.8739 88.593 13.9904 83.7026 8.60904C83.1298 7.9788 82.5693 7.33641 81.918 6.60491C82.2874 6.40239 82.5709 6.18773 82.8909 6.07999C90.1281 3.64085 97.4495 1.54842 105.041 0.488845C112.781 -0.591795 119.379 1.81818 125.045 6.97592C130.017 11.5018 134.805 16.2327 139.812 20.7188C143.822 24.3115 148.013 27.7066 152.19 31.1073C152.945 31.7205 153.137 32.2154 152.913 33.1041C149.059 48.4591 141.312 61.4883 129.457 71.9877C120.113 80.2626 109.35 85.9785 96.9586 89.3265V89.3257Z" fill="#155DFC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_287_1020">
|
||||
<rect width="128" height="128" rx="6" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
22
blueprints/databasus/docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
databasus:
|
||||
image: databasus/databasus:latest
|
||||
ports:
|
||||
- "4005"
|
||||
volumes:
|
||||
# Persistent data storage
|
||||
- databasus-data:/databasus-data
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Optional: Set timezone
|
||||
- TZ=UTC
|
||||
healthcheck:
|
||||
test: [ "CMD", "databasus", "healthcheck" ]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
databasus-data:
|
||||
driver: local
|
||||
17
blueprints/databasus/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "databasus",
|
||||
"name": "Databasus",
|
||||
"version": "latest",
|
||||
"description": "Free, open source and self-hosted tool to backup PostgreSQL, MySQL, MariaDB and MongoDB. Multiple storages (S3, Google Drive, FTP, etc.), notifications and Point-in-Time Recovery. Successor of Postgresus",
|
||||
"logo": "databasus.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/databasus/databasus",
|
||||
"website": "https://databasus.com",
|
||||
"docs": "https://databasus.com/installation"
|
||||
},
|
||||
"tags": [
|
||||
"postgres",
|
||||
"backup",
|
||||
"s3"
|
||||
]
|
||||
}
|
||||
11
blueprints/databasus/template.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = []
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "databasus"
|
||||
port = 4005
|
||||
host = "${main_domain}"
|
||||
256
blueprints/dify/docker-compose.yml
Normal file
@@ -0,0 +1,256 @@
|
||||
# Dify - open-source LLM app development platform
|
||||
# Adapted from the official docker/docker-compose.yaml (tag 1.15.0).
|
||||
# Minimal functional stack: api + worker + worker_beat + web + postgres
|
||||
# (pgvector, reused as the vector store) + redis + sandbox + ssrf_proxy
|
||||
# + plugin_daemon, fronted by a lightweight internal nginx gateway that
|
||||
# mirrors the upstream path routing (TLS terminates at Traefik).
|
||||
|
||||
x-shared-api-env: &shared-api-env
|
||||
LOG_LEVEL: INFO
|
||||
DEPLOY_ENV: PRODUCTION
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
MIGRATION_ENABLED: "true"
|
||||
# Public URLs - a single domain, path-routed like the upstream nginx
|
||||
CONSOLE_API_URL: https://${DIFY_DOMAIN}
|
||||
CONSOLE_WEB_URL: https://${DIFY_DOMAIN}
|
||||
SERVICE_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_WEB_URL: https://${DIFY_DOMAIN}
|
||||
FILES_URL: https://${DIFY_DOMAIN}
|
||||
INTERNAL_FILES_URL: http://api:5001
|
||||
TRIGGER_URL: https://${DIFY_DOMAIN}
|
||||
ENDPOINT_URL_TEMPLATE: https://${DIFY_DOMAIN}/e/{hook_id}
|
||||
# The dedicated api_websocket collaboration service is not deployed
|
||||
ENABLE_COLLABORATION_MODE: "false"
|
||||
WEB_API_CORS_ALLOW_ORIGINS: "*"
|
||||
CONSOLE_CORS_ALLOW_ORIGINS: "*"
|
||||
# PostgreSQL
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_HOST: db
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE: dify
|
||||
# Redis / Celery
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
REDIS_DB: "0"
|
||||
CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/1
|
||||
# File storage on a local volume
|
||||
STORAGE_TYPE: opendal
|
||||
OPENDAL_SCHEME: fs
|
||||
OPENDAL_FS_ROOT: storage
|
||||
# Vector store: pgvector, reusing the same PostgreSQL instance
|
||||
VECTOR_STORE: pgvector
|
||||
PGVECTOR_HOST: db
|
||||
PGVECTOR_PORT: "5432"
|
||||
PGVECTOR_USER: postgres
|
||||
PGVECTOR_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
PGVECTOR_DATABASE: dify
|
||||
PGVECTOR_MIN_CONNECTION: "1"
|
||||
PGVECTOR_MAX_CONNECTION: "5"
|
||||
# Code execution sandbox
|
||||
CODE_EXECUTION_ENDPOINT: http://sandbox:8194
|
||||
CODE_EXECUTION_API_KEY: ${SANDBOX_API_KEY}
|
||||
# SSRF proxy (squid) for user-triggered outbound requests
|
||||
SSRF_PROXY_HTTP_URL: http://ssrf_proxy:3128
|
||||
SSRF_PROXY_HTTPS_URL: http://ssrf_proxy:3128
|
||||
# Plugin daemon (model providers / tools are plugins since Dify 1.0)
|
||||
PLUGIN_DAEMON_URL: http://plugin_daemon:5002
|
||||
PLUGIN_DAEMON_KEY: ${PLUGIN_DAEMON_KEY}
|
||||
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_INNER_API_KEY}
|
||||
PLUGIN_MAX_PACKAGE_SIZE: "52428800"
|
||||
PLUGIN_REMOTE_INSTALL_HOST: localhost
|
||||
PLUGIN_REMOTE_INSTALL_PORT: "5003"
|
||||
MARKETPLACE_ENABLED: "true"
|
||||
MARKETPLACE_API_URL: https://marketplace.dify.ai
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ../files/dify-nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
web:
|
||||
condition: service_started
|
||||
plugin_daemon:
|
||||
condition: service_started
|
||||
|
||||
# The dify-api image runs as the non-root user 1001, so the shared
|
||||
# storage volume has to be handed over to it once (same trick as the
|
||||
# upstream init_permissions container).
|
||||
init_permissions:
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
FLAG_FILE="/app/api/storage/.init_permissions"
|
||||
if [ -f "$$FLAG_FILE" ]; then
|
||||
echo "Permissions already initialized."
|
||||
exit 0
|
||||
fi
|
||||
echo "Initializing permissions for /app/api/storage"
|
||||
chown -R 1001:1001 /app/api/storage && touch "$$FLAG_FILE"
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
restart: "no"
|
||||
|
||||
api:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: api
|
||||
depends_on:
|
||||
init_permissions:
|
||||
condition: service_completed_successfully
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5001/health"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 120s
|
||||
|
||||
worker:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: worker
|
||||
depends_on:
|
||||
init_permissions:
|
||||
condition: service_completed_successfully
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: beat
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
web:
|
||||
image: langgenius/dify-web:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_API_URL: https://${DIFY_DOMAIN}
|
||||
SERVER_CONSOLE_API_URL: http://api:5001
|
||||
ENABLE_COLLABORATION_MODE: "false"
|
||||
MARKETPLACE_API_URL: https://marketplace.dify.ai
|
||||
MARKETPLACE_URL: https://marketplace.dify.ai
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: dify
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- dify-db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d dify"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
restart: always
|
||||
environment:
|
||||
REDISCLI_AUTH: ${REDIS_PASSWORD}
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- dify-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.15
|
||||
restart: always
|
||||
environment:
|
||||
API_KEY: ${SANDBOX_API_KEY}
|
||||
GIN_MODE: release
|
||||
WORKER_TIMEOUT: "15"
|
||||
ENABLE_NETWORK: "true"
|
||||
HTTP_PROXY: http://ssrf_proxy:3128
|
||||
HTTPS_PROXY: http://ssrf_proxy:3128
|
||||
SANDBOX_PORT: "8194"
|
||||
volumes:
|
||||
- dify-sandbox-deps:/dependencies
|
||||
|
||||
ssrf_proxy:
|
||||
image: ubuntu/squid:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ../files/dify-squid.conf:/etc/squid/squid.conf:ro
|
||||
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.6.3-local
|
||||
restart: always
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "5432"
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_DATABASE: dify_plugin
|
||||
DB_SSL_MODE: disable
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
SERVER_PORT: "5002"
|
||||
SERVER_KEY: ${PLUGIN_DAEMON_KEY}
|
||||
MAX_PLUGIN_PACKAGE_SIZE: "52428800"
|
||||
DIFY_INNER_API_URL: http://api:5001
|
||||
DIFY_INNER_API_KEY: ${PLUGIN_INNER_API_KEY}
|
||||
PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0
|
||||
PLUGIN_REMOTE_INSTALLING_PORT: "5003"
|
||||
PLUGIN_WORKING_PATH: /app/storage/cwd
|
||||
FORCE_VERIFYING_SIGNATURE: "true"
|
||||
PYTHON_ENV_INIT_TIMEOUT: "120"
|
||||
PLUGIN_MAX_EXECUTION_TIMEOUT: "600"
|
||||
PLUGIN_STORAGE_TYPE: local
|
||||
PLUGIN_STORAGE_LOCAL_ROOT: /app/storage
|
||||
PLUGIN_INSTALLED_PATH: plugin
|
||||
PLUGIN_PACKAGE_CACHE_PATH: plugin_packages
|
||||
PLUGIN_MEDIA_CACHE_PATH: assets
|
||||
volumes:
|
||||
- dify-plugin-storage:/app/storage
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
dify-app-storage:
|
||||
dify-db-data:
|
||||
dify-redis-data:
|
||||
dify-sandbox-deps:
|
||||
dify-plugin-storage:
|
||||
15
blueprints/dify/instructions.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Instructions
|
||||
|
||||
### First setup
|
||||
|
||||
- The first boot runs the database migrations, so it can take **2-4 minutes** before the application responds.
|
||||
- Open your domain: you will be redirected to `/install`, where you create the admin (workspace owner) account.
|
||||
- After signing in, go to **Settings -> Model Provider** and install a model provider plugin (OpenAI, Anthropic, Ollama, etc.) from the marketplace, then configure your API keys.
|
||||
|
||||
### Notes
|
||||
|
||||
- **HTTPS is required for the console login**: Dify issues `__Host-`/`Secure` session cookies, so make sure the domain has a certificate (e.g. Let's Encrypt in the domain settings). Over plain HTTP the browser will drop the session cookies and login will not persist.
|
||||
|
||||
- The vector store is **pgvector**, running inside the bundled PostgreSQL instance (no separate Weaviate/Qdrant container needed).
|
||||
- All routing (`/console/api`, `/api`, `/v1`, `/files`, `/e/`, ...) is handled by the bundled internal nginx gateway, mirroring the upstream deployment, so the Dify service API is available at `https://<your-domain>/v1`.
|
||||
- Code execution runs in the isolated `sandbox` service, and user-triggered outbound HTTP requests go through the `ssrf_proxy` (squid) service, like in the official deployment.
|
||||
12
blueprints/dify/logo.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="48" height="22" viewBox="0 0 48 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="White=False">
|
||||
<g id="if">
|
||||
<path d="M21.2002 3.73454C22.5633 3.73454 23.0666 2.89917 23.0666 1.86812C23.0666 0.837081 22.5623 0.00170898 21.2002 0.00170898C19.838 0.00170898 19.3337 0.837081 19.3337 1.86812C19.3337 2.89917 19.838 3.73454 21.2002 3.73454Z" fill="#0033FF"/>
|
||||
<path d="M27.7336 4.13435V5.33473H24.6668V8.00171H27.7336V14.6687H22.6668V5.33567H15.9998V8.00265H19.7336V14.6696H15.3337V17.3366H35.3337V14.6696H30.6668V8.00265H35.3337V5.33567H30.6668V2.66869H35.3337V0.00170898H31.8671C29.5877 0.00170898 27.7336 1.8559 27.7336 4.13529V4.13435Z" fill="#0033FF"/>
|
||||
</g>
|
||||
<g id="Dy">
|
||||
<path d="M5.66698 0.000940576H0V17.334H5.66698C12.667 17.334 14.667 13.334 14.667 8.66698C14.667 4 12.667 0 5.66698 0V0.000940576ZM5.73377 14.6679H3.20038V2.66792H5.73377C9.75823 2.66792 11.4666 4.64346 11.4666 8.66792C11.4666 12.6924 9.75823 14.6679 5.73377 14.6679Z" fill="black"/>
|
||||
<path d="M44.8335 5.3349L42.1665 14.0019L39.4995 5.3349H36.333L40.2013 16.5466C40.604 17.714 39.9229 18.6679 38.6886 18.6679H37.333V21.3349H39.3255C41.063 21.3349 42.6265 20.2361 43.2145 18.6011L48 5.3349H44.8335Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
17
blueprints/dify/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "dify",
|
||||
"name": "Dify",
|
||||
"version": "1.15.0",
|
||||
"description": "Dify is an open-source LLM app development platform. Build AI workflows, RAG pipelines, agents and chatbots with a visual interface and publish them as APIs or web apps.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/langgenius/dify",
|
||||
"website": "https://dify.ai/",
|
||||
"docs": "https://docs.dify.ai/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"llm",
|
||||
"rag"
|
||||
]
|
||||
}
|
||||
168
blueprints/dify/template.toml
Normal file
@@ -0,0 +1,168 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${base64:42}"
|
||||
postgres_password = "${password:32}"
|
||||
redis_password = "${password:32}"
|
||||
sandbox_api_key = "${password:32}"
|
||||
plugin_daemon_key = "${base64:42}"
|
||||
plugin_inner_api_key = "${base64:42}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"DIFY_DOMAIN=${main_domain}",
|
||||
"SECRET_KEY=${secret_key}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"REDIS_PASSWORD=${redis_password}",
|
||||
"SANDBOX_API_KEY=${sandbox_api_key}",
|
||||
"PLUGIN_DAEMON_KEY=${plugin_daemon_key}",
|
||||
"PLUGIN_INNER_API_KEY=${plugin_inner_api_key}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "nginx"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
# Internal nginx gateway replicating the upstream Dify nginx routing
|
||||
# (TLS terminates at Traefik, which forwards everything to this nginx).
|
||||
[[config.mounts]]
|
||||
filePath = "dify-nginx.conf"
|
||||
content = '''
|
||||
map $http_x_forwarded_proto $dify_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 100M;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
|
||||
location /console/api { proxy_pass http://api:5001; }
|
||||
location /api { proxy_pass http://api:5001; }
|
||||
location /v1 { proxy_pass http://api:5001; }
|
||||
location /openapi { proxy_pass http://api:5001; }
|
||||
location /files { proxy_pass http://api:5001; }
|
||||
location /mcp { proxy_pass http://api:5001; }
|
||||
location /triggers { proxy_pass http://api:5001; }
|
||||
location /explore { proxy_pass http://web:3000; }
|
||||
|
||||
location /e/ {
|
||||
proxy_pass http://plugin_daemon:5002;
|
||||
# nginx drops inherited proxy_set_header directives as soon as a
|
||||
# location defines its own, so the shared ones are repeated here.
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Dify-Hook-Url $dify_forwarded_proto://$host$request_uri;
|
||||
}
|
||||
|
||||
location / { proxy_pass http://web:3000; }
|
||||
}
|
||||
'''
|
||||
|
||||
# Squid SSRF proxy configuration, rendered from the upstream
|
||||
# docker/ssrf_proxy/squid.conf.template with its default values.
|
||||
[[config.mounts]]
|
||||
filePath = "dify-squid.conf"
|
||||
content = '''
|
||||
acl client_localnet src 0.0.0.1-0.255.255.255
|
||||
acl client_localnet src 10.0.0.0/8
|
||||
acl client_localnet src 100.64.0.0/10
|
||||
acl client_localnet src 169.254.0.0/16
|
||||
acl client_localnet src 172.16.0.0/12
|
||||
acl client_localnet src 192.168.0.0/16
|
||||
acl client_localnet src fc00::/7
|
||||
acl client_localnet src fe80::/10
|
||||
acl to_private_networks dst 0.0.0.0/8
|
||||
acl to_private_networks dst 10.0.0.0/8
|
||||
acl to_private_networks dst 100.64.0.0/10
|
||||
acl to_private_networks dst 127.0.0.0/8
|
||||
acl to_private_networks dst 169.254.0.0/16
|
||||
acl to_private_networks dst 172.16.0.0/12
|
||||
acl to_private_networks dst 192.168.0.0/16
|
||||
acl to_private_networks dst 224.0.0.0/4
|
||||
acl to_private_networks dst 240.0.0.0/4
|
||||
acl to_private_networks dst ::/128
|
||||
acl to_private_networks dst ::1/128
|
||||
acl to_private_networks dst ::ffff:0:0/96
|
||||
acl to_private_networks dst ::/96
|
||||
acl to_private_networks dst fc00::/7
|
||||
acl to_private_networks dst fe80::/10
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80
|
||||
acl Safe_ports port 21
|
||||
acl Safe_ports port 443
|
||||
acl Safe_ports port 70
|
||||
acl Safe_ports port 210
|
||||
acl Safe_ports port 1025-65535
|
||||
acl Safe_ports port 280
|
||||
acl Safe_ports port 488
|
||||
acl Safe_ports port 591
|
||||
acl Safe_ports port 777
|
||||
acl CONNECT method CONNECT
|
||||
acl allowed_domains dstdomain .marketplace.dify.ai
|
||||
|
||||
http_port 3128
|
||||
|
||||
http_access deny !Safe_ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
http_access deny to_private_networks
|
||||
http_access allow allowed_domains
|
||||
http_access allow client_localnet
|
||||
http_access allow localhost
|
||||
http_access deny all
|
||||
tcp_outgoing_address 0.0.0.0
|
||||
|
||||
coredump_dir /var/spool/squid
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
client_request_buffer_max_size 100 MB
|
||||
max_filedescriptors 65536
|
||||
|
||||
connect_timeout 30 seconds
|
||||
request_timeout 2 minutes
|
||||
read_timeout 2 minutes
|
||||
client_lifetime 5 minutes
|
||||
shutdown_lifetime 30 seconds
|
||||
|
||||
server_persistent_connections on
|
||||
client_persistent_connections on
|
||||
persistent_request_timeout 30 seconds
|
||||
pconn_timeout 1 minute
|
||||
|
||||
client_db on
|
||||
server_idle_pconn_timeout 2 minutes
|
||||
client_idle_pconn_timeout 2 minutes
|
||||
|
||||
quick_abort_min 16 KB
|
||||
quick_abort_max 16 MB
|
||||
quick_abort_pct 95
|
||||
|
||||
memory_cache_mode disk
|
||||
cache_mem 256 MB
|
||||
maximum_object_size_in_memory 512 KB
|
||||
|
||||
dns_timeout 30 seconds
|
||||
dns_retransmit_interval 5 seconds
|
||||
|
||||
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
|
||||
access_log daemon:/var/log/squid/access.log dify_log
|
||||
logfile_rotate 10
|
||||
'''
|
||||
@@ -1,15 +1,73 @@
|
||||
services:
|
||||
directus:
|
||||
image: directus/directus:12.1.1
|
||||
ports:
|
||||
- 8055
|
||||
volumes:
|
||||
- directus_uploads:/directus/uploads
|
||||
- directus_extensions:/directus/extensions
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:8055/server/ping || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_interval: 5s
|
||||
start_period: 30s
|
||||
environment:
|
||||
SECRET: ${DIRECTUS_SECRET}
|
||||
|
||||
DB_CLIENT: "pg"
|
||||
DB_HOST: "database"
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE: ${DB_NAME}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
CACHE_ENABLED: "true"
|
||||
CACHE_AUTO_PURGE: "true"
|
||||
CACHE_STORE: "redis"
|
||||
REDIS: "redis://cache:6379"
|
||||
|
||||
WEBSOCKETS_ENABLED: "true"
|
||||
PUBLIC_URL: ${PUBLIC_URL}
|
||||
LICENSE_KEY: ${LICENSE_KEY}
|
||||
|
||||
# After first successful login, remove ADMIN_EMAIL and ADMIN_PASSWORD below.
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
|
||||
STORAGE_LOCATIONS: "local"
|
||||
STORAGE_LOCAL_DRIVER: "local"
|
||||
STORAGE_LOCAL_ROOT: "/directus/uploads"
|
||||
|
||||
# S3-compatible storage — uncomment and set S3_* env vars in template.toml.
|
||||
# STORAGE_LOCATIONS: "s3"
|
||||
# STORAGE_S3_DRIVER: "s3"
|
||||
# STORAGE_S3_KEY: ${S3_ACCESS_KEY}
|
||||
# STORAGE_S3_SECRET: ${S3_SECRET_KEY}
|
||||
# STORAGE_S3_BUCKET: ${S3_BUCKET}
|
||||
# STORAGE_S3_REGION: ${S3_REGION}
|
||||
# STORAGE_S3_ENDPOINT: ${S3_ENDPOINT}
|
||||
# STORAGE_S3_ROOT: ${S3_ROOT}
|
||||
# STORAGE_S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE}
|
||||
# STORAGE_S3_ACL: "private"
|
||||
|
||||
database:
|
||||
image: postgis/postgis:13-master
|
||||
volumes:
|
||||
- directus_database:/var/lib/postgresql/data
|
||||
|
||||
environment:
|
||||
POSTGRES_USER: "directus"
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
POSTGRES_DB: "directus"
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "--host=localhost", "--username=directus"]
|
||||
test: ["CMD-SHELL", "pg_isready --host=localhost --username=$$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -26,38 +84,6 @@ services:
|
||||
start_interval: 5s
|
||||
start_period: 30s
|
||||
|
||||
|
||||
directus:
|
||||
image: directus/directus:11.12.0
|
||||
ports:
|
||||
- 8055
|
||||
volumes:
|
||||
- directus_uploads:/directus/uploads
|
||||
- directus_extensions:/directus/extensions
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
cache:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
SECRET: ${DIRECTUS_SECRET}
|
||||
|
||||
DB_CLIENT: "pg"
|
||||
DB_HOST: "database"
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE: "directus"
|
||||
DB_USER: "directus"
|
||||
DB_PASSWORD: ${DATABASE_PASSWORD}
|
||||
|
||||
CACHE_ENABLED: "true"
|
||||
CACHE_AUTO_PURGE: "true"
|
||||
CACHE_STORE: "redis"
|
||||
REDIS: "redis://cache:6379"
|
||||
|
||||
# After first successful login, remove the admin email/password env. variables below
|
||||
# as these will now be stored in the database.
|
||||
ADMIN_EMAIL: "admin@example.com"
|
||||
ADMIN_PASSWORD: "d1r3ctu5"
|
||||
volumes:
|
||||
directus_uploads:
|
||||
directus_extensions:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "directus",
|
||||
"name": "Directus",
|
||||
"version": "11.0.2",
|
||||
"version": "12.1.1",
|
||||
"description": "Directus is an open source headless CMS that provides an API-first solution for building custom backends.",
|
||||
"logo": "directus.jpg",
|
||||
"links": {
|
||||
"github": "https://github.com/directus/directus",
|
||||
"website": "https://directus.io/",
|
||||
"docs": "https://docs.directus.io/"
|
||||
"docs": "https://directus.com/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"cms"
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
public_url = "https://${main_domain}"
|
||||
directus_secret = "${base64:64}"
|
||||
database_password = "${password}"
|
||||
db_password = "${password:32}"
|
||||
db_user = "directus"
|
||||
db_name = "directus"
|
||||
admin_email = "${email}"
|
||||
admin_password = "${password:24}"
|
||||
license_key = ""
|
||||
|
||||
# Object storage (S3-compatible) — uncomment when switching from local to S3.
|
||||
# s3_access_key = ""
|
||||
# s3_secret_key = ""
|
||||
# s3_bucket = ""
|
||||
# s3_region = "us-east-1"
|
||||
# s3_endpoint = "https://s3.amazonaws.com"
|
||||
# s3_root = ""
|
||||
# s3_force_path_style = "false"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"DATABASE_PASSWORD=${database_password}",
|
||||
"PUBLIC_URL=${public_url}",
|
||||
"DIRECTUS_SECRET=${directus_secret}",
|
||||
"DB_PASSWORD=${db_password}",
|
||||
"DB_USER=${db_user}",
|
||||
"DB_NAME=${db_name}",
|
||||
"ADMIN_EMAIL=${admin_email}",
|
||||
"ADMIN_PASSWORD=${admin_password}",
|
||||
"LICENSE_KEY=${license_key}",
|
||||
# Uncomment when enabling S3 storage in docker-compose.yml:
|
||||
# "S3_ACCESS_KEY=${s3_access_key}",
|
||||
# "S3_SECRET_KEY=${s3_secret_key}",
|
||||
# "S3_BUCKET=${s3_bucket}",
|
||||
# "S3_REGION=${s3_region}",
|
||||
# "S3_ENDPOINT=${s3_endpoint}",
|
||||
# "S3_ROOT=${s3_root}",
|
||||
# "S3_FORCE_PATH_STYLE=${s3_force_path_style}",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/documenso/documenso",
|
||||
"website": "https://documenso.com/",
|
||||
"docs": "https://documenso.com/docs"
|
||||
"docs": "https://docs.documenso.com/"
|
||||
},
|
||||
"logo": "documenso.png",
|
||||
"tags": [
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
},
|
||||
"tags": [
|
||||
"media",
|
||||
"media system"
|
||||
"media-system"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"docs": "https://evershop.io/docs/development/getting-started/introduction"
|
||||
},
|
||||
"tags": [
|
||||
"E-Commerce",
|
||||
"e-commerce",
|
||||
"shopping"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
evolution-api:
|
||||
image: evoapicloud/evolution-api:latest
|
||||
image: evoapicloud/evolution-api:v2.3.7
|
||||
restart: always
|
||||
volumes:
|
||||
- evolution-instances:/evolution/instances
|
||||
@@ -29,6 +29,13 @@ services:
|
||||
- CACHE_REDIS_URI=${CACHE_REDIS_URI}
|
||||
- CACHE_REDIS_PREFIX_KEY=${CACHE_REDIS_PREFIX_KEY}
|
||||
- CACHE_REDIS_SAVE_INSTANCES=${CACHE_REDIS_SAVE_INSTANCES}
|
||||
- TYPEBOT_ENABLED=${TYPEBOT_ENABLED}
|
||||
- TYPEBOT_API_VERSION=${TYPEBOT_API_VERSION}
|
||||
- CHATWOOT_ENABLED=${CHATWOOT_ENABLED}
|
||||
- OPENAI_ENABLED=${OPENAI_ENABLED}
|
||||
- DIFY_ENABLED=${DIFY_ENABLED}
|
||||
- N8N_ENABLED=${N8N_ENABLED}
|
||||
- EVOAI_ENABLED=${EVOAI_ENABLED}
|
||||
|
||||
evolution-postgres:
|
||||
image: postgres:16-alpine
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "evolutionapi",
|
||||
"name": "Evolution API",
|
||||
"version": "v2.1.2",
|
||||
"version": "v2.3.7",
|
||||
"description": "Evolution API is a robust platform dedicated to empowering small businesses with limited resources, going beyond a simple messaging solution via WhatsApp.",
|
||||
"logo": "evolutionapi.png",
|
||||
"links": {
|
||||
|
||||
@@ -31,6 +31,13 @@ env = [
|
||||
"CACHE_REDIS_URI=redis://evolution-redis:6379",
|
||||
"CACHE_REDIS_PREFIX_KEY=evolution",
|
||||
"CACHE_REDIS_SAVE_INSTANCES=true",
|
||||
"TYPEBOT_ENABLED=true",
|
||||
"TYPEBOT_API_VERSION=latest",
|
||||
"CHATWOOT_ENABLED=true",
|
||||
"OPENAI_ENABLED=true",
|
||||
"DIFY_ENABLED=true",
|
||||
"N8N_ENABLED=true",
|
||||
"EVOAI_ENABLED=true",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"docs": "https://docs.flowiseai.com/"
|
||||
},
|
||||
"tags": [
|
||||
"AI",
|
||||
"LLM",
|
||||
"ai",
|
||||
"llm",
|
||||
"workflow",
|
||||
"automation"
|
||||
]
|
||||
|
||||
313
blueprints/frappe-lms/docker-compose.yml
Normal file
@@ -0,0 +1,313 @@
|
||||
x-custom-image: &custom_image
|
||||
image: ${IMAGE_NAME:-ghcr.io/frappe/lms}:${VERSION:-v2.52.0}
|
||||
pull_policy: ${PULL_POLICY:-always}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
|
||||
services:
|
||||
backend:
|
||||
<<: *custom_image
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
until [[ -f "sites/${SITE_NAME}/.create-site-done" ]]; do
|
||||
echo "Waiting for site ${SITE_NAME} to be created (first boot takes a few minutes)";
|
||||
sleep 5;
|
||||
done;
|
||||
exec /home/frappe/frappe-bench/env/bin/gunicorn --chdir=/home/frappe/frappe-bench/sites --bind=0.0.0.0:8000 --threads=4 --workers=2 --worker-class=gthread --worker-tmp-dir=/dev/shm --timeout=120 --preload frappe.app:application
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "127.0.0.1:8000"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 600s
|
||||
|
||||
frontend:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_started
|
||||
websocket:
|
||||
condition: service_started
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
|
||||
SOCKETIO: websocket:9000
|
||||
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
|
||||
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
|
||||
UPSTREAM_REAL_IP_RECURSIVE: "off"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "127.0.0.1:8080"]
|
||||
interval: 2s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
|
||||
queue-default:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- default
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
queue-long:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
queue-short:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- short
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
scheduler:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- bench
|
||||
- schedule
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
websocket:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "websocket:9000"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
configurator:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CONFIGURE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
echo "[configurator] starting";
|
||||
if [[ $${REGENERATE_APPS_TXT} == "1" ]]; then
|
||||
echo "[configurator] regenerating sites/apps.txt";
|
||||
ls -1 apps > sites/apps.txt;
|
||||
fi;
|
||||
existing_db_host=$$(grep -hs '^' sites/common_site_config.json | jq -r ".db_host // empty" 2>/dev/null || true);
|
||||
if [[ -n "$${existing_db_host}" ]]; then
|
||||
echo "[configurator] common_site_config.json already configured, skipping";
|
||||
exit 0;
|
||||
fi;
|
||||
echo "[configurator] applying bench global config";
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -g redis_socketio "redis://$$REDIS_SOCKETIO";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
echo "[configurator] done";
|
||||
environment:
|
||||
DB_HOST: "${DB_HOST:-db}"
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
REDIS_SOCKETIO: redis-socketio:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
create-site:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CREATE_SITE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
wait-for-it -t 300 $${DB_HOST}:$${DB_PORT};
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
[[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && touch sites/${SITE_NAME}/.create-site-done && exit 0;
|
||||
sed -i "s/^required_apps.*/required_apps = []/" apps/lms/lms/hooks.py;
|
||||
bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME} || exit 1;
|
||||
if [[ $${ENABLE_SCHEDULER} == "1" ]]; then
|
||||
bench --site $${SITE_NAME} enable-scheduler;
|
||||
else
|
||||
echo "Skipping scheduler enable";
|
||||
fi;
|
||||
bench --site $${SITE_NAME} clear-cache;
|
||||
touch sites/$${SITE_NAME}/.create-site-done;
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
environment:
|
||||
SITE_NAME: ${SITE_NAME}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
DB_HOST: ${DB_HOST:-db}
|
||||
DB_PORT: "${DB_PORT:-3306}"
|
||||
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
|
||||
ENABLE_SCHEDULER: "${ENABLE_SCHEDULER}"
|
||||
|
||||
migration:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${MIGRATE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
curl -f -H "Host: ${SITE_NAME}" http://frontend:8080/api/method/ping || { echo "Site not reachable yet, skipping migration"; exit 0; };
|
||||
bench --site all set-config -p maintenance_mode 1;
|
||||
bench --site all set-config -p pause_scheduler 1;
|
||||
bench --site all migrate;
|
||||
bench --site all set-config -p maintenance_mode 0;
|
||||
bench --site all set-config -p pause_scheduler 0;
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
db:
|
||||
image: mariadb:${DB_VERSION:-11.8.6}
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${ENABLE_DB:-0}
|
||||
restart_policy:
|
||||
condition: always
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
start_period: 120s
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --skip-character-set-client-handshake
|
||||
environment:
|
||||
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
|
||||
redis-cache:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-queue:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-queue-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-socketio:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-socketio-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
redis-cache-data:
|
||||
redis-queue-data:
|
||||
redis-socketio-data:
|
||||
sites:
|
||||
driver_opts:
|
||||
type: "${SITE_VOLUME_TYPE}"
|
||||
o: "${SITE_VOLUME_OPTS}"
|
||||
device: "${SITE_VOLUME_DEV}"
|
||||
17
blueprints/frappe-lms/instructions.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Frappe LMS (Frappe Learning)
|
||||
|
||||
## Initial setup
|
||||
|
||||
The first deployment creates the Frappe site and installs the LMS app, which can take **3-6 minutes** after all containers are up. The site is ready when the domain returns the Frappe login page.
|
||||
|
||||
## Default credentials
|
||||
|
||||
- **Username:** `Administrator`
|
||||
- **Password:** the value of the `ADMIN_PASSWORD` environment variable (auto-generated, check the Environment tab of the service)
|
||||
|
||||
After logging in, the learning portal is available at `/lms` and the Frappe admin backend at `/app`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The template pins `ghcr.io/frappe/lms:v2.52.0`, the latest upstream image that ships the LMS app (newer upstream tags are currently published without the app baked in).
|
||||
- The upstream image does not include the `payments` app, so paid-course checkout features are unavailable. Free courses, batches, quizzes and certifications work normally.
|
||||
BIN
blueprints/frappe-lms/lms-logo.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
19
blueprints/frappe-lms/meta.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "frappe-lms",
|
||||
"name": "Frappe LMS",
|
||||
"version": "v2.52.0",
|
||||
"description": "Easy to use, open source learning management system to structure courses and reach out to learners, built on the Frappe Framework.",
|
||||
"logo": "lms-logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/frappe/lms",
|
||||
"docs": "https://docs.frappe.io/learning",
|
||||
"website": "https://frappe.io/learning"
|
||||
},
|
||||
"tags": [
|
||||
"lms",
|
||||
"learning",
|
||||
"education",
|
||||
"courses",
|
||||
"frappe"
|
||||
]
|
||||
}
|
||||
30
blueprints/frappe-lms/template.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
db_root_password = "${password:32}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "frontend"
|
||||
port = 8_080
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SITE_NAME = "${main_domain}"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
DB_ROOT_PASSWORD = "${db_root_password}"
|
||||
MIGRATE = "1"
|
||||
ENABLE_DB = "1"
|
||||
DB_HOST = "db"
|
||||
CREATE_SITE = "1"
|
||||
CONFIGURE = "1"
|
||||
REGENERATE_APPS_TXT = "1"
|
||||
INSTALL_APP_ARGS = "--install-app lms"
|
||||
ENABLE_SCHEDULER = "1"
|
||||
IMAGE_NAME = "ghcr.io/frappe/lms"
|
||||
VERSION = "v2.52.0"
|
||||
FRAPPE_SITE_NAME_HEADER = ""
|
||||
DB_VERSION = "11.8.6"
|
||||
SITE_VOLUME_TYPE = ""
|
||||
SITE_VOLUME_OPTS = ""
|
||||
SITE_VOLUME_DEV = ""
|
||||
@@ -1,65 +1,24 @@
|
||||
services:
|
||||
gitlab:
|
||||
image: gitlab/gitlab-ce:latest
|
||||
image: gitlab/gitlab-ce:19.1.2-ce.0
|
||||
restart: unless-stopped
|
||||
hostname: gitlab.example.com
|
||||
shm_size: "256m"
|
||||
environment:
|
||||
GITLAB_OMNIBUS_CONFIG: |
|
||||
external_url 'http://${GITLAB_HOST}'
|
||||
gitlab_rails['gitlab_ssh_host'] = '${GITLAB_HOST}'
|
||||
gitlab_rails['gitlab_shell_ssh_port'] = 2224
|
||||
gitlab_rails['db_adapter'] = 'postgresql'
|
||||
gitlab_rails['db_host'] = 'postgresql'
|
||||
gitlab_rails['db_port'] = '5432'
|
||||
gitlab_rails['db_database'] = '${POSTGRES_DB}'
|
||||
gitlab_rails['db_username'] = '${POSTGRES_USER}'
|
||||
gitlab_rails['db_password'] = '${POSTGRES_PASSWORD}'
|
||||
# Redis config for external TCP connection
|
||||
gitlab_rails['redis_url'] = 'redis://redis:6379/0'
|
||||
gitlab_rails['redis_host'] = 'redis'
|
||||
gitlab_rails['redis_port'] = 6379
|
||||
gitlab_rails['redis_socket'] = nil
|
||||
gitlab_rails['gitlab_email_enabled'] = false
|
||||
gitlab_rails['gitlab_default_can_create_group'] = true
|
||||
gitlab_rails['gitlab_username_changing_enabled'] = false
|
||||
unicorn['worker_processes'] = 2
|
||||
unicorn['worker_timeout'] = 60
|
||||
postgresql['enable'] = false
|
||||
redis['enable'] = false
|
||||
nginx['enable'] = true
|
||||
nginx['listen_port'] = 80
|
||||
nginx['listen_https'] = false
|
||||
gitlab_rails['initial_root_password'] = '${GITLAB_ROOT_PASSWORD}'
|
||||
gitlab_rails['gitlab_email_enabled'] = false
|
||||
puma['worker_processes'] = 0
|
||||
sidekiq['concurrency'] = 10
|
||||
prometheus_monitoring['enable'] = false
|
||||
ports:
|
||||
- "80"
|
||||
- "2224"
|
||||
volumes:
|
||||
- gitlab_config:/etc/gitlab
|
||||
- gitlab_logs:/var/log/gitlab
|
||||
- gitlab_data:/var/opt/gitlab
|
||||
depends_on:
|
||||
- postgresql
|
||||
- redis
|
||||
|
||||
postgresql:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgresql_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
gitlab_config:
|
||||
gitlab_logs:
|
||||
gitlab_data:
|
||||
postgresql_data:
|
||||
redis_data:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "gitlab-ce",
|
||||
"name": "GitLab CE",
|
||||
"version": "latest",
|
||||
"version": "19.1.2",
|
||||
"description": "GitLab Community Edition is a free and open source platform for managing Git repositories, CI/CD pipelines, and project management.",
|
||||
"logo": "gitlab-ce.svg",
|
||||
"links": {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_db = "gitlab"
|
||||
postgres_user = "gitlab"
|
||||
postgres_password = "${password:32}"
|
||||
gitlab_root_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"GITLAB_HOST=${main_domain}",
|
||||
"POSTGRES_DB=${postgres_db}",
|
||||
"POSTGRES_USER=${postgres_user}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"GITLAB_ROOT_PASSWORD=${gitlab_root_password}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
"streaming",
|
||||
"webrtc",
|
||||
"video",
|
||||
"home assistant"
|
||||
"home-assistant"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ services:
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ../homarr/appdata:/appdata
|
||||
- homarr_appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
|
||||
ports:
|
||||
- 7575
|
||||
|
||||
volumes:
|
||||
homarr_appdata:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:64}"
|
||||
secret_key = "${hash:64}"
|
||||
|
||||
[config]
|
||||
env = ["SECRET_ENCRYPTION_KEY=${secret_key}"]
|
||||
|
||||
@@ -2,10 +2,10 @@ version: "3.9"
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
image: ghcr.io/immich-app/immich-server:v2.1.0
|
||||
image: ghcr.io/immich-app/immich-server:v3.0.2
|
||||
|
||||
volumes:
|
||||
- immich-library:/usr/src/app/upload
|
||||
- immich-library:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
immich-redis:
|
||||
@@ -13,9 +13,6 @@ services:
|
||||
immich-database:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PORT: 2283
|
||||
SERVER_URL: ${SERVER_URL}
|
||||
FRONT_BASE_URL: ${FRONT_BASE_URL}
|
||||
# Database Configuration
|
||||
DB_HOSTNAME: ${DB_HOSTNAME}
|
||||
DB_PORT: ${DB_PORT}
|
||||
@@ -30,70 +27,45 @@ services:
|
||||
TZ: ${TZ}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:2283/server-info/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
disable: false
|
||||
|
||||
immich-machine-learning:
|
||||
image: ghcr.io/immich-app/immich-machine-learning:v2.1.0
|
||||
image: ghcr.io/immich-app/immich-machine-learning:v3.0.2
|
||||
|
||||
volumes:
|
||||
- immich-model-cache:/cache
|
||||
environment:
|
||||
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
|
||||
REDIS_PORT: ${REDIS_PORT}
|
||||
REDIS_DBINDEX: ${REDIS_DBINDEX}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3003/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
disable: false
|
||||
|
||||
immich-redis:
|
||||
image: redis:6.2-alpine
|
||||
image: valkey/valkey:9
|
||||
|
||||
volumes:
|
||||
- immich-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
test: redis-cli ping || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
immich-database:
|
||||
image: tensorchord/pgvecto-rs:pg14-v0.3.0
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
|
||||
volumes:
|
||||
- immich-postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: immich
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||
shm_size: 128mb
|
||||
healthcheck:
|
||||
test: pg_isready -U ${DB_USERNAME} -d immich || exit 1
|
||||
test: pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME} || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command:
|
||||
[
|
||||
'postgres',
|
||||
'-c',
|
||||
'shared_preload_libraries=vectors.so',
|
||||
'-c',
|
||||
'search_path="$$user", public, vectors',
|
||||
'-c',
|
||||
'logging_collector=on',
|
||||
'-c',
|
||||
'max_wal_size=2GB',
|
||||
'-c',
|
||||
'shared_buffers=512MB',
|
||||
'-c',
|
||||
'wal_compression=on',
|
||||
]
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "immich",
|
||||
"name": "Immich",
|
||||
"version": "v1.121.0",
|
||||
"version": "v3.0.2",
|
||||
"description": "High performance self-hosted photo and video backup solution directly from your mobile phone.",
|
||||
"logo": "immich.svg",
|
||||
"links": {
|
||||
|
||||
@@ -5,9 +5,6 @@ db_user = "immich"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"IMMICH_HOST=${main_domain}",
|
||||
"SERVER_URL=https://${main_domain}",
|
||||
"FRONT_BASE_URL=https://${main_domain}",
|
||||
"DB_HOSTNAME=immich-database",
|
||||
"DB_PORT=5432",
|
||||
"DB_USERNAME=${db_user}",
|
||||
|
||||
@@ -1,58 +1,29 @@
|
||||
services:
|
||||
db-migration:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
image: infisical/infisical:v0.135.0-postgres
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
command: npm run migration:latest
|
||||
pull_policy: always
|
||||
|
||||
backend:
|
||||
image: infisical/infisical:v0.162.6
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
db-migration:
|
||||
condition: service_completed_successfully
|
||||
image: infisical/infisical:v0.135.0-postgres
|
||||
pull_policy: always
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
- AUTH_SECRET=${AUTH_SECRET}
|
||||
- SITE_URL=${SITE_URL}
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_PORT=${SMTP_PORT}
|
||||
- SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS}
|
||||
- SMTP_FROM_NAME=${SMTP_FROM_NAME}
|
||||
- SMTP_USERNAME=${SMTP_USERNAME}
|
||||
- SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||
|
||||
redis:
|
||||
image: redis:7.4.1
|
||||
env_file: .env
|
||||
restart: always
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
|
||||
volumes:
|
||||
- redis_infisical_data:/data
|
||||
|
||||
@@ -60,12 +31,11 @@ services:
|
||||
image: postgres:14-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_DB
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- pg_infisical_data:/var/lib/postgresql/data
|
||||
|
||||
healthcheck:
|
||||
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
|
||||
interval: 5s
|
||||
@@ -75,6 +45,3 @@ services:
|
||||
volumes:
|
||||
pg_infisical_data:
|
||||
redis_infisical_data:
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "infisical",
|
||||
"name": "Infisical",
|
||||
"version": "0.135.0",
|
||||
"version": "0.162.6",
|
||||
"description": "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.",
|
||||
"logo": "infisical.jpg",
|
||||
"links": {
|
||||
|
||||
@@ -3,56 +3,27 @@ main_domain = "${domain}"
|
||||
postgres_password = "${password}"
|
||||
postgres_user = "infisical"
|
||||
postgres_db = "infisical"
|
||||
encryption_key = "${hash:32}"
|
||||
auth_secret = "${base64:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218",
|
||||
"AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=",
|
||||
"ENCRYPTION_KEY=${encryption_key}",
|
||||
"AUTH_SECRET=${auth_secret}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"POSTGRES_USER=${postgres_user}",
|
||||
"POSTGRES_DB=${postgres_db}",
|
||||
"SITE_URL=http://${main_domain}:8080",
|
||||
"SITE_URL=http://${main_domain}",
|
||||
"SMTP_HOST=",
|
||||
"SMTP_PORT=",
|
||||
"SMTP_NAME=",
|
||||
"SMTP_PORT=587",
|
||||
"SMTP_FROM_ADDRESS=",
|
||||
"SMTP_FROM_NAME=Infisical",
|
||||
"SMTP_USERNAME=",
|
||||
"SMTP_PASSWORD=",
|
||||
"CLIENT_ID_HEROKU=",
|
||||
"CLIENT_ID_VERCEL=",
|
||||
"CLIENT_ID_NETLIFY=",
|
||||
"CLIENT_ID_GITHUB=",
|
||||
"CLIENT_ID_GITHUB_APP=",
|
||||
"CLIENT_SLUG_GITHUB_APP=",
|
||||
"CLIENT_ID_GITLAB=",
|
||||
"CLIENT_ID_BITBUCKET=",
|
||||
"CLIENT_SECRET_HEROKU=",
|
||||
"CLIENT_SECRET_VERCEL=",
|
||||
"CLIENT_SECRET_NETLIFY=",
|
||||
"CLIENT_SECRET_GITHUB=",
|
||||
"CLIENT_SECRET_GITHUB_APP=",
|
||||
"CLIENT_SECRET_GITLAB=",
|
||||
"CLIENT_SECRET_BITBUCKET=",
|
||||
"CLIENT_SLUG_VERCEL=",
|
||||
"CLIENT_PRIVATE_KEY_GITHUB_APP=",
|
||||
"CLIENT_APP_ID_GITHUB_APP=",
|
||||
"SENTRY_DSN=",
|
||||
"POSTHOG_HOST=",
|
||||
"POSTHOG_PROJECT_API_KEY=",
|
||||
"CLIENT_ID_GOOGLE_LOGIN=",
|
||||
"CLIENT_SECRET_GOOGLE_LOGIN=",
|
||||
"CLIENT_ID_GITHUB_LOGIN=",
|
||||
"CLIENT_SECRET_GITHUB_LOGIN=",
|
||||
"CLIENT_ID_GITLAB_LOGIN=",
|
||||
"CLIENT_SECRET_GITLAB_LOGIN=",
|
||||
"CAPTCHA_SECRET=",
|
||||
"NEXT_PUBLIC_CAPTCHA_SITE_KEY=",
|
||||
"PLAIN_API_KEY=",
|
||||
"PLAIN_WISH_LABEL_IDS=",
|
||||
"SSL_CLIENT_CERTIFICATE_HEADER_KEY=",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "backend"
|
||||
port = 8_080
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"docs": "https://jellyfin.org/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"media system"
|
||||
"media-system"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"docs": "https://kaneo.app/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"Task Tracking"
|
||||
"task-tracking"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "librechat",
|
||||
"name": "LibreChat",
|
||||
"version": "latest",
|
||||
"description": "LibreChat is the ultimate open-source app for all your AI conversations, fully customizable and compatible with any AI provider (Openai, Ollama, Google etc.) — all in one sleek interface.",
|
||||
"description": "LibreChat is the ultimate open-source app for all your AI conversations, fully customizable and compatible with any AI provider (Openai, Ollama, Google etc.) \u00e2\u20ac\u201d all in one sleek interface.",
|
||||
"logo": "librechat.png",
|
||||
"links": {
|
||||
"github": "https://github.com/danny-avila/librechat",
|
||||
@@ -13,8 +13,8 @@
|
||||
"ai",
|
||||
"chatbot",
|
||||
"llm",
|
||||
"MIT-license",
|
||||
"BYOK",
|
||||
"mit-license",
|
||||
"byok",
|
||||
"generative-ai"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"docs": "https://docs.livekit.io/"
|
||||
},
|
||||
"tags": [
|
||||
"Video",
|
||||
"Audio",
|
||||
"Real-time",
|
||||
"Streaming",
|
||||
"Webrtc"
|
||||
"video",
|
||||
"audio",
|
||||
"real-time",
|
||||
"streaming",
|
||||
"webrtc"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"docs": "https://lobehub.com/docs/self-hosting/platform/docker-compose"
|
||||
},
|
||||
"tags": [
|
||||
"IA",
|
||||
"ia",
|
||||
"chat"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/logto-io/logto:1.41.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
image: ghcr.io/logto-io/logto:1.27.0
|
||||
entrypoint: ["sh", "-c", "npm run cli db seed -- --swe && npm start"]
|
||||
ports:
|
||||
- 3001
|
||||
- 3002
|
||||
|
||||
environment:
|
||||
TRUST_PROXY_HEADER: 1
|
||||
DB_URL: postgres://logto:${LOGTO_POSTGRES_PASSWORD}@postgres:5432/logto
|
||||
@@ -18,20 +15,19 @@ services:
|
||||
- logto-connectors:/etc/logto/packages/core/connectors
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
user: postgres
|
||||
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: logto
|
||||
POSTGRES_PASSWORD: ${LOGTO_POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: logto
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
test: ["CMD-SHELL", "pg_isready -U logto -d logto"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
|
||||
volumes:
|
||||
logto-connectors:
|
||||
postgres-data:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "logto",
|
||||
"name": "Logto",
|
||||
"version": "1.27.0",
|
||||
"version": "1.41.0",
|
||||
"description": "Logto is an open-source Identity and Access Management (IAM) platform designed to streamline Customer Identity and Access Management (CIAM) and Workforce Identity Management.",
|
||||
"logo": "logto.png",
|
||||
"links": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
admin_domain = "${domain}"
|
||||
postgres_password = "${password}"
|
||||
postgres_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
@@ -17,6 +17,6 @@ port = 3_002
|
||||
host = "${admin_domain}"
|
||||
|
||||
[config.env]
|
||||
LOGTO_ENDPOINT = "http://${admin_domain}"
|
||||
LOGTO_ENDPOINT = "http://${main_domain}"
|
||||
LOGTO_ADMIN_ENDPOINT = "http://${admin_domain}"
|
||||
LOGTO_POSTGRES_PASSWORD = "${postgres_password}"
|
||||
|
||||
27
blueprints/mailcatcher-ng/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
mailcatcher-ng:
|
||||
# Only the "latest" tag is published, so the image is pinned by digest
|
||||
# (v2.3.9 of the image, MailCatcher NG 1.6.8).
|
||||
image: stpaquet/alpinemailcatcher:latest@sha256:499c0eabca5e82b23d030706ba9d5b8b27bba5a5f34a43154ee5e68b07cba52c
|
||||
restart: unless-stopped
|
||||
# Same as the image default CMD, plus --persistence so caught mail
|
||||
# survives container restarts (stored in the named volume below).
|
||||
command: sh -c "mailcatcher --foreground --smtp-port=1025 --http-port=1080 --ip=0.0.0.0 --messages-limit=$$MAIL_LIMIT --persistence --no-quit"
|
||||
environment:
|
||||
- MAIL_LIMIT=${MAIL_LIMIT}
|
||||
expose:
|
||||
# SMTP: reachable from other services in the same Dokploy network
|
||||
# at mailcatcher-ng:1025 (intentionally not published on the host).
|
||||
- 1025
|
||||
# Web UI (routed through the domain below)
|
||||
- 1080
|
||||
volumes:
|
||||
- mailcatcher-data:/home/mailcatcher/.mailcatcher
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:1080']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
mailcatcher-data:
|
||||
10
blueprints/mailcatcher-ng/mailcatcher-ng.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg class="logo-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d9488;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:#0f766e;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M 20 30 L 50 55 L 80 30 L 80 75 Q 80 80 75 80 L 25 80 Q 20 80 20 75 Z" fill="url(#logoGradient)"></path>
|
||||
<path d="M 20 30 L 50 55 L 80 30" stroke="white" stroke-width="2" fill="none"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 558 B |
17
blueprints/mailcatcher-ng/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "mailcatcher-ng",
|
||||
"name": "MailCatcher NG",
|
||||
"version": "1.6.8",
|
||||
"description": "MailCatcher NG runs a super simple SMTP server which catches any message sent to it to display in a web interface. Point your app's SMTP settings at mailcatcher-ng:1025 and inspect every captured email in the browser.",
|
||||
"logo": "mailcatcher-ng.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/spaquet/mailcatcher",
|
||||
"website": "https://spaquet.github.io/mailcatcher/",
|
||||
"docs": "https://spaquet.github.io/docker-alpine-mailcatcher/"
|
||||
},
|
||||
"tags": [
|
||||
"email",
|
||||
"smtp",
|
||||
"development"
|
||||
]
|
||||
}
|
||||
13
blueprints/mailcatcher-ng/template.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"MAIL_LIMIT=50",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "mailcatcher-ng"
|
||||
port = 1_080
|
||||
host = "${main_domain}"
|
||||
146
blueprints/mailu/docker-compose.yml
Normal file
@@ -0,0 +1,146 @@
|
||||
version: "3.8"
|
||||
|
||||
# Shared Mailu configuration (equivalent to the upstream mailu.env file)
|
||||
x-mailu-env: &mailu-env
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- DOMAIN=${DOMAIN}
|
||||
- HOSTNAMES=${DOMAIN}
|
||||
- POSTMASTER=admin
|
||||
# TLS for the mail protocols is obtained through Let's Encrypt (HTTP-01
|
||||
# through Traefik on port 80). The web UI itself is served plain HTTP to
|
||||
# Traefik, which terminates HTTPS.
|
||||
- TLS_FLAVOR=mail-letsencrypt
|
||||
# Mailu grants relay/XCLIENT trust to this network range. It must cover the
|
||||
# Docker networks of this project (Docker allocates them from 172.16.0.0/12
|
||||
# by default). Narrow it down if you know your exact subnet.
|
||||
- SUBNET=${SUBNET}
|
||||
- ADMIN=true
|
||||
- WEBMAIL=roundcube
|
||||
- API=false
|
||||
- WEBDAV=none
|
||||
- ANTIVIRUS=none
|
||||
- SCAN_MACROS=false
|
||||
# Ports enabled inside the front container. 4190 (sieve) stays internal for
|
||||
# the webmail filters UI and is not published on the host.
|
||||
- PORTS=25,80,443,465,587,993,4190
|
||||
- MESSAGE_SIZE_LIMIT=50000000
|
||||
- FULL_TEXT_SEARCH=en
|
||||
- WEBROOT_REDIRECT=/webmail
|
||||
- WEB_ADMIN=/admin
|
||||
- WEB_WEBMAIL=/webmail
|
||||
- SITENAME=Mailu
|
||||
- WEBSITE=https://mailu.io
|
||||
# Trust Traefik (dokploy-network) to forward the real client IP for rate limiting
|
||||
- REAL_IP_FROM=${SUBNET}
|
||||
- REAL_IP_HEADER=X-Forwarded-For
|
||||
# Initial admin account: admin@${DOMAIN} (created only if it does not exist)
|
||||
- INITIAL_ADMIN_ACCOUNT=${INITIAL_ADMIN_ACCOUNT}
|
||||
- INITIAL_ADMIN_DOMAIN=${DOMAIN}
|
||||
- INITIAL_ADMIN_PW=${INITIAL_ADMIN_PW}
|
||||
- INITIAL_ADMIN_MODE=ifmissing
|
||||
|
||||
# Public DNSSEC-validating resolvers. The admin container refuses to start
|
||||
# without DNSSEC validation, and Postfix needs it for outbound DANE. Service
|
||||
# discovery still uses Docker's embedded DNS; these are only upstreams.
|
||||
x-mailu-dns: &mailu-dns
|
||||
- "1.1.1.1"
|
||||
- "8.8.8.8"
|
||||
|
||||
services:
|
||||
front:
|
||||
image: ghcr.io/mailu/nginx:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
# Mail protocol ports are published directly on the host (same approach as
|
||||
# the poste.io template). They will fail to bind if another mail server
|
||||
# already uses them on this machine.
|
||||
ports:
|
||||
- "25:25" # SMTP (server to server)
|
||||
- "465:465" # SMTPS submission
|
||||
- "587:587" # Submission (STARTTLS)
|
||||
- "993:993" # IMAPS
|
||||
volumes:
|
||||
- mailu-certs:/certs
|
||||
# The image's built-in healthcheck also requires the mail (Dovecot) proxy,
|
||||
# which only starts once TLS certificates exist. Behind Traefik that
|
||||
# deadlocks: unhealthy -> Traefik drops the route -> the ACME challenge can
|
||||
# never be answered. Check only nginx instead (health endpoint returns 204).
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -m3 -skfLo /dev/null http://127.0.0.1:10204/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
depends_on:
|
||||
- admin
|
||||
|
||||
admin:
|
||||
image: ghcr.io/mailu/admin:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-data:/data
|
||||
- mailu-dkim:/dkim
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
imap:
|
||||
image: ghcr.io/mailu/dovecot:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-mail:/mail
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
smtp:
|
||||
image: ghcr.io/mailu/postfix:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-mailqueue:/queue
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
antispam:
|
||||
image: ghcr.io/mailu/rspamd:2024.06
|
||||
restart: unless-stopped
|
||||
hostname: antispam
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-filter:/var/lib/rspamd
|
||||
depends_on:
|
||||
- front
|
||||
- redis
|
||||
|
||||
webmail:
|
||||
image: ghcr.io/mailu/webmail:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-webmail:/data
|
||||
depends_on:
|
||||
- front
|
||||
- imap
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- mailu-redis:/data
|
||||
|
||||
volumes:
|
||||
mailu-certs: {}
|
||||
mailu-data: {}
|
||||
mailu-dkim: {}
|
||||
mailu-mail: {}
|
||||
mailu-mailqueue: {}
|
||||
mailu-filter: {}
|
||||
mailu-webmail: {}
|
||||
mailu-redis: {}
|
||||
23
blueprints/mailu/instructions.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Mailu
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Point the domain you assign in Dokploy (for example `mail.example.com`) at your server **before** deploying: an `A` record, plus an `MX` record for your mail domain targeting it.
|
||||
2. Deploy the template and open the domain: `/webmail` is Roundcube, `/admin` is the admin UI.
|
||||
3. Log in at `/admin` with `admin@<your domain>` and the auto-generated `INITIAL_ADMIN_PW` (Dokploy → your service → Environment). The account is created only on first boot (`INITIAL_ADMIN_MODE=ifmissing`); change the password from the admin UI afterwards.
|
||||
4. In the admin UI, open **Mail domains → your domain → Details** and create the DNS records it shows (SPF, DKIM, DMARC). Also set the **PTR/reverse DNS** record of your server IP to your mail hostname — most providers require this to accept your mail.
|
||||
|
||||
## Ports
|
||||
|
||||
The mail protocol ports are published directly on the host: **25** (SMTP), **465** (SMTPS), **587** (submission) and **993** (IMAPS). The deployment fails to start if another mail server (or a previous Mailu deployment) already binds them on the same machine. Many VPS providers block outbound port 25 by default — ask your provider to unblock it, or configure a relay host in Mailu.
|
||||
|
||||
## TLS
|
||||
|
||||
- The web UI is served through Traefik like any other Dokploy app. Enable **HTTPS with Let's Encrypt** on the Dokploy domain: the web login cookie requires HTTPS, and Mailu's internal certbot self-check follows Traefik's HTTP→HTTPS redirect and needs a valid certificate there.
|
||||
- The mail ports get their own Let's Encrypt certificate: the `front` container runs certbot internally and answers the HTTP-01 challenge through Traefik on port 80. This only succeeds once the DNS record of your domain points at the server. If the certificate was obtained *after* the first boot, restart the `front` service once so the TLS mail listeners (465/587/993) come up.
|
||||
|
||||
## Notes
|
||||
|
||||
- `SUBNET` (default `172.16.0.0/12`) is the network range Mailu trusts for its internal traffic (Postfix relay/XCLIENT, Dovecot proxying, Rspamd). It covers Docker's default address pools; if your Docker daemon uses custom pools, adjust it to match. Note this means other containers on the same Docker host are treated as trusted senders.
|
||||
- The containers use public DNSSEC-validating resolvers (`1.1.1.1`, `8.8.8.8`) as upstream DNS: the admin container requires DNSSEC validation and Postfix uses it for DANE. Heavy production use benefits from a dedicated local resolver instead, because public resolvers are rate-limited by DNSBLs (see the [Mailu DNS FAQ](https://mailu.io/2024.06/faq.html)).
|
||||
- Additional mail domains, users, aliases and fetchmail can be managed in the admin UI. ClamAV antivirus is not included in this template to keep memory usage low (it needs >1 GB RAM); see the Mailu docs to add it.
|
||||
137
blueprints/mailu/mailu.svg
Normal file
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="logo.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 263.62305,623.95312 -0.95703,0.28907 c 0,0 8.73472,28.99871 31.14453,57.99219 22.40981,28.99347 58.55631,58.02539 113.28125,58.02539 109.39431,0 155.50976,-97.76563 155.50976,-97.76563 l -0.90429,-0.42578 c 0,0 -45.80684,97.19141 -154.60547,97.19141 -54.37155,0 -90.2008,-28.79897 -112.49024,-57.63672 -22.28944,-28.83775 -30.97851,-57.66993 -30.97851,-57.66993 z"
|
||||
id="path4142-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="77.860065"
|
||||
inkscape:cy="631.62981"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:15;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4136"
|
||||
cx="416.00003"
|
||||
cy="574.93365"
|
||||
r="196.7318" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="297.14285"
|
||||
y="609.79077"
|
||||
id="text4138"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4140"
|
||||
x="297.14285"
|
||||
y="609.79077"
|
||||
style="font-size:90px">Mailu</tspan></text>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 263.62305,623.95312 -0.95703,0.28907 c 0,0 8.73472,28.99871 31.14453,57.99219 22.40981,28.99347 58.55631,58.02539 113.28125,58.02539 109.39431,0 155.50976,-97.76563 155.50976,-97.76563 l -0.90429,-0.42578 c 0,0 -45.80684,97.19141 -154.60547,97.19141 -54.37155,0 -90.2008,-28.79897 -112.49024,-57.63672 -22.28944,-28.83775 -30.97851,-57.66993 -30.97851,-57.66993 z"
|
||||
id="path4142"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
id="text4144"
|
||||
sodipodi:linespacing="125%"><textPath
|
||||
xlink:href="#path4142"
|
||||
id="textPath4150"><tspan
|
||||
id="tspan4146"
|
||||
style="fill:#000000;fill-opacity:1;"> email since 2016</tspan></textPath></text>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 275.13574,527.65954 -0.91835,-0.23149 c 0,0 8.38171,-23.22244 29.88583,-46.44069 21.50412,-23.21824 56.18977,-46.46727 108.70301,-46.46727 104.97317,0 149.22487,78.29163 149.22487,78.29163 l -0.86774,0.34096 c 0,0 -43.95557,-77.83178 -148.35713,-77.83178 -52.17413,0 -86.55535,23.06248 -107.94397,46.15602 -21.38862,23.09354 -29.72652,46.18262 -29.72652,46.18262 z"
|
||||
id="path4184"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
y="-623.17346"
|
||||
x="-262.66602"
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text4144-6"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="translate(404.56768,384.57019)"><textPath
|
||||
xlink:href="#path4142-3"
|
||||
id="textPath4150-7"><tspan
|
||||
id="tspan4146-5"
|
||||
style="fill:#000000;fill-opacity:1"> email since 2016</tspan></textPath></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text4203"
|
||||
sodipodi:linespacing="125%"
|
||||
x="-2"
|
||||
transform="translate(-4,10)"><textPath
|
||||
xlink:href="#path4184"
|
||||
id="textPath4207"><tspan
|
||||
id="tspan4205">delivering insular</tspan></textPath></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.61572051;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 228.79956,370.50924 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4214"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.61572051"
|
||||
d="m 210.94153,406.39617 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4230-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.61572051"
|
||||
d="m 192.75878,438.21597 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4230-3-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.6 KiB |
21
blueprints/mailu/meta.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"id": "mailu",
|
||||
"name": "Mailu",
|
||||
"version": "2024.06",
|
||||
"description": "Full-featured open-source mail server as a set of Docker containers: SMTP (Postfix), IMAP (Dovecot), Rspamd antispam, admin UI with DKIM management and Roundcube webmail.",
|
||||
"logo": "mailu.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/Mailu/Mailu",
|
||||
"website": "https://mailu.io/",
|
||||
"docs": "https://mailu.io/2024.06/",
|
||||
"docker": "https://github.com/orgs/Mailu/packages"
|
||||
},
|
||||
"tags": [
|
||||
"email",
|
||||
"mail-server",
|
||||
"smtp",
|
||||
"imap",
|
||||
"antispam",
|
||||
"webmail"
|
||||
]
|
||||
}
|
||||
17
blueprints/mailu/template.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:16}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "front"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
DOMAIN = "${main_domain}"
|
||||
SECRET_KEY = "${secret_key}"
|
||||
INITIAL_ADMIN_ACCOUNT = "admin"
|
||||
INITIAL_ADMIN_PW = "${admin_password}"
|
||||
SUBNET = "172.16.0.0/12"
|
||||
39
blueprints/markup/docker-compose.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- markup-postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: markup
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: markup
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U markup -d markup"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
markup:
|
||||
image: ghcr.io/pphilfre/markup:main
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
NEXT_PUBLIC_DB_PROVIDER: postgres
|
||||
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL}
|
||||
# WorkOS AuthKit (optional): required only for user accounts and cloud sync.
|
||||
# Placeholders keep the app running; replace them with real credentials
|
||||
# from https://dashboard.workos.com to enable sign-in.
|
||||
WORKOS_CLIENT_ID: ${WORKOS_CLIENT_ID}
|
||||
WORKOS_API_KEY: ${WORKOS_API_KEY}
|
||||
WORKOS_COOKIE_PASSWORD: ${WORKOS_COOKIE_PASSWORD}
|
||||
NEXT_PUBLIC_WORKOS_REDIRECT_URI: ${NEXT_PUBLIC_WORKOS_REDIRECT_URI}
|
||||
NODE_ENV: production
|
||||
|
||||
volumes:
|
||||
markup-postgres-data:
|
||||
5
blueprints/markup/markup.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<rect width="100" height="100" rx="20" fill="#0a0a0a"/>
|
||||
<text x="50" y="68" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-weight="700" font-size="48" fill="#7c3aed">M</text>
|
||||
<rect x="22" y="76" width="56" height="3" rx="1.5" fill="#7c3aed" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
18
blueprints/markup/meta.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "markup",
|
||||
"name": "Markup",
|
||||
"version": "main",
|
||||
"description": "Markup is a fast, keyboard-first markdown workspace with live preview, graph view, whiteboards, mind maps, and PostgreSQL-powered cloud sync.",
|
||||
"logo": "markup.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/pphilfre/markup",
|
||||
"website": "https://home.markup.freddiephilpot.dev/",
|
||||
"docs": "https://docs.markup.freddiephilpot.dev/"
|
||||
},
|
||||
"tags": [
|
||||
"markdown",
|
||||
"notes",
|
||||
"editor",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
25
blueprints/markup/template.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_password = "${password:32}"
|
||||
cookie_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"DATABASE_URL=postgresql://markup:${postgres_password}@db:5432/markup",
|
||||
"NEXT_PUBLIC_SITE_URL=https://${main_domain}",
|
||||
"# WorkOS AuthKit is optional: it powers user accounts and cloud sync.",
|
||||
"# The app works without it (local, in-browser storage). To enable sign-in,",
|
||||
"# create an app at https://dashboard.workos.com, set the redirect URI below",
|
||||
"# in the WorkOS dashboard, and replace the placeholder credentials.",
|
||||
"WORKOS_CLIENT_ID=client_id_change_me",
|
||||
"WORKOS_API_KEY=sk_change_me",
|
||||
"WORKOS_COOKIE_PASSWORD=${cookie_password}",
|
||||
"NEXT_PUBLIC_WORKOS_REDIRECT_URI=https://${main_domain}/callback",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "markup"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
@@ -7,7 +7,7 @@
|
||||
"links": {
|
||||
"github": "https://github.com/maybe-finance/maybe",
|
||||
"website": "https://maybe.finance/",
|
||||
"docs": "https://docs.maybe.finance/"
|
||||
"docs": "https://github.com/maybe-finance/maybe/blob/main/docs/hosting/docker.md"
|
||||
},
|
||||
"tags": [
|
||||
"finance",
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
services:
|
||||
minio:
|
||||
# after RELEASE.2025-04-22T22-12-26Z, minio removed most of the admin UI, if you want to use the admin UI, uncomment the line below
|
||||
# image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
# if you uncommented the line above, comment the line below
|
||||
image: minio/minio
|
||||
# MinIO Inc. stopped publishing Docker images and pre-built binaries in October 2025
|
||||
# (https://github.com/minio/minio/issues/21647) and archived the minio/minio repository
|
||||
# in February 2026. The official minio/minio image is frozen at RELEASE.2025-09-07T16-13-09Z
|
||||
# and no longer receives security fixes (e.g. the fix for CVE-2025-62506 was never published
|
||||
# as an image). This template uses pgsty/minio, the actively maintained community fork
|
||||
# (https://github.com/pgsty/minio, AGPLv3). It is a drop-in replacement: same environment
|
||||
# variables, same `server` command, same /data on-disk format (existing minio-data volumes
|
||||
# keep working), and it restores the full web console that upstream removed.
|
||||
image: pgsty/minio:RELEASE.2026-06-18T00-00-00Z
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# by default, the MinIO container will use a volume named minio-data
|
||||
@@ -19,12 +24,10 @@ services:
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||
- MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL}
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
# by default, the MinIO container will use port 9000 to expose its API
|
||||
# and port 9001 to expose its web console
|
||||
# minio requires port to be specified when making a request to the API
|
||||
- 9000:9000
|
||||
expose:
|
||||
# port 9000 serves the S3 API (routed through the api domain defined in template.toml)
|
||||
# port 9001 serves the web console (routed through the main domain)
|
||||
- 9000
|
||||
- 9001
|
||||
|
||||
# comment the line below if you specified a local directory in the volumes section of the minio service
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "minio",
|
||||
"name": "Minio",
|
||||
"description": "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.",
|
||||
"description": "MinIO is an open source object storage server compatible with Amazon S3 cloud storage service. This template uses pgsty/minio, the actively maintained community fork, because MinIO Inc. stopped publishing Docker images in October 2025 and archived the upstream open source project in February 2026.",
|
||||
"logo": "minio.png",
|
||||
"version": "latest",
|
||||
"version": "RELEASE.2026-06-18T00-00-00Z",
|
||||
"links": {
|
||||
"github": "https://github.com/minio/minio",
|
||||
"website": "https://minio.io/",
|
||||
"docs": "https://docs.minio.io/"
|
||||
"github": "https://github.com/pgsty/minio",
|
||||
"website": "https://silo.pigsty.io/",
|
||||
"docs": "https://silo.pigsty.io/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"storage"
|
||||
|
||||
@@ -5,11 +5,18 @@ api_domain = "${domain}"
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
# Web console - log in with the generated root credentials below
|
||||
[[config.domains]]
|
||||
serviceName = "minio"
|
||||
port = 9_001
|
||||
host = "${main_domain}"
|
||||
|
||||
# S3-compatible API - point AWS CLI / SDKs / rclone / backup tools at this domain
|
||||
[[config.domains]]
|
||||
serviceName = "minio"
|
||||
port = 9_000
|
||||
host = "${api_domain}"
|
||||
|
||||
[config.env]
|
||||
MINIO_ROOT_USER = "minioadmin"
|
||||
MINIO_ROOT_PASSWORD = "${password:16}"
|
||||
|
||||