diff --git a/apps/dokploy/components/layouts/dashboard-layout.tsx b/apps/dokploy/components/layouts/dashboard-layout.tsx
index 8dd7e599..451ec45d 100644
--- a/apps/dokploy/components/layouts/dashboard-layout.tsx
+++ b/apps/dokploy/components/layouts/dashboard-layout.tsx
@@ -1,25 +1,34 @@
+import Head from "next/head";
import { Navbar } from "./navbar";
import { NavigationTabs, type TabState } from "./navigation-tabs";
interface Props {
children: React.ReactNode;
tab: TabState;
+ metaName?: string;
}
-export const DashboardLayout = ({ children, tab }: Props) => {
+export const DashboardLayout = ({ children, tab, metaName }: Props) => {
return (
-
-
-
-
-
- {children}
-
-
+ <>
+
+
+ {metaName ?? tab.charAt(0).toUpperCase() + tab.slice(1)} | Dokploy
+
+
+
-
+ >
);
};
diff --git a/apps/dokploy/pages/dashboard/project/[projectId].tsx b/apps/dokploy/pages/dashboard/project/[projectId].tsx
index 82b43ae5..5fd8bd62 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId].tsx
@@ -39,6 +39,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { type ReactElement } from "react";
@@ -189,6 +190,9 @@ const Project = (
{data?.name}
+
+
Project {data?.name} | Dokploy
+
{data?.name}
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
index bcbd4b78..1682348c 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/application/[applicationId].tsx
@@ -41,6 +41,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -101,6 +102,11 @@ const Service = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
index 60ddfeab..6b9cae51 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/compose/[composeId].tsx
@@ -35,6 +35,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -94,6 +95,11 @@ const Service = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
index 31e92976..0a5cfed8 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mariadb/[mariadbId].tsx
@@ -35,6 +35,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -82,6 +83,11 @@ const Mariadb = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
index 409d9129..fcf39eff 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mongo/[mongoId].tsx
@@ -35,6 +35,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -83,6 +84,11 @@ const Mongo = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
index e69c5a35..4d155e64 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/mysql/[mysqlId].tsx
@@ -35,6 +35,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -81,6 +82,11 @@ const MySql = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
index 3e1e3ea8..c7152dd5 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/postgres/[postgresId].tsx
@@ -35,6 +35,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -82,6 +83,11 @@ const Postgresql = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
index b68baeab..f4bf3743 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/services/redis/[redisId].tsx
@@ -34,6 +34,7 @@ import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
+import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { useState, type ReactElement } from "react";
@@ -81,6 +82,11 @@ const Redis = (
{data?.name}
+
+
+ Project {data?.project.name} | {data?.name} | Dokploy
+
+
diff --git a/apps/dokploy/pages/dashboard/settings/appearance.tsx b/apps/dokploy/pages/dashboard/settings/appearance.tsx
index f7f49746..51c61fc7 100644
--- a/apps/dokploy/pages/dashboard/settings/appearance.tsx
+++ b/apps/dokploy/pages/dashboard/settings/appearance.tsx
@@ -21,7 +21,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/billing.tsx b/apps/dokploy/pages/dashboard/settings/billing.tsx
index 4aa7682c..191f25e2 100644
--- a/apps/dokploy/pages/dashboard/settings/billing.tsx
+++ b/apps/dokploy/pages/dashboard/settings/billing.tsx
@@ -16,7 +16,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/certificates.tsx b/apps/dokploy/pages/dashboard/settings/certificates.tsx
index e64db687..c648ad38 100644
--- a/apps/dokploy/pages/dashboard/settings/certificates.tsx
+++ b/apps/dokploy/pages/dashboard/settings/certificates.tsx
@@ -19,7 +19,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/cluster.tsx b/apps/dokploy/pages/dashboard/settings/cluster.tsx
index 3320a9c3..f01294cd 100644
--- a/apps/dokploy/pages/dashboard/settings/cluster.tsx
+++ b/apps/dokploy/pages/dashboard/settings/cluster.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/destinations.tsx b/apps/dokploy/pages/dashboard/settings/destinations.tsx
index fdf91ca1..9653d4a1 100644
--- a/apps/dokploy/pages/dashboard/settings/destinations.tsx
+++ b/apps/dokploy/pages/dashboard/settings/destinations.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/git-providers.tsx b/apps/dokploy/pages/dashboard/settings/git-providers.tsx
index b5862eb9..cab53928 100644
--- a/apps/dokploy/pages/dashboard/settings/git-providers.tsx
+++ b/apps/dokploy/pages/dashboard/settings/git-providers.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/notifications.tsx b/apps/dokploy/pages/dashboard/settings/notifications.tsx
index d783355e..cdc47fc0 100644
--- a/apps/dokploy/pages/dashboard/settings/notifications.tsx
+++ b/apps/dokploy/pages/dashboard/settings/notifications.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/profile.tsx b/apps/dokploy/pages/dashboard/settings/profile.tsx
index a645a4af..87a285b1 100644
--- a/apps/dokploy/pages/dashboard/settings/profile.tsx
+++ b/apps/dokploy/pages/dashboard/settings/profile.tsx
@@ -33,7 +33,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/registry.tsx b/apps/dokploy/pages/dashboard/settings/registry.tsx
index 16fded94..7cadd495 100644
--- a/apps/dokploy/pages/dashboard/settings/registry.tsx
+++ b/apps/dokploy/pages/dashboard/settings/registry.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/server.tsx b/apps/dokploy/pages/dashboard/settings/server.tsx
index c1d3d548..2ea721fc 100644
--- a/apps/dokploy/pages/dashboard/settings/server.tsx
+++ b/apps/dokploy/pages/dashboard/settings/server.tsx
@@ -23,7 +23,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/servers.tsx b/apps/dokploy/pages/dashboard/settings/servers.tsx
index a7c104a1..7aea531b 100644
--- a/apps/dokploy/pages/dashboard/settings/servers.tsx
+++ b/apps/dokploy/pages/dashboard/settings/servers.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
index 55b97976..808e4e8c 100644
--- a/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
+++ b/apps/dokploy/pages/dashboard/settings/ssh-keys.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/pages/dashboard/settings/users.tsx b/apps/dokploy/pages/dashboard/settings/users.tsx
index f8628459..d1b598e4 100644
--- a/apps/dokploy/pages/dashboard/settings/users.tsx
+++ b/apps/dokploy/pages/dashboard/settings/users.tsx
@@ -20,7 +20,7 @@ export default Page;
Page.getLayout = (page: ReactElement) => {
return (
-
+
{page}
);
diff --git a/apps/dokploy/public/templates/huly.svg b/apps/dokploy/public/templates/huly.svg
new file mode 100644
index 00000000..55a98587
--- /dev/null
+++ b/apps/dokploy/public/templates/huly.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/langflow.svg b/apps/dokploy/public/templates/langflow.svg
new file mode 100644
index 00000000..3665f824
--- /dev/null
+++ b/apps/dokploy/public/templates/langflow.svg
@@ -0,0 +1,6 @@
+
diff --git a/apps/dokploy/public/templates/penpot.svg b/apps/dokploy/public/templates/penpot.svg
new file mode 100644
index 00000000..05454092
--- /dev/null
+++ b/apps/dokploy/public/templates/penpot.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/dokploy/public/templates/unsend.png b/apps/dokploy/public/templates/unsend.png
new file mode 100644
index 00000000..0bbe5e0f
Binary files /dev/null and b/apps/dokploy/public/templates/unsend.png differ
diff --git a/apps/dokploy/templates/huly/docker-compose.yml b/apps/dokploy/templates/huly/docker-compose.yml
new file mode 100644
index 00000000..471ee7e9
--- /dev/null
+++ b/apps/dokploy/templates/huly/docker-compose.yml
@@ -0,0 +1,184 @@
+name: ${DOCKER_NAME}
+version: "3"
+services:
+ nginx:
+ networks:
+ - dokploy-network
+ image: "nginx:1.21.3"
+ ports:
+ - 80
+ volumes:
+ - ../files/volumes/nginx/.huly.nginx:/etc/nginx/conf.d/default.conf
+ restart: unless-stopped
+
+ mongodb:
+ networks:
+ - dokploy-network
+ image: "mongo:7-jammy"
+ environment:
+ - PUID=1000
+ - PGID=1000
+ volumes:
+ - db:/data/db
+ restart: unless-stopped
+
+ minio:
+ networks:
+ - dokploy-network
+ image: "minio/minio:RELEASE.2024-11-07T00-52-20Z"
+ command: server /data --address ":9000" --console-address ":9001"
+ volumes:
+ - files:/data
+ restart: unless-stopped
+
+ elastic:
+ networks:
+ - dokploy-network
+ image: "elasticsearch:7.14.2"
+ command: |
+ /bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment;
+ /usr/local/bin/docker-entrypoint.sh eswrapper"
+ volumes:
+ - elastic:/usr/share/elasticsearch/data
+ environment:
+ - ELASTICSEARCH_PORT_NUMBER=9200
+ - BITNAMI_DEBUG=true
+ - discovery.type=single-node
+ - ES_JAVA_OPTS=-Xms1024m -Xmx1024m
+ - http.cors.enabled=true
+ - http.cors.allow-origin=http://localhost:8082
+ healthcheck:
+ interval: 20s
+ retries: 10
+ test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
+ restart: unless-stopped
+
+ rekoni:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/rekoni-service:${HULY_VERSION}
+ environment:
+ - SECRET=${SECRET}
+ deploy:
+ resources:
+ limits:
+ memory: 500M
+ restart: unless-stopped
+
+ transactor:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/transactor:${HULY_VERSION}
+ environment:
+ - SERVER_PORT=3333
+ - SERVER_SECRET=${SECRET}
+ - SERVER_CURSOR_MAXTIMEMS=30000
+ - DB_URL=mongodb://mongodb:27017
+ - MONGO_URL=mongodb://mongodb:27017
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ - FRONT_URL=http://localhost:8087
+ - ACCOUNTS_URL=http://account:3000
+ - FULLTEXT_URL=http://fulltext:4700
+ - STATS_URL=http://stats:4900
+ - LAST_NAME_FIRST=${LAST_NAME_FIRST:-true}
+ restart: unless-stopped
+
+ collaborator:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/collaborator:${HULY_VERSION}
+ environment:
+ - COLLABORATOR_PORT=3078
+ - SECRET=${SECRET}
+ - ACCOUNTS_URL=http://account:3000
+ - DB_URL=mongodb://mongodb:27017
+ - STATS_URL=http://stats:4900
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ restart: unless-stopped
+
+ account:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/account:${HULY_VERSION}
+ environment:
+ - SERVER_PORT=3000
+ - SERVER_SECRET=${SECRET}
+ - DB_URL=mongodb://mongodb:27017
+ - MONGO_URL=mongodb://mongodb:27017
+ - TRANSACTOR_URL=ws://transactor:3333;ws${SECURE:+s}://${HOST_ADDRESS}/_transactor
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ - FRONT_URL=http://front:8080
+ - STATS_URL=http://stats:4900
+ - MODEL_ENABLED=*
+ - ACCOUNTS_URL=http://localhost:3000
+ - ACCOUNT_PORT=3000
+ restart: unless-stopped
+
+ workspace:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/workspace:${HULY_VERSION}
+ environment:
+ - SERVER_SECRET=${SECRET}
+ - DB_URL=mongodb://mongodb:27017
+ - MONGO_URL=mongodb://mongodb:27017
+ - TRANSACTOR_URL=ws://transactor:3333;ws${SECURE:+s}://${HOST_ADDRESS}/_transactor
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ - MODEL_ENABLED=*
+ - ACCOUNTS_URL=http://account:3000
+ - STATS_URL=http://stats:4900
+ restart: unless-stopped
+
+ front:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/front:${HULY_VERSION}
+ environment:
+ - SERVER_PORT=8080
+ - SERVER_SECRET=${SECRET}
+ - LOVE_ENDPOINT=http${SECURE:+s}://${HOST_ADDRESS}/_love
+ - ACCOUNTS_URL=http${SECURE:+s}://${HOST_ADDRESS}/_accounts
+ - REKONI_URL=http${SECURE:+s}://${HOST_ADDRESS}/_rekoni
+ - CALENDAR_URL=http${SECURE:+s}://${HOST_ADDRESS}/_calendar
+ - GMAIL_URL=http${SECURE:+s}://${HOST_ADDRESS}/_gmail
+ - TELEGRAM_URL=http${SECURE:+s}://${HOST_ADDRESS}/_telegram
+ - STATS_URL=http${SECURE:+s}://${HOST_ADDRESS}/_stats
+ - UPLOAD_URL=/files
+ - ELASTIC_URL=http://elastic:9200
+ - COLLABORATOR_URL=ws${SECURE:+s}://${HOST_ADDRESS}/_collaborator
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ - DB_URL=mongodb://mongodb:27017
+ - MONGO_URL=mongodb://mongodb:27017
+ - TITLE=${TITLE:-Huly Self Host}
+ - DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE:-en}
+ - LAST_NAME_FIRST=${LAST_NAME_FIRST:-true}
+ - DESKTOP_UPDATES_CHANNEL=selfhost
+ restart: unless-stopped
+
+ fulltext:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/fulltext:${HULY_VERSION}
+ environment:
+ - SERVER_SECRET=${SECRET}
+ - DB_URL=mongodb://mongodb:27017
+ - FULLTEXT_DB_URL=http://elastic:9200
+ - ELASTIC_INDEX_NAME=huly_storage_index
+ - STORAGE_CONFIG=minio|minio?accessKey=minioadmin&secretKey=minioadmin
+ - REKONI_URL=http://rekoni:4004
+ - ACCOUNTS_URL=http://account:3000
+ - STATS_URL=http://stats:4900
+ restart: unless-stopped
+
+ stats:
+ networks:
+ - dokploy-network
+ image: hardcoreeng/stats:${HULY_VERSION}
+ environment:
+ - PORT=4900
+ - SERVER_SECRET=${SECRET}
+ restart: unless-stopped
+volumes:
+ db:
+ elastic:
+ files:
diff --git a/apps/dokploy/templates/huly/index.ts b/apps/dokploy/templates/huly/index.ts
new file mode 100644
index 00000000..deb5c7db
--- /dev/null
+++ b/apps/dokploy/templates/huly/index.ts
@@ -0,0 +1,152 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const hulySecret = generateBase64(64);
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 80,
+ serviceName: "nginx",
+ },
+ ];
+
+ const envs = [
+ "HULY_VERSION=v0.6.377",
+ "DOCKER_NAME=huly",
+ "",
+ "# The address of the host or server from which you will access your Huly instance.",
+ "# This can be a domain name (e.g., huly.example.com) or an IP address (e.g., 192.168.1.1).",
+ `HOST_ADDRESS=${mainDomain}`,
+ "",
+ "# Set this variable to 'true' to enable SSL (HTTPS/WSS). ",
+ "# Leave it empty to use non-SSL (HTTP/WS).",
+ "SECURE=",
+ "",
+ "# Specify the IP address to bind to; leave blank to bind to all interfaces (0.0.0.0).",
+ "# Do not use IP:PORT format in HTTP_BIND or HTTP_PORT.",
+ "HTTP_PORT=80",
+ "HTTP_BIND=",
+ "",
+ "# Huly specific variables",
+ "TITLE=Huly",
+ "DEFAULT_LANGUAGE=en",
+ "LAST_NAME_FIRST=true",
+ "",
+ "# The following configs are auto-generated by the setup script. ",
+ "# Please do not manually overwrite.",
+ "",
+ "# Run with --secret to regenerate.",
+ `SECRET=${hulySecret}`,
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/volumes/nginx/.huly.nginx",
+ content: `server {
+ listen 80;
+ server_name _;
+ location / {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_pass http://front:8080;
+ }
+
+ location /_accounts {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ rewrite ^/_accounts(/.*)$ $1 break;
+ proxy_pass http://account:3000/;
+ }
+
+ #location /_love {
+ # proxy_set_header Host $host;
+ # proxy_set_header X-Real-IP $remote_addr;
+ # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # proxy_set_header X-Forwarded-Proto $scheme;
+
+ # proxy_http_version 1.1;
+ # proxy_set_header Upgrade $http_upgrade;
+ # proxy_set_header Connection "upgrade";
+ # rewrite ^/_love(/.*)$ $1 break;
+ # proxy_pass http://love:8096/;
+ #}
+
+ location /_collaborator {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ rewrite ^/_collaborator(/.*)$ $1 break;
+ proxy_pass http://collaborator:3078/;
+ }
+
+ location /_transactor {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ rewrite ^/_transactor(/.*)$ $1 break;
+ proxy_pass http://transactor:3333/;
+ }
+
+ location ~ ^/eyJ {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_pass http://transactor:3333;
+ }
+
+ location /_rekoni {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ rewrite ^/_rekoni(/.*)$ $1 break;
+ proxy_pass http://rekoni:4004/;
+ }
+
+ location /_stats {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ rewrite ^/_stats(/.*)$ $1 break;
+ proxy_pass http://stats:4900/;
+ }
+}`,
+ },
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/langflow/docker-compose.yml b/apps/dokploy/templates/langflow/docker-compose.yml
new file mode 100644
index 00000000..75bb73dd
--- /dev/null
+++ b/apps/dokploy/templates/langflow/docker-compose.yml
@@ -0,0 +1,33 @@
+version: "3.8"
+
+services:
+ langflow:
+ image: langflowai/langflow:v1.1.1
+ ports:
+ - 7860
+ depends_on:
+ - postgres-langflow
+ environment:
+ - LANGFLOW_DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres-langflow:5432/langflow
+ # This variable defines where the logs, file storage, monitor data and secret keys are stored.
+ volumes:
+ - langflow-data:/app/langflow
+ networks:
+ - dokploy-network
+
+ postgres-langflow:
+ image: postgres:16
+ environment:
+ POSTGRES_USER: ${DB_USERNAME}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ POSTGRES_DB: langflow
+ ports:
+ - 5432
+ volumes:
+ - langflow-postgres:/var/lib/postgresql/data
+ networks:
+ - dokploy-network
+
+volumes:
+ langflow-postgres:
+ langflow-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/langflow/index.ts b/apps/dokploy/templates/langflow/index.ts
new file mode 100644
index 00000000..75f6db58
--- /dev/null
+++ b/apps/dokploy/templates/langflow/index.ts
@@ -0,0 +1,28 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const dbPassword = generatePassword();
+ const dbUsername = "langflow";
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 7860,
+ serviceName: "langflow",
+ },
+ ];
+
+ const envs = [`DB_PASSWORD=${dbPassword}`, `DB_USERNAME=${dbUsername}`];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/penpot/docker-compose.yml b/apps/dokploy/templates/penpot/docker-compose.yml
new file mode 100644
index 00000000..55abdb53
--- /dev/null
+++ b/apps/dokploy/templates/penpot/docker-compose.yml
@@ -0,0 +1,213 @@
+## Common flags:
+# demo-users
+# email-verification
+# log-emails
+# log-invitation-tokens
+# login-with-github
+# login-with-gitlab
+# login-with-google
+# login-with-ldap
+# login-with-oidc
+# login-with-password
+# prepl-server
+# registration
+# secure-session-cookies
+# smtp
+# smtp-debug
+# telemetry
+# webhooks
+##
+## You can read more about all available flags and other
+## environment variables here:
+## https://help.penpot.app/technical-guide/configuration/#advanced-configuration
+#
+# WARNING: if you're exposing Penpot to the internet, you should remove the flags
+# 'disable-secure-session-cookies' and 'disable-email-verification'
+
+volumes:
+ penpot_postgres_v15:
+ penpot_assets:
+ penpot_traefik:
+ # penpot_minio:
+
+services:
+
+ penpot-frontend:
+ image: "penpotapp/frontend:2.3.2"
+ restart: always
+ ports:
+ - 8080
+ - 9001
+
+ volumes:
+ - penpot_assets:/opt/data/assets
+
+ depends_on:
+ - penpot-backend
+ - penpot-exporter
+
+ networks:
+ - dokploy-network
+
+ environment:
+ PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies
+
+ penpot-backend:
+ image: "penpotapp/backend:2.3.2"
+ restart: always
+
+ volumes:
+ - penpot_assets:/opt/data/assets
+
+ depends_on:
+ - penpot-postgres
+ - penpot-redis
+
+ networks:
+ - dokploy-network
+
+ ## Configuration envronment variables for the backend
+ ## container.
+
+ environment:
+ PENPOT_PUBLIC_URI: http://${DOMAIN_NAME}
+ PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies
+
+ ## Penpot SECRET KEY. It serves as a master key from which other keys for subsystems
+ ## (eg http sessions, or invitations) are derived.
+ ##
+ ## If you leave it commented, all created sessions and invitations will
+ ## become invalid on container restart.
+ ##
+ ## If you going to uncomment this, we recommend to use a trully randomly generated
+ ## 512 bits base64 encoded string here. You can generate one with:
+ ##
+ ## python3 -c "import secrets; print(secrets.token_urlsafe(64))"
+
+ # PENPOT_SECRET_KEY: my-insecure-key
+
+ ## The PREPL host. Mainly used for external programatic access to penpot backend
+ ## (example: admin). By default it will listen on `localhost` but if you are going to use
+ ## the `admin`, you will need to uncomment this and set the host to `0.0.0.0`.
+
+ # PENPOT_PREPL_HOST: 0.0.0.0
+
+ ## Database connection parameters. Don't touch them unless you are using custom
+ ## postgresql connection parameters.
+
+ PENPOT_DATABASE_URI: postgresql://penpot-postgres/penpot
+ PENPOT_DATABASE_USERNAME: penpot
+ PENPOT_DATABASE_PASSWORD: penpot
+
+ ## Redis is used for the websockets notifications. Don't touch unless the redis
+ ## container has different parameters or different name.
+
+ PENPOT_REDIS_URI: redis://penpot-redis/0
+
+ ## Default configuration for assets storage: using filesystem based with all files
+ ## stored in a docker volume.
+
+ PENPOT_ASSETS_STORAGE_BACKEND: assets-fs
+ PENPOT_STORAGE_ASSETS_FS_DIRECTORY: /opt/data/assets
+
+ ## Also can be configured to to use a S3 compatible storage
+ ## service like MiniIO. Look below for minio service setup.
+
+ # AWS_ACCESS_KEY_ID:
+ # AWS_SECRET_ACCESS_KEY:
+ # PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
+ # PENPOT_STORAGE_ASSETS_S3_ENDPOINT: http://penpot-minio:9000
+ # PENPOT_STORAGE_ASSETS_S3_BUCKET:
+
+ ## Telemetry. When enabled, a periodical process will send anonymous data about this
+ ## instance. Telemetry data will enable us to learn how the application is used,
+ ## based on real scenarios. If you want to help us, please leave it enabled. You can
+ ## audit what data we send with the code available on github.
+
+ PENPOT_TELEMETRY_ENABLED: true
+
+ ## Example SMTP/Email configuration. By default, emails are sent to the mailcatch
+ ## service, but for production usage it is recommended to setup a real SMTP
+ ## provider. Emails are used to confirm user registrations & invitations. Look below
+ ## how the mailcatch service is configured.
+
+ PENPOT_SMTP_DEFAULT_FROM: no-reply@example.com
+ PENPOT_SMTP_DEFAULT_REPLY_TO: no-reply@example.com
+ PENPOT_SMTP_HOST: penpot-mailcatch
+ PENPOT_SMTP_PORT: 1025
+ PENPOT_SMTP_USERNAME:
+ PENPOT_SMTP_PASSWORD:
+ PENPOT_SMTP_TLS: false
+ PENPOT_SMTP_SSL: false
+
+ penpot-exporter:
+ image: "penpotapp/exporter:2.3.2"
+ restart: always
+ networks:
+ - dokploy-network
+
+ environment:
+ # Don't touch it; this uses an internal docker network to
+ # communicate with the frontend.
+ PENPOT_PUBLIC_URI: http://penpot-frontend
+
+ ## Redis is used for the websockets notifications.
+ PENPOT_REDIS_URI: redis://penpot-redis/0
+
+ penpot-postgres:
+ image: "postgres:15"
+ restart: always
+ stop_signal: SIGINT
+
+ volumes:
+ - penpot_postgres_v15:/var/lib/postgresql/data
+
+ networks:
+ - dokploy-network
+
+ environment:
+ - POSTGRES_INITDB_ARGS=--data-checksums
+ - POSTGRES_DB=penpot
+ - POSTGRES_USER=penpot
+ - POSTGRES_PASSWORD=penpot
+
+ penpot-redis:
+ image: redis:7.2
+ restart: always
+ networks:
+ - dokploy-network
+
+ ## A mailcatch service, used as temporal SMTP server. You can access via HTTP to the
+ ## port 1080 for read all emails the penpot platform has sent. Should be only used as a
+ ## temporal solution while no real SMTP provider is configured.
+
+ penpot-mailcatch:
+ image: sj26/mailcatcher:latest
+ restart: always
+ expose:
+ - '1025'
+ ports:
+ - 1080
+ networks:
+ - dokploy-network
+
+ ## Example configuration of MiniIO (S3 compatible object storage service); If you don't
+ ## have preference, then just use filesystem, this is here just for the completeness.
+
+ # minio:
+ # image: "minio/minio:latest"
+ # command: minio server /mnt/data --console-address ":9001"
+ # restart: always
+ #
+ # volumes:
+ # - "penpot_minio:/mnt/data"
+ #
+ # environment:
+ # - MINIO_ROOT_USER=minioadmin
+ # - MINIO_ROOT_PASSWORD=minioadmin
+ #
+ # ports:
+ # - 9000:9000
+ # - 9001:9001
+
+
diff --git a/apps/dokploy/templates/penpot/index.ts b/apps/dokploy/templates/penpot/index.ts
new file mode 100644
index 00000000..f657c698
--- /dev/null
+++ b/apps/dokploy/templates/penpot/index.ts
@@ -0,0 +1,27 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "penpot-frontend",
+ },
+ ];
+
+ const envs = [`DOMAIN_NAME=${mainDomain}`];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index 86aa9ae8..9aaf1ee0 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -1062,4 +1062,63 @@ export const templates: TemplateData[] = [
tags: ["identity", "auth"],
load: () => import("./logto/index").then((m) => m.generate),
},
+ {
+ id: "penpot",
+ name: "Penpot",
+ version: "2.3.2",
+ description:
+ "Penpot is the web-based open-source design tool that bridges the gap between designers and developers.",
+ logo: "penpot.svg",
+ links: {
+ github: "https://github.com/penpot/penpot",
+ website: "https://penpot.app/",
+ docs: "https://docs.penpot.app/",
+ },
+ tags: ["desing", "collaboration"],
+ load: () => import("./penpot/index").then((m) => m.generate),
+ },
+ {
+ id: "huly",
+ name: "Huly",
+ version: "0.6.377",
+ description:
+ "Huly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)",
+ logo: "huly.svg",
+ links: {
+ github: "https://github.com/hcengineering/huly-selfhost",
+ website: "https://huly.io/",
+ docs: "https://docs.huly.io/",
+ },
+ tags: ["project-management", "community", "discussion"],
+ load: () => import("./huly/index").then((m) => m.generate),
+ },
+ {
+ id: "unsend",
+ name: "Unsend",
+ version: "v1.2.4",
+ description: "Open source alternative to Resend,Sendgrid, Postmark etc. ",
+ logo: "unsend.png", // we defined the name and the extension of the logo
+ links: {
+ github: "https://github.com/unsend-dev/unsend",
+ website: "https://unsend.dev/",
+ docs: "https://docs.unsend.dev/get-started/",
+ },
+ tags: ["e-mail", "marketing", "business"],
+ load: () => import("./unsend/index").then((m) => m.generate),
+ },
+ {
+ id: "langflow",
+ name: "Langflow",
+ version: "1.1.1",
+ description:
+ "Langflow is a low-code app builder for RAG and multi-agent AI applications. It’s Python-based and agnostic to any model, API, or database. ",
+ logo: "langflow.svg",
+ links: {
+ github: "https://github.com/langflow-ai/langflow/tree/main",
+ website: "https://www.langflow.org/",
+ docs: "https://docs.langflow.org/",
+ },
+ tags: ["ai"],
+ load: () => import("./langflow/index").then((m) => m.generate),
+ },
];
diff --git a/apps/dokploy/templates/unsend/docker-compose.yml b/apps/dokploy/templates/unsend/docker-compose.yml
new file mode 100644
index 00000000..cdf02de6
--- /dev/null
+++ b/apps/dokploy/templates/unsend/docker-compose.yml
@@ -0,0 +1,78 @@
+name: unsend-prod
+
+services:
+ unsend-db-prod:
+ image: postgres:16
+ networks:
+ - dokploy-network
+ restart: always
+ environment:
+ - POSTGRES_USER=${POSTGRES_USER:?err}
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?err}
+ - POSTGRES_DB=${POSTGRES_DB:?err}
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ # ports:
+ # - "5432:5432"
+ volumes:
+ - database:/var/lib/postgresql/data
+
+ unsend-redis-prod:
+ image: redis:7
+ networks:
+ - dokploy-network
+ restart: always
+ # ports:
+ # - "6379:6379"
+ volumes:
+ - cache:/data
+ command: ["redis-server", "--maxmemory-policy", "noeviction"]
+
+ unsend-storage-prod:
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
+ networks:
+ - dokploy-network
+ ports:
+ - 9002
+ - 9001
+ volumes:
+ - storage:/data
+ environment:
+ MINIO_ROOT_USER: unsend
+ MINIO_ROOT_PASSWORD: password
+ entrypoint: sh
+ command: -c 'mkdir -p /data/unsend && minio server /data --console-address ":9001" --address ":9002"'
+
+ unsend:
+ image: unsend/unsend:v1.2.4
+ networks:
+ - dokploy-network
+ restart: always
+ ports:
+ - ${PORT:-3000}
+ environment:
+ - PORT=${PORT:-3000}
+ - DATABASE_URL=${DATABASE_URL:?err}
+ - NEXTAUTH_URL=${NEXTAUTH_URL:?err}
+ - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:?err}
+ - AWS_ACCESS_KEY=${AWS_ACCESS_KEY:?err}
+ - AWS_SECRET_KEY=${AWS_SECRET_KEY:?err}
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:?err}
+ - GITHUB_ID=${GITHUB_ID:?err}
+ - GITHUB_SECRET=${GITHUB_SECRET:?err}
+ - REDIS_URL=${REDIS_URL:?err}
+ - NEXT_PUBLIC_IS_CLOUD=${NEXT_PUBLIC_IS_CLOUD:-false}
+ - API_RATE_LIMIT=${API_RATE_LIMIT:-1}
+ depends_on:
+ unsend-db-prod:
+ condition: service_healthy
+ unsend-redis-prod:
+ condition: service_started
+
+volumes:
+ database:
+ cache:
+ storage:
diff --git a/apps/dokploy/templates/unsend/index.ts b/apps/dokploy/templates/unsend/index.ts
new file mode 100644
index 00000000..a383b771
--- /dev/null
+++ b/apps/dokploy/templates/unsend/index.ts
@@ -0,0 +1,44 @@
+import {
+ generateHash,
+ generateRandomDomain,
+ generateBase64,
+ type Template,
+ type Schema,
+ type DomainSchema,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const secretBase = generateBase64(64);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "unsend",
+ },
+ ];
+
+ const envs = [
+ "REDIS_URL=redis://unsend-redis-prod:6379",
+ "POSTGRES_USER=postgres",
+ "POSTGRES_PASSWORD=postgres",
+ "POSTGRES_DB=unsend",
+ "DATABASE_URL=postgresql://postgres:postgres@unsend-db-prod:5432/unsend",
+ "NEXTAUTH_URL=http://localhost:3000",
+ `NEXTAUTH_SECRET=${secretBase}`,
+ "GITHUB_ID='Fill'",
+ "GITHUB_SECRET='Fill'",
+ "AWS_DEFAULT_REGION=us-east-1",
+ "AWS_SECRET_KEY='Fill'",
+ "AWS_ACCESS_KEY='Fill'",
+ "DOCKER_OUTPUT=1",
+ "API_RATE_LIMIT=1",
+ "DISCORD_WEBHOOK_URL=",
+ ];
+
+ return {
+ envs,
+ domains,
+ };
+}