Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6245f84dc | ||
|
|
a3f2655baa | ||
|
|
13d03f9a92 | ||
|
|
51922e71b6 | ||
|
|
3a6f9d709a | ||
|
|
0beb68a228 | ||
|
|
7d252ead5c | ||
|
|
fe94f4ce0b | ||
|
|
3eb1d3b7bd | ||
|
|
15db93df3b | ||
|
|
cc3b93518b | ||
|
|
e7285cea05 | ||
|
|
a80a073389 | ||
|
|
77b078faf3 | ||
|
|
31f95474d6 | ||
|
|
fabecbe728 | ||
|
|
4159d3a9f0 | ||
|
|
abe36ad330 | ||
|
|
89505f0b54 | ||
|
|
981811f55f | ||
|
|
ffeaca14af | ||
|
|
77145ad581 | ||
|
|
c60bfa8c72 | ||
|
|
dd986039a7 | ||
|
|
0633b987d5 | ||
|
|
5d6afe20a6 | ||
|
|
618cda2366 | ||
|
|
74cec8f74f | ||
|
|
64fb0f6573 | ||
|
|
9e02e96e3a | ||
|
|
59e323483c | ||
|
|
33c3fcdadb | ||
|
|
493e824f70 | ||
|
|
6f27a9427c | ||
|
|
f76c31ef89 | ||
|
|
478c5197b2 | ||
|
|
8c15db8a2c | ||
|
|
a837539398 | ||
|
|
c33f72b32a | ||
|
|
1c2921417d | ||
|
|
6f1b083c57 | ||
|
|
bd457e0310 | ||
|
|
311faa1ea9 | ||
|
|
ee35ed1a42 | ||
|
|
7f3a08eb00 | ||
|
|
a266e36a4d | ||
|
|
3ddbdde081 | ||
|
|
0bfb50d62f | ||
|
|
62850c3696 | ||
|
|
d1ef9333a3 | ||
|
|
1c2dea39c3 | ||
|
|
b23ddd0b98 | ||
|
|
7683947842 | ||
|
|
03a60bb417 | ||
|
|
b7c2d6c35d | ||
|
|
f036ddae92 | ||
|
|
030d9d1089 | ||
|
|
af16250561 | ||
|
|
f946051a6c | ||
|
|
9bdbe266e5 | ||
|
|
25563213f2 | ||
|
|
fb64197738 | ||
|
|
cd16441901 | ||
|
|
29a7970397 | ||
|
|
1e486813b4 | ||
|
|
3c416eaba3 | ||
|
|
31158e2340 | ||
|
|
6786237720 | ||
|
|
8f84a52855 | ||
|
|
c6a0b1b1b5 | ||
|
|
744ff52770 | ||
|
|
55aa82489d | ||
|
|
59770d4ae8 |
@@ -14,6 +14,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
|
||||
import { Label } from "./ui/label";
|
||||
import { Clipboard } from "lucide-react";
|
||||
import { Input } from "./ui/input";
|
||||
import Markdown from "./ui/markdown";
|
||||
|
||||
interface Template {
|
||||
id: string;
|
||||
@@ -32,6 +33,7 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateDialogProps {
|
||||
@@ -169,8 +171,23 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs defaultValue="docker-compose" className="w-full">
|
||||
<Tabs
|
||||
defaultValue={
|
||||
templateFiles?.instructions
|
||||
? "instructions"
|
||||
: "docker-compose"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="w-full justify-start">
|
||||
{templateFiles?.instructions && (
|
||||
<TabsTrigger
|
||||
value="instructions"
|
||||
className="data-[state=active]:font-bold"
|
||||
>
|
||||
Instructions
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger
|
||||
value="docker-compose"
|
||||
className="data-[state=active]:font-bold"
|
||||
@@ -184,6 +201,24 @@ const TemplateDialog: React.FC<TemplateDialogProps> = ({
|
||||
Configuration
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{templateFiles?.instructions && (
|
||||
<TabsContent value="instructions" className="mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="flex flex-col items-start w-fit justify-start gap-1">
|
||||
<span className="leading-tight text-xl font-semibold">
|
||||
Setup Instructions
|
||||
</span>
|
||||
<span className="leading-tight text-sm text-gray-500">
|
||||
instructions.md
|
||||
</span>
|
||||
</Label>
|
||||
<div className="rounded-md border p-4">
|
||||
<Markdown content={templateFiles.instructions} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
<TabsContent value="docker-compose" className="mt-4">
|
||||
{templateFiles?.dockerCompose && (
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Template {
|
||||
interface TemplateFiles {
|
||||
dockerCompose: string | null;
|
||||
config: string | null;
|
||||
instructions: string | null;
|
||||
}
|
||||
|
||||
interface TemplateGridProps {
|
||||
@@ -80,20 +81,34 @@ const TemplateGrid: React.FC<TemplateGridProps> = ({ view }) => {
|
||||
const fetchTemplateFiles = async (templateId: string) => {
|
||||
setModalLoading(true);
|
||||
try {
|
||||
const [dockerComposeRes, configRes] = await Promise.all([
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
]);
|
||||
const [dockerComposeRes, configRes, instructionsRes] = await Promise.all(
|
||||
[
|
||||
fetch(`/blueprints/${templateId}/docker-compose.yml`),
|
||||
fetch(`/blueprints/${templateId}/template.toml`),
|
||||
fetch(`/blueprints/${templateId}/instructions.md`),
|
||||
]
|
||||
);
|
||||
|
||||
const dockerCompose = dockerComposeRes.ok
|
||||
? await dockerComposeRes.text()
|
||||
: null;
|
||||
const config = configRes.ok ? await configRes.text() : null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config });
|
||||
// Guard against SPA fallbacks that return index.html with a 200 status
|
||||
// for templates that don't have an instructions.md file.
|
||||
const instructionsIsMarkdown =
|
||||
instructionsRes.ok &&
|
||||
!(instructionsRes.headers.get("content-type") || "").includes(
|
||||
"text/html"
|
||||
);
|
||||
const instructions = instructionsIsMarkdown
|
||||
? await instructionsRes.text()
|
||||
: null;
|
||||
|
||||
setTemplateFiles({ dockerCompose, config, instructions });
|
||||
} catch (err) {
|
||||
console.error("Error fetching template files:", err);
|
||||
setTemplateFiles({ dockerCompose: null, config: null });
|
||||
setTemplateFiles({ dockerCompose: null, config: null, instructions: null });
|
||||
} finally {
|
||||
setModalLoading(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import React from "react";
|
||||
|
||||
// Minimal markdown renderer (no external dependencies) for the per-template
|
||||
// instructions.md files. Supports headings, paragraphs, fenced code blocks,
|
||||
// ordered/unordered lists, and inline code / bold / links.
|
||||
|
||||
const INLINE_PATTERN =
|
||||
/(`[^`]+`)|(\*\*[^*]+\*\*)|(\[[^\]]+\]\([^)\s]+\))/g;
|
||||
|
||||
const renderInline = (text: string): React.ReactNode[] => {
|
||||
const nodes: React.ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
let key = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
INLINE_PATTERN.lastIndex = 0;
|
||||
|
||||
while ((match = INLINE_PATTERN.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
nodes.push(text.slice(lastIndex, match.index));
|
||||
}
|
||||
const token = match[0];
|
||||
if (token.startsWith("`")) {
|
||||
nodes.push(
|
||||
<code
|
||||
key={key++}
|
||||
className="px-1.5 py-0.5 rounded bg-muted font-mono text-[0.85em]"
|
||||
>
|
||||
{token.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
} else if (token.startsWith("**")) {
|
||||
nodes.push(<strong key={key++}>{token.slice(2, -2)}</strong>);
|
||||
} else {
|
||||
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(token);
|
||||
if (link) {
|
||||
nodes.push(
|
||||
<a
|
||||
key={key++}
|
||||
href={link[2]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 dark:text-blue-400 underline underline-offset-2"
|
||||
>
|
||||
{link[1]}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
nodes.push(token);
|
||||
}
|
||||
}
|
||||
lastIndex = match.index + token.length;
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
nodes.push(text.slice(lastIndex));
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
const HEADING_CLASSES: Record<number, string> = {
|
||||
1: "text-2xl font-bold mt-2",
|
||||
2: "text-xl font-semibold mt-6 border-b pb-1",
|
||||
3: "text-lg font-semibold mt-4",
|
||||
4: "text-base font-semibold mt-3",
|
||||
};
|
||||
|
||||
interface MarkdownProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const Markdown: React.FC<MarkdownProps> = ({ content }) => {
|
||||
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
||||
const blocks: React.ReactNode[] = [];
|
||||
let key = 0;
|
||||
let i = 0;
|
||||
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
|
||||
// Blank line
|
||||
if (line.trim() === "") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fenced code block
|
||||
if (line.trim().startsWith("```")) {
|
||||
const code: string[] = [];
|
||||
i++;
|
||||
while (i < lines.length && !lines[i].trim().startsWith("```")) {
|
||||
code.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
i++; // skip closing fence
|
||||
blocks.push(
|
||||
<pre
|
||||
key={key++}
|
||||
className="bg-muted rounded-md p-4 overflow-x-auto text-sm font-mono whitespace-pre"
|
||||
>
|
||||
<code>{code.join("\n")}</code>
|
||||
</pre>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Heading
|
||||
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
||||
if (heading) {
|
||||
const level = heading[1].length;
|
||||
const Tag = `h${Math.min(level + 1, 6)}` as keyof React.JSX.IntrinsicElements;
|
||||
blocks.push(
|
||||
<Tag key={key++} className={HEADING_CLASSES[level]}>
|
||||
{renderInline(heading[2])}
|
||||
</Tag>
|
||||
);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unordered list
|
||||
if (/^[-*]\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^[-*]\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ul key={key++} className="list-disc pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ordered list
|
||||
if (/^\d+\.\s+/.test(line)) {
|
||||
const items: string[] = [];
|
||||
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
||||
items.push(lines[i].replace(/^\d+\.\s+/, ""));
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<ol key={key++} className="list-decimal pl-6 space-y-1">
|
||||
{items.map((item, idx) => (
|
||||
<li key={idx}>{renderInline(item)}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Paragraph: consume consecutive plain lines
|
||||
const paragraph: string[] = [line];
|
||||
i++;
|
||||
while (
|
||||
i < lines.length &&
|
||||
lines[i].trim() !== "" &&
|
||||
!/^(#{1,4})\s+/.test(lines[i]) &&
|
||||
!/^[-*]\s+/.test(lines[i]) &&
|
||||
!/^\d+\.\s+/.test(lines[i]) &&
|
||||
!lines[i].trim().startsWith("```")
|
||||
) {
|
||||
paragraph.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
blocks.push(
|
||||
<p key={key++} className="leading-relaxed">
|
||||
{renderInline(paragraph.join(" "))}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="space-y-3 text-sm">{blocks}</div>;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
## Instructions
|
||||
|
||||
We don't have nothing to show here....
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
affinepro:
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3010
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -19,16 +20,21 @@ services:
|
||||
- MAILER_PASSWORD=${MAILER_PASSWORD}
|
||||
- MAILER_SENDER=${MAILER_SENDER}
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
migration:
|
||||
condition: service_completed_successfully
|
||||
|
||||
migration:
|
||||
image: ghcr.io/toeverything/affine-graphql:stable-780dd83
|
||||
command: node ./scripts/self-host-predeploy.js
|
||||
image: ghcr.io/toeverything/affine:stable
|
||||
command: ["sh", "-c", "node ./scripts/self-host-predeploy.js"]
|
||||
environment:
|
||||
- REDIS_SERVER_HOST=redis
|
||||
- REDIS_SERVER_PASSWORD=${REDIS_PASSWORD}
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/affinepro
|
||||
- AFFINE_INDEXER_ENABLED=false
|
||||
- AFFINE_SERVER_HOST=${DOMAIN}
|
||||
- MAILER_HOST=${MAILER_HOST}
|
||||
- MAILER_PORT=${MAILER_PORT}
|
||||
@@ -39,17 +45,25 @@ services:
|
||||
- affine-storage:/root/.affine/storage
|
||||
- affine-config:/root/.affine/config
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
image: pgvector/pgvector:pg16
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=affinepro
|
||||
- POSTGRES_INITDB_ARGS=--data-checksums
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "affinepro"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
@@ -57,9 +71,14 @@ services:
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a '${REDIS_PASSWORD}' ping | grep PONG"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
affine-storage: {}
|
||||
affine-config: {}
|
||||
postgres-data: {}
|
||||
redis-data: {}
|
||||
redis-data: {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "affinepro",
|
||||
"name": "Affine Pro",
|
||||
"version": "stable-780dd83",
|
||||
"version": "stable",
|
||||
"description": "Affine Pro is a modern, self-hosted platform designed for collaborative content creation and project management. It offers an intuitive interface, seamless real-time collaboration, and powerful tools for organizing tasks, notes, and ideas.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
automatisch:
|
||||
image: dockeriddonuts/automatisch:2.0
|
||||
image: automatischio/automatisch:0.15.0
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 3000
|
||||
environment:
|
||||
- HOST=${DOMAIN}
|
||||
- PROTOCOL=http
|
||||
- PORT=3000
|
||||
- API_URL=http://${DOMAIN}
|
||||
- WEB_APP_URL=http://${DOMAIN}
|
||||
- APP_ENV=production
|
||||
- REDIS_HOST=automatisch-redis
|
||||
- REDIS_USERNAME=default
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- POSTGRES_HOST=automatisch-postgres
|
||||
- POSTGRES_DATABASE=automatisch
|
||||
@@ -23,11 +21,13 @@ services:
|
||||
volumes:
|
||||
- storage:/automatisch/storage
|
||||
depends_on:
|
||||
- automatisch-postgres
|
||||
- automatisch-redis
|
||||
automatisch-postgres:
|
||||
condition: service_healthy
|
||||
automatisch-redis:
|
||||
condition: service_started
|
||||
|
||||
automatisch-worker:
|
||||
image: dockeriddonuts/automatisch:2.0
|
||||
image: automatischio/automatisch:0.15.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_ENV=production
|
||||
@@ -44,8 +44,7 @@ services:
|
||||
volumes:
|
||||
- storage:/automatisch/storage
|
||||
depends_on:
|
||||
- automatisch-postgres
|
||||
- automatisch-redis
|
||||
- automatisch
|
||||
|
||||
automatisch-postgres:
|
||||
image: postgres:15-alpine
|
||||
@@ -56,18 +55,20 @@ services:
|
||||
- POSTGRES_DB=automatisch
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d automatisch"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
automatisch-redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
environment:
|
||||
- REDIS_USERNAME=default
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
storage: {}
|
||||
postgres_data: {}
|
||||
redis_data: {}
|
||||
redis_data: {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "automatisch",
|
||||
"name": "Automatisch",
|
||||
"version": "2.0",
|
||||
"version": "0.15.0",
|
||||
"description": "Automatisch is a powerful, self-hosted workflow automation tool designed for connecting your apps and automating repetitive tasks. With Automatisch, you can create workflows to sync data, send notifications, and perform various actions seamlessly across different services.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
|
||||
@@ -13,6 +13,7 @@ port = 3000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
DOMAIN = "${main_domain}"
|
||||
DB_PASSWORD = "${db_password}"
|
||||
REDIS_PASSWORD = "${redis_password}"
|
||||
ENCRYPTION_KEY = "${encryption_key}"
|
||||
|
||||
|
After Width: | Height: | Size: 72 KiB |
@@ -0,0 +1,84 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
bonfire:
|
||||
image: bonfirenetworks/bonfire:1.0.5-social-amd64
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
search:
|
||||
condition: service_started
|
||||
environment:
|
||||
# Instance
|
||||
- HOSTNAME=${BONFIRE_HOSTNAME}
|
||||
- PUBLIC_PORT=${PUBLIC_PORT:-80}
|
||||
- SERVER_PORT=4000
|
||||
- APP_NAME=${APP_NAME:-Bonfire}
|
||||
- LANG=en_US.UTF-8
|
||||
# Secrets
|
||||
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||
- SIGNING_SALT=${SIGNING_SALT}
|
||||
- ENCRYPTION_SALT=${ENCRYPTION_SALT}
|
||||
- RELEASE_COOKIE=${RELEASE_COOKIE}
|
||||
# Database (migrations run automatically on startup)
|
||||
- POSTGRES_HOST=db
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=bonfire_db
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
# Must stay false for the initial migrations; can be set to true
|
||||
# after the first deployment to speed up future upgrades
|
||||
- DB_MIGRATE_INDEXES_CONCURRENTLY=${DB_MIGRATE_INDEXES_CONCURRENTLY:-false}
|
||||
# Search (Meilisearch)
|
||||
- SEARCH_ADAPTER=meili
|
||||
- SEARCH_MEILI_INSTANCE=http://search:7700
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
# Email (the first account to sign up becomes admin and needs no
|
||||
# confirmation email; configure a mail backend for further signups)
|
||||
- MAIL_BACKEND=${MAIL_BACKEND:-none}
|
||||
- MAIL_SERVER=${MAIL_SERVER:-}
|
||||
- MAIL_PORT=${MAIL_PORT:-}
|
||||
- MAIL_USER=${MAIL_USER:-}
|
||||
- MAIL_PASSWORD=${MAIL_PASSWORD:-}
|
||||
- MAIL_DOMAIN=${MAIL_DOMAIN:-}
|
||||
- MAIL_FROM=${MAIL_FROM:-}
|
||||
- MAIL_KEY=${MAIL_KEY:-}
|
||||
# Max upload size in megabytes
|
||||
- UPLOAD_LIMIT=${UPLOAD_LIMIT:-20}
|
||||
volumes:
|
||||
- bonfire-uploads:/opt/app/data/uploads
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4000"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
|
||||
db:
|
||||
image: postgis/postgis:17-3.5-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=bonfire_db
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d bonfire_db"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
search:
|
||||
image: getmeili/meilisearch:v1.11
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||
- MEILI_ENV=production
|
||||
- MEILI_NO_ANALYTICS=true
|
||||
volumes:
|
||||
- search-data:/meili_data
|
||||
|
||||
volumes:
|
||||
bonfire-uploads:
|
||||
db-data:
|
||||
search-data:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "bonfire",
|
||||
"name": "Bonfire",
|
||||
"version": "1.0.5",
|
||||
"description": "Federated social networking toolkit for communities: customizable digital spaces with ActivityPub federation, flexible boundaries and moderation tools.",
|
||||
"logo": "bonfire.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/bonfire-networks/bonfire-app",
|
||||
"website": "https://bonfirenetworks.org",
|
||||
"docs": "https://docs.bonfirenetworks.org"
|
||||
},
|
||||
"tags": [
|
||||
"social",
|
||||
"fediverse",
|
||||
"activitypub"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key_base = "${password:64}"
|
||||
signing_salt = "${password:32}"
|
||||
encryption_salt = "${password:32}"
|
||||
release_cookie = "${password:32}"
|
||||
postgres_password = "${password:32}"
|
||||
meili_master_key = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "bonfire"
|
||||
port = 4000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
# Domain of your Bonfire instance (must match the domain above)
|
||||
"BONFIRE_HOSTNAME" = "${main_domain}"
|
||||
# Port used to build public URLs: keep 80 while the domain is plain HTTP,
|
||||
# and change to 443 once you enable HTTPS on the domain
|
||||
# (HTTPS is required for federation with other fediverse instances)
|
||||
"PUBLIC_PORT" = "80"
|
||||
# Display name of your instance
|
||||
"APP_NAME" = "Bonfire"
|
||||
# Max upload size in megabytes
|
||||
"UPLOAD_LIMIT" = "20"
|
||||
|
||||
# Secrets (auto-generated)
|
||||
"SECRET_KEY_BASE" = "${secret_key_base}"
|
||||
"SIGNING_SALT" = "${signing_salt}"
|
||||
"ENCRYPTION_SALT" = "${encryption_salt}"
|
||||
"RELEASE_COOKIE" = "${release_cookie}"
|
||||
"POSTGRES_PASSWORD" = "${postgres_password}"
|
||||
"MEILI_MASTER_KEY" = "${meili_master_key}"
|
||||
|
||||
# Optional: set to true only AFTER the first deployment finished its initial
|
||||
# database migrations (speeds up index creation during future upgrades)
|
||||
# "DB_MIGRATE_INDEXES_CONCURRENTLY" = "true"
|
||||
|
||||
# --- Email ---
|
||||
# The first account to sign up becomes the instance admin and does not need
|
||||
# a confirmation email. Further signups are invite-only by default (the admin
|
||||
# can create invite links or open up signups in the instance settings), and
|
||||
# require a working mail backend to confirm their email address.
|
||||
# Supported backends include smtp, mailgun, brevo, postmark, ses and more:
|
||||
# https://docs.bonfirenetworks.org/deploy.html
|
||||
# "MAIL_BACKEND" = "smtp"
|
||||
# "MAIL_SERVER" = "smtp.example.com"
|
||||
# "MAIL_PORT" = "587"
|
||||
# "MAIL_USER" = "postmaster@example.com"
|
||||
# "MAIL_PASSWORD" = ""
|
||||
# "MAIL_DOMAIN" = "example.com"
|
||||
# "MAIL_FROM" = "bonfire@example.com"
|
||||
# For API-based backends (e.g. mailgun) set the API key instead:
|
||||
# "MAIL_KEY" = ""
|
||||
@@ -0,0 +1,33 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
certmate:
|
||||
image: fabriziosalmi/certmate:2.21.3
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- SECRET_KEY=${SECRET_KEY} # 🔐 Flask session secret
|
||||
- API_BEARER_TOKEN=${API_BEARER_TOKEN} # 🔐 API / web UI token
|
||||
- BEHIND_PROXY=${BEHIND_PROXY}
|
||||
- CLOUDFLARE_TOKEN=${CLOUDFLARE_TOKEN}
|
||||
- LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
ports:
|
||||
- 8000
|
||||
volumes:
|
||||
- certmate_certificates:/app/certificates
|
||||
- certmate_data:/app/data
|
||||
- certmate_logs:/app/logs
|
||||
- certmate_backups:/app/backups
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
volumes:
|
||||
certmate_certificates:
|
||||
certmate_data:
|
||||
certmate_logs:
|
||||
certmate_backups:
|
||||
@@ -0,0 +1,13 @@
|
||||
# CertMate
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Deploy the template and open the app domain.
|
||||
2. Log in with the **API bearer token**: it is auto-generated by the template and stored in the `API_BEARER_TOKEN` environment variable (Dokploy → your service → Environment). The same token authenticates REST API requests (`Authorization: Bearer <token>`).
|
||||
3. In **Settings**, set your Let's Encrypt email and add credentials for your DNS provider (Cloudflare, Route53, etc.) to issue certificates through DNS-01 challenges.
|
||||
|
||||
## Notes
|
||||
|
||||
- `CLOUDFLARE_TOKEN` can optionally be set as an environment variable instead of configuring it in the UI.
|
||||
- Certificates, application data and backups are persisted in the `certmate_certificates`, `certmate_data` and `certmate_backups` volumes.
|
||||
- API documentation is available at `/docs/` (Swagger) and `/redoc/` on your domain.
|
||||
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "certmate",
|
||||
"name": "CertMate",
|
||||
"version": "2.21.3",
|
||||
"description": "CertMate is an SSL certificate management system with a web UI and REST API. It automates issuing and renewing Let's Encrypt certificates via DNS-01 challenges across 20+ DNS providers, with unified backups and multi-account support.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/fabriziosalmi/certmate",
|
||||
"website": "https://www.certmate.org/",
|
||||
"docs": "https://www.certmate.org/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"ssl",
|
||||
"certificates",
|
||||
"security",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
SECRET_KEY = "${password:32}"
|
||||
API_BEARER_TOKEN = "${password:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "certmate"
|
||||
port = 8000
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SECRET_KEY = "${SECRET_KEY}" # 🔐 Flask session secret
|
||||
API_BEARER_TOKEN = "${API_BEARER_TOKEN}" # 🔐 API / web UI token
|
||||
BEHIND_PROXY = "true"
|
||||
CLOUDFLARE_TOKEN = ""
|
||||
LETSENCRYPT_EMAIL = ""
|
||||
LOG_LEVEL = "INFO"
|
||||
@@ -11,8 +11,8 @@ services:
|
||||
- COCKPIT_DATABASE_SERVER=mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@mongo:27017
|
||||
- COCKPIT_DATABASE_NAME=cockpit
|
||||
volumes:
|
||||
- html:/var/www/html
|
||||
- data:/var/www/html/storage/data
|
||||
- config:/var/www/html/config
|
||||
- storage:/var/www/html/storage
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
@@ -25,6 +25,6 @@ services:
|
||||
- mongo-data:/data/db
|
||||
|
||||
volumes:
|
||||
html:
|
||||
data:
|
||||
config:
|
||||
storage:
|
||||
mongo-data:
|
||||
@@ -0,0 +1,25 @@
|
||||
# Access the Dagu Web UI at the assigned domain.
|
||||
# Sign in with the basic auth credentials from your .env file (default user: admin).
|
||||
# Place your DAG definitions (YAML) in the dags directory via the Web UI or the API.
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
dagu:
|
||||
image: ghcr.io/dagucloud/dagu:2.10.7
|
||||
command: dagu start-all
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 8080
|
||||
volumes:
|
||||
- dagu-data:/var/lib/dagu
|
||||
environment:
|
||||
# Dagu binds to 127.0.0.1 by default; 0.0.0.0 is required to reach it
|
||||
- DAGU_HOST=0.0.0.0
|
||||
- DAGU_PORT=8080
|
||||
- DAGU_AUTH_MODE=basic
|
||||
- DAGU_AUTH_BASIC_USERNAME=${ADMIN_USERNAME}
|
||||
- DAGU_AUTH_BASIC_PASSWORD=${ADMIN_PASSWORD}
|
||||
|
||||
volumes:
|
||||
dagu-data: {}
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "dagu",
|
||||
"name": "Dagu",
|
||||
"version": "2.10.7",
|
||||
"description": "Dagu is a self-contained workflow engine with a Web UI. Schedule and orchestrate jobs as DAGs defined in declarative YAML, with no DBMS required.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/dagucloud/dagu",
|
||||
"website": "https://dagu.sh",
|
||||
"docs": "https://docs.dagu.sh"
|
||||
},
|
||||
"tags": [
|
||||
"workflow",
|
||||
"automation",
|
||||
"scheduler",
|
||||
"cron",
|
||||
"devops"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "dagu"
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
ADMIN_USERNAME = "admin"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_287_1020)">
|
||||
<path d="M50.1522 115.189C50.1522 121.189 57.1564 121.193 59 118C60.1547 116 61 114 61 108C61 102 58.1044 96.9536 55.3194 91.5175C54.6026 90.1184 53.8323 88.6149 53.0128 86.9234C51.6073 84.0225 49.8868 81.3469 47.3885 79.2139C47.0053 78.8867 46.8935 78.0093 46.9624 77.422C47.2351 75.1036 47.5317 72.7876 47.8283 70.4718C48.3186 66.6436 48.8088 62.8156 49.1909 58.9766C49.459 56.2872 49.4542 53.5119 49.1156 50.8329C48.3833 45.0344 45.1292 40.7783 40.1351 37.9114C38.6818 37.0771 38.2533 36.1455 38.4347 34.5853C38.9402 30.2473 40.6551 26.3306 42.8342 22.6642C44.8356 19.297 47.1037 16.0858 49.3676 12.8804C49.6576 12.4699 49.9475 12.0594 50.2367 11.6488C50.6069 11.1231 51.5231 10.7245 52.1971 10.7075C60.4129 10.5017 68.6303 10.3648 76.8477 10.2636C77.4123 10.2563 78.1584 10.5196 78.5221 10.9246C83.6483 16.634 88.2284 22.712 90.9778 29.9784C91.1658 30.4758 91.3221 30.9869 91.4655 31.4997C92.4976 35.1683 92.4804 35.1803 89.5401 37.2499L89.4071 37.3436C83.8702 41.2433 81.8458 46.8198 82.0921 53.349C82.374 60.8552 84.0622 68.1313 85.9869 75.3539C86.3782 76.8218 86.6318 77.9073 85.2206 79.2609C82.3951 81.9698 81.2196 85.6872 80.6575 89.4687C80.0724 93.4081 79.599 97.3637 79.1254 101.32C78.8627 103.515 78.8497 105.368 78.318 107.904C76.2819 117.611 71 128 63 128H50.1522C45 128 41 123.189 41 115.189H50.1522Z" fill="#155DFC"/>
|
||||
<path d="M46.2429 6.56033C43.3387 11.1 40.3642 15.4031 37.7614 19.9209C35.413 23.9964 33.8487 28.4226 33.0913 33.1211C32.0998 39.2728 33.694 44.7189 38.0765 48.9775C41.6846 52.4835 42.6153 56.4472 42.152 61.1675C41.1426 71.4587 39.1174 81.5401 36.2052 91.4522C36.1769 91.5477 36.0886 91.6255 35.8974 91.8977C34.1517 91.3525 32.3161 90.8446 30.5266 90.2095C5.53011 81.3376 -12.7225 64.953 -24.1842 41.0298C-25.175 38.9625 -26.079 36.8498 -26.9263 34.7202C-27.0875 34.3151 -26.9749 33.5294 -26.6785 33.2531C-17.1479 24.3723 -7.64007 15.4647 2.00468 6.70938C8.64568 0.681612 16.5812 -1.21558 25.2457 0.739942C31.9378 2.24992 38.5131 4.27834 45.1363 6.09048C45.5843 6.2128 45.9998 6.45502 46.2429 6.56033Z" fill="#155DFC"/>
|
||||
<path d="M96.9586 89.3257C95.5888 84.7456 94.0796 80.4011 93.0111 75.9514C91.6065 70.0978 90.4683 64.1753 89.3739 58.2529C88.755 54.9056 89.3998 51.8176 91.89 49.2108C98.2669 42.5358 98.3933 34.7971 95.3312 26.7037C92.7471 19.8739 88.593 13.9904 83.7026 8.60904C83.1298 7.9788 82.5693 7.33641 81.918 6.60491C82.2874 6.40239 82.5709 6.18773 82.8909 6.07999C90.1281 3.64085 97.4495 1.54842 105.041 0.488845C112.781 -0.591795 119.379 1.81818 125.045 6.97592C130.017 11.5018 134.805 16.2327 139.812 20.7188C143.822 24.3115 148.013 27.7066 152.19 31.1073C152.945 31.7205 153.137 32.2154 152.913 33.1041C149.059 48.4591 141.312 61.4883 129.457 71.9877C120.113 80.2626 109.35 85.9785 96.9586 89.3265V89.3257Z" fill="#155DFC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_287_1020">
|
||||
<rect width="128" height="128" rx="6" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
databasus:
|
||||
image: databasus/databasus:latest
|
||||
ports:
|
||||
- "4005"
|
||||
volumes:
|
||||
# Persistent data storage
|
||||
- databasus-data:/databasus-data
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Optional: Set timezone
|
||||
- TZ=UTC
|
||||
healthcheck:
|
||||
test: [ "CMD", "databasus", "healthcheck" ]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
databasus-data:
|
||||
driver: local
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "databasus",
|
||||
"name": "Databasus",
|
||||
"version": "latest",
|
||||
"description": "Free, open source and self-hosted tool to backup PostgreSQL, MySQL, MariaDB and MongoDB. Multiple storages (S3, Google Drive, FTP, etc.), notifications and Point-in-Time Recovery. Successor of Postgresus",
|
||||
"logo": "databasus.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/databasus/databasus",
|
||||
"website": "https://databasus.com",
|
||||
"docs": "https://databasus.com/installation"
|
||||
},
|
||||
"tags": [
|
||||
"postgres",
|
||||
"backup",
|
||||
"s3"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = []
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "databasus"
|
||||
port = 4005
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,256 @@
|
||||
# Dify - open-source LLM app development platform
|
||||
# Adapted from the official docker/docker-compose.yaml (tag 1.15.0).
|
||||
# Minimal functional stack: api + worker + worker_beat + web + postgres
|
||||
# (pgvector, reused as the vector store) + redis + sandbox + ssrf_proxy
|
||||
# + plugin_daemon, fronted by a lightweight internal nginx gateway that
|
||||
# mirrors the upstream path routing (TLS terminates at Traefik).
|
||||
|
||||
x-shared-api-env: &shared-api-env
|
||||
LOG_LEVEL: INFO
|
||||
DEPLOY_ENV: PRODUCTION
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
MIGRATION_ENABLED: "true"
|
||||
# Public URLs - a single domain, path-routed like the upstream nginx
|
||||
CONSOLE_API_URL: https://${DIFY_DOMAIN}
|
||||
CONSOLE_WEB_URL: https://${DIFY_DOMAIN}
|
||||
SERVICE_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_WEB_URL: https://${DIFY_DOMAIN}
|
||||
FILES_URL: https://${DIFY_DOMAIN}
|
||||
INTERNAL_FILES_URL: http://api:5001
|
||||
TRIGGER_URL: https://${DIFY_DOMAIN}
|
||||
ENDPOINT_URL_TEMPLATE: https://${DIFY_DOMAIN}/e/{hook_id}
|
||||
# The dedicated api_websocket collaboration service is not deployed
|
||||
ENABLE_COLLABORATION_MODE: "false"
|
||||
WEB_API_CORS_ALLOW_ORIGINS: "*"
|
||||
CONSOLE_CORS_ALLOW_ORIGINS: "*"
|
||||
# PostgreSQL
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_HOST: db
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE: dify
|
||||
# Redis / Celery
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
REDIS_DB: "0"
|
||||
CELERY_BROKER_URL: redis://:${REDIS_PASSWORD}@redis:6379/1
|
||||
# File storage on a local volume
|
||||
STORAGE_TYPE: opendal
|
||||
OPENDAL_SCHEME: fs
|
||||
OPENDAL_FS_ROOT: storage
|
||||
# Vector store: pgvector, reusing the same PostgreSQL instance
|
||||
VECTOR_STORE: pgvector
|
||||
PGVECTOR_HOST: db
|
||||
PGVECTOR_PORT: "5432"
|
||||
PGVECTOR_USER: postgres
|
||||
PGVECTOR_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
PGVECTOR_DATABASE: dify
|
||||
PGVECTOR_MIN_CONNECTION: "1"
|
||||
PGVECTOR_MAX_CONNECTION: "5"
|
||||
# Code execution sandbox
|
||||
CODE_EXECUTION_ENDPOINT: http://sandbox:8194
|
||||
CODE_EXECUTION_API_KEY: ${SANDBOX_API_KEY}
|
||||
# SSRF proxy (squid) for user-triggered outbound requests
|
||||
SSRF_PROXY_HTTP_URL: http://ssrf_proxy:3128
|
||||
SSRF_PROXY_HTTPS_URL: http://ssrf_proxy:3128
|
||||
# Plugin daemon (model providers / tools are plugins since Dify 1.0)
|
||||
PLUGIN_DAEMON_URL: http://plugin_daemon:5002
|
||||
PLUGIN_DAEMON_KEY: ${PLUGIN_DAEMON_KEY}
|
||||
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_INNER_API_KEY}
|
||||
PLUGIN_MAX_PACKAGE_SIZE: "52428800"
|
||||
PLUGIN_REMOTE_INSTALL_HOST: localhost
|
||||
PLUGIN_REMOTE_INSTALL_PORT: "5003"
|
||||
MARKETPLACE_ENABLED: "true"
|
||||
MARKETPLACE_API_URL: https://marketplace.dify.ai
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ../files/dify-nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
web:
|
||||
condition: service_started
|
||||
plugin_daemon:
|
||||
condition: service_started
|
||||
|
||||
# The dify-api image runs as the non-root user 1001, so the shared
|
||||
# storage volume has to be handed over to it once (same trick as the
|
||||
# upstream init_permissions container).
|
||||
init_permissions:
|
||||
image: busybox:1.36
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
FLAG_FILE="/app/api/storage/.init_permissions"
|
||||
if [ -f "$$FLAG_FILE" ]; then
|
||||
echo "Permissions already initialized."
|
||||
exit 0
|
||||
fi
|
||||
echo "Initializing permissions for /app/api/storage"
|
||||
chown -R 1001:1001 /app/api/storage && touch "$$FLAG_FILE"
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
restart: "no"
|
||||
|
||||
api:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: api
|
||||
depends_on:
|
||||
init_permissions:
|
||||
condition: service_completed_successfully
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5001/health"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 120s
|
||||
|
||||
worker:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: worker
|
||||
depends_on:
|
||||
init_permissions:
|
||||
condition: service_completed_successfully
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- dify-app-storage:/app/api/storage
|
||||
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
<<: *shared-api-env
|
||||
MODE: beat
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
web:
|
||||
image: langgenius/dify-web:1.15.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: https://${DIFY_DOMAIN}
|
||||
APP_API_URL: https://${DIFY_DOMAIN}
|
||||
SERVER_CONSOLE_API_URL: http://api:5001
|
||||
ENABLE_COLLABORATION_MODE: "false"
|
||||
MARKETPLACE_API_URL: https://marketplace.dify.ai
|
||||
MARKETPLACE_URL: https://marketplace.dify.ai
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg16
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: dify
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
volumes:
|
||||
- dify-db-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d dify"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
restart: always
|
||||
environment:
|
||||
REDISCLI_AUTH: ${REDIS_PASSWORD}
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- dify-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
|
||||
sandbox:
|
||||
image: langgenius/dify-sandbox:0.2.15
|
||||
restart: always
|
||||
environment:
|
||||
API_KEY: ${SANDBOX_API_KEY}
|
||||
GIN_MODE: release
|
||||
WORKER_TIMEOUT: "15"
|
||||
ENABLE_NETWORK: "true"
|
||||
HTTP_PROXY: http://ssrf_proxy:3128
|
||||
HTTPS_PROXY: http://ssrf_proxy:3128
|
||||
SANDBOX_PORT: "8194"
|
||||
volumes:
|
||||
- dify-sandbox-deps:/dependencies
|
||||
|
||||
ssrf_proxy:
|
||||
image: ubuntu/squid:latest
|
||||
restart: always
|
||||
volumes:
|
||||
- ../files/dify-squid.conf:/etc/squid/squid.conf:ro
|
||||
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.6.3-local
|
||||
restart: always
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_PORT: "5432"
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_DATABASE: dify_plugin
|
||||
DB_SSL_MODE: disable
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
SERVER_PORT: "5002"
|
||||
SERVER_KEY: ${PLUGIN_DAEMON_KEY}
|
||||
MAX_PLUGIN_PACKAGE_SIZE: "52428800"
|
||||
DIFY_INNER_API_URL: http://api:5001
|
||||
DIFY_INNER_API_KEY: ${PLUGIN_INNER_API_KEY}
|
||||
PLUGIN_REMOTE_INSTALLING_HOST: 0.0.0.0
|
||||
PLUGIN_REMOTE_INSTALLING_PORT: "5003"
|
||||
PLUGIN_WORKING_PATH: /app/storage/cwd
|
||||
FORCE_VERIFYING_SIGNATURE: "true"
|
||||
PYTHON_ENV_INIT_TIMEOUT: "120"
|
||||
PLUGIN_MAX_EXECUTION_TIMEOUT: "600"
|
||||
PLUGIN_STORAGE_TYPE: local
|
||||
PLUGIN_STORAGE_LOCAL_ROOT: /app/storage
|
||||
PLUGIN_INSTALLED_PATH: plugin
|
||||
PLUGIN_PACKAGE_CACHE_PATH: plugin_packages
|
||||
PLUGIN_MEDIA_CACHE_PATH: assets
|
||||
volumes:
|
||||
- dify-plugin-storage:/app/storage
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
dify-app-storage:
|
||||
dify-db-data:
|
||||
dify-redis-data:
|
||||
dify-sandbox-deps:
|
||||
dify-plugin-storage:
|
||||
@@ -0,0 +1,15 @@
|
||||
## Instructions
|
||||
|
||||
### First setup
|
||||
|
||||
- The first boot runs the database migrations, so it can take **2-4 minutes** before the application responds.
|
||||
- Open your domain: you will be redirected to `/install`, where you create the admin (workspace owner) account.
|
||||
- After signing in, go to **Settings -> Model Provider** and install a model provider plugin (OpenAI, Anthropic, Ollama, etc.) from the marketplace, then configure your API keys.
|
||||
|
||||
### Notes
|
||||
|
||||
- **HTTPS is required for the console login**: Dify issues `__Host-`/`Secure` session cookies, so make sure the domain has a certificate (e.g. Let's Encrypt in the domain settings). Over plain HTTP the browser will drop the session cookies and login will not persist.
|
||||
|
||||
- The vector store is **pgvector**, running inside the bundled PostgreSQL instance (no separate Weaviate/Qdrant container needed).
|
||||
- All routing (`/console/api`, `/api`, `/v1`, `/files`, `/e/`, ...) is handled by the bundled internal nginx gateway, mirroring the upstream deployment, so the Dify service API is available at `https://<your-domain>/v1`.
|
||||
- Code execution runs in the isolated `sandbox` service, and user-triggered outbound HTTP requests go through the `ssrf_proxy` (squid) service, like in the official deployment.
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="48" height="22" viewBox="0 0 48 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="White=False">
|
||||
<g id="if">
|
||||
<path d="M21.2002 3.73454C22.5633 3.73454 23.0666 2.89917 23.0666 1.86812C23.0666 0.837081 22.5623 0.00170898 21.2002 0.00170898C19.838 0.00170898 19.3337 0.837081 19.3337 1.86812C19.3337 2.89917 19.838 3.73454 21.2002 3.73454Z" fill="#0033FF"/>
|
||||
<path d="M27.7336 4.13435V5.33473H24.6668V8.00171H27.7336V14.6687H22.6668V5.33567H15.9998V8.00265H19.7336V14.6696H15.3337V17.3366H35.3337V14.6696H30.6668V8.00265H35.3337V5.33567H30.6668V2.66869H35.3337V0.00170898H31.8671C29.5877 0.00170898 27.7336 1.8559 27.7336 4.13529V4.13435Z" fill="#0033FF"/>
|
||||
</g>
|
||||
<g id="Dy">
|
||||
<path d="M5.66698 0.000940576H0V17.334H5.66698C12.667 17.334 14.667 13.334 14.667 8.66698C14.667 4 12.667 0 5.66698 0V0.000940576ZM5.73377 14.6679H3.20038V2.66792H5.73377C9.75823 2.66792 11.4666 4.64346 11.4666 8.66792C11.4666 12.6924 9.75823 14.6679 5.73377 14.6679Z" fill="black"/>
|
||||
<path d="M44.8335 5.3349L42.1665 14.0019L39.4995 5.3349H36.333L40.2013 16.5466C40.604 17.714 39.9229 18.6679 38.6886 18.6679H37.333V21.3349H39.3255C41.063 21.3349 42.6265 20.2361 43.2145 18.6011L48 5.3349H44.8335Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "dify",
|
||||
"name": "Dify",
|
||||
"version": "1.15.0",
|
||||
"description": "Dify is an open-source LLM app development platform. Build AI workflows, RAG pipelines, agents and chatbots with a visual interface and publish them as APIs or web apps.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/langgenius/dify",
|
||||
"website": "https://dify.ai/",
|
||||
"docs": "https://docs.dify.ai/"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"llm",
|
||||
"rag"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${base64:42}"
|
||||
postgres_password = "${password:32}"
|
||||
redis_password = "${password:32}"
|
||||
sandbox_api_key = "${password:32}"
|
||||
plugin_daemon_key = "${base64:42}"
|
||||
plugin_inner_api_key = "${base64:42}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"DIFY_DOMAIN=${main_domain}",
|
||||
"SECRET_KEY=${secret_key}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"REDIS_PASSWORD=${redis_password}",
|
||||
"SANDBOX_API_KEY=${sandbox_api_key}",
|
||||
"PLUGIN_DAEMON_KEY=${plugin_daemon_key}",
|
||||
"PLUGIN_INNER_API_KEY=${plugin_inner_api_key}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "nginx"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
# Internal nginx gateway replicating the upstream Dify nginx routing
|
||||
# (TLS terminates at Traefik, which forwards everything to this nginx).
|
||||
[[config.mounts]]
|
||||
filePath = "dify-nginx.conf"
|
||||
content = '''
|
||||
map $http_x_forwarded_proto $dify_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 100M;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
|
||||
location /console/api { proxy_pass http://api:5001; }
|
||||
location /api { proxy_pass http://api:5001; }
|
||||
location /v1 { proxy_pass http://api:5001; }
|
||||
location /openapi { proxy_pass http://api:5001; }
|
||||
location /files { proxy_pass http://api:5001; }
|
||||
location /mcp { proxy_pass http://api:5001; }
|
||||
location /triggers { proxy_pass http://api:5001; }
|
||||
location /explore { proxy_pass http://web:3000; }
|
||||
|
||||
location /e/ {
|
||||
proxy_pass http://plugin_daemon:5002;
|
||||
# nginx drops inherited proxy_set_header directives as soon as a
|
||||
# location defines its own, so the shared ones are repeated here.
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $dify_forwarded_proto;
|
||||
proxy_set_header Connection "";
|
||||
proxy_set_header Dify-Hook-Url $dify_forwarded_proto://$host$request_uri;
|
||||
}
|
||||
|
||||
location / { proxy_pass http://web:3000; }
|
||||
}
|
||||
'''
|
||||
|
||||
# Squid SSRF proxy configuration, rendered from the upstream
|
||||
# docker/ssrf_proxy/squid.conf.template with its default values.
|
||||
[[config.mounts]]
|
||||
filePath = "dify-squid.conf"
|
||||
content = '''
|
||||
acl client_localnet src 0.0.0.1-0.255.255.255
|
||||
acl client_localnet src 10.0.0.0/8
|
||||
acl client_localnet src 100.64.0.0/10
|
||||
acl client_localnet src 169.254.0.0/16
|
||||
acl client_localnet src 172.16.0.0/12
|
||||
acl client_localnet src 192.168.0.0/16
|
||||
acl client_localnet src fc00::/7
|
||||
acl client_localnet src fe80::/10
|
||||
acl to_private_networks dst 0.0.0.0/8
|
||||
acl to_private_networks dst 10.0.0.0/8
|
||||
acl to_private_networks dst 100.64.0.0/10
|
||||
acl to_private_networks dst 127.0.0.0/8
|
||||
acl to_private_networks dst 169.254.0.0/16
|
||||
acl to_private_networks dst 172.16.0.0/12
|
||||
acl to_private_networks dst 192.168.0.0/16
|
||||
acl to_private_networks dst 224.0.0.0/4
|
||||
acl to_private_networks dst 240.0.0.0/4
|
||||
acl to_private_networks dst ::/128
|
||||
acl to_private_networks dst ::1/128
|
||||
acl to_private_networks dst ::ffff:0:0/96
|
||||
acl to_private_networks dst ::/96
|
||||
acl to_private_networks dst fc00::/7
|
||||
acl to_private_networks dst fe80::/10
|
||||
acl SSL_ports port 443
|
||||
acl Safe_ports port 80
|
||||
acl Safe_ports port 21
|
||||
acl Safe_ports port 443
|
||||
acl Safe_ports port 70
|
||||
acl Safe_ports port 210
|
||||
acl Safe_ports port 1025-65535
|
||||
acl Safe_ports port 280
|
||||
acl Safe_ports port 488
|
||||
acl Safe_ports port 591
|
||||
acl Safe_ports port 777
|
||||
acl CONNECT method CONNECT
|
||||
acl allowed_domains dstdomain .marketplace.dify.ai
|
||||
|
||||
http_port 3128
|
||||
|
||||
http_access deny !Safe_ports
|
||||
http_access deny CONNECT !SSL_ports
|
||||
http_access allow localhost manager
|
||||
http_access deny manager
|
||||
http_access deny to_private_networks
|
||||
http_access allow allowed_domains
|
||||
http_access allow client_localnet
|
||||
http_access allow localhost
|
||||
http_access deny all
|
||||
tcp_outgoing_address 0.0.0.0
|
||||
|
||||
coredump_dir /var/spool/squid
|
||||
refresh_pattern ^ftp: 1440 20% 10080
|
||||
refresh_pattern ^gopher: 1440 0% 1440
|
||||
refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
|
||||
refresh_pattern . 0 20% 4320
|
||||
|
||||
client_request_buffer_max_size 100 MB
|
||||
max_filedescriptors 65536
|
||||
|
||||
connect_timeout 30 seconds
|
||||
request_timeout 2 minutes
|
||||
read_timeout 2 minutes
|
||||
client_lifetime 5 minutes
|
||||
shutdown_lifetime 30 seconds
|
||||
|
||||
server_persistent_connections on
|
||||
client_persistent_connections on
|
||||
persistent_request_timeout 30 seconds
|
||||
pconn_timeout 1 minute
|
||||
|
||||
client_db on
|
||||
server_idle_pconn_timeout 2 minutes
|
||||
client_idle_pconn_timeout 2 minutes
|
||||
|
||||
quick_abort_min 16 KB
|
||||
quick_abort_max 16 MB
|
||||
quick_abort_pct 95
|
||||
|
||||
memory_cache_mode disk
|
||||
cache_mem 256 MB
|
||||
maximum_object_size_in_memory 512 KB
|
||||
|
||||
dns_timeout 30 seconds
|
||||
dns_retransmit_interval 5 seconds
|
||||
|
||||
logformat dify_log %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
|
||||
access_log daemon:/var/log/squid/access.log dify_log
|
||||
logfile_rotate 10
|
||||
'''
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
evolution-api:
|
||||
image: evoapicloud/evolution-api:latest
|
||||
image: evoapicloud/evolution-api:v2.3.7
|
||||
restart: always
|
||||
volumes:
|
||||
- evolution-instances:/evolution/instances
|
||||
@@ -29,6 +29,13 @@ services:
|
||||
- CACHE_REDIS_URI=${CACHE_REDIS_URI}
|
||||
- CACHE_REDIS_PREFIX_KEY=${CACHE_REDIS_PREFIX_KEY}
|
||||
- CACHE_REDIS_SAVE_INSTANCES=${CACHE_REDIS_SAVE_INSTANCES}
|
||||
- TYPEBOT_ENABLED=${TYPEBOT_ENABLED}
|
||||
- TYPEBOT_API_VERSION=${TYPEBOT_API_VERSION}
|
||||
- CHATWOOT_ENABLED=${CHATWOOT_ENABLED}
|
||||
- OPENAI_ENABLED=${OPENAI_ENABLED}
|
||||
- DIFY_ENABLED=${DIFY_ENABLED}
|
||||
- N8N_ENABLED=${N8N_ENABLED}
|
||||
- EVOAI_ENABLED=${EVOAI_ENABLED}
|
||||
|
||||
evolution-postgres:
|
||||
image: postgres:16-alpine
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "evolutionapi",
|
||||
"name": "Evolution API",
|
||||
"version": "v2.1.2",
|
||||
"version": "v2.3.7",
|
||||
"description": "Evolution API is a robust platform dedicated to empowering small businesses with limited resources, going beyond a simple messaging solution via WhatsApp.",
|
||||
"logo": "evolutionapi.png",
|
||||
"links": {
|
||||
|
||||
@@ -31,6 +31,13 @@ env = [
|
||||
"CACHE_REDIS_URI=redis://evolution-redis:6379",
|
||||
"CACHE_REDIS_PREFIX_KEY=evolution",
|
||||
"CACHE_REDIS_SAVE_INSTANCES=true",
|
||||
"TYPEBOT_ENABLED=true",
|
||||
"TYPEBOT_API_VERSION=latest",
|
||||
"CHATWOOT_ENABLED=true",
|
||||
"OPENAI_ENABLED=true",
|
||||
"DIFY_ENABLED=true",
|
||||
"N8N_ENABLED=true",
|
||||
"EVOAI_ENABLED=true",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
x-custom-image: &custom_image
|
||||
image: ${IMAGE_NAME:-ghcr.io/frappe/lms}:${VERSION:-v2.52.0}
|
||||
pull_policy: ${PULL_POLICY:-always}
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
|
||||
services:
|
||||
backend:
|
||||
<<: *custom_image
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
until [[ -f "sites/${SITE_NAME}/.create-site-done" ]]; do
|
||||
echo "Waiting for site ${SITE_NAME} to be created (first boot takes a few minutes)";
|
||||
sleep 5;
|
||||
done;
|
||||
exec /home/frappe/frappe-bench/env/bin/gunicorn --chdir=/home/frappe/frappe-bench/sites --bind=0.0.0.0:8000 --threads=4 --workers=2 --worker-class=gthread --worker-tmp-dir=/dev/shm --timeout=120 --preload frappe.app:application
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "127.0.0.1:8000"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 600s
|
||||
|
||||
frontend:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- nginx-entrypoint.sh
|
||||
depends_on:
|
||||
backend:
|
||||
condition: service_started
|
||||
websocket:
|
||||
condition: service_started
|
||||
environment:
|
||||
BACKEND: backend:8000
|
||||
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
|
||||
SOCKETIO: websocket:9000
|
||||
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
|
||||
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
|
||||
UPSTREAM_REAL_IP_RECURSIVE: "off"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "127.0.0.1:8080"]
|
||||
interval: 2s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
|
||||
queue-default:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- default
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
queue-long:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- long
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
queue-short:
|
||||
<<: *custom_image
|
||||
command:
|
||||
- bench
|
||||
- worker
|
||||
- --queue
|
||||
- short
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
scheduler:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "redis-queue:6379"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- bench
|
||||
- schedule
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
websocket:
|
||||
<<: *custom_image
|
||||
healthcheck:
|
||||
test: ["CMD", "wait-for-it", "websocket:9000"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
command:
|
||||
- node
|
||||
- /home/frappe/frappe-bench/apps/frappe/socketio.js
|
||||
depends_on:
|
||||
configurator:
|
||||
condition: service_completed_successfully
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
configurator:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CONFIGURE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
echo "[configurator] starting";
|
||||
if [[ $${REGENERATE_APPS_TXT} == "1" ]]; then
|
||||
echo "[configurator] regenerating sites/apps.txt";
|
||||
ls -1 apps > sites/apps.txt;
|
||||
fi;
|
||||
existing_db_host=$$(grep -hs '^' sites/common_site_config.json | jq -r ".db_host // empty" 2>/dev/null || true);
|
||||
if [[ -n "$${existing_db_host}" ]]; then
|
||||
echo "[configurator] common_site_config.json already configured, skipping";
|
||||
exit 0;
|
||||
fi;
|
||||
echo "[configurator] applying bench global config";
|
||||
bench set-config -g db_host $$DB_HOST;
|
||||
bench set-config -gp db_port $$DB_PORT;
|
||||
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
|
||||
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
|
||||
bench set-config -g redis_socketio "redis://$$REDIS_SOCKETIO";
|
||||
bench set-config -gp socketio_port $$SOCKETIO_PORT;
|
||||
echo "[configurator] done";
|
||||
environment:
|
||||
DB_HOST: "${DB_HOST:-db}"
|
||||
DB_PORT: "3306"
|
||||
REDIS_CACHE: redis-cache:6379
|
||||
REDIS_QUEUE: redis-queue:6379
|
||||
REDIS_SOCKETIO: redis-socketio:6379
|
||||
SOCKETIO_PORT: "9000"
|
||||
REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
create-site:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${CREATE_SITE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
wait-for-it -t 300 $${DB_HOST}:$${DB_PORT};
|
||||
wait-for-it -t 120 redis-cache:6379;
|
||||
wait-for-it -t 120 redis-queue:6379;
|
||||
export start=`date +%s`;
|
||||
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
|
||||
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
|
||||
do
|
||||
echo "Waiting for sites/common_site_config.json to be created";
|
||||
sleep 5;
|
||||
if (( `date +%s`-start > 120 )); then
|
||||
echo "could not find sites/common_site_config.json with required keys";
|
||||
exit 1
|
||||
fi
|
||||
done;
|
||||
echo "sites/common_site_config.json found";
|
||||
[[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && touch sites/${SITE_NAME}/.create-site-done && exit 0;
|
||||
sed -i "s/^required_apps.*/required_apps = []/" apps/lms/lms/hooks.py;
|
||||
bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME} || exit 1;
|
||||
if [[ $${ENABLE_SCHEDULER} == "1" ]]; then
|
||||
bench --site $${SITE_NAME} enable-scheduler;
|
||||
else
|
||||
echo "Skipping scheduler enable";
|
||||
fi;
|
||||
bench --site $${SITE_NAME} clear-cache;
|
||||
touch sites/$${SITE_NAME}/.create-site-done;
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
environment:
|
||||
SITE_NAME: ${SITE_NAME}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
|
||||
DB_HOST: ${DB_HOST:-db}
|
||||
DB_PORT: "${DB_PORT:-3306}"
|
||||
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
|
||||
ENABLE_SCHEDULER: "${ENABLE_SCHEDULER}"
|
||||
|
||||
migration:
|
||||
<<: *custom_image
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${MIGRATE:-0}
|
||||
restart_policy:
|
||||
condition: none
|
||||
entrypoint: ["bash", "-c"]
|
||||
command:
|
||||
- >
|
||||
curl -f -H "Host: ${SITE_NAME}" http://frontend:8080/api/method/ping || { echo "Site not reachable yet, skipping migration"; exit 0; };
|
||||
bench --site all set-config -p maintenance_mode 1;
|
||||
bench --site all set-config -p pause_scheduler 1;
|
||||
bench --site all migrate;
|
||||
bench --site all set-config -p maintenance_mode 0;
|
||||
bench --site all set-config -p pause_scheduler 0;
|
||||
volumes:
|
||||
- sites:/home/frappe/frappe-bench/sites
|
||||
|
||||
db:
|
||||
image: mariadb:${DB_VERSION:-11.8.6}
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: ${ENABLE_DB:-0}
|
||||
restart_policy:
|
||||
condition: always
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
start_period: 120s
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --skip-character-set-client-handshake
|
||||
environment:
|
||||
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
|
||||
redis-cache:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-cache-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-queue:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-queue-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
redis-socketio:
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: always
|
||||
image: redis:6.2-alpine
|
||||
volumes:
|
||||
- redis-socketio-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
redis-cache-data:
|
||||
redis-queue-data:
|
||||
redis-socketio-data:
|
||||
sites:
|
||||
driver_opts:
|
||||
type: "${SITE_VOLUME_TYPE}"
|
||||
o: "${SITE_VOLUME_OPTS}"
|
||||
device: "${SITE_VOLUME_DEV}"
|
||||
@@ -0,0 +1,17 @@
|
||||
# Frappe LMS (Frappe Learning)
|
||||
|
||||
## Initial setup
|
||||
|
||||
The first deployment creates the Frappe site and installs the LMS app, which can take **3-6 minutes** after all containers are up. The site is ready when the domain returns the Frappe login page.
|
||||
|
||||
## Default credentials
|
||||
|
||||
- **Username:** `Administrator`
|
||||
- **Password:** the value of the `ADMIN_PASSWORD` environment variable (auto-generated, check the Environment tab of the service)
|
||||
|
||||
After logging in, the learning portal is available at `/lms` and the Frappe admin backend at `/app`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The template pins `ghcr.io/frappe/lms:v2.52.0`, the latest upstream image that ships the LMS app (newer upstream tags are currently published without the app baked in).
|
||||
- The upstream image does not include the `payments` app, so paid-course checkout features are unavailable. Free courses, batches, quizzes and certifications work normally.
|
||||
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "frappe-lms",
|
||||
"name": "Frappe LMS",
|
||||
"version": "v2.52.0",
|
||||
"description": "Easy to use, open source learning management system to structure courses and reach out to learners, built on the Frappe Framework.",
|
||||
"logo": "lms-logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/frappe/lms",
|
||||
"docs": "https://docs.frappe.io/learning",
|
||||
"website": "https://frappe.io/learning"
|
||||
},
|
||||
"tags": [
|
||||
"lms",
|
||||
"learning",
|
||||
"education",
|
||||
"courses",
|
||||
"frappe"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
db_root_password = "${password:32}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "frontend"
|
||||
port = 8_080
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SITE_NAME = "${main_domain}"
|
||||
ADMIN_PASSWORD = "${admin_password}"
|
||||
DB_ROOT_PASSWORD = "${db_root_password}"
|
||||
MIGRATE = "1"
|
||||
ENABLE_DB = "1"
|
||||
DB_HOST = "db"
|
||||
CREATE_SITE = "1"
|
||||
CONFIGURE = "1"
|
||||
REGENERATE_APPS_TXT = "1"
|
||||
INSTALL_APP_ARGS = "--install-app lms"
|
||||
ENABLE_SCHEDULER = "1"
|
||||
IMAGE_NAME = "ghcr.io/frappe/lms"
|
||||
VERSION = "v2.52.0"
|
||||
FRAPPE_SITE_NAME_HEADER = ""
|
||||
DB_VERSION = "11.8.6"
|
||||
SITE_VOLUME_TYPE = ""
|
||||
SITE_VOLUME_OPTS = ""
|
||||
SITE_VOLUME_DEV = ""
|
||||
@@ -1,65 +1,24 @@
|
||||
services:
|
||||
gitlab:
|
||||
image: gitlab/gitlab-ce:latest
|
||||
image: gitlab/gitlab-ce:19.1.2-ce.0
|
||||
restart: unless-stopped
|
||||
hostname: gitlab.example.com
|
||||
shm_size: "256m"
|
||||
environment:
|
||||
GITLAB_OMNIBUS_CONFIG: |
|
||||
external_url 'http://${GITLAB_HOST}'
|
||||
gitlab_rails['gitlab_ssh_host'] = '${GITLAB_HOST}'
|
||||
gitlab_rails['gitlab_shell_ssh_port'] = 2224
|
||||
gitlab_rails['db_adapter'] = 'postgresql'
|
||||
gitlab_rails['db_host'] = 'postgresql'
|
||||
gitlab_rails['db_port'] = '5432'
|
||||
gitlab_rails['db_database'] = '${POSTGRES_DB}'
|
||||
gitlab_rails['db_username'] = '${POSTGRES_USER}'
|
||||
gitlab_rails['db_password'] = '${POSTGRES_PASSWORD}'
|
||||
# Redis config for external TCP connection
|
||||
gitlab_rails['redis_url'] = 'redis://redis:6379/0'
|
||||
gitlab_rails['redis_host'] = 'redis'
|
||||
gitlab_rails['redis_port'] = 6379
|
||||
gitlab_rails['redis_socket'] = nil
|
||||
gitlab_rails['gitlab_email_enabled'] = false
|
||||
gitlab_rails['gitlab_default_can_create_group'] = true
|
||||
gitlab_rails['gitlab_username_changing_enabled'] = false
|
||||
unicorn['worker_processes'] = 2
|
||||
unicorn['worker_timeout'] = 60
|
||||
postgresql['enable'] = false
|
||||
redis['enable'] = false
|
||||
nginx['enable'] = true
|
||||
nginx['listen_port'] = 80
|
||||
nginx['listen_https'] = false
|
||||
gitlab_rails['initial_root_password'] = '${GITLAB_ROOT_PASSWORD}'
|
||||
gitlab_rails['gitlab_email_enabled'] = false
|
||||
puma['worker_processes'] = 0
|
||||
sidekiq['concurrency'] = 10
|
||||
prometheus_monitoring['enable'] = false
|
||||
ports:
|
||||
- "80"
|
||||
- "2224"
|
||||
volumes:
|
||||
- gitlab_config:/etc/gitlab
|
||||
- gitlab_logs:/var/log/gitlab
|
||||
- gitlab_data:/var/opt/gitlab
|
||||
depends_on:
|
||||
- postgresql
|
||||
- redis
|
||||
|
||||
postgresql:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgresql_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
gitlab_config:
|
||||
gitlab_logs:
|
||||
gitlab_data:
|
||||
postgresql_data:
|
||||
redis_data:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "gitlab-ce",
|
||||
"name": "GitLab CE",
|
||||
"version": "latest",
|
||||
"version": "19.1.2",
|
||||
"description": "GitLab Community Edition is a free and open source platform for managing Git repositories, CI/CD pipelines, and project management.",
|
||||
"logo": "gitlab-ce.svg",
|
||||
"links": {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_db = "gitlab"
|
||||
postgres_user = "gitlab"
|
||||
postgres_password = "${password:32}"
|
||||
gitlab_root_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"GITLAB_HOST=${main_domain}",
|
||||
"POSTGRES_DB=${postgres_db}",
|
||||
"POSTGRES_USER=${postgres_user}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"GITLAB_ROOT_PASSWORD=${gitlab_root_password}",
|
||||
]
|
||||
|
||||
[[config.domains]]
|
||||
|
||||
@@ -4,8 +4,11 @@ services:
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ../homarr/appdata:/appdata
|
||||
- homarr_appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
|
||||
ports:
|
||||
- 7575
|
||||
|
||||
volumes:
|
||||
homarr_appdata:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:64}"
|
||||
secret_key = "${hash:64}"
|
||||
|
||||
[config]
|
||||
env = ["SECRET_ENCRYPTION_KEY=${secret_key}"]
|
||||
|
||||
@@ -2,10 +2,10 @@ version: "3.9"
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
image: ghcr.io/immich-app/immich-server:v2.1.0
|
||||
image: ghcr.io/immich-app/immich-server:v3.0.2
|
||||
|
||||
volumes:
|
||||
- immich-library:/usr/src/app/upload
|
||||
- immich-library:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
immich-redis:
|
||||
@@ -13,9 +13,6 @@ services:
|
||||
immich-database:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PORT: 2283
|
||||
SERVER_URL: ${SERVER_URL}
|
||||
FRONT_BASE_URL: ${FRONT_BASE_URL}
|
||||
# Database Configuration
|
||||
DB_HOSTNAME: ${DB_HOSTNAME}
|
||||
DB_PORT: ${DB_PORT}
|
||||
@@ -30,70 +27,45 @@ services:
|
||||
TZ: ${TZ}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:2283/server-info/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
disable: false
|
||||
|
||||
immich-machine-learning:
|
||||
image: ghcr.io/immich-app/immich-machine-learning:v2.1.0
|
||||
image: ghcr.io/immich-app/immich-machine-learning:v3.0.2
|
||||
|
||||
volumes:
|
||||
- immich-model-cache:/cache
|
||||
environment:
|
||||
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
|
||||
REDIS_PORT: ${REDIS_PORT}
|
||||
REDIS_DBINDEX: ${REDIS_DBINDEX}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3003/ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
disable: false
|
||||
|
||||
immich-redis:
|
||||
image: redis:6.2-alpine
|
||||
image: valkey/valkey:9
|
||||
|
||||
volumes:
|
||||
- immich-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
test: redis-cli ping || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
immich-database:
|
||||
image: tensorchord/pgvecto-rs:pg14-v0.3.0
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
|
||||
volumes:
|
||||
- immich-postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: immich
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||
shm_size: 128mb
|
||||
healthcheck:
|
||||
test: pg_isready -U ${DB_USERNAME} -d immich || exit 1
|
||||
test: pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE_NAME} || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command:
|
||||
[
|
||||
'postgres',
|
||||
'-c',
|
||||
'shared_preload_libraries=vectors.so',
|
||||
'-c',
|
||||
'search_path="$$user", public, vectors',
|
||||
'-c',
|
||||
'logging_collector=on',
|
||||
'-c',
|
||||
'max_wal_size=2GB',
|
||||
'-c',
|
||||
'shared_buffers=512MB',
|
||||
'-c',
|
||||
'wal_compression=on',
|
||||
]
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "immich",
|
||||
"name": "Immich",
|
||||
"version": "v1.121.0",
|
||||
"version": "v3.0.2",
|
||||
"description": "High performance self-hosted photo and video backup solution directly from your mobile phone.",
|
||||
"logo": "immich.svg",
|
||||
"links": {
|
||||
|
||||
@@ -5,9 +5,6 @@ db_user = "immich"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"IMMICH_HOST=${main_domain}",
|
||||
"SERVER_URL=https://${main_domain}",
|
||||
"FRONT_BASE_URL=https://${main_domain}",
|
||||
"DB_HOSTNAME=immich-database",
|
||||
"DB_PORT=5432",
|
||||
"DB_USERNAME=${db_user}",
|
||||
|
||||
@@ -1,58 +1,29 @@
|
||||
services:
|
||||
db-migration:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
image: infisical/infisical:v0.135.0-postgres
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
command: npm run migration:latest
|
||||
pull_policy: always
|
||||
|
||||
backend:
|
||||
image: infisical/infisical:v0.162.6
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
db-migration:
|
||||
condition: service_completed_successfully
|
||||
image: infisical/infisical:v0.135.0-postgres
|
||||
pull_policy: always
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
- AUTH_SECRET=${AUTH_SECRET}
|
||||
- SITE_URL=${SITE_URL}
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_PORT=${SMTP_PORT}
|
||||
- SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS}
|
||||
- SMTP_FROM_NAME=${SMTP_FROM_NAME}
|
||||
- SMTP_USERNAME=${SMTP_USERNAME}
|
||||
- SMTP_PASSWORD=${SMTP_PASSWORD}
|
||||
|
||||
redis:
|
||||
image: redis:7.4.1
|
||||
env_file: .env
|
||||
restart: always
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
|
||||
volumes:
|
||||
- redis_infisical_data:/data
|
||||
|
||||
@@ -60,12 +31,11 @@ services:
|
||||
image: postgres:14-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_DB
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- pg_infisical_data:/var/lib/postgresql/data
|
||||
|
||||
healthcheck:
|
||||
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
|
||||
interval: 5s
|
||||
@@ -75,6 +45,3 @@ services:
|
||||
volumes:
|
||||
pg_infisical_data:
|
||||
redis_infisical_data:
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "infisical",
|
||||
"name": "Infisical",
|
||||
"version": "0.135.0",
|
||||
"version": "0.162.6",
|
||||
"description": "All-in-one platform to securely manage application configuration and secrets across your team and infrastructure.",
|
||||
"logo": "infisical.jpg",
|
||||
"links": {
|
||||
|
||||
@@ -3,56 +3,27 @@ main_domain = "${domain}"
|
||||
postgres_password = "${password}"
|
||||
postgres_user = "infisical"
|
||||
postgres_db = "infisical"
|
||||
encryption_key = "${hash:32}"
|
||||
auth_secret = "${base64:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218",
|
||||
"AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=",
|
||||
"ENCRYPTION_KEY=${encryption_key}",
|
||||
"AUTH_SECRET=${auth_secret}",
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"POSTGRES_USER=${postgres_user}",
|
||||
"POSTGRES_DB=${postgres_db}",
|
||||
"SITE_URL=http://${main_domain}:8080",
|
||||
"SITE_URL=http://${main_domain}",
|
||||
"SMTP_HOST=",
|
||||
"SMTP_PORT=",
|
||||
"SMTP_NAME=",
|
||||
"SMTP_PORT=587",
|
||||
"SMTP_FROM_ADDRESS=",
|
||||
"SMTP_FROM_NAME=Infisical",
|
||||
"SMTP_USERNAME=",
|
||||
"SMTP_PASSWORD=",
|
||||
"CLIENT_ID_HEROKU=",
|
||||
"CLIENT_ID_VERCEL=",
|
||||
"CLIENT_ID_NETLIFY=",
|
||||
"CLIENT_ID_GITHUB=",
|
||||
"CLIENT_ID_GITHUB_APP=",
|
||||
"CLIENT_SLUG_GITHUB_APP=",
|
||||
"CLIENT_ID_GITLAB=",
|
||||
"CLIENT_ID_BITBUCKET=",
|
||||
"CLIENT_SECRET_HEROKU=",
|
||||
"CLIENT_SECRET_VERCEL=",
|
||||
"CLIENT_SECRET_NETLIFY=",
|
||||
"CLIENT_SECRET_GITHUB=",
|
||||
"CLIENT_SECRET_GITHUB_APP=",
|
||||
"CLIENT_SECRET_GITLAB=",
|
||||
"CLIENT_SECRET_BITBUCKET=",
|
||||
"CLIENT_SLUG_VERCEL=",
|
||||
"CLIENT_PRIVATE_KEY_GITHUB_APP=",
|
||||
"CLIENT_APP_ID_GITHUB_APP=",
|
||||
"SENTRY_DSN=",
|
||||
"POSTHOG_HOST=",
|
||||
"POSTHOG_PROJECT_API_KEY=",
|
||||
"CLIENT_ID_GOOGLE_LOGIN=",
|
||||
"CLIENT_SECRET_GOOGLE_LOGIN=",
|
||||
"CLIENT_ID_GITHUB_LOGIN=",
|
||||
"CLIENT_SECRET_GITHUB_LOGIN=",
|
||||
"CLIENT_ID_GITLAB_LOGIN=",
|
||||
"CLIENT_SECRET_GITLAB_LOGIN=",
|
||||
"CAPTCHA_SECRET=",
|
||||
"NEXT_PUBLIC_CAPTCHA_SITE_KEY=",
|
||||
"PLAIN_API_KEY=",
|
||||
"PLAIN_WISH_LABEL_IDS=",
|
||||
"SSL_CLIENT_CERTIFICATE_HEADER_KEY=",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "backend"
|
||||
port = 8_080
|
||||
port = 8080
|
||||
host = "${main_domain}"
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
kener:
|
||||
image: rajnandan1/kener:latest
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- KENER_SECRET_KEY=${KENER_SECRET_KEY} # 🔐 API key / secret
|
||||
- ORIGIN=${ORIGIN}
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- KENER_BASE_PATH=${KENER_BASE_PATH}
|
||||
- ORIGIN=${ORIGIN}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY} # 🔐 API key
|
||||
- RESEND_SENDER_EMAIL=${RESEND_SENDER_EMAIL}
|
||||
ports:
|
||||
- 3000
|
||||
volumes:
|
||||
- kener_db:/app/database
|
||||
- ../files/uploads:/app/uploads
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:alpine
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # 🔐 DB password
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
restart: unless-stopped
|
||||
|
||||
mysql:
|
||||
image: mariadb:11
|
||||
environment:
|
||||
- MYSQL_USER=${MYSQL_USER}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD} # 🔐 DB password
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD=true
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- kener_redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
kener_db: {}
|
||||
kener_redis: {}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
KENER_SECRET_KEY = "${password:64}"
|
||||
POSTGRES_PASSWORD = "${password:32}"
|
||||
MYSQL_PASSWORD = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
@@ -13,25 +11,9 @@ host = "${main_domain}"
|
||||
[config.env]
|
||||
TZ = "Etc/UTC"
|
||||
KENER_SECRET_KEY = "${KENER_SECRET_KEY}" # 🔐 API key / secret
|
||||
ORIGIN = "https://${main_domain}"
|
||||
REDIS_URL = "redis://redis:6379"
|
||||
DATABASE_URL = "sqlite://./database/kener.sqlite.db"
|
||||
KENER_BASE_PATH = ""
|
||||
ORIGIN = "http://localhost:3000"
|
||||
RESEND_API_KEY = ""
|
||||
RESEND_SENDER_EMAIL = "Accounts <accounts@resend.dev>"
|
||||
POSTGRES_USER = "user"
|
||||
POSTGRES_DB = "kener_db"
|
||||
MYSQL_USER = "user"
|
||||
MYSQL_DATABASE = "kener_db"
|
||||
MYSQL_RANDOM_ROOT_PASSWORD = "true"
|
||||
MYSQL_PASSWORD = "${MYSQL_PASSWORD}" # 🔐 DB password
|
||||
POSTGRES_PASSWORD = "${POSTGRES_PASSWORD}" # 🔐 DB password
|
||||
|
||||
[[config.mounts]]
|
||||
type = "volume"
|
||||
source = "kener_db"
|
||||
target = "/app/database"
|
||||
|
||||
[[config.mounts]]
|
||||
type = "bind"
|
||||
source = "../files/uploads"
|
||||
target = "/app/uploads"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
mailcatcher-ng:
|
||||
# Only the "latest" tag is published, so the image is pinned by digest
|
||||
# (v2.3.9 of the image, MailCatcher NG 1.6.8).
|
||||
image: stpaquet/alpinemailcatcher:latest@sha256:499c0eabca5e82b23d030706ba9d5b8b27bba5a5f34a43154ee5e68b07cba52c
|
||||
restart: unless-stopped
|
||||
# Same as the image default CMD, plus --persistence so caught mail
|
||||
# survives container restarts (stored in the named volume below).
|
||||
command: sh -c "mailcatcher --foreground --smtp-port=1025 --http-port=1080 --ip=0.0.0.0 --messages-limit=$$MAIL_LIMIT --persistence --no-quit"
|
||||
environment:
|
||||
- MAIL_LIMIT=${MAIL_LIMIT}
|
||||
expose:
|
||||
# SMTP: reachable from other services in the same Dokploy network
|
||||
# at mailcatcher-ng:1025 (intentionally not published on the host).
|
||||
- 1025
|
||||
# Web UI (routed through the domain below)
|
||||
- 1080
|
||||
volumes:
|
||||
- mailcatcher-data:/home/mailcatcher/.mailcatcher
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:1080']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
mailcatcher-data:
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg class="logo-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#0d9488;stop-opacity:1"></stop>
|
||||
<stop offset="100%" style="stop-color:#0f766e;stop-opacity:1"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M 20 30 L 50 55 L 80 30 L 80 75 Q 80 80 75 80 L 25 80 Q 20 80 20 75 Z" fill="url(#logoGradient)"></path>
|
||||
<path d="M 20 30 L 50 55 L 80 30" stroke="white" stroke-width="2" fill="none"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 558 B |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "mailcatcher-ng",
|
||||
"name": "MailCatcher NG",
|
||||
"version": "1.6.8",
|
||||
"description": "MailCatcher NG runs a super simple SMTP server which catches any message sent to it to display in a web interface. Point your app's SMTP settings at mailcatcher-ng:1025 and inspect every captured email in the browser.",
|
||||
"logo": "mailcatcher-ng.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/spaquet/mailcatcher",
|
||||
"website": "https://spaquet.github.io/mailcatcher/",
|
||||
"docs": "https://spaquet.github.io/docker-alpine-mailcatcher/"
|
||||
},
|
||||
"tags": [
|
||||
"email",
|
||||
"smtp",
|
||||
"development"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"MAIL_LIMIT=50",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "mailcatcher-ng"
|
||||
port = 1_080
|
||||
host = "${main_domain}"
|
||||
@@ -0,0 +1,146 @@
|
||||
version: "3.8"
|
||||
|
||||
# Shared Mailu configuration (equivalent to the upstream mailu.env file)
|
||||
x-mailu-env: &mailu-env
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- DOMAIN=${DOMAIN}
|
||||
- HOSTNAMES=${DOMAIN}
|
||||
- POSTMASTER=admin
|
||||
# TLS for the mail protocols is obtained through Let's Encrypt (HTTP-01
|
||||
# through Traefik on port 80). The web UI itself is served plain HTTP to
|
||||
# Traefik, which terminates HTTPS.
|
||||
- TLS_FLAVOR=mail-letsencrypt
|
||||
# Mailu grants relay/XCLIENT trust to this network range. It must cover the
|
||||
# Docker networks of this project (Docker allocates them from 172.16.0.0/12
|
||||
# by default). Narrow it down if you know your exact subnet.
|
||||
- SUBNET=${SUBNET}
|
||||
- ADMIN=true
|
||||
- WEBMAIL=roundcube
|
||||
- API=false
|
||||
- WEBDAV=none
|
||||
- ANTIVIRUS=none
|
||||
- SCAN_MACROS=false
|
||||
# Ports enabled inside the front container. 4190 (sieve) stays internal for
|
||||
# the webmail filters UI and is not published on the host.
|
||||
- PORTS=25,80,443,465,587,993,4190
|
||||
- MESSAGE_SIZE_LIMIT=50000000
|
||||
- FULL_TEXT_SEARCH=en
|
||||
- WEBROOT_REDIRECT=/webmail
|
||||
- WEB_ADMIN=/admin
|
||||
- WEB_WEBMAIL=/webmail
|
||||
- SITENAME=Mailu
|
||||
- WEBSITE=https://mailu.io
|
||||
# Trust Traefik (dokploy-network) to forward the real client IP for rate limiting
|
||||
- REAL_IP_FROM=${SUBNET}
|
||||
- REAL_IP_HEADER=X-Forwarded-For
|
||||
# Initial admin account: admin@${DOMAIN} (created only if it does not exist)
|
||||
- INITIAL_ADMIN_ACCOUNT=${INITIAL_ADMIN_ACCOUNT}
|
||||
- INITIAL_ADMIN_DOMAIN=${DOMAIN}
|
||||
- INITIAL_ADMIN_PW=${INITIAL_ADMIN_PW}
|
||||
- INITIAL_ADMIN_MODE=ifmissing
|
||||
|
||||
# Public DNSSEC-validating resolvers. The admin container refuses to start
|
||||
# without DNSSEC validation, and Postfix needs it for outbound DANE. Service
|
||||
# discovery still uses Docker's embedded DNS; these are only upstreams.
|
||||
x-mailu-dns: &mailu-dns
|
||||
- "1.1.1.1"
|
||||
- "8.8.8.8"
|
||||
|
||||
services:
|
||||
front:
|
||||
image: ghcr.io/mailu/nginx:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
# Mail protocol ports are published directly on the host (same approach as
|
||||
# the poste.io template). They will fail to bind if another mail server
|
||||
# already uses them on this machine.
|
||||
ports:
|
||||
- "25:25" # SMTP (server to server)
|
||||
- "465:465" # SMTPS submission
|
||||
- "587:587" # Submission (STARTTLS)
|
||||
- "993:993" # IMAPS
|
||||
volumes:
|
||||
- mailu-certs:/certs
|
||||
# The image's built-in healthcheck also requires the mail (Dovecot) proxy,
|
||||
# which only starts once TLS certificates exist. Behind Traefik that
|
||||
# deadlocks: unhealthy -> Traefik drops the route -> the ACME challenge can
|
||||
# never be answered. Check only nginx instead (health endpoint returns 204).
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -m3 -skfLo /dev/null http://127.0.0.1:10204/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
depends_on:
|
||||
- admin
|
||||
|
||||
admin:
|
||||
image: ghcr.io/mailu/admin:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-data:/data
|
||||
- mailu-dkim:/dkim
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
imap:
|
||||
image: ghcr.io/mailu/dovecot:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-mail:/mail
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
smtp:
|
||||
image: ghcr.io/mailu/postfix:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-mailqueue:/queue
|
||||
depends_on:
|
||||
- front
|
||||
|
||||
antispam:
|
||||
image: ghcr.io/mailu/rspamd:2024.06
|
||||
restart: unless-stopped
|
||||
hostname: antispam
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-filter:/var/lib/rspamd
|
||||
depends_on:
|
||||
- front
|
||||
- redis
|
||||
|
||||
webmail:
|
||||
image: ghcr.io/mailu/webmail:2024.06
|
||||
restart: unless-stopped
|
||||
environment: *mailu-env
|
||||
dns: *mailu-dns
|
||||
volumes:
|
||||
- mailu-webmail:/data
|
||||
depends_on:
|
||||
- front
|
||||
- imap
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- mailu-redis:/data
|
||||
|
||||
volumes:
|
||||
mailu-certs: {}
|
||||
mailu-data: {}
|
||||
mailu-dkim: {}
|
||||
mailu-mail: {}
|
||||
mailu-mailqueue: {}
|
||||
mailu-filter: {}
|
||||
mailu-webmail: {}
|
||||
mailu-redis: {}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Mailu
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Point the domain you assign in Dokploy (for example `mail.example.com`) at your server **before** deploying: an `A` record, plus an `MX` record for your mail domain targeting it.
|
||||
2. Deploy the template and open the domain: `/webmail` is Roundcube, `/admin` is the admin UI.
|
||||
3. Log in at `/admin` with `admin@<your domain>` and the auto-generated `INITIAL_ADMIN_PW` (Dokploy → your service → Environment). The account is created only on first boot (`INITIAL_ADMIN_MODE=ifmissing`); change the password from the admin UI afterwards.
|
||||
4. In the admin UI, open **Mail domains → your domain → Details** and create the DNS records it shows (SPF, DKIM, DMARC). Also set the **PTR/reverse DNS** record of your server IP to your mail hostname — most providers require this to accept your mail.
|
||||
|
||||
## Ports
|
||||
|
||||
The mail protocol ports are published directly on the host: **25** (SMTP), **465** (SMTPS), **587** (submission) and **993** (IMAPS). The deployment fails to start if another mail server (or a previous Mailu deployment) already binds them on the same machine. Many VPS providers block outbound port 25 by default — ask your provider to unblock it, or configure a relay host in Mailu.
|
||||
|
||||
## TLS
|
||||
|
||||
- The web UI is served through Traefik like any other Dokploy app. Enable **HTTPS with Let's Encrypt** on the Dokploy domain: the web login cookie requires HTTPS, and Mailu's internal certbot self-check follows Traefik's HTTP→HTTPS redirect and needs a valid certificate there.
|
||||
- The mail ports get their own Let's Encrypt certificate: the `front` container runs certbot internally and answers the HTTP-01 challenge through Traefik on port 80. This only succeeds once the DNS record of your domain points at the server. If the certificate was obtained *after* the first boot, restart the `front` service once so the TLS mail listeners (465/587/993) come up.
|
||||
|
||||
## Notes
|
||||
|
||||
- `SUBNET` (default `172.16.0.0/12`) is the network range Mailu trusts for its internal traffic (Postfix relay/XCLIENT, Dovecot proxying, Rspamd). It covers Docker's default address pools; if your Docker daemon uses custom pools, adjust it to match. Note this means other containers on the same Docker host are treated as trusted senders.
|
||||
- The containers use public DNSSEC-validating resolvers (`1.1.1.1`, `8.8.8.8`) as upstream DNS: the admin container requires DNSSEC validation and Postfix uses it for DANE. Heavy production use benefits from a dedicated local resolver instead, because public resolvers are rate-limited by DNSBLs (see the [Mailu DNS FAQ](https://mailu.io/2024.06/faq.html)).
|
||||
- Additional mail domains, users, aliases and fetchmail can be managed in the admin UI. ClamAV antivirus is not included in this template to keep memory usage low (it needs >1 GB RAM); see the Mailu docs to add it.
|
||||
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="logo.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 263.62305,623.95312 -0.95703,0.28907 c 0,0 8.73472,28.99871 31.14453,57.99219 22.40981,28.99347 58.55631,58.02539 113.28125,58.02539 109.39431,0 155.50976,-97.76563 155.50976,-97.76563 l -0.90429,-0.42578 c 0,0 -45.80684,97.19141 -154.60547,97.19141 -54.37155,0 -90.2008,-28.79897 -112.49024,-57.63672 -22.28944,-28.83775 -30.97851,-57.66993 -30.97851,-57.66993 z"
|
||||
id="path4142-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="77.860065"
|
||||
inkscape:cy="631.62981"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1080"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:15;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4136"
|
||||
cx="416.00003"
|
||||
cy="574.93365"
|
||||
r="196.7318" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="297.14285"
|
||||
y="609.79077"
|
||||
id="text4138"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4140"
|
||||
x="297.14285"
|
||||
y="609.79077"
|
||||
style="font-size:90px">Mailu</tspan></text>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 263.62305,623.95312 -0.95703,0.28907 c 0,0 8.73472,28.99871 31.14453,57.99219 22.40981,28.99347 58.55631,58.02539 113.28125,58.02539 109.39431,0 155.50976,-97.76563 155.50976,-97.76563 l -0.90429,-0.42578 c 0,0 -45.80684,97.19141 -154.60547,97.19141 -54.37155,0 -90.2008,-28.79897 -112.49024,-57.63672 -22.28944,-28.83775 -30.97851,-57.66993 -30.97851,-57.66993 z"
|
||||
id="path4142"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
id="text4144"
|
||||
sodipodi:linespacing="125%"><textPath
|
||||
xlink:href="#path4142"
|
||||
id="textPath4150"><tspan
|
||||
id="tspan4146"
|
||||
style="fill:#000000;fill-opacity:1;"> email since 2016</tspan></textPath></text>
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 275.13574,527.65954 -0.91835,-0.23149 c 0,0 8.38171,-23.22244 29.88583,-46.44069 21.50412,-23.21824 56.18977,-46.46727 108.70301,-46.46727 104.97317,0 149.22487,78.29163 149.22487,78.29163 l -0.86774,0.34096 c 0,0 -43.95557,-77.83178 -148.35713,-77.83178 -52.17413,0 -86.55535,23.06248 -107.94397,46.15602 -21.38862,23.09354 -29.72652,46.18262 -29.72652,46.18262 z"
|
||||
id="path4184"
|
||||
inkscape:connector-curvature="0" />
|
||||
<text
|
||||
y="-623.17346"
|
||||
x="-262.66602"
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text4144-6"
|
||||
sodipodi:linespacing="125%"
|
||||
transform="translate(404.56768,384.57019)"><textPath
|
||||
xlink:href="#path4142-3"
|
||||
id="textPath4150-7"><tspan
|
||||
id="tspan4146-5"
|
||||
style="fill:#000000;fill-opacity:1"> email since 2016</tspan></textPath></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text4203"
|
||||
sodipodi:linespacing="125%"
|
||||
x="-2"
|
||||
transform="translate(-4,10)"><textPath
|
||||
xlink:href="#path4184"
|
||||
id="textPath4207"><tspan
|
||||
id="tspan4205">delivering insular</tspan></textPath></text>
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.61572051;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 228.79956,370.50924 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4214"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.61572051"
|
||||
d="m 210.94153,406.39617 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4230-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.61572051"
|
||||
d="m 192.75878,438.21597 c 0,0 113.13708,-21.21321 163.13963,81.82235 50.00255,103.03556 58.08377,183.34269 168.1904,213.14219 110.10663,29.7995 99.50003,25.75889 99.50003,25.75889"
|
||||
id="path4230-3-5"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.6 KiB |
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"id": "mailu",
|
||||
"name": "Mailu",
|
||||
"version": "2024.06",
|
||||
"description": "Full-featured open-source mail server as a set of Docker containers: SMTP (Postfix), IMAP (Dovecot), Rspamd antispam, admin UI with DKIM management and Roundcube webmail.",
|
||||
"logo": "mailu.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/Mailu/Mailu",
|
||||
"website": "https://mailu.io/",
|
||||
"docs": "https://mailu.io/2024.06/",
|
||||
"docker": "https://github.com/orgs/Mailu/packages"
|
||||
},
|
||||
"tags": [
|
||||
"email",
|
||||
"mail-server",
|
||||
"smtp",
|
||||
"imap",
|
||||
"antispam",
|
||||
"webmail"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:16}"
|
||||
admin_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "front"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
DOMAIN = "${main_domain}"
|
||||
SECRET_KEY = "${secret_key}"
|
||||
INITIAL_ADMIN_ACCOUNT = "admin"
|
||||
INITIAL_ADMIN_PW = "${admin_password}"
|
||||
SUBNET = "172.16.0.0/12"
|
||||
@@ -0,0 +1,39 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- markup-postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: markup
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: markup
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U markup -d markup"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
markup:
|
||||
image: ghcr.io/pphilfre/markup:main
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
NEXT_PUBLIC_DB_PROVIDER: postgres
|
||||
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL}
|
||||
# WorkOS AuthKit (optional): required only for user accounts and cloud sync.
|
||||
# Placeholders keep the app running; replace them with real credentials
|
||||
# from https://dashboard.workos.com to enable sign-in.
|
||||
WORKOS_CLIENT_ID: ${WORKOS_CLIENT_ID}
|
||||
WORKOS_API_KEY: ${WORKOS_API_KEY}
|
||||
WORKOS_COOKIE_PASSWORD: ${WORKOS_COOKIE_PASSWORD}
|
||||
NEXT_PUBLIC_WORKOS_REDIRECT_URI: ${NEXT_PUBLIC_WORKOS_REDIRECT_URI}
|
||||
NODE_ENV: production
|
||||
|
||||
volumes:
|
||||
markup-postgres-data:
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<rect width="100" height="100" rx="20" fill="#0a0a0a"/>
|
||||
<text x="50" y="68" text-anchor="middle" font-family="system-ui, -apple-system, sans-serif" font-weight="700" font-size="48" fill="#7c3aed">M</text>
|
||||
<rect x="22" y="76" width="56" height="3" rx="1.5" fill="#7c3aed" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 375 B |
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "markup",
|
||||
"name": "Markup",
|
||||
"version": "main",
|
||||
"description": "Markup is a fast, keyboard-first markdown workspace with live preview, graph view, whiteboards, mind maps, and PostgreSQL-powered cloud sync.",
|
||||
"logo": "markup.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/pphilfre/markup",
|
||||
"website": "https://home.markup.freddiephilpot.dev/",
|
||||
"docs": "https://docs.markup.freddiephilpot.dev/"
|
||||
},
|
||||
"tags": [
|
||||
"markdown",
|
||||
"notes",
|
||||
"editor",
|
||||
"self-hosted"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_password = "${password:32}"
|
||||
cookie_password = "${password:32}"
|
||||
|
||||
[config]
|
||||
env = [
|
||||
"POSTGRES_PASSWORD=${postgres_password}",
|
||||
"DATABASE_URL=postgresql://markup:${postgres_password}@db:5432/markup",
|
||||
"NEXT_PUBLIC_SITE_URL=https://${main_domain}",
|
||||
"# WorkOS AuthKit is optional: it powers user accounts and cloud sync.",
|
||||
"# The app works without it (local, in-browser storage). To enable sign-in,",
|
||||
"# create an app at https://dashboard.workos.com, set the redirect URI below",
|
||||
"# in the WorkOS dashboard, and replace the placeholder credentials.",
|
||||
"WORKOS_CLIENT_ID=client_id_change_me",
|
||||
"WORKOS_API_KEY=sk_change_me",
|
||||
"WORKOS_COOKIE_PASSWORD=${cookie_password}",
|
||||
"NEXT_PUBLIC_WORKOS_REDIRECT_URI=https://${main_domain}/callback",
|
||||
]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "markup"
|
||||
port = 3000
|
||||
host = "${main_domain}"
|
||||
@@ -1,9 +1,14 @@
|
||||
services:
|
||||
minio:
|
||||
# after RELEASE.2025-04-22T22-12-26Z, minio removed most of the admin UI, if you want to use the admin UI, uncomment the line below
|
||||
# image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
# if you uncommented the line above, comment the line below
|
||||
image: minio/minio
|
||||
# MinIO Inc. stopped publishing Docker images and pre-built binaries in October 2025
|
||||
# (https://github.com/minio/minio/issues/21647) and archived the minio/minio repository
|
||||
# in February 2026. The official minio/minio image is frozen at RELEASE.2025-09-07T16-13-09Z
|
||||
# and no longer receives security fixes (e.g. the fix for CVE-2025-62506 was never published
|
||||
# as an image). This template uses pgsty/minio, the actively maintained community fork
|
||||
# (https://github.com/pgsty/minio, AGPLv3). It is a drop-in replacement: same environment
|
||||
# variables, same `server` command, same /data on-disk format (existing minio-data volumes
|
||||
# keep working), and it restores the full web console that upstream removed.
|
||||
image: pgsty/minio:RELEASE.2026-06-18T00-00-00Z
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# by default, the MinIO container will use a volume named minio-data
|
||||
@@ -19,12 +24,10 @@ services:
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||
- MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL}
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
# by default, the MinIO container will use port 9000 to expose its API
|
||||
# and port 9001 to expose its web console
|
||||
# minio requires port to be specified when making a request to the API
|
||||
- 9000:9000
|
||||
expose:
|
||||
# port 9000 serves the S3 API (routed through the api domain defined in template.toml)
|
||||
# port 9001 serves the web console (routed through the main domain)
|
||||
- 9000
|
||||
- 9001
|
||||
|
||||
# comment the line below if you specified a local directory in the volumes section of the minio service
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "minio",
|
||||
"name": "Minio",
|
||||
"description": "Minio is an open source object storage server compatible with Amazon S3 cloud storage service.",
|
||||
"description": "MinIO is an open source object storage server compatible with Amazon S3 cloud storage service. This template uses pgsty/minio, the actively maintained community fork, because MinIO Inc. stopped publishing Docker images in October 2025 and archived the upstream open source project in February 2026.",
|
||||
"logo": "minio.png",
|
||||
"version": "latest",
|
||||
"version": "RELEASE.2026-06-18T00-00-00Z",
|
||||
"links": {
|
||||
"github": "https://github.com/minio/minio",
|
||||
"website": "https://minio.io/",
|
||||
"docs": "https://docs.minio.io/"
|
||||
"github": "https://github.com/pgsty/minio",
|
||||
"website": "https://silo.pigsty.io/",
|
||||
"docs": "https://silo.pigsty.io/docs/"
|
||||
},
|
||||
"tags": [
|
||||
"storage"
|
||||
|
||||
@@ -5,11 +5,18 @@ api_domain = "${domain}"
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
# Web console - log in with the generated root credentials below
|
||||
[[config.domains]]
|
||||
serviceName = "minio"
|
||||
port = 9_001
|
||||
host = "${main_domain}"
|
||||
|
||||
# S3-compatible API - point AWS CLI / SDKs / rclone / backup tools at this domain
|
||||
[[config.domains]]
|
||||
serviceName = "minio"
|
||||
port = 9_000
|
||||
host = "${api_domain}"
|
||||
|
||||
[config.env]
|
||||
MINIO_ROOT_USER = "minioadmin"
|
||||
MINIO_ROOT_PASSWORD = "${password:16}"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
start_period: 30s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
environment:
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep PONG"]
|
||||
start_period: 20s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Main instance: serves the editor UI and webhooks, publishes executions
|
||||
# to the Redis (Bull) queue instead of running them itself.
|
||||
n8n:
|
||||
image: n8nio/n8n:2.30.4
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# PostgreSQL
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=postgres
|
||||
- DB_POSTGRESDB_PORT=5432
|
||||
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
||||
- DB_POSTGRESDB_USER=${POSTGRES_USER}
|
||||
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
|
||||
# Queue mode (Redis / Bull)
|
||||
- EXECUTIONS_MODE=queue
|
||||
- QUEUE_BULL_REDIS_HOST=redis
|
||||
- QUEUE_BULL_REDIS_PORT=6379
|
||||
- QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- QUEUE_HEALTH_CHECK_ACTIVE=true
|
||||
- OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true
|
||||
|
||||
# Encryption key shared between the main instance and the workers
|
||||
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
|
||||
|
||||
# Networking
|
||||
- N8N_HOST=${N8N_HOST}
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=http
|
||||
- N8N_PROXY_HOPS=1
|
||||
- NODE_ENV=production
|
||||
- N8N_WEBHOOK_URL=https://${N8N_HOST}/
|
||||
- N8N_EDITOR_BASE_URL=https://${N8N_HOST}/
|
||||
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
|
||||
- N8N_SECURE_COOKIE=false
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5678/healthz"]
|
||||
start_period: 60s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Queue workers: pull executions from Redis and run them. Scale them by
|
||||
# changing the N8N_WORKER_REPLICAS environment variable and redeploying.
|
||||
n8n-worker:
|
||||
image: n8nio/n8n:2.30.4
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
deploy:
|
||||
replicas: ${N8N_WORKER_REPLICAS:-2}
|
||||
environment:
|
||||
# PostgreSQL
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=postgres
|
||||
- DB_POSTGRESDB_PORT=5432
|
||||
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
||||
- DB_POSTGRESDB_USER=${POSTGRES_USER}
|
||||
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
|
||||
# Queue mode (Redis / Bull)
|
||||
- EXECUTIONS_MODE=queue
|
||||
- QUEUE_BULL_REDIS_HOST=redis
|
||||
- QUEUE_BULL_REDIS_PORT=6379
|
||||
- QUEUE_BULL_REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- QUEUE_HEALTH_CHECK_ACTIVE=true
|
||||
|
||||
# Must be the same key as the main instance
|
||||
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
|
||||
|
||||
- NODE_ENV=production
|
||||
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
|
||||
depends_on:
|
||||
n8n:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:5678/healthz"]
|
||||
start_period: 60s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
n8n_data:
|
||||
@@ -0,0 +1,24 @@
|
||||
# n8n Queue Mode
|
||||
|
||||
This template runs n8n in [queue mode](https://docs.n8n.io/hosting/scaling/queue-mode/):
|
||||
|
||||
- **n8n** (main): serves the editor UI and receives webhooks/triggers, then publishes executions to the queue.
|
||||
- **n8n-worker** (x2 by default): pulls executions from the Redis (Bull) queue and runs them.
|
||||
- **redis**: the message broker for the queue (password protected).
|
||||
- **postgres**: stores workflows, credentials and execution data.
|
||||
|
||||
All n8n containers share the same `N8N_ENCRYPTION_KEY` (generated automatically), which is required for workers to decrypt credentials.
|
||||
|
||||
## Scaling workers
|
||||
|
||||
Worker replicas are controlled by the `N8N_WORKER_REPLICAS` environment variable (default `2`):
|
||||
|
||||
1. Open the compose service in Dokploy and go to the **Environment** tab.
|
||||
2. Change `N8N_WORKER_REPLICAS` to the number of workers you want.
|
||||
3. Redeploy. Docker Compose will scale the `n8n-worker` service to the requested number of replicas.
|
||||
|
||||
Each worker runs up to 10 concurrent executions by default. You can tune this by adding `N8N_CONCURRENCY_PRODUCTION_LIMIT` to the worker environment.
|
||||
|
||||
## Optional: dedicated webhook processors
|
||||
|
||||
For very high webhook volumes, n8n supports dedicated webhook processor instances (`command: webhook`). To use them, add another service to the compose file with the same environment as `n8n-worker` but with `command: webhook`, and route the `/webhook/` path of your domain to it. See the [n8n queue mode docs](https://docs.n8n.io/hosting/scaling/queue-mode/#webhook-processors) for details.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "n8n-queue",
|
||||
"name": "n8n Queue Mode",
|
||||
"version": "2.30.4",
|
||||
"description": "n8n running in queue mode for high-throughput production workloads: the main instance handles the editor and webhooks while a scalable pool of workers executes workflows from a Redis (Bull) queue, backed by PostgreSQL.",
|
||||
"logo": "n8n.png",
|
||||
"links": {
|
||||
"github": "https://github.com/n8n-io/n8n",
|
||||
"website": "https://n8n.io/",
|
||||
"docs": "https://docs.n8n.io/hosting/scaling/queue-mode/"
|
||||
},
|
||||
"tags": [
|
||||
"automation",
|
||||
"workflow",
|
||||
"low-code",
|
||||
"queue"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,33 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
postgres_user = "${username}"
|
||||
postgres_password = "${password:24}"
|
||||
postgres_db = "n8n"
|
||||
redis_password = "${password:24}"
|
||||
n8n_encryption_key = "${base64:32}"
|
||||
|
||||
[config]
|
||||
mounts = []
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "n8n"
|
||||
port = 5_678
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
N8N_HOST = "${main_domain}"
|
||||
GENERIC_TIMEZONE = "Europe/Berlin"
|
||||
|
||||
# Number of queue workers. Change it and redeploy to scale.
|
||||
N8N_WORKER_REPLICAS = "2"
|
||||
|
||||
# PostgreSQL
|
||||
POSTGRES_USER = "${postgres_user}"
|
||||
POSTGRES_PASSWORD = "${postgres_password}"
|
||||
POSTGRES_DB = "${postgres_db}"
|
||||
|
||||
# Redis (Bull queue)
|
||||
REDIS_PASSWORD = "${redis_password}"
|
||||
|
||||
# Encryption key shared by the main instance and the workers (IMPORTANT)
|
||||
N8N_ENCRYPTION_KEY = "${n8n_encryption_key}"
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:stable
|
||||
image: nextcloud:33.0.6
|
||||
restart: always
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
- nextcloud_redis
|
||||
|
||||
nextcloud_cron:
|
||||
image: nextcloud:stable
|
||||
image: nextcloud:33.0.6
|
||||
restart: always
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "nextcloud-aio",
|
||||
"name": "Nextcloud",
|
||||
"version": "stable",
|
||||
"version": "33.0.6",
|
||||
"description": "Nextcloud is a self-hosted file storage and sync platform with powerful collaboration capabilities. It integrates Files, Talk, Groupware, Office, Assistant and more into a single platform for remote work and data protection.",
|
||||
"logo": "nextcloud.png",
|
||||
"links": {
|
||||
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
op-api:
|
||||
image: lindesvard/openpanel-api:2
|
||||
image: lindesvard/openpanel-api:2.2.1
|
||||
restart: always
|
||||
command: >
|
||||
sh -c "
|
||||
@@ -97,7 +97,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
op-dashboard:
|
||||
image: lindesvard/openpanel-dashboard:2
|
||||
image: lindesvard/openpanel-dashboard:2.2.1
|
||||
restart: always
|
||||
depends_on:
|
||||
op-api:
|
||||
@@ -111,7 +111,7 @@ services:
|
||||
retries: 5
|
||||
|
||||
op-worker:
|
||||
image: lindesvard/openpanel-worker:2
|
||||
image: lindesvard/openpanel-worker:2.2.1
|
||||
restart: always
|
||||
depends_on:
|
||||
op-api:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "openpanel",
|
||||
"name": "OpenPanel",
|
||||
"version": "latest",
|
||||
"version": "2.2.1",
|
||||
"description": "An open-source web and product analytics platform that combines the power of Mixpanel with the ease of Plausible and one of the best Google Analytics replacements.",
|
||||
"logo": "logo.svg",
|
||||
"links": {
|
||||
|
||||
@@ -26,7 +26,7 @@ content = """
|
||||
<part_log remove="remove"/>
|
||||
<listen_host>0.0.0.0</listen_host>
|
||||
<interserver_listen_host>0.0.0.0</interserver_listen_host>
|
||||
<interserver_http_host>opch</interserver_http_host>
|
||||
<interserver_http_host>op-ch</interserver_http_host>
|
||||
<!-- Disable cgroup memory observer -->
|
||||
<cgroups_memory_usage_observer_wait_time>0</cgroups_memory_usage_observer_wait_time>
|
||||
<!-- Not used anymore, but kept for backwards compatibility -->
|
||||
|
||||
@@ -5,7 +5,7 @@ services:
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: tu_base_de_datos
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
volumes:
|
||||
@@ -19,6 +19,8 @@ services:
|
||||
PMA_USER: ${MYSQL_USER}
|
||||
PMA_PASSWORD: ${MYSQL_PASSWORD}
|
||||
PMA_ARBITRARY: 1
|
||||
UPLOAD_LIMIT: ${UPLOAD_LIMIT}
|
||||
MAX_EXECUTION_TIME: ${MAX_EXECUTION_TIME}
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
MYSQL_ROOT_PASSWORD = "${root_password}"
|
||||
MYSQL_DATABASE = "mysql"
|
||||
MYSQL_DATABASE = "phpmyadmin"
|
||||
MYSQL_USER = "phpmyadmin"
|
||||
MYSQL_PASSWORD = "${user_password}"
|
||||
UPLOAD_LIMIT = "512M"
|
||||
MAX_EXECUTION_TIME = "600"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
services:
|
||||
postgresus:
|
||||
image: rostislavdugin/postgresus:latest
|
||||
# Postgresus was renamed upstream to Databasus (https://github.com/databasus/databasus).
|
||||
# This image is frozen at its final release; use the "databasus" template for new deployments.
|
||||
image: rostislavdugin/postgresus:v2.15.3
|
||||
ports:
|
||||
- "4005"
|
||||
volumes:
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "postgresus",
|
||||
"name": "Postgresus",
|
||||
"version": "latest",
|
||||
"description": "Free, open source and self-hosted solution for automated PostgreSQL backups. With multiple storage options and notifications",
|
||||
"version": "v2.15.3",
|
||||
"description": "Deprecated: the project was renamed to Databasus, use the Databasus template instead. Free, open source and self-hosted solution for automated PostgreSQL backups, frozen at its final release",
|
||||
"logo": "postgresus.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/RostislavDugin/postgresus",
|
||||
"website": "https://postgresus.com",
|
||||
"docs": "https://postgresus.com"
|
||||
"website": "https://databasus.com",
|
||||
"docs": "https://databasus.com"
|
||||
},
|
||||
"tags": [
|
||||
"postgres",
|
||||
|
||||
@@ -0,0 +1,646 @@
|
||||
# PostHog self-hosted ("hobby" deployment, trimmed for a single VPS).
|
||||
# Based on https://github.com/PostHog/posthog/blob/master/docker-compose.hobby.yml
|
||||
# Omitted vs upstream hobby (heavy/optional): Temporal + Elasticsearch (data
|
||||
# warehouse / batch exports), browserless (image exports / heatmap screenshots),
|
||||
# personhog, cymbal + error-tracking/logs/traces ingestion, capture-ai and
|
||||
# capture-logs. Everything needed for product analytics, feature flags,
|
||||
# surveys, session replay and the CDP pipeline is included.
|
||||
|
||||
x-logging: &default-logging
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
|
||||
x-django-env: &django-env
|
||||
# Postgres
|
||||
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PGHOST: db
|
||||
PGUSER: posthog
|
||||
PGPASSWORD: posthog
|
||||
PERSONS_DB_WRITER_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PERSONS_DB_READER_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
# ClickHouse (users match docker/clickhouse/users.xml from the PostHog repo)
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_DATABASE: posthog
|
||||
CLICKHOUSE_SECURE: "false"
|
||||
CLICKHOUSE_VERIFY: "false"
|
||||
CLICKHOUSE_API_USER: api
|
||||
CLICKHOUSE_API_PASSWORD: apipass
|
||||
CLICKHOUSE_APP_USER: app
|
||||
CLICKHOUSE_APP_PASSWORD: apppass
|
||||
CLICKHOUSE_LOGS_CLUSTER_HOST: clickhouse
|
||||
CLICKHOUSE_LOGS_CLUSTER_SECURE: "false"
|
||||
# Redis / Kafka
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
FLAGS_REDIS_ENABLED: "false"
|
||||
# Object storage (MinIO)
|
||||
OBJECT_STORAGE_ENABLED: "true"
|
||||
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
|
||||
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${POSTHOG_SITE_URL}
|
||||
OBJECT_STORAGE_ACCESS_KEY_ID: object_storage_root_user
|
||||
OBJECT_STORAGE_SECRET_ACCESS_KEY: object_storage_root_password
|
||||
OBJECT_STORAGE_FORCE_PATH_STYLE: "true"
|
||||
# Session replay storage
|
||||
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
|
||||
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
|
||||
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
|
||||
RECORDING_API_URL: http://recording-api:6738
|
||||
# App
|
||||
SITE_URL: ${POSTHOG_SITE_URL}
|
||||
SECRET_KEY: ${POSTHOG_SECRET_KEY}
|
||||
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
|
||||
DEPLOYMENT: hobby
|
||||
IS_BEHIND_PROXY: "true"
|
||||
DISABLE_SECURE_SSL_REDIRECT: "true"
|
||||
# "false" so login works over plain HTTP; set to "true" once the domain uses HTTPS
|
||||
SECURE_COOKIES: ${POSTHOG_SECURE_COOKIES}
|
||||
CDP_API_URL: http://plugins:6738
|
||||
FEATURE_FLAGS_SERVICE_URL: http://feature-flags:3001
|
||||
LIVESTREAM_HOST: ${POSTHOG_SITE_URL}/livestream
|
||||
API_QUERIES_PER_TEAM: '{"1": 100}'
|
||||
OTEL_SDK_DISABLED: "true"
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ""
|
||||
|
||||
services:
|
||||
# One-shot: fetches the ClickHouse configuration, Kafka table schemas and
|
||||
# funnel UDFs from the PostHog repo (pinned to the same commit as the app
|
||||
# image) plus the GeoLite2 GeoIP database. Idempotent across redeploys.
|
||||
init-assets:
|
||||
image: alpine:3.21
|
||||
restart: on-failure
|
||||
logging: *default-logging
|
||||
entrypoint: /bin/sh
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
apk add --no-cache git curl brotli >/dev/null
|
||||
if [ ! -f /assets/.ok-${POSTHOG_VERSION} ]; then
|
||||
echo "Fetching PostHog repo assets @ ${POSTHOG_VERSION}"
|
||||
rm -rf /tmp/ph && mkdir -p /tmp/ph && cd /tmp/ph
|
||||
git init -q .
|
||||
git remote add origin https://github.com/PostHog/posthog.git
|
||||
git sparse-checkout set --no-cone /docker/clickhouse/ /posthog/idl/ /posthog/user_scripts/
|
||||
git -c protocol.version=2 fetch -q --depth 1 --filter=blob:none origin ${POSTHOG_VERSION}
|
||||
git checkout -q FETCH_HEAD
|
||||
rm -rf /assets/clickhouse /assets/idl /assets/user_scripts /assets/.ok-*
|
||||
cp -r docker/clickhouse /assets/clickhouse
|
||||
cp -r posthog/idl /assets/idl
|
||||
cp -r posthog/user_scripts /assets/user_scripts
|
||||
touch /assets/.ok-${POSTHOG_VERSION}
|
||||
echo "Repo assets ready"
|
||||
fi
|
||||
if [ ! -s /share/GeoLite2-City.mmdb ]; then
|
||||
echo "Downloading GeoLite2 database"
|
||||
curl -sL --http1.1 https://mmdbcdn.posthog.net/ | brotli --decompress --output=/share/GeoLite2-City.mmdb
|
||||
chmod 644 /share/GeoLite2-City.mmdb
|
||||
fi
|
||||
echo "init-assets done"
|
||||
volumes:
|
||||
- posthog-assets:/assets
|
||||
- geoip-data:/share
|
||||
|
||||
db:
|
||||
image: postgres:15.12-alpine
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
environment:
|
||||
POSTGRES_USER: posthog
|
||||
POSTGRES_DB: posthog
|
||||
POSTGRES_PASSWORD: posthog
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U posthog"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
- ../files/db-init:/docker-entrypoint-initdb.d:ro
|
||||
|
||||
redis7:
|
||||
image: redis:7.2-alpine
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
command: redis-server --maxmemory-policy allkeys-lru --maxmemory 200mb
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
# Required by ClickHouse for distributed DDL (docker/clickhouse/config.xml
|
||||
# points at zookeeper:2181)
|
||||
zookeeper:
|
||||
image: zookeeper:3.7.0
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
environment:
|
||||
ZOO_AUTOPURGE_PURGEINTERVAL: 1
|
||||
ZOO_AUTOPURGE_SNAPRETAINCOUNT: 3
|
||||
volumes:
|
||||
- zookeeper-data:/data
|
||||
- zookeeper-datalog:/datalog
|
||||
|
||||
# Kafka API provided by Redpanda (as upstream), tuned down for small servers
|
||||
kafka:
|
||||
image: docker.io/redpandadata/redpanda:v25.1.9
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
command:
|
||||
- redpanda
|
||||
- start
|
||||
- --kafka-addr internal://0.0.0.0:9092
|
||||
- --advertise-kafka-addr internal://kafka:9092
|
||||
- --rpc-addr kafka:33145
|
||||
- --advertise-rpc-addr kafka:33145
|
||||
- --mode dev-container
|
||||
- --smp 1
|
||||
- --memory 1G
|
||||
- --reserve-memory 0M
|
||||
- --overprovisioned
|
||||
- --set redpanda.empty_seed_starts_cluster=false
|
||||
- --seeds kafka:33145
|
||||
- --set redpanda.auto_create_topics_enabled=true
|
||||
volumes:
|
||||
- redpanda-data:/var/lib/redpanda/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:9644/v1/status/ready || exit 1"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 20s
|
||||
|
||||
# One-shot: pre-creates the Kafka topics that consumers check at startup
|
||||
kafka-init:
|
||||
image: docker.io/redpandadata/redpanda:v25.1.9
|
||||
restart: on-failure
|
||||
logging: *default-logging
|
||||
entrypoint: /bin/sh
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
TOPICS="clickhouse_events_json \
|
||||
clickhouse_ai_events_json \
|
||||
clickhouse_heatmap_events \
|
||||
clickhouse_ingestion_warnings \
|
||||
events_plugin_ingestion \
|
||||
events_plugin_ingestion_dlq \
|
||||
events_plugin_ingestion_overflow \
|
||||
events_plugin_ingestion_async \
|
||||
events_plugin_ingestion_historical \
|
||||
ingestion-clientwarnings-main-1 \
|
||||
heatmaps_ingestion \
|
||||
session_recording_snapshot_item_events \
|
||||
session_recording_snapshot_item_overflow \
|
||||
clickhouse_groups \
|
||||
clickhouse_person \
|
||||
clickhouse_person_distinct_id \
|
||||
clickhouse_app_metrics2 \
|
||||
log_entries \
|
||||
clickhouse_tophog"
|
||||
for topic in $$TOPICS; do
|
||||
rpk topic create "$$topic" --brokers kafka:9092 -p 1 -r 1 2>/dev/null \
|
||||
|| rpk topic list --brokers kafka:9092 | grep -q "$$topic"
|
||||
done
|
||||
echo "Kafka topics ready"
|
||||
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:26.6.1.1193
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
init-assets:
|
||||
condition: service_completed_successfully
|
||||
zookeeper:
|
||||
condition: service_started
|
||||
kafka:
|
||||
condition: service_started
|
||||
entrypoint: /bin/bash
|
||||
command:
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
cp /assets/clickhouse/config.xml /etc/clickhouse-server/config.xml
|
||||
cp /assets/clickhouse/config.d/default.xml /etc/clickhouse-server/config.d/default.xml
|
||||
cp /assets/clickhouse/users.xml /etc/clickhouse-server/users.xml
|
||||
cp /assets/clickhouse/user_defined_function.xml /etc/clickhouse-server/user_defined_function.xml
|
||||
mkdir -p /var/lib/clickhouse/format_schemas /var/lib/clickhouse/user_scripts
|
||||
cp -r /assets/idl/. /var/lib/clickhouse/format_schemas/
|
||||
cp -r /assets/user_scripts/. /var/lib/clickhouse/user_scripts/
|
||||
chmod -R 755 /var/lib/clickhouse/user_scripts
|
||||
chown -R clickhouse:clickhouse /var/lib/clickhouse/format_schemas /var/lib/clickhouse/user_scripts || true
|
||||
# Materialize system log tables (system.crash_log is required by the
|
||||
# custom_metrics ClickHouse migration), mirroring upstream init-db.sh
|
||||
(
|
||||
i=0
|
||||
while [ $$i -lt 90 ]; do
|
||||
if clickhouse-client --query "SELECT 1" >/dev/null 2>&1; then
|
||||
clickhouse-client --query "SYSTEM FLUSH LOGS" && echo "system logs flushed" && break
|
||||
fi
|
||||
sleep 2
|
||||
i=$$((i+1))
|
||||
done
|
||||
) &
|
||||
exec /entrypoint.sh
|
||||
environment:
|
||||
CLICKHOUSE_SKIP_USER_SETUP: 1
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
volumes:
|
||||
- posthog-assets:/assets:ro
|
||||
- clickhouse-data:/var/lib/clickhouse
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 30
|
||||
start_period: 30s
|
||||
|
||||
# Django app: runs migrations, then serves the API + UI on :8000
|
||||
web:
|
||||
image: posthog/posthog:${POSTHOG_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
command: ["sh", "-c", "./bin/migrate && exec ./bin/docker-server"]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
kafka-init:
|
||||
condition: service_completed_successfully
|
||||
objectstorage:
|
||||
condition: service_started
|
||||
environment:
|
||||
<<: *django-env
|
||||
NGINX_UNIT_APP_PROCESSES: 2
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fs http://localhost:8000/_health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 600s
|
||||
|
||||
# Celery worker + scheduler
|
||||
worker:
|
||||
image: posthog/posthog:${POSTHOG_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
command: ./bin/docker-worker-celery --with-scheduler
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
web:
|
||||
condition: service_started
|
||||
environment:
|
||||
<<: *django-env
|
||||
POSTHOG_SKIP_MIGRATION_CHECKS: "1"
|
||||
WEB_CONCURRENCY: 2
|
||||
|
||||
# Node plugin server: CDP (destinations, webhooks, transformations)
|
||||
plugins:
|
||||
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_started
|
||||
kafka-init:
|
||||
condition: service_completed_successfully
|
||||
init-assets:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
# Run only the pipelines this template ships (CDP destinations/workflows,
|
||||
# realtime cohorts, feature-flag evaluation). The default mode would also
|
||||
# start the logs/error-tracking ingestion consumers, which this template
|
||||
# intentionally omits.
|
||||
NODEJS_CAPABILITY_GROUPS: cdp_workflows,realtime_cohorts,feature_flags
|
||||
SITE_URL: ${POSTHOG_SITE_URL}
|
||||
SECRET_KEY: ${POSTHOG_SECRET_KEY}
|
||||
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
|
||||
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
BEHAVIORAL_COHORTS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
CYCLOTRON_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
CDP_REDIS_HOST: redis7
|
||||
CDP_REDIS_PORT: 6379
|
||||
# TLS for these defaults to "true" in production images; this stack's
|
||||
# Redis is plaintext (a TLS handshake against it hangs until timeout and
|
||||
# crash-loops the process if these consumers ever run).
|
||||
LOGS_REDIS_HOST: redis7
|
||||
LOGS_REDIS_TLS: "false"
|
||||
TRACES_REDIS_HOST: redis7
|
||||
TRACES_REDIS_TLS: "false"
|
||||
COOKIELESS_REDIS_HOST: redis7
|
||||
COOKIELESS_REDIS_PORT: 6379
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_DATABASE: posthog
|
||||
CLICKHOUSE_SECURE: "false"
|
||||
CLICKHOUSE_VERIFY: "false"
|
||||
OBJECT_STORAGE_ENABLED: "true"
|
||||
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
|
||||
OBJECT_STORAGE_PUBLIC_ENDPOINT: ${POSTHOG_SITE_URL}
|
||||
OBJECT_STORAGE_ACCESS_KEY_ID: object_storage_root_user
|
||||
OBJECT_STORAGE_SECRET_ACCESS_KEY: object_storage_root_password
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ""
|
||||
volumes:
|
||||
- geoip-data:/share:ro
|
||||
|
||||
# Node plugin server: event ingestion (Kafka -> person processing -> ClickHouse)
|
||||
ingestion-general:
|
||||
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_started
|
||||
kafka-init:
|
||||
condition: service_completed_successfully
|
||||
init-assets:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
PLUGIN_SERVER_MODE: ingestion-v2-combined
|
||||
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
|
||||
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
BEHAVIORAL_COHORTS_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
COOKIELESS_REDIS_HOST: redis7
|
||||
COOKIELESS_REDIS_PORT: 6379
|
||||
CDP_REDIS_HOST: redis7
|
||||
LOGS_REDIS_HOST: redis7
|
||||
LOGS_REDIS_TLS: "false"
|
||||
TRACES_REDIS_HOST: redis7
|
||||
TRACES_REDIS_TLS: "false"
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_DATABASE: posthog
|
||||
CLICKHOUSE_SECURE: "false"
|
||||
CLICKHOUSE_VERIFY: "false"
|
||||
volumes:
|
||||
- geoip-data:/share:ro
|
||||
|
||||
# Node plugin server: session replay blob ingestion (Kafka -> S3)
|
||||
ingestion-sessionreplay:
|
||||
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
kafka-init:
|
||||
condition: service_completed_successfully
|
||||
objectstorage:
|
||||
condition: service_started
|
||||
environment:
|
||||
PLUGIN_SERVER_MODE: recordings-blob-ingestion-v2
|
||||
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
|
||||
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
|
||||
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
|
||||
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
|
||||
SESSION_RECORDING_V2_S3_TIMEOUT_MS: 120000
|
||||
|
||||
# Node plugin server: session replay playback API
|
||||
recording-api:
|
||||
image: posthog/posthog-node:${POSTHOG_NODE_VERSION}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
clickhouse:
|
||||
condition: service_started
|
||||
objectstorage:
|
||||
condition: service_started
|
||||
environment:
|
||||
PLUGIN_SERVER_MODE: recording-api
|
||||
ENCRYPTION_SALT_KEYS: ${POSTHOG_ENCRYPTION_SALT_KEYS}
|
||||
DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
SESSION_RECORDING_API_REDIS_HOST: redis7
|
||||
SESSION_RECORDING_API_REDIS_PORT: 6379
|
||||
CLICKHOUSE_HOST: clickhouse
|
||||
CLICKHOUSE_DATABASE: posthog
|
||||
CLICKHOUSE_SECURE: "false"
|
||||
CLICKHOUSE_VERIFY: "false"
|
||||
SESSION_RECORDING_V2_S3_ENDPOINT: http://objectstorage:19000
|
||||
SESSION_RECORDING_V2_S3_ACCESS_KEY_ID: object_storage_root_user
|
||||
SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY: object_storage_root_password
|
||||
|
||||
# Rust event capture endpoint (/e, /batch, /capture, /i/v0)
|
||||
capture:
|
||||
image: ghcr.io/posthog/posthog/capture:master
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ADDRESS: 0.0.0.0:3000
|
||||
KAFKA_TOPIC: events_plugin_ingestion
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
CAPTURE_MODE: events
|
||||
RUST_LOG: info,rdkafka=warn
|
||||
CAPTURE_V1_SINKS: msk
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_HOSTS: kafka:9092
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_MAIN: events_plugin_ingestion
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_HISTORICAL: events_plugin_ingestion_historical
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_OVERFLOW: events_plugin_ingestion_overflow
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_DLQ: events_plugin_ingestion_dlq
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_EXCEPTION: ingestion-errortracking-main
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_HEATMAP: heatmaps_ingestion
|
||||
CAPTURE_V1_SINK_MSK_KAFKA_TOPIC_CLIENT_INGESTION_WARNING: ingestion-clientwarnings-main-1
|
||||
|
||||
# Rust capture endpoint for session recordings (/s)
|
||||
replay-capture:
|
||||
image: ghcr.io/posthog/posthog/capture:master
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ADDRESS: 0.0.0.0:3000
|
||||
KAFKA_TOPIC: session_recording_snapshot_item_events
|
||||
KAFKA_HOSTS: kafka:9092
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
CAPTURE_MODE: recordings
|
||||
|
||||
# Rust feature flag evaluation service (/flags)
|
||||
feature-flags:
|
||||
image: ghcr.io/posthog/posthog/feature-flags:master
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
init-assets:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
WRITE_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
READ_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PERSONS_WRITE_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
PERSONS_READ_DATABASE_URL: postgres://posthog:posthog@db:5432/posthog
|
||||
MAXMIND_DB_PATH: /share/GeoLite2-City.mmdb
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
ADDRESS: 0.0.0.0:3001
|
||||
RUST_LOG: info
|
||||
COOKIELESS_REDIS_HOST: redis7
|
||||
COOKIELESS_REDIS_PORT: 6379
|
||||
volumes:
|
||||
- geoip-data:/share:ro
|
||||
|
||||
# Rust hypercache server (/array remote config + /surveys)
|
||||
hypercache-server:
|
||||
image: ghcr.io/posthog/posthog/hypercache-server:master
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
redis7:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
REDIS_URL: redis://redis7:6379/
|
||||
ADDRESS: 0.0.0.0:3002
|
||||
RUST_LOG: info
|
||||
|
||||
# Live events stream (/livestream)
|
||||
livestream:
|
||||
image: ghcr.io/posthog/posthog/livestream:master
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
LIVESTREAM_JWT_SECRET: ${POSTHOG_SECRET_KEY}
|
||||
volumes:
|
||||
- ../files/livestream/configs.yml:/configs/configs.yml:ro
|
||||
|
||||
objectstorage:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
environment:
|
||||
MINIO_ROOT_USER: object_storage_root_user
|
||||
MINIO_ROOT_PASSWORD: object_storage_root_password
|
||||
entrypoint: sh
|
||||
command: -c 'mkdir -p /data/posthog && minio server --address ":19000" --console-address ":19001" /data'
|
||||
volumes:
|
||||
- objectstorage-data:/data
|
||||
|
||||
# Caddy routes the single public domain to the right internal service,
|
||||
# exactly like the upstream hobby deployment (TLS is terminated by Traefik)
|
||||
proxy:
|
||||
image: caddy:2.10-alpine
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
depends_on:
|
||||
web:
|
||||
condition: service_started
|
||||
entrypoint: sh
|
||||
command: -c 'printf "%s" "$$CADDYFILE" > /etc/caddy/Caddyfile && exec caddy run -c /etc/caddy/Caddyfile'
|
||||
environment:
|
||||
CADDYFILE: |
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
:80 {
|
||||
@capture path /e /e/ /e/* /i/v0 /i/v0/ /i/v0/* /i/v1/analytics/events /i/v1/analytics/events/ /batch /batch/ /batch/* /capture /capture/ /capture/*
|
||||
@replay-capture path /s /s/ /s/*
|
||||
@flags path /flags /flags/ /flags/* /api/feature_flag/local_evaluation /api/feature_flag/local_evaluation/ /api/feature_flag/local_evaluation/*
|
||||
@surveys path /surveys /surveys/ /api/surveys /api/surveys/
|
||||
@remote-config path /array/*
|
||||
@webhooks path /public/webhooks /public/webhooks/ /public/webhooks/* /public/m/ /public/m/*
|
||||
@livestream path /livestream /livestream/ /livestream/*
|
||||
@objectstorage path /posthog /posthog/ /posthog/*
|
||||
|
||||
handle @capture {
|
||||
reverse_proxy capture:3000
|
||||
}
|
||||
handle @replay-capture {
|
||||
reverse_proxy replay-capture:3000
|
||||
}
|
||||
handle @flags {
|
||||
reverse_proxy feature-flags:3001
|
||||
}
|
||||
handle @surveys {
|
||||
reverse_proxy hypercache-server:3002
|
||||
}
|
||||
handle @remote-config {
|
||||
reverse_proxy hypercache-server:3002
|
||||
}
|
||||
handle @webhooks {
|
||||
reverse_proxy plugins:6738
|
||||
}
|
||||
handle @livestream {
|
||||
uri strip_prefix /livestream
|
||||
reverse_proxy livestream:8080 {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
handle @objectstorage {
|
||||
reverse_proxy objectstorage:19000
|
||||
}
|
||||
handle {
|
||||
reverse_proxy web:8000
|
||||
}
|
||||
}
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
zookeeper-data:
|
||||
zookeeper-datalog:
|
||||
redpanda-data:
|
||||
clickhouse-data:
|
||||
posthog-assets:
|
||||
geoip-data:
|
||||
objectstorage-data:
|
||||
@@ -0,0 +1,27 @@
|
||||
# PostHog
|
||||
|
||||
Self-hosted PostHog based on the official "hobby" deployment, trimmed to run on a single server.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **RAM: at least 8 GB free** is strongly recommended (the stack runs Django, a Celery worker, two Node plugin-server processes, ClickHouse, Redpanda/Kafka, Postgres, Redis, MinIO and several small Rust services).
|
||||
- ~15 GB of free disk for the images and data volumes.
|
||||
|
||||
## First boot
|
||||
|
||||
The first deployment takes **5-10 minutes**: an init container fetches the ClickHouse configuration and the GeoIP database, and the `web` service runs all Postgres and ClickHouse migrations before it starts serving. Watch the `web` service logs; once you see the Unit server start, open the domain and create the first account.
|
||||
|
||||
## After enabling HTTPS
|
||||
|
||||
If you serve PostHog over HTTPS (recommended for production), update these environment variables and redeploy:
|
||||
|
||||
- `POSTHOG_SITE_URL` to `https://<your-domain>` (otherwise logins/CSRF and SDK snippets point at the wrong scheme)
|
||||
- `POSTHOG_SECURE_COOKIES` to `true` (marks session/CSRF cookies as Secure; the default `false` is what allows logging in over the initial plain-HTTP domain)
|
||||
|
||||
## What is not included
|
||||
|
||||
Compared to the full upstream hobby stack, this template omits Temporal + Elasticsearch (data warehouse / batch exports), browserless (dashboard image exports and heatmap screenshots), and the error-tracking/logs/traces ingestion pipelines. Product analytics, feature flags, experiments, surveys, session replay, live events and the CDP destinations pipeline all work.
|
||||
|
||||
## Versioning
|
||||
|
||||
PostHog no longer publishes tagged releases; images are pinned to a tested master commit via the `POSTHOG_VERSION` and `POSTHOG_NODE_VERSION` environment variables. To upgrade, set `POSTHOG_VERSION` to a newer commit sha of `posthog/posthog` (Docker Hub tag) and redeploy.
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "posthog",
|
||||
"name": "PostHog",
|
||||
"version": "master-2026-07-14",
|
||||
"description": "PostHog is an open-source product analytics platform with feature flags, session replay, surveys, A/B testing and a customer data pipeline, all in one tool.",
|
||||
"logo": "posthog.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/PostHog/posthog",
|
||||
"website": "https://posthog.com/",
|
||||
"docs": "https://posthog.com/docs/self-host"
|
||||
},
|
||||
"tags": [
|
||||
"analytics",
|
||||
"product-analytics",
|
||||
"feature-flags",
|
||||
"session-replay",
|
||||
"ab-testing"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="142px" viewBox="0 0 256 142" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<title>PostHog</title>
|
||||
<g>
|
||||
<path d="M0,112.012238 C0,107.259346 5.74647061,104.878898 9.10731886,108.239907 L33.5593478,132.692042 C36.9201961,136.053051 34.5399086,141.799308 29.7870161,141.799308 L5.33492848,141.799308 C2.38852989,141.799308 0,139.410858 0,136.464374 L0,112.012238 Z M0,86.2562448 C0,87.6710693 0.562072622,89.0282764 1.56256477,90.0285765 L51.7707846,140.236706 C52.7712447,141.237006 54.1282385,141.799308 55.5430629,141.799308 L83.1204028,141.799308 C87.8732953,141.799308 90.2537428,136.053051 86.8927345,132.692042 L9.10731886,54.9065733 C5.74647061,51.545565 0,53.9260124 0,58.678905 L0,86.2562448 Z M0,32.9228582 C0,34.3377359 0.562072622,35.6947297 1.56256477,36.6951899 L105.104065,140.236706 C106.104365,141.237006 107.461572,141.799308 108.876396,141.799308 L136.453736,141.799308 C141.206629,141.799308 143.587076,136.053051 140.226068,132.692042 L9.10731886,1.57329328 C5.74652396,-1.78750162 0,0.592732447 0,5.34562498 L0,32.9228582 Z M53.3333333,32.9228582 C53.3333333,34.3377359 53.8956354,35.6947297 54.8959354,36.6951899 L150.892734,132.692042 C154.253743,136.053051 160,133.672603 160,128.919711 L160,101.342371 C160,99.9275462 159.437698,98.5703391 158.437398,97.570039 L62.4405988,1.57323993 C59.0795905,-1.78755497 53.3333333,0.592732447 53.3333333,5.34562498 L53.3333333,32.9228582 Z M115.773932,1.57329328 C112.412924,-1.78755497 106.666667,0.592732447 106.666667,5.34562498 L106.666667,32.9228582 C106.666667,34.3377359 107.228969,35.6947297 108.229269,36.6951899 L150.892734,79.3587089 C154.253743,82.7197172 160,80.3392697 160,75.5863772 L160,48.0090373 C160,46.5942129 159.437698,45.2370057 158.437398,44.2367056 L115.773932,1.57329328 Z" fill="#F9BD2B"></path>
|
||||
<path d="M226.866993,112.666301 L176.650862,62.4496362 C173.289854,59.0886279 167.543596,61.4690753 167.543596,66.2219679 L167.543596,136.46384 C167.543596,139.410324 169.932046,141.798774 172.87853,141.798774 L250.665066,141.798774 C253.61155,141.798774 256,139.410324 256,136.46384 L256,130.067255 C256,127.120771 253.601414,124.768065 250.679471,124.387684 C241.713047,123.219867 233.325998,119.124772 226.866993,112.666301 Z M193.143276,124.732321 C188.433063,124.732321 184.61005,120.909307 184.61005,116.199094 C184.61005,111.488348 188.433063,107.665334 193.143276,107.665334 C197.854023,107.665334 201.677036,111.488348 201.677036,116.199094 C201.677036,120.909307 197.854023,124.732321 193.143276,124.732321 Z" fill="#000000"></path>
|
||||
<path d="M8.65608667,107.833264 L9.10731886,108.239373 L33.5594012,132.691509 C36.7740745,135.906386 34.7360374,141.303609 30.3932265,141.766855 L29.7870161,141.798774 L5.33493381,141.798774 C2.59898732,141.798774 0.344064852,139.739346 0.0358919824,137.086021 L0,136.46384 L0,112.011705 C0,107.46546 5.2576404,105.089845 8.65608667,107.833264 Z M8.65608667,54.5009977 L9.10731886,54.9071068 L53.3333333,99.1331746 L53.3333333,141.799841 L1.56256477,90.02911 C0.728821313,89.195082 0.199535861,88.1139429 0.0462669171,86.9579665 L0,86.2567783 L0,58.6794385 C0,54.1331934 5.2576404,51.7575787 8.65608667,54.5009977 Z M0,5.34562498 C0,0.799379948 5.2576404,-1.57608838 8.65608667,1.16720334 L9.10731886,1.57329328 L53.3333333,45.7993078 L53.3333333,88.4659744 L1.56256477,36.6951899 C0.728821313,35.8614731 0.199535861,34.7801635 0.0462669171,33.6240907 L0,32.9228582 L0,5.34562498 Z" fill="#1D4AFF"></path>
|
||||
<path d="M53.3333867,45.7998412 L105.104065,97.5705725 C105.938093,98.4041559 106.467214,99.4855914 106.620421,100.641679 L106.666667,101.342904 L106.666667,141.799841 L54.8959354,90.02911 C54.062352,89.195082 53.532971,88.1139429 53.3796658,86.9579665 L53.3333867,86.2567783 L53.3333867,45.7998412 Z M53.3333867,99.1321076 L86.8927345,132.691509 C90.107612,135.906386 88.0694373,141.303609 83.7266141,141.766855 L83.1204028,141.798774 L53.3333867,141.798774 L53.3333867,99.1321076 Z M61.9894118,1.16720334 L62.4405988,1.57329328 L105.104065,44.2367056 C105.938093,45.070289 106.467214,46.1517246 106.620421,47.3078122 L106.666667,48.0090373 L106.666667,88.4659744 L54.8959354,36.6951899 C54.062352,35.8614731 53.532971,34.7801635 53.3796658,33.6240907 L53.3333867,32.9228582 L53.3333867,5.34562498 C53.3333867,0.799379948 58.5912711,-1.57608838 61.9894118,1.16720334 Z" fill="#F54E00"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,73 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
secret_key = "${password:64}"
|
||||
salt_keys = "${hash:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "proxy"
|
||||
port = 80
|
||||
host = "${main_domain}"
|
||||
path = "/"
|
||||
|
||||
[config.env]
|
||||
# PostHog stopped publishing tagged releases; images are pinned to a tested
|
||||
# master commit. POSTHOG_VERSION is both the posthog/posthog image tag and the
|
||||
# git ref used to fetch the matching ClickHouse configuration.
|
||||
POSTHOG_VERSION = "1e7b36ebe0da38a738fed250d175809a899ef422"
|
||||
POSTHOG_NODE_VERSION = "sha-82ea668"
|
||||
# Set to https://<your-domain> after enabling HTTPS on the domain
|
||||
POSTHOG_SITE_URL = "http://${main_domain}"
|
||||
# Set to "true" once the domain uses HTTPS (marks session/CSRF cookies Secure)
|
||||
POSTHOG_SECURE_COOKIES = "false"
|
||||
POSTHOG_SECRET_KEY = "${secret_key}"
|
||||
POSTHOG_ENCRYPTION_SALT_KEYS = "${salt_keys}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/db-init/create-extra-dbs.sh"
|
||||
content = """
|
||||
#!/bin/bash
|
||||
# Creates the auxiliary databases PostHog services can point at
|
||||
# (mirrors docker/postgres-init-scripts in the PostHog repo)
|
||||
set -e
|
||||
set -u
|
||||
|
||||
for db in cyclotron posthog_persons behavioral_cohorts; do
|
||||
DB_EXISTS=$(psql -U "$POSTGRES_USER" -tAc "SELECT 1 FROM pg_database WHERE datname='$db'")
|
||||
if [ -z "$DB_EXISTS" ]; then
|
||||
echo "Creating database '$db'..."
|
||||
psql -U "$POSTGRES_USER" -c "CREATE DATABASE $db;"
|
||||
psql -U "$POSTGRES_USER" -c "GRANT ALL PRIVILEGES ON DATABASE $db TO $POSTGRES_USER;"
|
||||
fi
|
||||
done
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/livestream/configs.yml"
|
||||
content = """
|
||||
debug: false
|
||||
kafka:
|
||||
brokers: 'kafka:9092'
|
||||
topic: 'events_plugin_ingestion'
|
||||
group_id: 'livestream'
|
||||
security_protocol: 'PLAINTEXT'
|
||||
session_recording_enabled: true
|
||||
session_recording_security_protocol: 'PLAINTEXT'
|
||||
consumers:
|
||||
event:
|
||||
enabled: true
|
||||
brokers: 'kafka:9092'
|
||||
topic: 'events_plugin_ingestion'
|
||||
security_protocol: 'PLAINTEXT'
|
||||
group_id: 'livestream'
|
||||
session_recording:
|
||||
enabled: true
|
||||
brokers: 'kafka:9092'
|
||||
topic: 'session_recording_snapshot_item_events'
|
||||
security_protocol: 'PLAINTEXT'
|
||||
group_id: 'livestream-session-recordings'
|
||||
notification:
|
||||
enabled: false
|
||||
mmdb:
|
||||
path: 'GeoLite2-City.mmdb'
|
||||
"""
|
||||
@@ -1,13 +1,13 @@
|
||||
# https://www.rybbit.io/docs/self-hosting-advanced
|
||||
|
||||
# NOTE: there are two sample HTTP traefik domain entries created:
|
||||
# - rybbit_backend (port 3001, path /api),
|
||||
# - rybbit_backend (port 3001, path /api),
|
||||
# - rybbit_client (port 3002, path /)
|
||||
#
|
||||
# You should treat these as placeholders - Rybbit only supports HTTPS.
|
||||
#
|
||||
# You should also update the `BASE_URL`, and `DOMAIN_NAME` environment
|
||||
# variable when updating the domain entries with your custom domain.
|
||||
# You should also update the `BASE_URL` environment variable when
|
||||
# updating the domain entries with your custom domain.
|
||||
|
||||
services:
|
||||
rybbit_clickhouse:
|
||||
@@ -50,8 +50,41 @@ services:
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
|
||||
rybbit_redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
# Backs session tracking and the BullMQ queues. noeviction is required by
|
||||
# BullMQ (evicting job keys corrupts queues); AOF (everysec) keeps queues
|
||||
# durable across restarts.
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- ${REDIS_PASSWORD}
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --appendfsync
|
||||
- everysec
|
||||
- --maxmemory-policy
|
||||
- noeviction
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"redis-cli",
|
||||
"-a",
|
||||
"${REDIS_PASSWORD}",
|
||||
"--no-auth-warning",
|
||||
"ping",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
restart: unless-stopped
|
||||
|
||||
rybbit_backend:
|
||||
image: ghcr.io/rybbit-io/rybbit-backend:v1.5.1
|
||||
image: ghcr.io/rybbit-io/rybbit-backend:v2.7.0
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- CLICKHOUSE_HOST=http://rybbit_clickhouse:8123
|
||||
@@ -63,15 +96,20 @@ services:
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- REDIS_HOST=rybbit_redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- BASE_URL=${BASE_URL}
|
||||
- DOMAIN_NAME=${DOMAIN_NAME}
|
||||
- DISABLE_SIGNUP=${DISABLE_SIGNUP}
|
||||
- DISABLE_TELEMETRY=${DISABLE_TELEMETRY}
|
||||
depends_on:
|
||||
rybbit_clickhouse:
|
||||
condition: service_healthy
|
||||
rybbit_postgres:
|
||||
condition: service_started
|
||||
rybbit_redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3001/api/health"]
|
||||
interval: 30s
|
||||
@@ -81,11 +119,10 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
rybbit_client:
|
||||
image: ghcr.io/rybbit-io/rybbit-client:v1.5.1
|
||||
image: ghcr.io/rybbit-io/rybbit-client:v2.7.0
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
|
||||
- DOMAIN_NAME=${DOMAIN_NAME}
|
||||
- NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_SIGNUP}
|
||||
depends_on:
|
||||
- rybbit_backend
|
||||
@@ -94,3 +131,4 @@ services:
|
||||
volumes:
|
||||
clickhouse_data:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "rybbit",
|
||||
"name": "Rybbit",
|
||||
"version": "v1.5.1",
|
||||
"version": "v2.7.0",
|
||||
"description": "Open-source and privacy-friendly alternative to Google Analytics that is 10x more intuitive",
|
||||
"logo": "rybbit.png",
|
||||
"links": {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
better_auth_secret = "${password:32}"
|
||||
better_auth_secret = "${base64:32}"
|
||||
clickhouse_password = "${password:32}"
|
||||
postgres_password = "${password:32}"
|
||||
redis_password = "${password:32}"
|
||||
|
||||
[[config.domains]]
|
||||
serviceName = "rybbit_backend"
|
||||
@@ -17,15 +18,16 @@ host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
BASE_URL = "http://${main_domain}"
|
||||
DOMAIN_NAME= "${main_domain}"
|
||||
BETTER_AUTH_SECRET = "${better_auth_secret}"
|
||||
DISABLE_SIGNUP = "false"
|
||||
DISABLE_TELEMETRY = "false"
|
||||
CLICKHOUSE_DB = "analytics"
|
||||
CLICKHOUSE_USER = "default"
|
||||
CLICKHOUSE_PASSWORD = "${clickhouse_password}"
|
||||
POSTGRES_DB = "analytics"
|
||||
POSTGRES_USER = "frog"
|
||||
POSTGRES_PASSWORD = "${postgres_password}"
|
||||
REDIS_PASSWORD = "${redis_password}"
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "./clickhouse_config/enable_json.xml"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
x-common: &common
|
||||
networks:
|
||||
- signoz-net
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
options:
|
||||
@@ -89,11 +87,12 @@ services:
|
||||
- ZOO_PROMETHEUS_METRICS_PORT_NUMBER=9141
|
||||
clickhouse:
|
||||
!!merge <<: *clickhouse-defaults
|
||||
container_name: signoz-clickhouse
|
||||
hostname: clickhouse
|
||||
volumes:
|
||||
- ../files/clickhouse/config.xml:/etc/clickhouse-server/config.xml
|
||||
- ../files/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
- ../files/clickhouse/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
|
||||
- ../files/clickhouse/docker_related_config.xml:/etc/clickhouse-server/config.d/docker_related_config.xml
|
||||
- clickhouse:/var/lib/clickhouse/
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
@@ -156,9 +155,6 @@ services:
|
||||
- --dsn=tcp://clickhouse:9000
|
||||
- --up=
|
||||
restart: on-failure
|
||||
networks:
|
||||
signoz-net:
|
||||
name: signoz-net
|
||||
volumes:
|
||||
clickhouse:
|
||||
name: signoz-clickhouse
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
otel_domain = "${domain}"
|
||||
jwt_secret = "${password:64}"
|
||||
|
||||
[config]
|
||||
@@ -12,7 +13,7 @@ path = "/"
|
||||
[[config.domains]]
|
||||
serviceName = "otel-collector"
|
||||
port = 4318
|
||||
host = "${main_domain}"
|
||||
host = "${otel_domain}"
|
||||
path = "/"
|
||||
|
||||
[config.env]
|
||||
@@ -1246,7 +1247,23 @@ content = """
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/signoz/prometheus.xml"
|
||||
filePath = "/clickhouse/docker_related_config.xml"
|
||||
content = """
|
||||
<?xml version="1.0"?>
|
||||
<clickhouse>
|
||||
<!-- Overrides the docker image's default config.d/docker_related_config.xml,
|
||||
which listens on the IPv6 wildcard address ([::]) first. On hosts without
|
||||
IPv6 support the bind fails, so listen on IPv4 only. -->
|
||||
<listen_host>0.0.0.0</listen_host>
|
||||
<listen_try>1</listen_try>
|
||||
<!-- Container IPs can change and DNS records (zookeeper-1, clickhouse) may not
|
||||
be ready the moment ClickHouse starts. Do not cache DNS lookup failures. -->
|
||||
<disable_internal_dns_cache>1</disable_internal_dns_cache>
|
||||
</clickhouse>
|
||||
"""
|
||||
|
||||
[[config.mounts]]
|
||||
filePath = "/signoz/prometheus.yml"
|
||||
content = """
|
||||
# my global config
|
||||
global:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
silex:
|
||||
image: silexlabs/silex:3.7.0
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 6805
|
||||
environment:
|
||||
- SILEX_URL=${SILEX_URL}
|
||||
- SILEX_SESSION_SECRET=${SILEX_SESSION_SECRET}
|
||||
- STORAGE_CONNECTORS=fs
|
||||
- HOSTING_CONNECTORS=fs,download
|
||||
- SILEX_FS_ROOT=/silex/silex/storage
|
||||
- SILEX_FS_HOSTING_ROOT=/silex/silex/hosting
|
||||
volumes:
|
||||
- silex-storage:/silex/silex/storage
|
||||
- silex-hosting:/silex/silex/hosting
|
||||
|
||||
volumes:
|
||||
silex-storage:
|
||||
silex-hosting:
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "silex",
|
||||
"name": "Silex",
|
||||
"version": "3.7.0",
|
||||
"description": "Silex is a free and open-source website builder for professional designers. It lets you create static websites visually with a drag-and-drop editor, custom CSS and JavaScript, and publish them anywhere.",
|
||||
"logo": "logo.png",
|
||||
"links": {
|
||||
"github": "https://github.com/silexlabs/Silex",
|
||||
"website": "https://www.silex.me",
|
||||
"docs": "https://docs.silex.me"
|
||||
},
|
||||
"tags": [
|
||||
"website-builder",
|
||||
"no-code",
|
||||
"static-site"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[variables]
|
||||
main_domain = "${domain}"
|
||||
session_secret = "${password:32}"
|
||||
|
||||
[config]
|
||||
[[config.domains]]
|
||||
serviceName = "silex"
|
||||
port = 6805
|
||||
host = "${main_domain}"
|
||||
|
||||
[config.env]
|
||||
SILEX_URL = "http://${main_domain}"
|
||||
SILEX_SESSION_SECRET = "${session_secret}"
|
||||
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
simstudio:
|
||||
image: ghcr.io/simstudioai/simstudio:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
|
||||
- BETTER_AUTH_URL=${SIM_APP_URL}
|
||||
- NEXT_PUBLIC_APP_URL=${SIM_APP_URL}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
||||
- API_ENCRYPTION_KEY=${API_ENCRYPTION_KEY}
|
||||
- INTERNAL_API_SECRET=${INTERNAL_API_SECRET}
|
||||
- SOCKET_SERVER_URL=http://realtime:3002
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
migrations:
|
||||
condition: service_completed_successfully
|
||||
realtime:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3000"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
realtime:
|
||||
image: ghcr.io/simstudioai/realtime:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
|
||||
- NEXT_PUBLIC_APP_URL=${SIM_APP_URL}
|
||||
- BETTER_AUTH_URL=${SIM_APP_URL}
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
- INTERNAL_API_SECRET=${INTERNAL_API_SECRET}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:3002/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
|
||||
migrations:
|
||||
image: ghcr.io/simstudioai/migrations:latest
|
||||
working_dir: /app/packages/db
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/simstudio
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
command: ["bun", "run", "db:migrate"]
|
||||
restart: "no"
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg17
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=simstudio
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "sim",
|
||||
"name": "Sim",
|
||||
"version": "latest",
|
||||
"description": "Open-source visual workflow builder for building and deploying AI agent workflows.",
|
||||
"logo": "sim.svg",
|
||||
"links": {
|
||||
"github": "https://github.com/simstudioai/sim",
|
||||
"website": "https://sim.ai",
|
||||
"docs": "https://docs.sim.ai"
|
||||
},
|
||||
"tags": [
|
||||
"ai",
|
||||
"automation",
|
||||
"workflow"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.4 KiB |