Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8b535d34 |
@@ -14,7 +14,6 @@ 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;
|
||||
@@ -33,7 +32,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateDialogProps {
|
||||
@@ -171,23 +169,8 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
defaultValue={
|
||||
templateFiles?.instructions
|
||||
? "instructions"
|
||||
: "docker-compose"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<Tabs defaultValue="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"
|
||||
@@ -201,24 +184,6 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
Configuration
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{templateFiles?.instructions && (
|
||||
<TabsContent value="instructions" className="mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="flex flex-col items-start w-fit justify-start gap-1">
|
||||
<span className="leading-tight text-xl font-semibold">
|
||||
Setup Instructions
|
||||
</span>
|
||||
<span className="leading-tight text-sm text-gray-500">
|
||||
instructions.md
|
||||
</span>
|
||||
</Label>
|
||||
<div className="rounded-md border p-4">
|
||||
<Markdown content={templateFiles.instructions} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="docker-compose" className="mt-4">
|
||||
{templateFiles?.dockerCompose && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -28,7 +28,6 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateGridProps {
|
||||
@@ -81,34 +80,20 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
const fetchTemplateFiles = async (templateId: string) => {
|
||||
setModalLoading(true);
|
||||
try {
|
||||
const [dockerComposeRes, configRes, instructionsRes] = await Promise.all(
|
||||
[
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
fetch(`/blueprints/${templateId}/instructions.md`),
|
||||
]
|
||||
);
|
||||
const [dockerComposeRes, configRes] = await Promise.all([
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
]);
|
||||
|
||||
const dockerCompose = dockerComposeRes.ok
|
||||
? await dockerComposeRes.text()
|
||||
: null;
|
||||
const config = configRes.ok ? await configRes.text() : null;
|
||||
|
||||
// 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 });
|
||||
setTemplateFiles({ dockerCompose, config });
|
||||
} catch (err) {
|
||||
console.error("Error fetching template files:", err);
|
||||
setTemplateFiles({ dockerCompose: null, config: null, instructions: null });
|
||||
setTemplateFiles({ dockerCompose: null, config: null });
|
||||
} finally {
|
||||
setModalLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
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;
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
## Instructions
|
||||
|
||||
We don't have nothing to show here....
|
||||
|
Before Width: | Height: | Size: 72 KiB |
@@ -1,84 +0,0 @@
|
||||
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:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
[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" = ""
|
||||
@@ -1,33 +0,0 @@
|
||||
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:
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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.
|
||||
|
Before Width: | Height: | Size: 46 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[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"
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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: {}
|
||||
|
Before Width: | Height: | Size: 7.6 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[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}"
|
||||
@@ -1,256 +0,0 @@
|
||||
# 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:
|
||||
@@ -1,15 +0,0 @@
|
||||
## 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.
|
||||
@@ -1,12 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${base64:42}"
|
||||
postgres_password = "${password:32}"
|
||||
redis_password = "${password:32}"
|
||||
sandbox_api_key = "${password:32}"
|
||||
plugin_daemon_key = "${base64:42}"
|
||||
plugin_inner_api_key = "${base64:42}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"DIFY_DOMAIN=${main_domain}",
|
||||
"SECRET_KEY=${secret_key}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"REDIS_PASSWORD=${redis_password}",
|
||||
"SANDBOX_API_KEY=${sandbox_api_key}",
|
||||
"PLUGIN_DAEMON_KEY=${plugin_daemon_key}",
|
||||
"PLUGIN_INNER_API_KEY=${plugin_inner_api_key}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "nginx"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
# Internal nginx gateway replicating the upstream Dify nginx routing
|
||||
# (TLS terminates at Traefik, which forwards everything to this nginx).
|
||||
[[config.mounts]]
|
||||
filePath = "dify-nginx.conf"
|
||||
content = '''
|
||||
map $http_x_forwarded_proto $dify_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 100M;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
|
||||
location /console/api { proxy_pass http://api:5001; }
|
||||
location /api { proxy_pass http://api:5001; }
|
||||
location /v1 { proxy_pass http://api:5001; }
|
||||
location /openapi { proxy_pass http://api:5001; }
|
||||
location /files { proxy_pass http://api:5001; }
|
||||
location /mcp { proxy_pass http://api:5001; }
|
||||
location /triggers { proxy_pass http://api:5001; }
|
||||
location /explore { proxy_pass http://web:3000; }
|
||||
|
||||
location /e/ {
|
||||
proxy_pass http://plugin_daemon:5002;
|
||||
# nginx drops inherited proxy_set_header directives as soon as a
|
||||
# location defines its own, so the shared ones are repeated here.
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Dify-Hook-Url $dify_forwarded_proto://$host$request_uri;
|
||||
}
|
||||
|
||||
location / { proxy_pass http://web:3000; }
|
||||
}
|
||||
'''
|
||||
|
||||
# Squid SSRF proxy configuration, rendered from the upstream
|
||||
# docker/ssrf_proxy/squid.conf.template with its default values.
|
||||
[[config.mounts]]
|
||||
filePath = "dify-squid.conf"
|
||||
content = '''
|
||||
acl client_localnet src 0.0.0.1-0.255.255.255
|
||||
acl client_localnet src 10.0.0.0/8
|
||||
acl client_localnet src 100.64.0.0/10
|
||||
acl client_localnet src 169.254.0.0/16
|
||||
acl client_localnet src 172.16.0.0/12
|
||||
acl client_localnet src 192.168.0.0/16
|
||||
acl client_localnet src fc00::/7
|
||||
acl client_localnet src fe80::/10
|
||||
acl to_private_networks dst 0.0.0.0/8
|
||||
acl to_private_networks dst 10.0.0.0/8
|
||||
acl to_private_networks dst 100.64.0.0/10
|
||||
acl to_private_networks dst 127.0.0.0/8
|
||||
acl to_private_networks dst 169.254.0.0/16
|
||||
acl to_private_networks dst 172.16.0.0/12
|
||||
acl to_private_networks dst 192.168.0.0/16
|
||||
acl to_private_networks dst 224.0.0.0/4
|
||||
acl to_private_networks dst 240.0.0.0/4
|
||||
acl to_private_networks dst ::/128
|
||||
acl to_private_networks dst ::1/128
|
||||
acl to_private_networks dst ::ffff:0:0/96
|
||||
acl to_private_networks dst ::/96
|
||||
acl to_private_networks dst fc00::/7
|
||||
acl to_private_networks dst fe80::/10
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80
|
||||
acl Safe_ports port 21
|
||||
acl Safe_ports port 443
|
||||
acl Safe_ports port 70
|
||||
acl Safe_ports port 210
|
||||
acl Safe_ports port 1025-65535
|
||||
acl Safe_ports port 280
|
||||
acl Safe_ports port 488
|
||||
acl Safe_ports port 591
|
||||
acl Safe_ports port 777
|
||||
acl CONNECT method CONNECT
|
||||
acl allowed_domains dstdomain .marketplace.dify.ai
|
||||
|
||||
http_port 3128
|
||||
|
||||
http_access deny !Safe_ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
http_access deny to_private_networks
|
||||
http_access allow allowed_domains
|
||||
http_access allow client_localnet
|
||||
http_access allow localhost
|
||||
http_access deny all
|
||||
tcp_outgoing_address 0.0.0.0
|
||||
|
||||
coredump_dir /var/spool/squid
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
client_request_buffer_max_size 100 MB
|
||||
max_filedescriptors 65536
|
||||
|
||||
connect_timeout 30 seconds
|
||||
request_timeout 2 minutes
|
||||
read_timeout 2 minutes
|
||||
client_lifetime 5 minutes
|
||||
shutdown_lifetime 30 seconds
|
||||
|
||||
server_persistent_connections on
|
||||
client_persistent_connections on
|
||||
persistent_request_timeout 30 seconds
|
||||
pconn_timeout 1 minute
|
||||
|
||||
client_db on
|
||||
server_idle_pconn_timeout 2 minutes
|
||||
client_idle_pconn_timeout 2 minutes
|
||||
|
||||
quick_abort_min 16 KB
|
||||
quick_abort_max 16 MB
|
||||
quick_abort_pct 95
|
||||
|
||||
memory_cache_mode disk
|
||||
cache_mem 256 MB
|
||||
maximum_object_size_in_memory 512 KB
|
||||
|
||||
dns_timeout 30 seconds
|
||||
dns_retransmit_interval 5 seconds
|
||||
|
||||
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
|
||||
access_log daemon:/var/log/squid/access.log dify_log
|
||||
logfile_rotate 10
|
||||
'''
|
||||
@@ -1,313 +0,0 @@
|
||||
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}"
|
||||
@@ -1,17 +0,0 @@
|
||||
# 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.
|
||||
|
Before Width: | Height: | Size: 43 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
db_root_password = "${password:32}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "frontend"
|
||||
port = 8_080
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SITE_NAME = "${main_domain}"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
DB_ROOT_PASSWORD = "${db_root_password}"
|
||||
MIGRATE = "1"
|
||||
ENABLE_DB = "1"
|
||||
DB_HOST = "db"
|
||||
CREATE_SITE = "1"
|
||||
CONFIGURE = "1"
|
||||
REGENERATE_APPS_TXT = "1"
|
||||
INSTALL_APP_ARGS = "--install-app lms"
|
||||
ENABLE_SCHEDULER = "1"
|
||||
IMAGE_NAME = "ghcr.io/frappe/lms"
|
||||
VERSION = "v2.52.0"
|
||||
FRAPPE_SITE_NAME_HEADER = ""
|
||||
DB_VERSION = "11.8.6"
|
||||
SITE_VOLUME_TYPE = ""
|
||||
SITE_VOLUME_OPTS = ""
|
||||
SITE_VOLUME_DEV = ""
|
||||
@@ -1,27 +0,0 @@
|
||||
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:
|
||||
@@ -1,10 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 558 B |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
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: {}
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,137 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 9.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
[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"
|
||||
@@ -1,14 +1,9 @@
|
||||
services:
|
||||
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
|
||||
# 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
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# by default, the MinIO container will use a volume named minio-data
|
||||
@@ -24,10 +19,12 @@ services:
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||
- MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL}
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
# by default, the MinIO container will use port 9000 to expose its API
|
||||
# and port 9001 to expose its web console
|
||||
# minio requires port to be specified when making a request to the API
|
||||
- 9000:9000
|
||||
expose:
|
||||
# port 9000 serves the S3 API (routed through the api domain defined in template.toml)
|
||||
# port 9001 serves the web console (routed through the main domain)
|
||||
- 9000
|
||||
- 9001
|
||||
|
||||
# comment the line below if you specified a local directory in the volumes section of the minio service
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "minio",
|
||||
"name": "Minio",
|
||||
"description": "MinIO is an open source object storage server compatible with Amazon S3 cloud storage service. 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.",
|
||||
"description": "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.",
|
||||
"logo": "minio.png",
|
||||
"version": "RELEASE.2026-06-18T00-00-00Z",
|
||||
"version": "latest",
|
||||
"links": {
|
||||
"github": "https://github.com/pgsty/minio",
|
||||
"website": "https://silo.pigsty.io/",
|
||||
"docs": "https://silo.pigsty.io/docs/"
|
||||
"github": "https://github.com/minio/minio",
|
||||
"website": "https://minio.io/",
|
||||
"docs": "https://docs.minio.io/"
|
||||
},
|
||||
"tags": [
|
||||
"storage"
|
||||
|
||||
@@ -5,18 +5,11 @@ 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}"
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
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:
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.4 KiB |
@@ -1,33 +0,0 @@
|
||||
[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}"
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:33.0.6
|
||||
image: nextcloud:stable
|
||||
restart: always
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
- nextcloud_redis
|
||||
|
||||
nextcloud_cron:
|
||||
image: nextcloud:33.0.6
|
||||
image: nextcloud:stable
|
||||
restart: always
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "nextcloud-aio",
|
||||
"name": "Nextcloud",
|
||||
"version": "33.0.6",
|
||||
"version": "stable",
|
||||
"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": {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
oryx:
|
||||
image: ossrs/oryx:5.15.20
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# RTMP, WebRTC and SRT are not HTTP protocols, so Traefik cannot route
|
||||
# them through the domain. They must be published directly on the host
|
||||
# (same approach as the poste.io template with its mail ports). Use the
|
||||
# server IP (not the domain) for these protocols.
|
||||
- "1935:1935" # RTMP ingest/playback (TCP)
|
||||
- "8000:8000/udp" # WebRTC media transport (UDP)
|
||||
- "10080:10080/udp" # SRT ingest/playback (UDP)
|
||||
volumes:
|
||||
- oryx-data:/data
|
||||
|
||||
volumes:
|
||||
oryx-data: {}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Oryx (SRS Stack)
|
||||
|
||||
Oryx is an all-in-one video streaming server built on [SRS](https://github.com/ossrs/srs), with a web console for managing streams, recording, forwarding, transcoding and virtual live streaming.
|
||||
|
||||
## First login
|
||||
|
||||
Open the domain assigned to this service. On the first visit Oryx asks you to **create the admin password** — set it right away, since anyone who reaches the page first can claim the instance. The password (and all other state) is stored in the `oryx-data` volume, so it survives restarts and upgrades.
|
||||
|
||||
## Publishing streams
|
||||
|
||||
The web console (Scenarios > Streaming) shows ready-to-copy URLs that include your stream secret. The streaming protocols are **not HTTP**, so Traefik cannot route them through your domain — they are published directly on the server's host ports instead. Use the **server IP address** (not the domain) for these:
|
||||
|
||||
| Protocol | URL format | Host port |
|
||||
| --- | --- | --- |
|
||||
| RTMP (OBS, ffmpeg) | `rtmp://SERVER_IP/live/livestream?secret=xxx` | `1935/tcp` |
|
||||
| SRT | `srt://SERVER_IP:10080?streamid=#!::r=live/livestream,secret=xxx,m=publish` | `10080/udp` |
|
||||
| WebRTC (WHIP) | `https://your-domain/rtc/v1/whip/?app=live&stream=livestream&secret=xxx` | signaling via domain, media via `8000/udp` |
|
||||
|
||||
Make sure your server firewall / cloud security group allows `1935/tcp`, `8000/udp` and `10080/udp`. Because these are fixed host ports, only one Oryx instance can run per server.
|
||||
|
||||
## Playback
|
||||
|
||||
HTTP-based playback goes through your domain (HTTPS via Traefik):
|
||||
|
||||
- HLS: `https://your-domain/live/livestream.m3u8`
|
||||
- HTTP-FLV: `https://your-domain/live/livestream.flv`
|
||||
- WebRTC (WHEP): available from the console's preview page; media flows over `8000/udp`.
|
||||
|
||||
If WebRTC playback or publishing connects but produces no media, verify that UDP port `8000` is reachable from the client — WebRTC media bypasses the reverse proxy entirely.
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0 0 64 32" height="32px" width="64px" id="svg2" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs id="defs6">
|
||||
<linearGradient id="linearGradient28" spreadMethod="pad" gradientTransform="matrix(148.91527,0,0,-148.91527,151.81076,224.17897)" gradientUnits="userSpaceOnUse" y2="0" x2="1" y1="0" x1="0">
|
||||
<stop id="stop24" offset="0" style="stop-opacity:1;stop-color:#3caae1"/>
|
||||
<stop id="stop26" offset="1" style="stop-opacity:1;stop-color:#b65c9e"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient48" spreadMethod="pad" gradientTransform="matrix(155.05911,0,0,-155.05911,49.431911,216.24139)" gradientUnits="userSpaceOnUse" y2="0" x2="1" y1="0" x1="0">
|
||||
<stop id="stop44" offset="0" style="stop-opacity:1;stop-color:#2ca0d4"/>
|
||||
<stop id="stop46" offset="1" style="stop-opacity:1;stop-color:#305ba7"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clipPath58" clipPathUnits="userSpaceOnUse">
|
||||
<path id="path56" d="M 0,350.158 H 350.158 V 0 H 0 Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g transform="matrix(0.172146, 0, 0, -0.188192, -9.741413, 53.288761)" id="g10" style="">
|
||||
<g id="g12" style="" transform="matrix(0.654687, 0, 0, 0.654687, 47.49966, 55.96529)">
|
||||
<g id="g14">
|
||||
<g id="g20">
|
||||
<g id="g22">
|
||||
<path id="path30" style="fill:url(#linearGradient28);stroke:none" d="m 151.811,280.659 54.062,-54.064 14.238,14.586 c 13.253,11.772 33.681,8.665 42.764,-6.48 v 0 c 6.949,-11.585 4.152,-27.659 -6.248,-35.919 v 0 c -11.647,-9.249 -27.453,-8.66 -37.707,1.501 v 0 c -8.318,8.241 -16.529,16.588 -24.814,24.861 v 0 c -13.578,13.558 -26.814,27.483 -40.842,40.56 v 0 c -0.155,0.144 -0.318,0.269 -0.474,0.412 v 0 c 1.246,-1.658 40.823,-54.57 51.701,-97.422 v 0 c 14.746,-10.175 31.762,-12.852 50.25,-8.006 v 0 c 35.557,9.319 55.709,47.52 41.276,82.815 v 0 c -9.385,22.952 -27.32,35.949 -52.141,38.356 v 0 c -15.155,1.471 -27.697,-2.422 -38.597,-9.506 v 0 l -8.305,8.306 c -6.236,6.235 -14.409,9.353 -22.581,9.353 v 0 c -8.173,0 -16.346,-3.118 -22.582,-9.353"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g32" style="" transform="matrix(0.653075, 0, 0, 0.653075, 48.56073, 55.406696)">
|
||||
<g id="g34">
|
||||
<g id="g40">
|
||||
<g id="g42">
|
||||
<path id="path50" style="fill:url(#linearGradient48);stroke:none" d="m 50.99,234 c -1.85,-8.185 -1.733,-14.379 -1.264,-20.194 v 0 c 2.822,-30.213 29.126,-54.494 59.465,-55.353 v 0 c 13.788,-0.391 25.237,3.439 35.23,9.815 v 0 l 8.671,-8.669 c 12.471,-12.471 32.691,-12.471 45.163,0 v 0 l -52.152,52.151 c -0.639,0.65 -1.262,1.284 -1.904,1.935 v 0 c -4.061,-4.127 -8.071,-8.229 -12.109,-12.304 v 0 c -7.927,-8.003 -17.355,-11.004 -28.326,-8.029 v 0 C 92.462,196.418 84.207,206.453 83.4,217.68 v 0 c -0.881,12.257 5.312,23.099 16.04,28.075 v 0 c 10.826,5.024 23.504,2.646 32.091,-5.942 v 0 c 21.291,-21.289 42.367,-42.8 63.987,-63.751 v 0 c 2.884,-2.793 5.882,-5.235 8.973,-7.368 v 0 c -16.324,63.52 -50.455,95.764 -51.701,97.422 v 0 c -12.077,11.041 -26.76,16.121 -41.224,16.121 v 0 C 84.3,282.238 57.808,264.189 50.99,234"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="g52" style="" transform="matrix(0.797304, 0, 0, 0.797304, 194.474228, 136.918259)">
|
||||
<g clip-path="url(#clipPath58)" id="g54">
|
||||
<g transform="translate(126.2779,90.4397)" id="g60">
|
||||
<path id="path62" style="fill:#494545;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 h -19.449 c -3.396,0.381 -5.093,2.049 -5.093,5.005 l 0.132,0.879 c 0.731,2.487 2.385,3.731 4.961,3.731 H 5.357 c 1.2,0 2.327,0.16 3.381,0.484 4.479,1.843 6.717,5.005 6.717,9.483 0,0.233 -0.073,0.351 -0.219,0.351 h -35.783 c -2.458,0 -5.034,-0.776 -7.727,-2.328 -4.419,-3.453 -6.629,-7.361 -6.629,-11.721 0,-2.167 0.337,-4.289 1.009,-6.366 2.752,-6.586 8.109,-9.879 16.069,-9.879 h 18.09 c 3.395,-0.352 5.092,-2.005 5.092,-4.961 L 5.226,-16.2 c -0.732,-2.488 -2.387,-3.732 -4.961,-3.732 h -24.807 c -1.199,0 -2.326,-0.175 -3.381,-0.527 -4.477,-1.815 -6.717,-4.962 -6.717,-9.439 0,-0.264 0.074,-0.396 0.22,-0.396 H 1.362 c 2.458,0 5.034,0.79 7.728,2.371 4.419,3.453 6.629,7.361 6.629,11.723 0,2.371 -0.337,4.404 -1.011,6.102 C 12.075,-3.365 7.172,0 0,0"/>
|
||||
</g>
|
||||
<g transform="translate(257.773,80.342)" id="g64">
|
||||
<path id="path66" style="fill:#494545;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -2.633,6.732 -7.536,10.098 -14.708,10.098 h -19.449 c -3.396,0.381 -5.093,2.048 -5.093,5.005 l 0.131,0.878 c 0.732,2.488 2.386,3.732 4.962,3.732 h 24.805 c 1.201,0 2.328,0.16 3.381,0.483 4.48,1.844 6.717,5.005 6.717,9.484 0,0.233 -0.072,0.35 -0.218,0.35 h -35.783 c -2.458,0 -5.035,-0.775 -7.727,-2.327 -4.419,-3.453 -6.63,-7.361 -6.63,-11.722 0,-2.167 0.337,-4.289 1.01,-6.366 2.751,-6.586 8.108,-9.879 16.069,-9.879 h 18.089 c 3.395,-0.351 5.092,-2.005 5.092,-4.961 l -0.13,-0.878 c -0.733,-2.488 -2.387,-3.731 -4.962,-3.731 H -39.25 c -1.2,0 -2.327,-0.176 -3.381,-0.527 -4.477,-1.816 -6.717,-4.962 -6.717,-9.44 0,-0.263 0.073,-0.395 0.22,-0.395 h 35.781 c 2.459,0 5.035,0.79 7.728,2.371 4.419,3.453 6.63,7.361 6.63,11.722 C 1.011,-3.731 0.674,-1.698 0,0"/>
|
||||
</g>
|
||||
<g transform="translate(160.6578,100.5822)" id="g68">
|
||||
<path id="path70" style="fill:#494545;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 h 7.033 v 5.847 c 0,1.153 0.634,2.204 1.654,2.741 1.021,0.537 2.245,0.467 3.195,-0.185 L 20.445,2.54 C 21.284,1.965 21.787,1.017 21.791,0 h 2.004 c 2.986,0 4.77,-1.713 5.356,-5.138 L 29.02,-5.839 c -0.674,-3.015 -2.416,-4.523 -5.225,-4.523 H 0 Z m 23.531,-20.108 c 2.518,0 5.182,0.834 7.991,2.503 4.594,3.306 6.894,7.507 6.894,12.6 0,4.126 -1.626,7.815 -4.874,11.064 -3.103,2.545 -5.956,3.82 -8.562,3.82 H -9.528 l -0.22,-0.265 v -49.217 l 0.133,-0.13 c 2.048,0 3.832,0.482 5.356,1.448 2.838,2.107 4.259,4.317 4.259,6.63 v 20.722 l 26.648,-26.518 c 1.288,-1.99 3.66,-2.986 7.114,-2.986 3.891,0.235 5.84,0.908 5.84,2.02 v 0.22 c 0,0.146 -5.944,6.176 -17.826,18.089 z"/>
|
||||
</g>
|
||||
<g transform="translate(180.5738,101.9846)" id="g72">
|
||||
<path id="path74" style="fill:#494545;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 -8.563,5.863 c -0.323,0.222 -0.688,0.333 -1.055,0.333 -0.299,0 -0.598,-0.072 -0.878,-0.22 -0.627,-0.33 -1,-0.95 -1,-1.659 V -7.41 c 0,-0.708 0.373,-1.327 1,-1.657 0.627,-0.332 1.349,-0.289 1.933,0.111 L 0,-3.092 c 0.518,0.354 0.815,0.918 0.815,1.545 C 0.815,-0.918 0.518,-0.354 0,0"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": "oryx",
|
||||
"name": "Oryx",
|
||||
"version": "5.15.20",
|
||||
"description": "Oryx (formerly SRS Stack) is an all-in-one, open-source video streaming server with a web console. It ingests RTMP, WebRTC (WHIP) and SRT streams and plays them back over HLS, HTTP-FLV and WebRTC (WHEP), with built-in recording, forwarding, transcoding and virtual live streaming.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/ossrs/oryx",
|
||||
"website": "https://ossrs.io/",
|
||||
"docs": "https://ossrs.io/lts/en-us/docs/v6/doc/getting-started-oryx",
|
||||
"docker": "https://hub.docker.com/r/ossrs/oryx"
|
||||
},
|
||||
"tags": [
|
||||
"streaming",
|
||||
"video",
|
||||
"rtmp",
|
||||
"webrtc",
|
||||
"srt",
|
||||
"hls",
|
||||
"media-server"
|
||||
]
|
||||
}
|
||||
@@ -2,12 +2,7 @@
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"MAIL_LIMIT=50",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "mailcatcher-ng"
|
||||
port = 1_080
|
||||
serviceName = "oryx"
|
||||
port = 2022
|
||||
host = "${main_domain}"
|
||||
@@ -1,646 +0,0 @@
|
||||
# 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:
|
||||
@@ -1,27 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,73 +0,0 @@
|
||||
[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'
|
||||
"""
|
||||
@@ -1,76 +0,0 @@
|
||||
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:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,28 +0,0 @@
|
||||
[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"
|
||||
@@ -1,13 +0,0 @@
|
||||
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:
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -1,13 +0,0 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "steedos"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
STEEDOS_HOST = "${main_domain}"
|
||||
@@ -1,61 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,51 +0,0 @@
|
||||
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:
|
||||
@@ -1,39 +0,0 @@
|
||||
## 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).
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,93 +0,0 @@
|
||||
[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
|
||||
"""
|
||||
@@ -1,32 +0,0 @@
|
||||
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:
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
[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" = ""
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,54 +0,0 @@
|
||||
# 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).
|
||||
@@ -1,66 +0,0 @@
|
||||
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:
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
[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 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -1,27 +1,6 @@
|
||||
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
|
||||
@@ -31,7 +10,6 @@ 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);
|
||||
|
||||
@@ -9,8 +9,7 @@ env = [
|
||||
"WORDPRESS_DEBUG=0",
|
||||
"DB_NAME=${db_name}",
|
||||
"DB_USER=${db_user}",
|
||||
"DB_PASSWORD=${db_password}",
|
||||
"WP_DOMAIN=${main_domain}"
|
||||
"DB_PASSWORD=${db_password}"
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
|
||||