Compare commits

...
36 Commits
Author SHA1 Message Date
Mauricio SiuandClaude Fable 5 f6245f84dc 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>
2026-07-16 17:50:39 -06:00
Mauricio SiuandGitHub a3f2655baa Merge pull request #1028 from Dokploy/feat/posthog
feat: add PostHog template
2026-07-14 23:38:37 -06:00
Mauricio SiuandGitHub 13d03f9a92 Merge pull request #1030 from Dokploy/feat/bonfire
feat: add Bonfire template
2026-07-14 23:38:14 -06:00
Mauricio SiuandGitHub 51922e71b6 Merge pull request #1031 from knowsuchagency/add-dagu-template
Add Dagu template
2026-07-14 23:37:53 -06:00
Mauricio SiuandGitHub 3a6f9d709a Merge pull request #1029 from Dokploy/feat/n8n-queue
feat: add n8n queue mode template (scalable workers)
2026-07-14 23:36:47 -06:00
Stephan Fitzpatrick 0beb68a228 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
2026-07-14 16:41:48 -07:00
Mauricio SiuandClaude Fable 5 7d252ead5c 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>
2026-07-14 16:24:32 -06:00
Mauricio SiuandClaude Fable 5 fe94f4ce0b 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>
2026-07-14 16:15:50 -06:00
Mauricio SiuandClaude Fable 5 3eb1d3b7bd 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>
2026-07-14 15:50:16 -06:00
Mauricio SiuandGitHub 15db93df3b Merge pull request #1026 from Dokploy/feat/trek
feat: add TREK template
2026-07-14 14:00:35 -06:00
Mauricio SiuandGitHub cc3b93518b Merge pull request #1025 from Dokploy/feat/frappe-lms
feat: add Frappe LMS template
2026-07-14 14:00:26 -06:00
Mauricio SiuandGitHub e7285cea05 Merge pull request #1023 from Dokploy/feat/certmate
feat: add CertMate template
2026-07-14 14:00:22 -06:00
Mauricio SiuandGitHub a80a073389 Merge pull request #1020 from Dokploy/feat/steedos
feat: add Steedos template
2026-07-14 14:00:01 -06:00
Mauricio SiuandGitHub 77b078faf3 Merge pull request #1019 from Dokploy/feat/sim
feat: add Sim Studio template
2026-07-14 13:59:56 -06:00
Mauricio SiuandGitHub 31f95474d6 Merge pull request #1018 from Dokploy/feat/dify
feat: add Dify template
2026-07-14 13:59:47 -06:00
Mauricio SiuandGitHub fabecbe728 Merge pull request #1017 from Dokploy/feat/synapse
feat: add Synapse (Matrix homeserver) template
2026-07-14 13:59:37 -06:00
Mauricio SiuandGitHub 4159d3a9f0 Merge pull request #1016 from Dokploy/feat/weblate
feat: add Weblate template
2026-07-14 13:59:29 -06:00
Mauricio SiuandGitHub abe36ad330 Merge pull request #1015 from Dokploy/docs/template-instructions
docs: per-template setup instructions for Supabase and Trigger.dev
2026-07-14 13:59:20 -06:00
Mauricio SiuandGitHub 89505f0b54 Merge pull request #1014 from Dokploy/feat/mailcatcher-ng
feat: add MailCatcher NG template
2026-07-14 13:59:11 -06:00
Mauricio SiuandGitHub 981811f55f Merge pull request #1013 from Dokploy/fix/nextcloud-naming
fix(nextcloud-aio): drop AIO branding, pin nextcloud 33.0.6
2026-07-14 13:59:03 -06:00
Mauricio SiuandGitHub ffeaca14af Merge pull request #1012 from Dokploy/fix/minio-source-only
fix(minio): migrate to pgsty/minio community fork (upstream is source-only and archived)
2026-07-14 13:58:55 -06:00
Mauricio SiuandGitHub 77145ad581 Merge pull request #1011 from Dokploy/fix/wordpress-loopback
fix(wordpress): route loopback requests through Traefik to fix cURL error 28 timeouts
2026-07-14 13:58:48 -06:00
Mauricio SiuandClaude Fable 5 59e323483c feat: add TREK template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:57:21 -06:00
Mauricio SiuandClaude Fable 5 8c15db8a2c 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>
2026-07-14 13:53:41 -06:00
Mauricio SiuandClaude Fable 5 7f3a08eb00 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>
2026-07-14 13:43:38 -06:00
Mauricio SiuandClaude Fable 5 a266e36a4d feat: add CertMate template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:37:16 -06:00
Mauricio SiuandClaude Fable 5 62850c3696 feat: add Steedos template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:10:56 -06:00
Mauricio SiuandClaude Fable 5 d1ef9333a3 feat: add Sim Studio template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 12:41:18 -06:00
Mauricio SiuandClaude Fable 5 1c2dea39c3 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>
2026-07-14 12:29:15 -06:00
Mauricio SiuandClaude Fable 5 b23ddd0b98 feat: add Synapse (Matrix homeserver) template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 12:12:24 -06:00
Mauricio SiuandClaude Fable 5 7683947842 feat: add Weblate template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 12:07:41 -06:00
Mauricio SiuandClaude Fable 5 03a60bb417 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>
2026-07-14 12:04:30 -06:00
Mauricio SiuandClaude Fable 5 b7c2d6c35d feat: add MailCatcher NG template
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 12:03:04 -06:00
Mauricio SiuandClaude Fable 5 f036ddae92 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>
2026-07-14 11:58:34 -06:00
Mauricio SiuandClaude Fable 5 030d9d1089 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>
2026-07-14 11:55:53 -06:00
Mauricio SiuandClaude Fable 5 af16250561 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>
2026-07-14 11:55:19 -06:00
76 changed files with 3506 additions and 29 deletions
+36 -1
View File
@@ -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">
+21 -6
View File
@@ -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
View 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;
-4
View File
@@ -1,4 +0,0 @@
## Instructions
We don't have nothing to show here....
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 72 KiB

+84
View 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
View 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
View 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" = ""
+33
View 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
View 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+18
View 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
View 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"
+25
View 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: {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+19
View 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
View 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}"
+256
View 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
View 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
View 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
View 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
View 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
'''
+313
View 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
View 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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+19
View 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
View 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 = ""
@@ -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:
@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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"
+12 -9
View File
@@ -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
+5 -5
View File
@@ -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"
+7
View File
@@ -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}"
+124
View File
@@ -0,0 +1,124 @@
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
start_period: 30s
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep PONG"]
start_period: 20s
interval: 10s
timeout: 5s
retries: 5
# Main instance: serves the editor UI and webhooks, publishes executions
# to the Redis (Bull) queue instead of running them itself.
n8n:
image: n8nio/n8n:2.30.4
restart: unless-stopped
environment:
# PostgreSQL
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
# Queue mode (Redis / Bull)
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
- QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
- QUEUE_HEALTH_CHECK_ACTIVE=true
- OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true
# Encryption key shared between the main instance and the workers
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
# Networking
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- N8N_PROTOCOL=http
- N8N_PROXY_HOPS=1
- NODE_ENV=production
- N8N_WEBHOOK_URL=https://${N8N_HOST}/
- N8N_EDITOR_BASE_URL=https://${N8N_HOST}/
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- N8N_SECURE_COOKIE=false
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5678/healthz"]
start_period: 60s
interval: 10s
timeout: 5s
retries: 10
# Queue workers: pull executions from Redis and run them. Scale them by
# changing the N8N_WORKER_REPLICAS environment variable and redeploying.
n8n-worker:
image: n8nio/n8n:2.30.4
restart: unless-stopped
command: worker
deploy:
replicas: ${N8N_WORKER_REPLICAS:-2}
environment:
# PostgreSQL
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
# Queue mode (Redis / Bull)
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
- QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
- QUEUE_HEALTH_CHECK_ACTIVE=true
# Must be the same key as the main instance
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- NODE_ENV=production
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
depends_on:
n8n:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5678/healthz"]
start_period: 60s
interval: 10s
timeout: 5s
retries: 10
volumes:
postgres_data:
redis_data:
n8n_data:
+24
View File
@@ -0,0 +1,24 @@
# n8n Queue Mode
This template runs n8n in [queue mode](https://docs.n8n.io/hosting/scaling/queue-mode/):
- **n8n** (main): serves the editor UI and receives webhooks/triggers, then publishes executions to the queue.
- **n8n-worker** (x2 by default): pulls executions from the Redis (Bull) queue and runs them.
- **redis**: the message broker for the queue (password protected).
- **postgres**: stores workflows, credentials and execution data.
All n8n containers share the same `N8N_ENCRYPTION_KEY` (generated automatically), which is required for workers to decrypt credentials.
## Scaling workers
Worker replicas are controlled by the `N8N_WORKER_REPLICAS` environment variable (default `2`):
1. Open the compose service in Dokploy and go to the **Environment** tab.
2. Change `N8N_WORKER_REPLICAS` to the number of workers you want.
3. Redeploy. Docker Compose will scale the `n8n-worker` service to the requested number of replicas.
Each worker runs up to 10 concurrent executions by default. You can tune this by adding `N8N_CONCURRENCY_PRODUCTION_LIMIT` to the worker environment.
## Optional: dedicated webhook processors
For very high webhook volumes, n8n supports dedicated webhook processor instances (`command: webhook`). To use them, add another service to the compose file with the same environment as `n8n-worker` but with `command: webhook`, and route the `/webhook/` path of your domain to it. See the [n8n queue mode docs](https://docs.n8n.io/hosting/scaling/queue-mode/#webhook-processors) for details.
+18
View File
@@ -0,0 +1,18 @@
{
"id": "n8n-queue",
"name": "n8n Queue Mode",
"version": "2.30.4",
"description": "n8n running in queue mode for high-throughput production workloads: the main instance handles the editor and webhooks while a scalable pool of workers executes workflows from a Redis (Bull) queue, backed by PostgreSQL.",
"logo": "n8n.png",
"links": {
"github": "https://github.com/n8n-io/n8n",
"website": "https://n8n.io/",
"docs": "https://docs.n8n.io/hosting/scaling/queue-mode/"
},
"tags": [
"automation",
"workflow",
"low-code",
"queue"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+33
View File
@@ -0,0 +1,33 @@
[variables]
main_domain = "${domain}"
postgres_user = "${username}"
postgres_password = "${password:24}"
postgres_db = "n8n"
redis_password = "${password:24}"
n8n_encryption_key = "${base64:32}"
[config]
mounts = []
[[config.domains]]
serviceName = "n8n"
port = 5_678
host = "${main_domain}"
[config.env]
N8N_HOST = "${main_domain}"
GENERIC_TIMEZONE = "Europe/Berlin"
# Number of queue workers. Change it and redeploy to scale.
N8N_WORKER_REPLICAS = "2"
# PostgreSQL
POSTGRES_USER = "${postgres_user}"
POSTGRES_PASSWORD = "${postgres_password}"
POSTGRES_DB = "${postgres_db}"
# Redis (Bull queue)
REDIS_PASSWORD = "${redis_password}"
# Encryption key shared by the main instance and the workers (IMPORTANT)
N8N_ENCRYPTION_KEY = "${n8n_encryption_key}"
+2 -2
View File
@@ -1,6 +1,6 @@
services:
nextcloud:
image: nextcloud:stable
image: nextcloud:33.0.6
restart: always
volumes:
- nextcloud_data:/var/www/html
@@ -15,7 +15,7 @@ services:
- nextcloud_redis
nextcloud_cron:
image: nextcloud:stable
image: nextcloud:33.0.6
restart: always
volumes:
- nextcloud_data:/var/www/html
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "nextcloud-aio",
"name": "Nextcloud",
"version": "stable",
"version": "33.0.6",
"description": "Nextcloud is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.",
"logo": "nextcloud.png",
"links": {
+646
View File
@@ -0,0 +1,646 @@
# PostHog self-hosted ("hobby" deployment, trimmed for a single VPS).
# Based on https://github.com/PostHog/posthog/blob/master/docker-compose.hobby.yml
# Omitted vs upstream hobby (heavy/optional): Temporal + Elasticsearch (data
# warehouse / batch exports), browserless (image exports / heatmap screenshots),
# personhog, cymbal + error-tracking/logs/traces ingestion, capture-ai and
# capture-logs. Everything needed for product analytics, feature flags,
# surveys, session replay and the CDP pipeline is included.
x-logging: &default-logging
driver: json-file
options:
max-size: "50m"
max-file: "3"
x-django-env: &django-env
# Postgres
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
PGHOST: db
PGUSER: posthog
PGPASSWORD: posthog
PERSONS_DB_WRITER_URL: postgres://posthog:posthog@db:5432/posthog
PERSONS_DB_READER_URL: postgres://posthog:posthog@db:5432/posthog
# ClickHouse (users match docker/clickhouse/users.xml from the PostHog repo)
CLICKHOUSE_HOST: clickhouse
CLICKHOUSE_DATABASE: posthog
CLICKHOUSE_SECURE: "false"
CLICKHOUSE_VERIFY: "false"
CLICKHOUSE_API_USER: api
CLICKHOUSE_API_PASSWORD: apipass
CLICKHOUSE_APP_USER: app
CLICKHOUSE_APP_PASSWORD: apppass
CLICKHOUSE_LOGS_CLUSTER_HOST: clickhouse
CLICKHOUSE_LOGS_CLUSTER_SECURE: "false"
# Redis / Kafka
REDIS_URL: redis://redis7:6379/
KAFKA_HOSTS: kafka:9092
FLAGS_REDIS_ENABLED: "false"
# Object storage (MinIO)
OBJECT_STORAGE_ENABLED: "true"
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${POSTHOG_SITE_URL}
OBJECT_STORAGE_ACCESS_KEY_ID: object_storage_root_user
OBJECT_STORAGE_SECRET_ACCESS_KEY: object_storage_root_password
OBJECT_STORAGE_FORCE_PATH_STYLE: "true"
# Session replay storage
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
RECORDING_API_URL: http://recording-api:6738
# App
SITE_URL: ${POSTHOG_SITE_URL}
SECRET_KEY: ${POSTHOG_SECRET_KEY}
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
DEPLOYMENT: hobby
IS_BEHIND_PROXY: "true"
DISABLE_SECURE_SSL_REDIRECT: "true"
# "false" so login works over plain HTTP; set to "true" once the domain uses HTTPS
SECURE_COOKIES: ${POSTHOG_SECURE_COOKIES}
CDP_API_URL: http://plugins:6738
FEATURE_FLAGS_SERVICE_URL: http://feature-flags:3001
LIVESTREAM_HOST: ${POSTHOG_SITE_URL}/livestream
API_QUERIES_PER_TEAM: '{"1": 100}'
OTEL_SDK_DISABLED: "true"
OTEL_EXPORTER_OTLP_ENDPOINT: ""
services:
# One-shot: fetches the ClickHouse configuration, Kafka table schemas and
# funnel UDFs from the PostHog repo (pinned to the same commit as the app
# image) plus the GeoLite2 GeoIP database. Idempotent across redeploys.
init-assets:
image: alpine:3.21
restart: on-failure
logging: *default-logging
entrypoint: /bin/sh
command:
- -c
- |
set -e
apk add --no-cache git curl brotli >/dev/null
if [ ! -f /assets/.ok-${POSTHOG_VERSION} ]; then
echo "Fetching PostHog repo assets @ ${POSTHOG_VERSION}"
rm -rf /tmp/ph && mkdir -p /tmp/ph && cd /tmp/ph
git init -q .
git remote add origin https://github.com/PostHog/posthog.git
git sparse-checkout set --no-cone /docker/clickhouse/ /posthog/idl/ /posthog/user_scripts/
git -c protocol.version=2 fetch -q --depth 1 --filter=blob:none origin ${POSTHOG_VERSION}
git checkout -q FETCH_HEAD
rm -rf /assets/clickhouse /assets/idl /assets/user_scripts /assets/.ok-*
cp -r docker/clickhouse /assets/clickhouse
cp -r posthog/idl /assets/idl
cp -r posthog/user_scripts /assets/user_scripts
touch /assets/.ok-${POSTHOG_VERSION}
echo "Repo assets ready"
fi
if [ ! -s /share/GeoLite2-City.mmdb ]; then
echo "Downloading GeoLite2 database"
curl -sL --http1.1 https://mmdbcdn.posthog.net/ | brotli --decompress --output=/share/GeoLite2-City.mmdb
chmod 644 /share/GeoLite2-City.mmdb
fi
echo "init-assets done"
volumes:
- posthog-assets:/assets
- geoip-data:/share
db:
image: postgres:15.12-alpine
restart: unless-stopped
logging: *default-logging
environment:
POSTGRES_USER: posthog
POSTGRES_DB: posthog
POSTGRES_PASSWORD: posthog
healthcheck:
test: ["CMD-SHELL", "pg_isready -U posthog"]
interval: 5s
timeout: 10s
retries: 30
start_period: 10s
volumes:
- postgres-data:/var/lib/postgresql/data
- ../files/db-init:/docker-entrypoint-initdb.d:ro
redis7:
image: redis:7.2-alpine
restart: unless-stopped
logging: *default-logging
command: redis-server --maxmemory-policy allkeys-lru --maxmemory 200mb
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 10s
retries: 10
volumes:
- redis-data:/data
# Required by ClickHouse for distributed DDL (docker/clickhouse/config.xml
# points at zookeeper:2181)
zookeeper:
image: zookeeper:3.7.0
restart: unless-stopped
logging: *default-logging
environment:
ZOO_AUTOPURGE_PURGEINTERVAL: 1
ZOO_AUTOPURGE_SNAPRETAINCOUNT: 3
volumes:
- zookeeper-data:/data
- zookeeper-datalog:/datalog
# Kafka API provided by Redpanda (as upstream), tuned down for small servers
kafka:
image: docker.io/redpandadata/redpanda:v25.1.9
restart: unless-stopped
logging: *default-logging
command:
- redpanda
- start
- --kafka-addr internal://0.0.0.0:9092
- --advertise-kafka-addr internal://kafka:9092
- --rpc-addr kafka:33145
- --advertise-rpc-addr kafka:33145
- --mode dev-container
- --smp 1
- --memory 1G
- --reserve-memory 0M
- --overprovisioned
- --set redpanda.empty_seed_starts_cluster=false
- --seeds kafka:33145
- --set redpanda.auto_create_topics_enabled=true
volumes:
- redpanda-data:/var/lib/redpanda/data
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9644/v1/status/ready || exit 1"]
interval: 5s
timeout: 10s
retries: 30
start_period: 20s
# One-shot: pre-creates the Kafka topics that consumers check at startup
kafka-init:
image: docker.io/redpandadata/redpanda:v25.1.9
restart: on-failure
logging: *default-logging
entrypoint: /bin/sh
depends_on:
kafka:
condition: service_healthy
command:
- -c
- |
set -e
TOPICS="clickhouse_events_json \
clickhouse_ai_events_json \
clickhouse_heatmap_events \
clickhouse_ingestion_warnings \
events_plugin_ingestion \
events_plugin_ingestion_dlq \
events_plugin_ingestion_overflow \
events_plugin_ingestion_async \
events_plugin_ingestion_historical \
ingestion-clientwarnings-main-1 \
heatmaps_ingestion \
session_recording_snapshot_item_events \
session_recording_snapshot_item_overflow \
clickhouse_groups \
clickhouse_person \
clickhouse_person_distinct_id \
clickhouse_app_metrics2 \
log_entries \
clickhouse_tophog"
for topic in $$TOPICS; do
rpk topic create "$$topic" --brokers kafka:9092 -p 1 -r 1 2>/dev/null \
|| rpk topic list --brokers kafka:9092 | grep -q "$$topic"
done
echo "Kafka topics ready"
clickhouse:
image: clickhouse/clickhouse-server:26.6.1.1193
restart: unless-stopped
logging: *default-logging
depends_on:
init-assets:
condition: service_completed_successfully
zookeeper:
condition: service_started
kafka:
condition: service_started
entrypoint: /bin/bash
command:
- -c
- |
set -e
cp /assets/clickhouse/config.xml /etc/clickhouse-server/config.xml
cp /assets/clickhouse/config.d/default.xml /etc/clickhouse-server/config.d/default.xml
cp /assets/clickhouse/users.xml /etc/clickhouse-server/users.xml
cp /assets/clickhouse/user_defined_function.xml /etc/clickhouse-server/user_defined_function.xml
mkdir -p /var/lib/clickhouse/format_schemas /var/lib/clickhouse/user_scripts
cp -r /assets/idl/. /var/lib/clickhouse/format_schemas/
cp -r /assets/user_scripts/. /var/lib/clickhouse/user_scripts/
chmod -R 755 /var/lib/clickhouse/user_scripts
chown -R clickhouse:clickhouse /var/lib/clickhouse/format_schemas /var/lib/clickhouse/user_scripts || true
# Materialize system log tables (system.crash_log is required by the
# custom_metrics ClickHouse migration), mirroring upstream init-db.sh
(
i=0
while [ $$i -lt 90 ]; do
if clickhouse-client --query "SELECT 1" >/dev/null 2>&1; then
clickhouse-client --query "SYSTEM FLUSH LOGS" && echo "system logs flushed" && break
fi
sleep 2
i=$$((i+1))
done
) &
exec /entrypoint.sh
environment:
CLICKHOUSE_SKIP_USER_SETUP: 1
KAFKA_HOSTS: kafka:9092
volumes:
- posthog-assets:/assets:ro
- clickhouse-data:/var/lib/clickhouse
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1"]
interval: 5s
timeout: 10s
retries: 30
start_period: 30s
# Django app: runs migrations, then serves the API + UI on :8000
web:
image: posthog/posthog:${POSTHOG_VERSION}
restart: unless-stopped
logging: *default-logging
command: ["sh", "-c", "./bin/migrate && exec ./bin/docker-server"]
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
kafka-init:
condition: service_completed_successfully
objectstorage:
condition: service_started
environment:
<<: *django-env
NGINX_UNIT_APP_PROCESSES: 2
healthcheck:
test: ["CMD-SHELL", "curl -fs http://localhost:8000/_health || exit 1"]
interval: 30s
timeout: 10s
retries: 15
start_period: 600s
# Celery worker + scheduler
worker:
image: posthog/posthog:${POSTHOG_VERSION}
restart: unless-stopped
logging: *default-logging
command: ./bin/docker-worker-celery --with-scheduler
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
web:
condition: service_started
environment:
<<: *django-env
POSTHOG_SKIP_MIGRATION_CHECKS: "1"
WEB_CONCURRENCY: 2
# Node plugin server: CDP (destinations, webhooks, transformations)
plugins:
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
restart: unless-stopped
logging: *default-logging
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
kafka:
condition: service_healthy
clickhouse:
condition: service_started
kafka-init:
condition: service_completed_successfully
init-assets:
condition: service_completed_successfully
environment:
# Run only the pipelines this template ships (CDP destinations/workflows,
# realtime cohorts, feature-flag evaluation). The default mode would also
# start the logs/error-tracking ingestion consumers, which this template
# intentionally omits.
NODEJS_CAPABILITY_GROUPS: cdp_workflows,realtime_cohorts,feature_flags
SITE_URL: ${POSTHOG_SITE_URL}
SECRET_KEY: ${POSTHOG_SECRET_KEY}
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
BEHAVIORAL_COHORTS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
CYCLOTRON_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
KAFKA_HOSTS: kafka:9092
REDIS_URL: redis://redis7:6379/
CDP_REDIS_HOST: redis7
CDP_REDIS_PORT: 6379
# TLS for these defaults to "true" in production images; this stack's
# Redis is plaintext (a TLS handshake against it hangs until timeout and
# crash-loops the process if these consumers ever run).
LOGS_REDIS_HOST: redis7
LOGS_REDIS_TLS: "false"
TRACES_REDIS_HOST: redis7
TRACES_REDIS_TLS: "false"
COOKIELESS_REDIS_HOST: redis7
COOKIELESS_REDIS_PORT: 6379
CLICKHOUSE_HOST: clickhouse
CLICKHOUSE_DATABASE: posthog
CLICKHOUSE_SECURE: "false"
CLICKHOUSE_VERIFY: "false"
OBJECT_STORAGE_ENABLED: "true"
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${POSTHOG_SITE_URL}
OBJECT_STORAGE_ACCESS_KEY_ID: object_storage_root_user
OBJECT_STORAGE_SECRET_ACCESS_KEY: object_storage_root_password
OTEL_EXPORTER_OTLP_ENDPOINT: ""
volumes:
- geoip-data:/share:ro
# Node plugin server: event ingestion (Kafka -> person processing -> ClickHouse)
ingestion-general:
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
restart: unless-stopped
logging: *default-logging
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
kafka:
condition: service_healthy
clickhouse:
condition: service_started
kafka-init:
condition: service_completed_successfully
init-assets:
condition: service_completed_successfully
environment:
PLUGIN_SERVER_MODE: ingestion-v2-combined
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
BEHAVIORAL_COHORTS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
KAFKA_HOSTS: kafka:9092
REDIS_URL: redis://redis7:6379/
COOKIELESS_REDIS_HOST: redis7
COOKIELESS_REDIS_PORT: 6379
CDP_REDIS_HOST: redis7
LOGS_REDIS_HOST: redis7
LOGS_REDIS_TLS: "false"
TRACES_REDIS_HOST: redis7
TRACES_REDIS_TLS: "false"
CLICKHOUSE_HOST: clickhouse
CLICKHOUSE_DATABASE: posthog
CLICKHOUSE_SECURE: "false"
CLICKHOUSE_VERIFY: "false"
volumes:
- geoip-data:/share:ro
# Node plugin server: session replay blob ingestion (Kafka -> S3)
ingestion-sessionreplay:
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
restart: unless-stopped
logging: *default-logging
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
kafka:
condition: service_healthy
kafka-init:
condition: service_completed_successfully
objectstorage:
condition: service_started
environment:
PLUGIN_SERVER_MODE: recordings-blob-ingestion-v2
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
KAFKA_HOSTS: kafka:9092
REDIS_URL: redis://redis7:6379/
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
SESSION_RECORDING_V2_S3_TIMEOUT_MS: 120000
# Node plugin server: session replay playback API
recording-api:
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
restart: unless-stopped
logging: *default-logging
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
clickhouse:
condition: service_started
objectstorage:
condition: service_started
environment:
PLUGIN_SERVER_MODE: recording-api
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
REDIS_URL: redis://redis7:6379/
SESSION_RECORDING_API_REDIS_HOST: redis7
SESSION_RECORDING_API_REDIS_PORT: 6379
CLICKHOUSE_HOST: clickhouse
CLICKHOUSE_DATABASE: posthog
CLICKHOUSE_SECURE: "false"
CLICKHOUSE_VERIFY: "false"
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
# Rust event capture endpoint (/e, /batch, /capture, /i/v0)
capture:
image: ghcr.io/posthog/posthog/capture:master
restart: unless-stopped
logging: *default-logging
depends_on:
kafka:
condition: service_healthy
redis7:
condition: service_healthy
environment:
ADDRESS: 0.0.0.0:3000
KAFKA_TOPIC: events_plugin_ingestion
KAFKA_HOSTS: kafka:9092
REDIS_URL: redis://redis7:6379/
CAPTURE_MODE: events
RUST_LOG: info,rdkafka=warn
CAPTURE_V1_SINKS: msk
CAPTURE_V1_SINK_MSK_KAFKA_HOSTS: kafka:9092
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_MAIN: events_plugin_ingestion
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_HISTORICAL: events_plugin_ingestion_historical
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_OVERFLOW: events_plugin_ingestion_overflow
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_DLQ: events_plugin_ingestion_dlq
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_EXCEPTION: ingestion-errortracking-main
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_HEATMAP: heatmaps_ingestion
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_CLIENT_INGESTION_WARNING: ingestion-clientwarnings-main-1
# Rust capture endpoint for session recordings (/s)
replay-capture:
image: ghcr.io/posthog/posthog/capture:master
restart: unless-stopped
logging: *default-logging
depends_on:
kafka:
condition: service_healthy
redis7:
condition: service_healthy
environment:
ADDRESS: 0.0.0.0:3000
KAFKA_TOPIC: session_recording_snapshot_item_events
KAFKA_HOSTS: kafka:9092
REDIS_URL: redis://redis7:6379/
CAPTURE_MODE: recordings
# Rust feature flag evaluation service (/flags)
feature-flags:
image: ghcr.io/posthog/posthog/feature-flags:master
restart: unless-stopped
logging: *default-logging
depends_on:
db:
condition: service_healthy
redis7:
condition: service_healthy
init-assets:
condition: service_completed_successfully
environment:
WRITE_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
READ_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
PERSONS_WRITE_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
PERSONS_READ_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
MAXMIND_DB_PATH: /share/GeoLite2-City.mmdb
REDIS_URL: redis://redis7:6379/
ADDRESS: 0.0.0.0:3001
RUST_LOG: info
COOKIELESS_REDIS_HOST: redis7
COOKIELESS_REDIS_PORT: 6379
volumes:
- geoip-data:/share:ro
# Rust hypercache server (/array remote config + /surveys)
hypercache-server:
image: ghcr.io/posthog/posthog/hypercache-server:master
restart: unless-stopped
logging: *default-logging
depends_on:
redis7:
condition: service_healthy
environment:
REDIS_URL: redis://redis7:6379/
ADDRESS: 0.0.0.0:3002
RUST_LOG: info
# Live events stream (/livestream)
livestream:
image: ghcr.io/posthog/posthog/livestream:master
restart: unless-stopped
logging: *default-logging
depends_on:
kafka:
condition: service_healthy
environment:
LIVESTREAM_JWT_SECRET: ${POSTHOG_SECRET_KEY}
volumes:
- ../files/livestream/configs.yml:/configs/configs.yml:ro
objectstorage:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
restart: unless-stopped
logging: *default-logging
environment:
MINIO_ROOT_USER: object_storage_root_user
MINIO_ROOT_PASSWORD: object_storage_root_password
entrypoint: sh
command: -c 'mkdir -p /data/posthog && minio server --address ":19000" --console-address ":19001" /data'
volumes:
- objectstorage-data:/data
# Caddy routes the single public domain to the right internal service,
# exactly like the upstream hobby deployment (TLS is terminated by Traefik)
proxy:
image: caddy:2.10-alpine
restart: unless-stopped
logging: *default-logging
depends_on:
web:
condition: service_started
entrypoint: sh
command: -c 'printf "%s" "$$CADDYFILE" > /etc/caddy/Caddyfile && exec caddy run -c /etc/caddy/Caddyfile'
environment:
CADDYFILE: |
{
auto_https off
}
:80 {
@capture path /e /e/ /e/* /i/v0 /i/v0/ /i/v0/* /i/v1/analytics/events /i/v1/analytics/events/ /batch /batch/ /batch/* /capture /capture/ /capture/*
@replay-capture path /s /s/ /s/*
@flags path /flags /flags/ /flags/* /api/feature_flag/local_evaluation /api/feature_flag/local_evaluation/ /api/feature_flag/local_evaluation/*
@surveys path /surveys /surveys/ /api/surveys /api/surveys/
@remote-config path /array/*
@webhooks path /public/webhooks /public/webhooks/ /public/webhooks/* /public/m/ /public/m/*
@livestream path /livestream /livestream/ /livestream/*
@objectstorage path /posthog /posthog/ /posthog/*
handle @capture {
reverse_proxy capture:3000
}
handle @replay-capture {
reverse_proxy replay-capture:3000
}
handle @flags {
reverse_proxy feature-flags:3001
}
handle @surveys {
reverse_proxy hypercache-server:3002
}
handle @remote-config {
reverse_proxy hypercache-server:3002
}
handle @webhooks {
reverse_proxy plugins:6738
}
handle @livestream {
uri strip_prefix /livestream
reverse_proxy livestream:8080 {
flush_interval -1
}
}
handle @objectstorage {
reverse_proxy objectstorage:19000
}
handle {
reverse_proxy web:8000
}
}
volumes:
postgres-data:
redis-data:
zookeeper-data:
zookeeper-datalog:
redpanda-data:
clickhouse-data:
posthog-assets:
geoip-data:
objectstorage-data:
+27
View File
@@ -0,0 +1,27 @@
# PostHog
Self-hosted PostHog based on the official "hobby" deployment, trimmed to run on a single server.
## Requirements
- **RAM: at least 8 GB free** is strongly recommended (the stack runs Django, a Celery worker, two Node plugin-server processes, ClickHouse, Redpanda/Kafka, Postgres, Redis, MinIO and several small Rust services).
- ~15 GB of free disk for the images and data volumes.
## First boot
The first deployment takes **5-10 minutes**: an init container fetches the ClickHouse configuration and the GeoIP database, and the `web` service runs all Postgres and ClickHouse migrations before it starts serving. Watch the `web` service logs; once you see the Unit server start, open the domain and create the first account.
## After enabling HTTPS
If you serve PostHog over HTTPS (recommended for production), update these environment variables and redeploy:
- `POSTHOG_SITE_URL` to `https://<your-domain>` (otherwise logins/CSRF and SDK snippets point at the wrong scheme)
- `POSTHOG_SECURE_COOKIES` to `true` (marks session/CSRF cookies as Secure; the default `false` is what allows logging in over the initial plain-HTTP domain)
## What is not included
Compared to the full upstream hobby stack, this template omits Temporal + Elasticsearch (data warehouse / batch exports), browserless (dashboard image exports and heatmap screenshots), and the error-tracking/logs/traces ingestion pipelines. Product analytics, feature flags, experiments, surveys, session replay, live events and the CDP destinations pipeline all work.
## Versioning
PostHog no longer publishes tagged releases; images are pinned to a tested master commit via the `POSTHOG_VERSION` and `POSTHOG_NODE_VERSION` environment variables. To upgrade, set `POSTHOG_VERSION` to a newer commit sha of `posthog/posthog` (Docker Hub tag) and redeploy.
+19
View File
@@ -0,0 +1,19 @@
{
"id": "posthog",
"name": "PostHog",
"version": "master-2026-07-14",
"description": "PostHog is an open-source product analytics platform with feature flags, session replay, surveys, A/B testing and a customer data pipeline, all in one tool.",
"logo": "posthog.svg",
"links": {
"github": "https://github.com/PostHog/posthog",
"website": "https://posthog.com/",
"docs": "https://posthog.com/docs/self-host"
},
"tags": [
"analytics",
"product-analytics",
"feature-flags",
"session-replay",
"ab-testing"
]
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="142px" viewBox="0 0 256 142" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<title>PostHog</title>
<g>
<path d="M0,112.012238 C0,107.259346 5.74647061,104.878898 9.10731886,108.239907 L33.5593478,132.692042 C36.9201961,136.053051 34.5399086,141.799308 29.7870161,141.799308 L5.33492848,141.799308 C2.38852989,141.799308 0,139.410858 0,136.464374 L0,112.012238 Z M0,86.2562448 C0,87.6710693 0.562072622,89.0282764 1.56256477,90.0285765 L51.7707846,140.236706 C52.7712447,141.237006 54.1282385,141.799308 55.5430629,141.799308 L83.1204028,141.799308 C87.8732953,141.799308 90.2537428,136.053051 86.8927345,132.692042 L9.10731886,54.9065733 C5.74647061,51.545565 0,53.9260124 0,58.678905 L0,86.2562448 Z M0,32.9228582 C0,34.3377359 0.562072622,35.6947297 1.56256477,36.6951899 L105.104065,140.236706 C106.104365,141.237006 107.461572,141.799308 108.876396,141.799308 L136.453736,141.799308 C141.206629,141.799308 143.587076,136.053051 140.226068,132.692042 L9.10731886,1.57329328 C5.74652396,-1.78750162 0,0.592732447 0,5.34562498 L0,32.9228582 Z M53.3333333,32.9228582 C53.3333333,34.3377359 53.8956354,35.6947297 54.8959354,36.6951899 L150.892734,132.692042 C154.253743,136.053051 160,133.672603 160,128.919711 L160,101.342371 C160,99.9275462 159.437698,98.5703391 158.437398,97.570039 L62.4405988,1.57323993 C59.0795905,-1.78755497 53.3333333,0.592732447 53.3333333,5.34562498 L53.3333333,32.9228582 Z M115.773932,1.57329328 C112.412924,-1.78755497 106.666667,0.592732447 106.666667,5.34562498 L106.666667,32.9228582 C106.666667,34.3377359 107.228969,35.6947297 108.229269,36.6951899 L150.892734,79.3587089 C154.253743,82.7197172 160,80.3392697 160,75.5863772 L160,48.0090373 C160,46.5942129 159.437698,45.2370057 158.437398,44.2367056 L115.773932,1.57329328 Z" fill="#F9BD2B"></path>
<path d="M226.866993,112.666301 L176.650862,62.4496362 C173.289854,59.0886279 167.543596,61.4690753 167.543596,66.2219679 L167.543596,136.46384 C167.543596,139.410324 169.932046,141.798774 172.87853,141.798774 L250.665066,141.798774 C253.61155,141.798774 256,139.410324 256,136.46384 L256,130.067255 C256,127.120771 253.601414,124.768065 250.679471,124.387684 C241.713047,123.219867 233.325998,119.124772 226.866993,112.666301 Z M193.143276,124.732321 C188.433063,124.732321 184.61005,120.909307 184.61005,116.199094 C184.61005,111.488348 188.433063,107.665334 193.143276,107.665334 C197.854023,107.665334 201.677036,111.488348 201.677036,116.199094 C201.677036,120.909307 197.854023,124.732321 193.143276,124.732321 Z" fill="#000000"></path>
<path d="M8.65608667,107.833264 L9.10731886,108.239373 L33.5594012,132.691509 C36.7740745,135.906386 34.7360374,141.303609 30.3932265,141.766855 L29.7870161,141.798774 L5.33493381,141.798774 C2.59898732,141.798774 0.344064852,139.739346 0.0358919824,137.086021 L0,136.46384 L0,112.011705 C0,107.46546 5.2576404,105.089845 8.65608667,107.833264 Z M8.65608667,54.5009977 L9.10731886,54.9071068 L53.3333333,99.1331746 L53.3333333,141.799841 L1.56256477,90.02911 C0.728821313,89.195082 0.199535861,88.1139429 0.0462669171,86.9579665 L0,86.2567783 L0,58.6794385 C0,54.1331934 5.2576404,51.7575787 8.65608667,54.5009977 Z M0,5.34562498 C0,0.799379948 5.2576404,-1.57608838 8.65608667,1.16720334 L9.10731886,1.57329328 L53.3333333,45.7993078 L53.3333333,88.4659744 L1.56256477,36.6951899 C0.728821313,35.8614731 0.199535861,34.7801635 0.0462669171,33.6240907 L0,32.9228582 L0,5.34562498 Z" fill="#1D4AFF"></path>
<path d="M53.3333867,45.7998412 L105.104065,97.5705725 C105.938093,98.4041559 106.467214,99.4855914 106.620421,100.641679 L106.666667,101.342904 L106.666667,141.799841 L54.8959354,90.02911 C54.062352,89.195082 53.532971,88.1139429 53.3796658,86.9579665 L53.3333867,86.2567783 L53.3333867,45.7998412 Z M53.3333867,99.1321076 L86.8927345,132.691509 C90.107612,135.906386 88.0694373,141.303609 83.7266141,141.766855 L83.1204028,141.798774 L53.3333867,141.798774 L53.3333867,99.1321076 Z M61.9894118,1.16720334 L62.4405988,1.57329328 L105.104065,44.2367056 C105.938093,45.070289 106.467214,46.1517246 106.620421,47.3078122 L106.666667,48.0090373 L106.666667,88.4659744 L54.8959354,36.6951899 C54.062352,35.8614731 53.532971,34.7801635 53.3796658,33.6240907 L53.3333867,32.9228582 L53.3333867,5.34562498 C53.3333867,0.799379948 58.5912711,-1.57608838 61.9894118,1.16720334 Z" fill="#F54E00"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

+73
View File
@@ -0,0 +1,73 @@
[variables]
main_domain = "${domain}"
secret_key = "${password:64}"
salt_keys = "${hash:32}"
[config]
[[config.domains]]
serviceName = "proxy"
port = 80
host = "${main_domain}"
path = "/"
[config.env]
# PostHog stopped publishing tagged releases; images are pinned to a tested
# master commit. POSTHOG_VERSION is both the posthog/posthog image tag and the
# git ref used to fetch the matching ClickHouse configuration.
POSTHOG_VERSION = "1e7b36ebe0da38a738fed250d175809a899ef422"
POSTHOG_NODE_VERSION = "sha-82ea668"
# Set to https://<your-domain> after enabling HTTPS on the domain
POSTHOG_SITE_URL = "http://${main_domain}"
# Set to "true" once the domain uses HTTPS (marks session/CSRF cookies Secure)
POSTHOG_SECURE_COOKIES = "false"
POSTHOG_SECRET_KEY = "${secret_key}"
POSTHOG_ENCRYPTION_SALT_KEYS = "${salt_keys}"
[[config.mounts]]
filePath = "/db-init/create-extra-dbs.sh"
content = """
#!/bin/bash
# Creates the auxiliary databases PostHog services can point at
# (mirrors docker/postgres-init-scripts in the PostHog repo)
set -e
set -u
for db in cyclotron posthog_persons behavioral_cohorts; do
DB_EXISTS=$(psql -U "$POSTGRES_USER" -tAc "SELECT 1 FROM pg_database WHERE datname='$db'")
if [ -z "$DB_EXISTS" ]; then
echo "Creating database '$db'..."
psql -U "$POSTGRES_USER" -c "CREATE DATABASE $db;"
psql -U "$POSTGRES_USER" -c "GRANT ALL PRIVILEGES ON DATABASE $db TO $POSTGRES_USER;"
fi
done
"""
[[config.mounts]]
filePath = "/livestream/configs.yml"
content = """
debug: false
kafka:
brokers: 'kafka:9092'
topic: 'events_plugin_ingestion'
group_id: 'livestream'
security_protocol: 'PLAINTEXT'
session_recording_enabled: true
session_recording_security_protocol: 'PLAINTEXT'
consumers:
event:
enabled: true
brokers: 'kafka:9092'
topic: 'events_plugin_ingestion'
security_protocol: 'PLAINTEXT'
group_id: 'livestream'
session_recording:
enabled: true
brokers: 'kafka:9092'
topic: 'session_recording_snapshot_item_events'
security_protocol: 'PLAINTEXT'
group_id: 'livestream-session-recordings'
notification:
enabled: false
mmdb:
path: 'GeoLite2-City.mmdb'
"""
+76
View File
@@ -0,0 +1,76 @@
services:
simstudio:
image: ghcr.io/simstudioai/simstudio:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
- BETTER_AUTH_URL=${SIM_APP_URL}
- NEXT_PUBLIC_APP_URL=${SIM_APP_URL}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- API_ENCRYPTION_KEY=${API_ENCRYPTION_KEY}
- INTERNAL_API_SECRET=${INTERNAL_API_SECRET}
- SOCKET_SERVER_URL=http://realtime:3002
depends_on:
db:
condition: service_healthy
migrations:
condition: service_completed_successfully
realtime:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
realtime:
image: ghcr.io/simstudioai/realtime:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
- NEXT_PUBLIC_APP_URL=${SIM_APP_URL}
- BETTER_AUTH_URL=${SIM_APP_URL}
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
- INTERNAL_API_SECRET=${INTERNAL_API_SECRET}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3002/health"]
interval: 30s
timeout: 5s
retries: 5
start_period: 15s
migrations:
image: ghcr.io/simstudioai/migrations:latest
working_dir: /app/packages/db
environment:
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
depends_on:
db:
condition: service_healthy
command: ["bun", "run", "db:migrate"]
restart: "no"
db:
image: pgvector/pgvector:pg17
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=simstudio
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:
+17
View File
@@ -0,0 +1,17 @@
{
"id": "sim",
"name": "Sim",
"version": "latest",
"description": "Open-source visual workflow builder for building and deploying AI agent workflows.",
"logo": "sim.svg",
"links": {
"github": "https://github.com/simstudioai/sim",
"website": "https://sim.ai",
"docs": "https://docs.sim.ai"
},
"tags": [
"ai",
"automation",
"workflow"
]
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

+28
View File
@@ -0,0 +1,28 @@
[variables]
main_domain = "${domain}"
better_auth_secret = "${password:32}"
encryption_key = "${hash:64}"
api_encryption_key = "${hash:64}"
internal_api_secret = "${password:32}"
postgres_password = "${password:32}"
[config]
[config.env]
SIM_APP_URL = "http://${main_domain}"
BETTER_AUTH_SECRET = "${better_auth_secret}"
ENCRYPTION_KEY = "${encryption_key}"
API_ENCRYPTION_KEY = "${api_encryption_key}"
INTERNAL_API_SECRET = "${internal_api_secret}"
POSTGRES_PASSWORD = "${postgres_password}"
[[config.domains]]
serviceName = "simstudio"
port = 3000
host = "${main_domain}"
[[config.domains]]
serviceName = "realtime"
port = 3002
host = "${main_domain}"
path = "/socket.io"
+13
View File
@@ -0,0 +1,13 @@
version: "3.8"
services:
steedos:
image: steedos/steedos-community:3.0.14
restart: always
environment:
- ROOT_URL=http://${STEEDOS_HOST}
volumes:
- steedos-data:/steedos-storage
volumes:
steedos-data:
+16
View File
@@ -0,0 +1,16 @@
{
"id": "steedos",
"name": "Steedos",
"version": "3.0.14",
"description": "Steedos is an open-source low-code platform and alternative to Salesforce, designed to help you build enterprise applications like CRM with clicks, not code.",
"logo": "steedos.png",
"links": {
"github": "https://github.com/steedos/steedos-platform",
"website": "https://www.steedos.com/",
"docs": "https://docs.steedos.com/"
},
"tags": [
"crm",
"low-code"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+13
View File
@@ -0,0 +1,13 @@
[variables]
main_domain = "${domain}"
[config]
mounts = []
[[config.domains]]
serviceName = "steedos"
port = 80
host = "${main_domain}"
[config.env]
STEEDOS_HOST = "${main_domain}"
+61
View File
@@ -0,0 +1,61 @@
# Supabase Setup Instructions
## Deploy
1. In Dokploy, create the service from the **Supabase** template (requires Dokploy `>= 0.22.5`).
2. Dokploy automatically generates all secrets for you (`POSTGRES_PASSWORD`, `JWT_SECRET`, `ANON_KEY`, `SERVICE_ROLE_KEY`, `DASHBOARD_PASSWORD`, etc.). You can review them in the **Environment** tab of the service.
3. Deploy and wait for all containers to become healthy. The first deploy can take several minutes while the Postgres database initializes.
## Log in to Supabase Studio
The main domain of the template points to the `kong` API gateway (port `8000`), which protects Supabase Studio with basic authentication:
- **Username**: the value of `DASHBOARD_USERNAME` (default: `supabase`)
- **Password**: the value of `DASHBOARD_PASSWORD`
Both values are in the **Environment** tab of the service in Dokploy.
## API URL and keys
To connect an application (for example with `supabase-js`):
- **API URL**: `https://<your-domain>` (requests are routed through Kong)
- **anon key**: the value of `ANON_KEY` in the Environment tab
- **service_role key**: the value of `SERVICE_ROLE_KEY` in the Environment tab (server-side only, never expose it to browsers)
## Recommended configuration
Review these variables in the **Environment** tab before using Supabase in production:
- `SUPABASE_PUBLIC_URL` and `API_EXTERNAL_URL`: must point to your Supabase domain with the correct `http`/`https` scheme (the template sets them from your domain automatically).
- `SITE_URL` and `ADDITIONAL_REDIRECT_URLS`: must point to the application that uses Supabase for authentication.
- `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_ADMIN_EMAIL`, `SMTP_SENDER_NAME`: required for auth emails (sign-up confirmations, password resets). The template ships with placeholder values, so no real emails are sent until you configure a real SMTP provider.
## Warning: changing POSTGRES_PASSWORD after the first deploy
The Postgres data directory (mounted at `files/volumes/db/data`) is initialized **once**, on the first deploy, using the value of `POSTGRES_PASSWORD` at that moment. The same password is also assigned to the internal Supabase roles (`authenticator`, `pgbouncer`, `supabase_auth_admin`, `supabase_functions_admin`, `supabase_storage_admin`) by an init script that only runs on first boot.
If you later change `POSTGRES_PASSWORD` in the Environment tab and redeploy, the password stored **inside the database does not change**. The other services will start using the new password while the database still expects the old one, and you will see errors such as `invalid_password` or `password authentication failed`.
To actually change the password, use one of these options:
### Option A: change it inside the database (keeps your data)
1. Open a terminal into the `db` container (in Dokploy: your Supabase service, `db` container, **Terminal**) and run `psql -U postgres`.
2. Execute the following, using your new password:
```sql
ALTER USER postgres WITH PASSWORD 'your-new-password';
ALTER USER supabase_admin WITH PASSWORD 'your-new-password';
ALTER USER authenticator WITH PASSWORD 'your-new-password';
ALTER USER pgbouncer WITH PASSWORD 'your-new-password';
ALTER USER supabase_auth_admin WITH PASSWORD 'your-new-password';
ALTER USER supabase_functions_admin WITH PASSWORD 'your-new-password';
ALTER USER supabase_storage_admin WITH PASSWORD 'your-new-password';
```
3. Update `POSTGRES_PASSWORD` in the Environment tab to the same value and redeploy.
### Option B: reinitialize the database (deletes ALL data)
Only if the instance has no data you care about: stop the service, delete the `files/volumes/db/data` directory of the service, set the new `POSTGRES_PASSWORD`, and deploy again. The database will be initialized from scratch with the new password.
+51
View File
@@ -0,0 +1,51 @@
services:
synapse:
image: matrixdotorg/synapse:v1.156.0
restart: unless-stopped
# The stock entrypoint (/start.py) does not generate the signing key when a
# config file is provided, so generate any missing keys on first boot and
# make sure the data volume is owned by the synapse user (991) before
# handing over to the regular entrypoint.
entrypoint:
- /bin/sh
- -c
- |
python -m synapse.app.homeserver --config-path /config/homeserver.yaml --keys-directory /data --generate-keys
chown -R 991:991 /data
exec /start.py
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
volumes:
- synapse-data:/data
- ../files/homeserver.yaml:/config/homeserver.yaml:ro
- ../files/log.config:/config/log.config:ro
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
interval: 15s
timeout: 5s
retries: 10
start_period: 90s
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: synapse
# Synapse requires a database with C collation
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U synapse -d synapse"]
interval: 10s
timeout: 5s
retries: 5
volumes:
synapse-data:
postgres-data:
+39
View File
@@ -0,0 +1,39 @@
## Instructions
Synapse is deployed with public registration disabled, so you need to create the
first user (your admin account) from the command line.
### Create the first user
Open a terminal on the server running Dokploy (or use the container terminal in
the Dokploy UI) and run the following inside the `synapse` service container:
```bash
docker exec -it $(docker ps -qf "name=synapse" | head -n 1) \
register_new_matrix_user http://localhost:8008 -c /config/homeserver.yaml
```
The tool prompts for a username and password, and asks whether the user should
be an admin. It authenticates against the server using the
`registration_shared_secret` that was generated for this deployment (stored in
the mounted `homeserver.yaml`), so no registration needs to be enabled.
### Log in
Point any Matrix client (for example Element Web at https://app.element.io) at
your homeserver URL `https://your-domain` and log in with the user you just
created. You can verify the server is up with:
```bash
curl https://your-domain/_matrix/client/versions
```
### Notes
- Data (signing keys, media uploads) is stored in the `synapse-data` volume and
the database in the `postgres-data` volume.
- Federation with other Matrix servers works through `.well-known` delegation
over HTTPS (port 443). The dedicated federation port 8448 is not exposed.
- To allow open registration instead, edit `enable_registration` in the mounted
`homeserver.yaml` (see Synapse docs for the required spam-check options such
as CAPTCHA or email verification).
+16
View File
@@ -0,0 +1,16 @@
{
"id": "synapse",
"name": "Synapse",
"version": "v1.156.0",
"description": "Synapse is the reference homeserver implementation for Matrix, an open standard for secure, decentralised, real-time communication.",
"logo": "synapse.svg",
"links": {
"github": "https://github.com/element-hq/synapse",
"website": "https://element-hq.github.io/synapse/latest/",
"docs": "https://element-hq.github.io/synapse/latest/setup/installation.html"
},
"tags": [
"matrix",
"communication"
]
}
+5
View File
@@ -0,0 +1,5 @@
<svg width="75" height="32" xmlns="http://www.w3.org/2000/svg">
<g fill="#000000" fill-rule="nonzero">
<path d="M.936.732V31.25H3.13v.732H.095V0h3.034v.732zM9.386 10.407v1.544h.044a4.461 4.461 0 0 1 1.487-1.368c.58-.323 1.245-.485 1.993-.485.72 0 1.377.14 1.972.42.595.279 1.047.771 1.355 1.477.338-.5.796-.941 1.377-1.323.58-.383 1.266-.574 2.06-.574.602 0 1.16.074 1.674.22.514.148.954.383 1.322.707.366.323.653.746.859 1.268.205.522.308 1.15.308 1.887v7.633H20.71v-6.464c0-.383-.015-.743-.044-1.082a2.305 2.305 0 0 0-.242-.882 1.473 1.473 0 0 0-.584-.596c-.257-.146-.606-.22-1.047-.22-.44 0-.796.085-1.068.253-.272.17-.485.39-.639.662a2.654 2.654 0 0 0-.308.927 7.074 7.074 0 0 0-.078 1.048v6.354h-3.128v-6.398c0-.338-.007-.673-.021-1.004a2.825 2.825 0 0 0-.188-.916 1.411 1.411 0 0 0-.55-.673c-.258-.168-.636-.253-1.135-.253a2.33 2.33 0 0 0-.584.1 1.94 1.94 0 0 0-.705.374c-.228.184-.422.449-.584.794-.161.346-.242.798-.242 1.357v6.619H6.434V10.407h2.952zM25.842 12.084a3.751 3.751 0 0 1 1.233-1.17 5.37 5.37 0 0 1 1.685-.629 9.579 9.579 0 0 1 1.884-.187c.573 0 1.153.04 1.74.121.588.081 1.124.24 1.609.475.484.235.88.562 1.19.981.308.42.462.975.462 1.666v5.934c0 .516.03 1.008.088 1.478.058.471.161.824.308 1.06H32.87a4.435 4.435 0 0 1-.22-1.104c-.5.515-1.087.876-1.762 1.081a7.084 7.084 0 0 1-2.071.31c-.544 0-1.05-.067-1.52-.2a3.472 3.472 0 0 1-1.234-.617 2.87 2.87 0 0 1-.826-1.059c-.199-.426-.298-.934-.298-1.522 0-.647.114-1.18.342-1.6.227-.419.52-.753.881-1.004.36-.25.771-.437 1.234-.562.462-.125.929-.224 1.399-.298.47-.073.932-.132 1.387-.176.456-.044.86-.11 1.212-.199.353-.088.631-.217.837-.386.206-.169.301-.415.287-.74 0-.337-.055-.606-.166-.804a1.217 1.217 0 0 0-.44-.464 1.737 1.737 0 0 0-.639-.22 5.292 5.292 0 0 0-.782-.055c-.617 0-1.101.132-1.454.397-.352.264-.558.706-.617 1.323h-3.128c.044-.735.227-1.345.55-1.83zm6.179 4.423a5.095 5.095 0 0 1-.639.165 9.68 9.68 0 0 1-.716.11c-.25.03-.5.067-.749.11a5.616 5.616 0 0 0-.694.177 2.057 2.057 0 0 0-.594.298c-.17.125-.305.284-.408.474-.103.192-.154.434-.154.728 0 .28.051.515.154.706.103.192.242.342.419.453.176.11.381.187.617.231.234.044.477.066.726.066.617 0 1.094-.102 1.432-.309.338-.205.587-.452.75-.739.16-.286.26-.576.297-.87.036-.295.055-.53.055-.707v-1.17a1.4 1.4 0 0 1-.496.277zM43.884 10.407v2.096h-2.291v5.647c0 .53.088.883.264 1.059.176.177.529.265 1.057.265.177 0 .345-.007.507-.022.161-.015.316-.037.463-.066v2.426a7.49 7.49 0 0 1-.882.089 21.67 21.67 0 0 1-.947.022c-.484 0-.944-.034-1.377-.1a3.233 3.233 0 0 1-1.145-.386 2.04 2.04 0 0 1-.782-.816c-.191-.353-.287-.816-.287-1.39v-6.728H36.57v-2.096h1.894v-3.42h3.129v3.42h2.29zM48.355 10.407v2.118h.044a3.907 3.907 0 0 1 1.454-1.754 4.213 4.213 0 0 1 1.036-.497 3.734 3.734 0 0 1 1.145-.176c.206 0 .433.037.683.11v2.912a5.862 5.862 0 0 0-.528-.077 5.566 5.566 0 0 0-.595-.033c-.573 0-1.058.096-1.454.287a2.52 2.52 0 0 0-.958.783 3.143 3.143 0 0 0-.518 1.158 6.32 6.32 0 0 0-.154 1.434v5.14h-3.128V10.407h2.973zM54.039 8.642V6.06h3.128v2.582H54.04zm3.128 1.765v11.405H54.04V10.407h3.128zM58.797 10.407h3.569l2.005 2.978 1.982-2.978h3.459l-3.745 5.339 4.208 6.067h-3.57l-2.378-3.596-2.38 3.596h-3.502l4.097-6.001zM74.094 31.25V.732H71.9V0h3.035v31.982H71.9v-.732z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

+93
View File
@@ -0,0 +1,93 @@
[variables]
main_domain = "${domain}"
postgres_password = "${password:32}"
registration_shared_secret = "${password:32}"
macaroon_secret_key = "${password:32}"
form_secret = "${password:32}"
[config]
env = [
"POSTGRES_PASSWORD=${postgres_password}",
]
[[config.domains]]
serviceName = "synapse"
port = 8008
host = "${main_domain}"
[[config.mounts]]
filePath = "homeserver.yaml"
content = """
# Synapse homeserver configuration
# https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html
server_name: "${main_domain}"
public_baseurl: "https://${main_domain}/"
pid_file: /data/homeserver.pid
signing_key_path: "/data/signing.key"
log_config: "/config/log.config"
media_store_path: /data/media_store
report_stats: false
# Serve /.well-known/matrix/server so other homeservers can federate with
# this server over port 443 (the dedicated federation port 8448 is not exposed).
serve_server_wellknown: true
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ['0.0.0.0']
resources:
- names: [client, federation]
compress: false
database:
name: psycopg2
args:
user: synapse
password: "${postgres_password}"
database: synapse
host: postgres
port: 5432
cp_min: 5
cp_max: 10
# Public registration is disabled. Create users with the
# register_new_matrix_user tool (see the template instructions).
enable_registration: false
registration_shared_secret: "${registration_shared_secret}"
macaroon_secret_key: "${macaroon_secret_key}"
form_secret: "${form_secret}"
trusted_key_servers:
- server_name: "matrix.org"
suppress_key_server_warning: true
"""
[[config.mounts]]
filePath = "log.config"
content = """
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse.storage.SQL:
level: INFO
root:
level: INFO
handlers: [console]
disable_existing_loggers: false
"""
+32
View File
@@ -0,0 +1,32 @@
services:
trek:
image: mauriceboe/trek:3.3.0
restart: unless-stopped
environment:
- NODE_ENV=production
- PORT=3000
# Encryption key for secrets at rest. Persisted via the env below so it
# survives container recreation.
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- TZ=${TZ:-UTC}
- LOG_LEVEL=${LOG_LEVEL:-info}
# Public base URL, used for links in notifications and required for OIDC
- APP_URL=${APP_URL}
# Comma-separated origins for CORS and email notification links
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS}
# Initial admin credentials, only used on first boot when no users exist
- ADMIN_EMAIL=${ADMIN_EMAIL}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
volumes:
- trek-data:/app/data
- trek-uploads:/app/uploads
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
volumes:
trek-data:
trek-uploads:
+17
View File
@@ -0,0 +1,17 @@
{
"id": "trek",
"name": "TREK",
"version": "3.3.0",
"description": "A minimalist, self-hosted trip planner with Kanban-style task boards, budgets, packing lists, interactive maps, real-time collaboration, and OIDC single sign-on.",
"logo": "trek.png",
"links": {
"github": "https://github.com/mauriceboe/TREK",
"website": "https://github.com/mauriceboe/TREK",
"docs": "https://github.com/mauriceboe/TREK/wiki"
},
"tags": [
"travel",
"planning",
"productivity"
]
}
+46
View File
@@ -0,0 +1,46 @@
[variables]
main_domain = "${domain}"
encryption_key = "${password:64}"
admin_email = "${email}"
admin_password = "${password:16}"
[config]
[[config.domains]]
serviceName = "trek"
port = 3000
host = "${main_domain}"
[config.env]
# Encryption key for secrets at rest (equivalent to `openssl rand -hex 32`)
"ENCRYPTION_KEY" = "${encryption_key}"
# Timezone for logs, reminders and scheduled tasks (e.g. Europe/Berlin)
"TZ" = "UTC"
# info = concise user actions; debug = verbose admin-level details
"LOG_LEVEL" = "info"
# Public base URL, used for links in notifications and required for OIDC.
# Change to https:// once you enable SSL on the domain.
"APP_URL" = "http://${main_domain}"
# Comma-separated origins for CORS and email notification links
"ALLOWED_ORIGINS" = "http://${main_domain}"
# Initial admin credentials, only used on first boot when no users exist
"ADMIN_EMAIL" = "${admin_email}"
"ADMIN_PASSWORD" = "${admin_password}"
# --- Optional: OIDC single sign-on (Google, Apple, Authentik, Keycloak, ...) ---
# "OIDC_ISSUER" = "https://auth.example.com"
# "OIDC_CLIENT_ID" = "trek"
# "OIDC_CLIENT_SECRET" = "supersecret"
# "OIDC_DISPLAY_NAME" = "SSO"
# "OIDC_ONLY" = "false"
# "OIDC_SCOPE" = "openid email profile"
# "OIDC_ADMIN_CLAIM" = "groups"
# "OIDC_ADMIN_VALUE" = "app-trek-admins"
# "OIDC_DISCOVERY_URL" = ""
# --- Optional: extras ---
# Default language on the login page (de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar, ...)
# "DEFAULT_LANGUAGE" = "en"
# How long users stay logged in (1h | 12h | 7d | 30d | 90d). Default: 24h
# "SESSION_DURATION" = "30d"
# Unsplash Access Key for trip-cover / place-image search
# "UNSPLASH_ACCESS_KEY" = ""
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+54
View File
@@ -0,0 +1,54 @@
# Trigger.dev Setup Instructions
## Deploy
1. In Dokploy, create the service from the **Trigger.dev** template.
2. Dokploy generates all required secrets (`MAGIC_LINK_SECRET`, `SESSION_SECRET`, `ENCRYPTION_KEY`, database credentials, etc.) automatically.
3. Deploy and wait until the containers are running. The main domain points to the `webapp` service (port `3000`).
If you serve the app over HTTPS, set `TRIGGER_PROTOCOL=https` in the **Environment** tab (the template defaults to `http`) so that login links use the correct scheme, then redeploy.
## First login (no email server configured)
Trigger.dev logs you in with **magic links** sent by email. The template does not configure an email transport by default, so the email is never actually sent. Instead, **the magic link is printed to the logs of the `webapp` container**:
1. Open `https://<your-domain>` and enter your email address to request a magic link.
2. In Dokploy, go to your Trigger.dev service, open the **Logs** tab, and select the `webapp` container/service.
3. Look for a recent log entry containing a URL like `.../magic?token=...` (search for `magic`).
4. Copy that URL into your browser to complete the login.
## Configure email sending (optional)
To have magic links delivered by email, add these variables in the **Environment** tab and redeploy.
Using SMTP:
```
EMAIL_TRANSPORT=smtp
FROM_EMAIL=trigger@example.com
REPLY_TO_EMAIL=trigger@example.com
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-smtp-user
SMTP_PASSWORD=your-smtp-password
```
Using [Resend](https://resend.com):
```
EMAIL_TRANSPORT=resend
FROM_EMAIL=trigger@example.com
REPLY_TO_EMAIL=trigger@example.com
RESEND_API_KEY=your-resend-api-key
```
## Restrict who can sign up (recommended)
By default anyone who can reach the webapp can request a magic link. Restrict access with a regex of allowed email addresses:
```
WHITELISTED_EMAILS=you@example\.com|teammate@example\.com
```
You can also grant admin rights by email with `ADMIN_EMAILS` (same regex format).
+66
View File
@@ -0,0 +1,66 @@
services:
weblate:
image: weblate/weblate:2026.7.1.1
restart: unless-stopped
depends_on:
database:
condition: service_healthy
cache:
condition: service_healthy
volumes:
- weblate-data:/app/data
- weblate-cache:/app/cache
tmpfs:
- /run
- /tmp
environment:
WEBLATE_SITE_DOMAIN: ${WEBLATE_SITE_DOMAIN}
WEBLATE_SITE_TITLE: ${WEBLATE_SITE_TITLE}
WEBLATE_ADMIN_NAME: ${WEBLATE_ADMIN_NAME}
WEBLATE_ADMIN_EMAIL: ${WEBLATE_ADMIN_EMAIL}
WEBLATE_ADMIN_PASSWORD: ${WEBLATE_ADMIN_PASSWORD}
WEBLATE_SERVER_EMAIL: ${WEBLATE_SERVER_EMAIL}
WEBLATE_DEFAULT_FROM_EMAIL: ${WEBLATE_DEFAULT_FROM_EMAIL}
WEBLATE_ENABLE_HTTPS: ${WEBLATE_ENABLE_HTTPS}
WEBLATE_ALLOWED_HOSTS: "*"
WEBLATE_REGISTRATION_OPEN: ${WEBLATE_REGISTRATION_OPEN}
POSTGRES_HOST: database
POSTGRES_PORT: 5432
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
REDIS_HOST: cache
REDIS_PORT: 6379
database:
image: postgres:18-alpine
restart: unless-stopped
volumes:
- weblate-postgres-data:/var/lib/postgresql
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:8-alpine
restart: unless-stopped
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
volumes:
- weblate-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
weblate-data:
weblate-cache:
weblate-postgres-data:
weblate-redis-data:
+18
View File
@@ -0,0 +1,18 @@
{
"id": "weblate",
"name": "Weblate",
"version": "2026.7.1.1",
"description": "Weblate is a libre web-based continuous localization platform with tight version control integration, quality checks, and support for over 50 file formats including PO, XLIFF, JSON, and Android resources.",
"logo": "weblate.svg",
"links": {
"github": "https://github.com/WeblateOrg/weblate",
"website": "https://weblate.org/",
"docs": "https://docs.weblate.org/en/latest/admin/install/docker.html"
},
"tags": [
"localization",
"translation",
"i18n",
"self-hosted"
]
}
+26
View File
@@ -0,0 +1,26 @@
[variables]
main_domain = "${domain}"
admin_password = "${password:32}"
postgres_password = "${password:32}"
[config]
env = [
"WEBLATE_SITE_DOMAIN=${main_domain}",
"WEBLATE_SITE_TITLE=Weblate",
"WEBLATE_ADMIN_NAME=Weblate Admin",
"WEBLATE_ADMIN_EMAIL=admin@${main_domain}",
"WEBLATE_ADMIN_PASSWORD=${admin_password}",
"WEBLATE_SERVER_EMAIL=weblate@${main_domain}",
"WEBLATE_DEFAULT_FROM_EMAIL=weblate@${main_domain}",
"WEBLATE_ENABLE_HTTPS=0",
"WEBLATE_REGISTRATION_OPEN=1",
"POSTGRES_USER=weblate",
"POSTGRES_PASSWORD=${postgres_password}",
"POSTGRES_DB=weblate",
]
mounts = []
[[config.domains]]
serviceName = "weblate"
port = 8080
host = "${main_domain}"
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg31" width="300" height="300" version="1.1" viewBox="0 0 300 300"><metadata id="metadata37"/><linearGradient id="a" x1=".396" x2="98.808" y1="55.253" y2="55.253" gradientTransform="scale(0.98308415,1.0172069)" gradientUnits="userSpaceOnUse"><stop id="stop2" offset="0" stop-color="#00d2e6"/><stop id="stop4" offset="1" stop-color="#2eccaa"/></linearGradient><linearGradient id="b" x1="49.017" x2="99.793" y1="137.887" y2="113.962" gradientTransform="scale(1.1630616,0.85979965)" gradientUnits="userSpaceOnUse"><stop id="stop7" offset="0" stop-opacity="0"/><stop id="stop9" offset=".514"/><stop id="stop11" offset="1" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="201.822" x2="103.577" y1="57.649" y2="57.649" gradientTransform="scale(0.98308415,1.0172069)" gradientUnits="userSpaceOnUse"><stop id="stop14" offset="0" stop-color="#1fa385"/><stop id="stop16" offset="1" stop-color="#2eccaa"/></linearGradient><g style="fill:none;fill-rule:evenodd" id="g27" transform="translate(50,76)"><path style="fill:url(#a);fill-rule:nonzero" id="path21" d="m 127.24725,111.60641 c -2.88836,-0.0145 -5.76656,-0.6024 -8.47973,-1.78467 -6.11167,-2.66259 -11.49342,-7.69116 -15.87156,-14.494798 1.24856,-2.219307 2.3738,-4.517338 3.37843,-6.853465 4.40506,-10.243222 6.50003,-21.459724 6.6607,-32.59332 -0.0233,-0.220825 -0.0416,-0.442436 -0.0552,-0.664833 l -0.0121,-0.571316 c -0.01,-4.365422 -0.67459,-8.789784 -2.17667,-12.908841 -1.73043,-4.745776 -4.4887,-9.495481 -8.86498,-11.347741 -0.79519,-0.335952 -1.63162,-0.477013 -2.464161,-0.457367 -5.504872,-10.288802 -5.679939,-20.1485266 0,-29.53713169 0.10115,0 0.20619,3.9293e-4 0.30734,0.001179 6.701161,0.0738703 13.340071,2.14184679 19.021181,5.75363459 15.46928,9.8349701 23.18193,29.0007861 23.35194,47.8180741 0.002,0.220826 -3.9e-4,0.441258 -0.007,0.66169 h 0.0868 c -0.0226,19.886801 -4.80489,40.053612 -14.87495,56.978905 z m -34.299625,31.21585 c -14.447871,5.94246 -31.22819,5.62359 -45.54931,-1.02501 C 30.922601,134.1497 18.333366,119.28527 10.57986,102.31827 -2.6824097,73.29666 -2.9858585,38.603143 9.5917057,9.1367387 19.037523,12.915521 27.43683,6.8970531 27.43683,6.8970531 c 0,0 -0.01945,9.2605109 8.94785,13.9049119 -9.200724,21.556385 -8.978973,47.166601 0.241203,68.172888 4.438911,10.107269 11.219825,19.519057 20.618957,24.841647 3.354732,1.8996 7.040998,3.12602 10.833017,3.58618 0.01404,0.0219 0.02808,0.0439 0.04214,0.0658 6.696522,10.44898 15.132208,19.1568 24.827633,25.35378 z"/><path style="opacity:.3;fill:url(#b)" id="path23" d="m 127.24298,111.61346 c -2.88693,-0.0151 -5.76363,-0.60296 -8.47546,-1.78465 -6.11266,-2.66302 -11.49516,-7.69279 -15.87368,-14.498094 1.24941,-2.220476 2.37535,-4.519788 3.38055,-6.857239 1.32817,-3.088408 2.44626,-6.264833 3.36439,-9.500982 2.12803,-7.497839 30.38184,2.018075 26.0717,14.370923 -2.22386,6.372982 -5.03943,12.509222 -8.4675,18.270042 z m -34.302088,31.21157 c -14.446351,5.93959 -31.223682,5.61979 -45.542576,-1.02778 -16.475715,-7.64755 0.447392,-33.30334 9.846524,-27.98075 3.353286,1.89878 7.037819,3.12496 10.828112,3.58558 0.01567,0.0245 0.03135,0.049 0.04704,0.0735 6.694972,10.44656 15.128303,19.15278 24.8209,25.34948 z"/><path style="fill:url(#c);fill-rule:nonzero" id="path25" d="m 56.762435,54.627505 c -0.0066,-0.220432 -0.0093,-0.440865 -0.007,-0.66169 C 56.925444,35.148526 64.638101,15.982711 80.107377,6.1477407 85.788484,2.5359528 92.427399,0.46797642 99.12856,0.39410609 c 0.10115,-7.8585e-4 0.20619,-0.001179 0.307339,-0.001179 V 29.930059 c -0.832539,-0.01965 -1.668968,0.121414 -2.46416,0.457367 -4.376276,1.85226 -7.134548,6.601965 -8.864984,11.347741 -1.502072,4.119057 -2.166936,8.543418 -2.176662,12.908841 l -0.01206,0.571316 c -0.01362,0.222397 -0.0319,0.444008 -0.05524,0.664833 0.160672,11.133595 2.255636,22.350098 6.660701,32.59332 4.933377,11.471906 12.775196,22.025153 23.847186,26.848723 8.35263,3.63969 17.61171,2.78114 25.18236,-1.5057 9.39914,-5.32259 16.18005,-14.734378 20.61896,-24.841647 9.22018,-21.006287 9.44193,-46.616503 0.24121,-68.172888 8.9673,-4.644401 8.94785,-13.9049119 8.94785,-13.9049119 0,0 8.3993,6.0184679 17.84512,2.2396856 12.57756,29.4664043 12.27412,64.1599213 -0.98815,93.1815313 -7.75351,16.967 -20.34275,31.83144 -36.81846,39.47898 -14.66669,6.80904 -31.9127,6.97917 -46.59106,0.58389 C 91.618199,136.63222 80.890116,126.27466 73.171624,114.23104 61.993036,96.788212 56.699411,75.552849 56.675679,54.627505 Z"/></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

+22
View File
@@ -1,6 +1,27 @@
services:
wordpress:
image: wordpress:latest
# Resolve the site's own domain to the Traefik container instead of the
# server's public IP. Loopback requests (REST API, WP-Cron, Site Health)
# otherwise depend on hairpin NAT, which times out on many hosts
# (cURL error 28). See https://github.com/Dokploy/templates/issues/128
command:
- bash
- -c
- |
if [ -n "$$WP_DOMAIN" ]; then
(
for i in $$(seq 1 60); do
TRAEFIK_IP="$$(getent hosts dokploy-traefik | awk '{print $$1; exit}')"
if [ -n "$$TRAEFIK_IP" ]; then
grep -q "[[:space:]]$$WP_DOMAIN\$$" /etc/hosts || echo "$$TRAEFIK_IP $$WP_DOMAIN" >> /etc/hosts
break
fi
sleep 2
done
) &
fi
exec docker-entrypoint.sh apache2-foreground
volumes:
- wp_app:/var/www/html
- ../files/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
@@ -10,6 +31,7 @@ services:
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: $DB_PASSWORD
WORDPRESS_DEBUG: ${WORDPRESS_DEBUG:-0}
WP_DOMAIN: ${WP_DOMAIN}
WORDPRESS_CONFIG_EXTRA: |
define('WP_MEMORY_LIMIT', '256M');
define('DISALLOW_FILE_EDIT', true);
+2 -1
View File
@@ -9,7 +9,8 @@ env = [
"WORDPRESS_DEBUG=0",
"DB_NAME=${db_name}",
"DB_USER=${db_user}",
"DB_PASSWORD=${db_password}"
"DB_PASSWORD=${db_password}",
"WP_DOMAIN=${main_domain}"
]
[[config.domains]]