Compare commits

...

63 Commits

Author SHA1 Message Date
Mauricio Siu
213fa08210 Merge pull request #382 from Dokploy/canary
v0.7.2
2024-08-26 15:52:49 -06:00
Mauricio Siu
1eed1a356d chore(version): bump version 2024-08-26 15:41:28 -06:00
Mauricio Siu
a8f21ad717 Merge pull request #381 from Dokploy/security/apply-validation-when-creating-admin
refactor(auth): add validation to prevent create another admin when a…
2024-08-26 15:32:29 -06:00
Mauricio Siu
c1420bd6d8 chore: apply biome 2024-08-26 15:22:51 -06:00
Mauricio Siu
74ea9debd5 refactor(auth): add validation to prevent create another admin when a admin is present 2024-08-26 15:19:30 -06:00
Mauricio Siu
1df9f1f4df Merge pull request #361 from songtianlun/canary
feat: add supbase template
2024-08-24 20:55:02 -06:00
songtianlun
f06ac587c9 Merge branch 'upsteam_canary' into canary 2024-08-25 01:40:35 +08:00
songtianlun
c084cf84a0 fix: supabase generate annon and service jwt by servicekey 2024-08-24 00:43:00 +08:00
songtianlun
5e5cbdeef9 fix: supabase generate annon and service jwt by servicekey 2024-08-24 00:41:14 +08:00
Mauricio Siu
24929d8a4d Merge pull request #371 from freidev/canary
feat(template): add Aptabase template with ClickHouse and PostgreSQLCanary
2024-08-22 15:05:34 -06:00
Freilyn Bernabe
1e6e85ed5b style: run code formatting on two files 2024-08-22 16:08:48 -04:00
Freilyn Bernabe
137edf1250 feat: remove container_name prop 2024-08-22 15:12:03 -04:00
Freilyn Bernabe
b8741f1702 feat(template): aptabase set port 8080 2024-08-22 08:41:12 -04:00
Freilyn Bernabe
ac28aff022 Apply suggestions from code review
Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com>
2024-08-22 08:38:51 -04:00
songtianlun
217be3c6e9 Merge remote-tracking branch 'origin/canary' into canary 2024-08-22 13:23:38 +08:00
songtianlun
27a0dc3770 fix: supabase ts format 2024-08-22 13:18:06 +08:00
Freilynbp03
53b24534a8 feat(template): add Aptabase template with ClickHouse and PostgreSQL 2024-08-21 21:45:14 -04:00
Freilyn Bernabe
5a3d0f8288 feat: add aptabase template 2024-08-21 16:31:02 -04:00
Mauricio Siu
ff3e3513ef chore: add cloudblast io sponsor 2024-08-21 12:26:44 -06:00
songtianlun
7a1fba38b3 fix: supabase logflareApiKey to 32 passwd 2024-08-22 02:06:42 +08:00
songtianlun
2b65a3c119 fix: supabase MAILER_URLPATHS_CONFIRMATION env path 2024-08-22 01:51:32 +08:00
songtianlun
6d841497cc fix: remove SECRET_KEY_BASE global env 2024-08-22 01:47:33 +08:00
songtianlun
d37dc7c372 fix: supabase log vector.yml 2024-08-22 01:20:24 +08:00
songtianlun
f3e0cf861f fix: supbase hello function demo 2024-08-22 01:17:05 +08:00
songtianlun
e2578e5794 fix: supbase add init/data.sql 2024-08-22 01:13:38 +08:00
TianLun Song
16deec381c fix: ci replace circle config 2024-08-20 20:51:20 +08:00
songtianlun
91dc35a138 fix: supbase index.ts format 2024-08-20 20:39:08 +08:00
songtianlun
acfa032e61 fix: supabase dashboard passwd 2024-08-20 20:39:08 +08:00
songtianlun
83ee4b2c59 fix: ci set branch name 2024-08-20 20:39:08 +08:00
Mauricio Siu
d5c6a601d8 Merge pull request #367 from Dokploy/canary
v0.7.1
2024-08-19 16:03:39 -06:00
Mauricio Siu
afbe42a577 Update package.json 2024-08-19 14:12:14 -06:00
Mauricio Siu
866f700abf Merge pull request #365 from Dokploy/31-are-there-any-plans-to-support-wildcard-dns-records
31 are there any plans to support wildcard dns records
2024-08-19 00:08:55 -06:00
Mauricio Siu
f2b6b33b1f chore: lint 2024-08-18 23:24:20 -06:00
Mauricio Siu
813ffabb8c refactor: check if the traefik dashboard is enabled 2024-08-18 23:19:21 -06:00
Mauricio Siu
b5da9291b4 feat: add enviroment variables editor to traefik 2024-08-18 23:18:54 -06:00
songtianlun
b0d604d12b fix: supabase postgres passwd 2024-08-19 11:33:26 +08:00
Mauricio Siu
e9f40e1644 Merge pull request #364 from Dokploy/354-support-soketi
feat: add soketi template
2024-08-18 20:53:55 -06:00
Mauricio Siu
61d520c239 feat: add data templates 2024-08-18 20:46:18 -06:00
Mauricio Siu
124a884d2e feat: add soketi template 2024-08-18 20:43:30 -06:00
Mauricio Siu
b5e4b9af60 Merge pull request #363 from Dokploy/fix/domains-schema-templates
refactor(templates): use domains tab instead of envs
2024-08-18 19:58:23 -06:00
Mauricio Siu
840c24e3ca eslint 2024-08-18 19:51:59 -06:00
Mauricio Siu
d300eb73fb chore: add license 2024-08-18 19:49:36 -06:00
Mauricio Siu
fb4e06116c chore: add example domains schema templates 2024-08-18 19:49:17 -06:00
Mauricio Siu
2d3b903edc refactor(templates): use domains tab instead of envs 2024-08-18 19:47:19 -06:00
Mauricio Siu
75c13df22f Merge pull request #362 from ErickLuis00/canary
fix: wrong Docker version in Add Node commands
2024-08-18 18:17:49 -06:00
Erick Luis
68b81cb48d pnpm run check 2024-08-18 22:40:05 +00:00
Erick Luis
9d6f2df25a fix: wrong Docker version in Add Node commands 2024-08-18 22:18:45 +00:00
songtianlun
724de2c1b9 fix: ci revert name 2024-08-18 22:07:35 +08:00
songtianlun
86946b6b15 fix: ci revert name 2024-08-18 22:07:02 +08:00
songtianlun
957bb3d3e6 fix: supabase domain is set 2024-08-18 20:41:27 +08:00
songtianlun
7558029271 fix: supabase domain in one path 2024-08-18 13:34:37 +08:00
songtianlun
757c28dad1 fix: supabase logo 2024-08-18 13:26:02 +08:00
songtianlun
8d3dc38816 fix: supabase logo 2024-08-18 13:17:05 +08:00
songtianlun
9c8061a447 fix: supabase replace ports to expost 2024-08-18 13:05:44 +08:00
songtianlun
3a8b2867b6 fix: supabase add network 2024-08-18 13:02:25 +08:00
songtianlun
389956d1a2 fix: supabase docker volume content 2024-08-18 12:33:26 +08:00
songtianlun
a84bdd1c8e fix: supabase docker volume path 2024-08-18 01:55:41 +08:00
songtianlun
eb219221be Merge remote-tracking branch 'my/canary' into canary 2024-08-18 01:40:34 +08:00
songtianlun
7b176bd877 feat: add supabase templates 2024-08-18 01:38:25 +08:00
TianLun Song
6970923253 fix docker name 2024-08-18 01:20:24 +08:00
TianLun Song
a3e23d54d8 fix docker push prefix 2024-08-18 01:09:09 +08:00
TianLun Song
8f11207d72 fix push docker prefix 2024-08-18 01:04:22 +08:00
songtianlun
6bd98350d9 feat: add supabase templates 2024-08-18 00:58:09 +08:00
78 changed files with 2237 additions and 578 deletions

View File

@@ -166,20 +166,26 @@ import {
generateRandomDomain,
type Template,
type Schema,
type DomainSchema,
} from "../utils";
export function generate(schema: Schema): Template {
// do your stuff here, like create a new domain, generate random passwords, mounts.
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const secretBase = generateBase64(64);
const toptKeyBase = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "plausible",
},
];
const envs = [
// If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
`PLAUSIBLE_HOST=${randomDomain}`,
"PLAUSIBLE_PORT=8000",
`BASE_URL=http://${randomDomain}`,
`BASE_URL=http://${mainDomain}`,
`SECRET_KEY_BASE=${secretBase}`,
`TOTP_VAULT_KEY=${toptKeyBase}`,
`HASH=${mainServiceHash}`,
@@ -195,6 +201,7 @@ export function generate(schema: Schema): Template {
return {
envs,
mounts,
domains,
};
}
```

26
LICENSE.MD Normal file
View File

@@ -0,0 +1,26 @@
# License
## Core License (Apache License 2.0)
Copyright 2024 Mauricio Siu.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
## Additional Terms for Specific Features
The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version.
- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
For further inquiries or permissions, please contact us directly.

View File

@@ -70,6 +70,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Lightspeed.run"/></a>
</div>
### Community Backers 🤝

View File

@@ -23,12 +23,14 @@ export const AddManager = () => {
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version 24.0
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy("curl https://get.docker.com | sh -s -- --version 24.0");
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
@@ -43,12 +45,12 @@ export const AddManager = () => {
cluster
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data}
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data || "");
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>

View File

@@ -22,12 +22,14 @@ export const AddWorker = () => {
<div className="flex flex-col gap-2.5 text-sm">
<span>1. Go to your new server and run the following command</span>
<span className="bg-muted rounded-lg p-2 flex justify-between">
curl https://get.docker.com | sh -s -- --version 24.0
curl https://get.docker.com | sh -s -- --version {data?.version}
<button
type="button"
className="self-center"
onClick={() => {
copy("curl https://get.docker.com | sh -s -- --version 24.0");
copy(
`curl https://get.docker.com | sh -s -- --version ${data?.version}`,
);
toast.success("Copied to clipboard");
}}
>
@@ -42,12 +44,12 @@ export const AddWorker = () => {
</span>
<span className="bg-muted rounded-lg p-2 flex">
{data}
{data?.command}
<button
type="button"
className="self-start"
onClick={() => {
copy(data || "");
copy(data?.command || "");
toast.success("Copied to clipboard");
}}
>

View File

@@ -22,6 +22,7 @@ import {
import { api } from "@/utils/api";
import { toast } from "sonner";
import { DockerTerminalModal } from "./web-server/docker-terminal-modal";
import { EditTraefikEnv } from "./web-server/edit-traefik-env";
import { ShowMainTraefikConfig } from "./web-server/show-main-traefik-config";
import { ShowModalLogs } from "./web-server/show-modal-logs";
import { ShowServerMiddlewareConfig } from "./web-server/show-server-middleware-config";
@@ -67,6 +68,9 @@ export const WebServer = () => {
const { mutateAsync: updateDockerCleanup } =
api.settings.updateDockerCleanup.useMutation();
const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } =
api.settings.haveTraefikDashboardPortEnabled.useQuery();
return (
<Card className="rounded-lg w-full bg-transparent">
<CardHeader>
@@ -167,37 +171,38 @@ export const WebServer = () => {
<span>View Traefik config</span>
</DropdownMenuItem>
</ShowMainTraefikConfig>
<EditTraefikEnv>
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
className="w-full cursor-pointer space-x-3"
>
<span>Modify Env</span>
</DropdownMenuItem>
</EditTraefikEnv>
<DropdownMenuItem
onClick={async () => {
await toggleDashboard({
enableDashboard: true,
enableDashboard: !haveTraefikDashboardPortEnabled,
})
.then(async () => {
toast.success("Dashboard Enabled");
toast.success(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
refetchDashboard();
})
.catch(() => {
toast.error("Error to enable Dashboard");
toast.error(
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
);
});
}}
className="w-full cursor-pointer space-x-3"
>
<span>Enable Dashboard</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={async () => {
await toggleDashboard({
enableDashboard: false,
})
.then(async () => {
toast.success("Dashboard Disabled");
})
.catch(() => {
toast.error("Error to disable Dashboard");
});
}}
className="w-full cursor-pointer space-x-3"
>
<span>Disable Dashboard</span>
<span>
{haveTraefikDashboardPortEnabled ? "Disable" : "Enable"}{" "}
Dashboard
</span>
</DropdownMenuItem>
<DockerTerminalModal appName="dokploy-traefik">

View File

@@ -0,0 +1,146 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const schema = z.object({
env: z.string(),
});
type Schema = z.infer<typeof schema>;
interface Props {
children?: React.ReactNode;
}
export const EditTraefikEnv = ({ children }: Props) => {
const [canEdit, setCanEdit] = useState(true);
const { data } = api.settings.readTraefikEnv.useQuery();
const { mutateAsync, isLoading, error, isError } =
api.settings.writeTraefikEnv.useMutation();
const form = useForm<Schema>({
defaultValues: {
env: data || "",
},
disabled: canEdit,
resolver: zodResolver(schema),
});
useEffect(() => {
if (data) {
form.reset({
env: data || "",
});
}
}, [form, form.reset, data]);
const onSubmit = async (data: Schema) => {
await mutateAsync(data.env)
.then(async () => {
toast.success("Traefik Env Updated");
})
.catch(() => {
toast.error("Error to update the traefik env");
});
};
return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
<DialogHeader>
<DialogTitle>Update Traefik Env</DialogTitle>
<DialogDescription>Update the traefik env</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-update-server-traefik-config"
onSubmit={form.handleSubmit(onSubmit)}
className="w-full space-y-4 relative overflow-auto"
>
<div className="flex flex-col">
<FormField
control={form.control}
name="env"
render={({ field }) => (
<FormItem className="relative">
<FormLabel>Env</FormLabel>
<FormControl>
<CodeEditor
language="properties"
wrapperClassName="h-[35rem] font-mono"
placeholder={`TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=test@localhost.com
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_STORAGE=/etc/dokploy/traefik/dynamic/acme.json
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_HTTP_CHALLENGE=true
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_HTTP_CHALLENGE_PRETTY=true
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_HTTP_CHALLENGE_ENTRYPOINT=web
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_HTTP_CHALLENGE_DNS_CHALLENGE=true
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_HTTP_CHALLENGE_DNS_PROVIDER=cloudflare
`}
{...field}
/>
</FormControl>
<pre>
<FormMessage />
</pre>
<div className="flex justify-end absolute z-50 right-6 top-0">
<Button
className="shadow-sm"
variant="secondary"
type="button"
onClick={async () => {
setCanEdit(!canEdit);
}}
>
{canEdit ? "Unlock" : "Lock"}
</Button>
</div>
</FormItem>
)}
/>
</div>
</form>
<DialogFooter>
<Button
isLoading={isLoading}
disabled={canEdit}
form="hook-form-update-server-traefik-config"
type="submit"
>
Update
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.7.0",
"version": "v0.7.2",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -0,0 +1,5 @@
<svg class="w-12 text-primary" viewBox="0 0 1000 760" xmlns="http://www.w3.org/2000/svg">
<path fill="#1a61ff"
d="M626.7 177.36c-55.8-98.4-197.59-98.4-253.39 0L112.97 636.44H500c0-51.67 41.88-93.55 93.55-93.55h22.09l57.82 93.55h213.57L626.69 177.37Zm-11.06 365.52-70.21-123.82c-20.01-35.28-70.84-35.28-90.85 0l-70.21 123.82H273.58l181.01-319.19c20.01-35.28 70.84-35.28 90.85 0l181.01 319.19H615.66Z"
style="--darkreader-inline-fill:currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,15 @@
<svg width="109" height="113" viewBox="0 0 109 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z" fill="url(#paint0_linear)"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
<defs>
<linearGradient id="paint0_linear" x1="53.9738" y1="54.9738" x2="94.1635" y2="71.8293" gradientUnits="userSpaceOnUse">
<stop stop-color="#249361"/>
<stop offset="1" stop-color="#3ECF8E"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="36.1558" y1="30.5779" x2="54.4844" y2="65.0804" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -12,6 +12,7 @@ import {
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { db } from "../../db";
import {
createAdmin,
createUser,
@@ -33,6 +34,14 @@ export const authRouter = createTRPCRouter({
.input(apiCreateAdmin)
.mutation(async ({ ctx, input }) => {
try {
const admin = await db.query.admins.findFirst({});
if (admin) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Admin already exists",
});
}
const newAdmin = await createAdmin(input);
const session = await lucia.createSession(newAdmin.id || "", {});
ctx.res.appendHeader(

View File

@@ -35,14 +35,23 @@ export const clusterRouter = createTRPCRouter({
}),
addWorker: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
return `docker swarm join --token ${
result.JoinTokens.Worker
} ${await getPublicIpWithFallback()}:2377`;
const docker_version = await docker.version();
return {
command: `docker swarm join --token ${
result.JoinTokens.Worker
} ${await getPublicIpWithFallback()}:2377`,
version: docker_version.Version,
};
}),
addManager: protectedProcedure.query(async ({ input }) => {
const result = await docker.swarmInspect();
return `docker swarm join --token ${
result.JoinTokens.Manager
} ${await getPublicIpWithFallback()}:2377`;
const docker_version = await docker.version();
return {
command: `docker swarm join --token ${
result.JoinTokens.Manager
} ${await getPublicIpWithFallback()}:2377`,
version: docker_version.Version,
};
}),
});

View File

@@ -41,7 +41,7 @@ import {
updateCompose,
} from "../services/compose";
import { removeDeploymentsByComposeId } from "../services/deployment";
import { findDomainsByComposeId } from "../services/domain";
import { createDomain, findDomainsByComposeId } from "../services/domain";
import { createMount } from "../services/mount";
import { findProjectById } from "../services/project";
import { addNewService, checkServiceAccess } from "../services/user";
@@ -236,7 +236,7 @@ export const composeRouter = createTRPCRouter({
const project = await findProjectById(input.projectId);
const projectName = slugify(`${project.name} ${input.id}`);
const { envs, mounts } = generate({
const { envs, mounts, domains } = generate({
serverIp: admin.serverIp,
projectName: projectName,
});
@@ -244,7 +244,7 @@ export const composeRouter = createTRPCRouter({
const compose = await createComposeByTemplate({
...input,
composeFile: composeFile,
env: envs.join("\n"),
env: envs?.join("\n"),
name: input.id,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
@@ -267,6 +267,17 @@ export const composeRouter = createTRPCRouter({
}
}
if (domains && domains?.length > 0) {
for (const domain of domains) {
await createDomain({
...domain,
domainType: "compose",
certificateType: "none",
composeId: compose.composeId,
});
}
}
return null;
}),

View File

@@ -15,6 +15,7 @@ import {
cleanUpSystemPrune,
cleanUpUnusedImages,
cleanUpUnusedVolumes,
prepareEnvironmentVariables,
startService,
stopService,
} from "@/server/utils/docker/utils";
@@ -37,6 +38,7 @@ import {
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
import { TRPCError } from "@trpc/server";
import { scheduleJob, scheduledJobs } from "node-schedule";
import { z } from "zod";
import { appRouter } from "../root";
import { findAdmin, updateAdmin } from "../services/admin";
import {
@@ -69,7 +71,9 @@ export const settingsRouter = createTRPCRouter({
toggleDashboard: adminProcedure
.input(apiEnableDashboard)
.mutation(async ({ input }) => {
await initializeTraefik(input.enableDashboard);
await initializeTraefik({
enableDashboard: input.enableDashboard,
});
return true;
}),
@@ -309,4 +313,37 @@ export const settingsRouter = createTRPCRouter({
return openApiDocument;
},
),
readTraefikEnv: adminProcedure.query(async () => {
const { stdout } = await execAsync(
"docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik",
);
return stdout.trim();
}),
writeTraefikEnv: adminProcedure
.input(z.string())
.mutation(async ({ input }) => {
const envs = prepareEnvironmentVariables(input);
await initializeTraefik({
env: envs,
});
return true;
}),
haveTraefikDashboardPortEnabled: adminProcedure.query(async () => {
const { stdout } = await execAsync(
"docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
);
const parsed: any[] = JSON.parse(stdout.trim());
for (const port of parsed) {
if (port.PublishedPort === 8080) {
return true;
}
}
return false;
}),
});

View File

@@ -11,7 +11,15 @@ const TRAEFIK_SSL_PORT =
Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443;
const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80;
export const initializeTraefik = async (enableDashboard = false) => {
interface TraefikOptions {
enableDashboard?: boolean;
env?: string[];
}
export const initializeTraefik = async ({
enableDashboard = false,
env = [],
}: TraefikOptions = {}) => {
const imageName = "traefik:v2.5";
const containerName = "dokploy-traefik";
const settings: CreateServiceOptions = {
@@ -19,6 +27,7 @@ export const initializeTraefik = async (enableDashboard = false) => {
TaskTemplate: {
ContainerSpec: {
Image: imageName,
Env: env,
Mounts: [
{
Type: "bind",

View File

@@ -2,17 +2,5 @@ version: "3.8"
services:
appsmith:
image: index.docker.io/appsmith/appsmith-ee:v1.29
networks:
- dokploy-network
ports:
- ${APP_SMITH_PORT}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}"
volumes:
- ../files/stacks:/appsmith-stacks
networks:
dokploy-network:
external: true

View File

@@ -1,4 +1,5 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -7,14 +8,16 @@ import {
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`APP_SMITH_HOST=${randomDomain}`,
"APP_SMITH_PORT=80",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: generateRandomDomain(schema),
port: 80,
serviceName: "appsmith",
},
];
return {
envs,
domains,
};
}

View File

@@ -0,0 +1,51 @@
services:
aptabase_db:
image: postgres:15-alpine
restart: always
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: aptabase
POSTGRES_PASSWORD: sTr0NGp4ssw0rd
networks:
- dokploy-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aptabase"]
interval: 10s
timeout: 5s
retries: 5
aptabase_events_db:
image: clickhouse/clickhouse-server:23.8.16.16-alpine
restart: always
volumes:
- events-db-data:/var/lib/clickhouse
environment:
CLICKHOUSE_USER: aptabase
CLICKHOUSE_PASSWORD: sTr0NGp4ssw0rd
ulimits:
nofile:
soft: 262144
hard: 262144
networks:
- dokploy-network
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8123 || exit 1"]
interval: 10s
timeout: 5s
retries: 5
aptabase:
image: ghcr.io/aptabase/aptabase:main
restart: always
environment:
BASE_URL: http://${APTABASE_HOST}
AUTH_SECRET: ${AUTH_SECRET}
DATABASE_URL: Server=aptabase_db;Port=5432;User Id=aptabase;Password=sTr0NGp4ssw0rd;Database=aptabase
CLICKHOUSE_URL: Host=aptabase_events_db;Port=8123;Username=aptabase;Password=sTr0NGp4ssw0rd
volumes:
db-data:
driver: local
events-db-data:
driver: local

View File

@@ -0,0 +1,27 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const authSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8080,
serviceName: "aptabase",
},
];
const envs = [`APTABASE_HOST=${mainDomain}`, `AUTH_SECRET=${authSecret}`];
return {
envs,
domains,
};
}

View File

@@ -2,21 +2,9 @@ version: "3.8"
services:
baserow:
image: baserow/baserow:1.25.2
networks:
- dokploy-network
environment:
BASEROW_PUBLIC_URL: "http://${BASEROW_HOST}"
ports:
- ${BASEROW_PORT}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${BASEROW_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${BASEROW_PORT}
volumes:
- baserow_data:/baserow/data
volumes:
baserow_data:
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,24 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`BASEROW_HOST=${randomDomain}`,
"BASEROW_PORT=80",
`HASH=${mainServiceHash}`,
const mainHost = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainHost,
port: 80,
serviceName: "baserow",
},
];
const envs = [`BASEROW_HOST=${mainHost}`];
return {
envs,
domains,
};
}

View File

@@ -21,16 +21,6 @@ services:
- DATABASE_URL=postgres://postgres:password@postgres:5432/db
- NEXT_PUBLIC_WEBAPP_URL=http://${CALCOM_HOST}
- NEXTAUTH_URL=http://${CALCOM_HOST}/api/auth
networks:
- dokploy-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${CALCOM_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${CALCOM_PORT}"
networks:
dokploy-network:
external: true
volumes:
calcom-data:

View File

@@ -1,27 +1,32 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
// https://cal.com/
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const calcomEncryptionKey = generateBase64(32);
const nextAuthSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "calcom",
},
];
const envs = [
`CALCOM_HOST=${randomDomain}`,
"CALCOM_PORT=3000",
`HASH=${mainServiceHash}`,
`CALCOM_HOST=${mainDomain}`,
`NEXTAUTH_SECRET=${nextAuthSecret}`,
`CALENDSO_ENCRYPTION_KEY=${calcomEncryptionKey}`,
];
return {
envs,
domains,
};
}

View File

@@ -18,8 +18,6 @@ services:
directus:
image: directus/directus:10.12.1
networks:
- dokploy-network
ports:
- 8055
volumes:
@@ -28,10 +26,6 @@ services:
depends_on:
- cache
- database
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${DIRECTUS_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${DIRECTUS_PORT}
environment:
SECRET: "replace-with-secure-random-value"
@@ -49,8 +43,5 @@ services:
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "d1r3ctu5"
networks:
dokploy-network:
external: true
volumes:
directus:

View File

@@ -1,20 +1,20 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`DIRECTUS_HOST=${randomDomain}`,
"DIRECTUS_PORT=8055",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: generateRandomDomain(schema),
port: 8055,
serviceName: "directus",
},
];
return {
envs,
domains,
};
}

View File

@@ -19,8 +19,6 @@ services:
documenso:
image: documenso/documenso:1.5.6-rc.2
networks:
- dokploy-network
depends_on:
postgres:
condition: service_healthy
@@ -38,16 +36,8 @@ services:
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
ports:
- ${DOCUMENSO_PORT}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${DOCUMENSO_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${DOCUMENSO_PORT}"
volumes:
- /opt/documenso/cert.p12:/opt/documenso/cert.p12
networks:
dokploy-network:
external: true
volumes:
documenso-data:

View File

@@ -1,24 +1,29 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const nextAuthSecret = generateBase64(32);
const documensoEncryptionKey = generatePassword(32);
const documensoSecondaryEncryptionKey = generatePassword(64);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "documenso",
},
];
const envs = [
`DOCUMENSO_HOST=${randomDomain}`,
`DOCUMENSO_HOST=${mainDomain}`,
"DOCUMENSO_PORT=3000",
`HASH=${mainServiceHash}`,
`NEXTAUTH_SECRET=${nextAuthSecret}`,
`NEXT_PRIVATE_ENCRYPTION_KEY=${documensoEncryptionKey}`,
`NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${documensoSecondaryEncryptionKey}`,
@@ -26,5 +31,6 @@ export function generate(schema: Schema): Template {
return {
envs,
domains,
};
}

View File

@@ -2,10 +2,6 @@ services:
doublezero:
restart: always
image: liltechnomancer/double-zero:0.2.1
ports:
- ${DOUBLEZERO_PORT}
networks:
- dokploy-network
volumes:
- db-data:/var/lib/doublezero/data
environment:
@@ -17,15 +13,7 @@ services:
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
PHX_HOST: ${DOUBLEZERO_HOST}
DATABASE_PATH: ./00.db
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${DOUBLEZERO_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${DOUBLEZERO_PORT}"
volumes:
db-data:
driver: local
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,26 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const secretKeyBase = generateBase64(64);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 4000,
serviceName: "doublezero",
},
];
const envs = [
`DOUBLEZERO_HOST=${randomDomain}`,
`DOUBLEZERO_HOST=${mainDomain}`,
"DOUBLEZERO_PORT=4000",
`HASH=${mainServiceHash}`,
`SECRET_KEY_BASE=${secretKeyBase}`,
"AWS_ACCESS_KEY_ID=your-aws-access-key",
"AWS_SECRET_ACCESS_KEY=your-aws-secret-key",
@@ -25,5 +31,6 @@ export function generate(schema: Schema): Template {
return {
envs,
domains,
};
}

View File

@@ -1,17 +1,7 @@
version: '3.8'
version: "3.8"
services:
excalidraw:
networks:
- dokploy-network
image: excalidraw/excalidraw:latest
ports:
- ${EXCALIDRAW_PORT}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${EXCALIDRAW_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${EXCALIDRAW_PORT}
networks:
dokploy-network:
external: true

View File

@@ -1,4 +1,5 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -6,15 +7,17 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`EXCALIDRAW_HOST=${randomDomain}`,
"EXCALIDRAW_PORT=80",
`HASH=${mainServiceHash}`,
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 80,
serviceName: "excalidraw",
},
];
return {
envs,
domains,
};
}

View File

@@ -1,13 +1,8 @@
version: "3.8"
services:
ghost:
image: ghost:5-alpine
restart: always
networks:
- dokploy-network
ports:
- ${GHOST_PORT}
environment:
database__client: mysql
database__connection__host: db
@@ -15,10 +10,7 @@ services:
database__connection__password: example
database__connection__database: ghost
url: http://${GHOST_HOST}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${GHOST_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${GHOST_PORT}
volumes:
- ghost:/var/lib/ghost/content
@@ -35,7 +27,3 @@ services:
volumes:
ghost:
db:
networks:
dokploy-network:
external: true

View File

@@ -1,4 +1,5 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
@@ -6,15 +7,19 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`GHOST_HOST=${randomDomain}`,
"GHOST_PORT=2368",
`HASH=${mainServiceHash}`,
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 2368,
serviceName: "ghost",
},
];
const envs = [`GHOST_HOST=${mainDomain}`];
return {
envs,
domains,
};
}

View File

@@ -1,16 +1,14 @@
x-environment:
&default-environment
x-environment: &default-environment
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
SECRET_KEY: ${SECRET_KEY}
PORT: ${GLITCHTIP_PORT}
EMAIL_URL: consolemail://
GLITCHTIP_DOMAIN: http://${GLITCHTIP_HOST}
GLITCHTIP_DOMAIN: http://${GLITCHTIP_HOST}
DEFAULT_FROM_EMAIL: email@glitchtip.com
CELERY_WORKER_AUTOSCALE: "1,3"
CELERY_WORKER_AUTOSCALE: "1,3"
CELERY_WORKER_MAX_TASKS_PER_CHILD: "10000"
x-depends_on:
&default-depends_on
x-depends_on: &default-depends_on
- postgres
- redis
@@ -18,7 +16,7 @@ services:
postgres:
image: postgres:16
environment:
POSTGRES_HOST_AUTH_METHOD: "trust"
POSTGRES_HOST_AUTH_METHOD: "trust"
restart: unless-stopped
volumes:
- pg-data:/var/lib/postgresql/data
@@ -36,21 +34,15 @@ services:
- ${GLITCHTIP_PORT}
environment: *default-environment
restart: unless-stopped
volumes:
volumes:
- uploads:/code/uploads
networks:
- dokploy-network
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${GLITCHTIP_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${GLITCHTIP_PORT}
worker:
image: glitchtip/glitchtip:v4.0
command: ./bin/run-celery-with-beat.sh
depends_on: *default-depends_on
environment: *default-environment
restart: unless-stopped
volumes:
volumes:
- uploads:/code/uploads
networks:
- dokploy-network
@@ -65,7 +57,3 @@ services:
volumes:
pg-data:
uploads:
networks:
dokploy-network:
external: true

View File

@@ -1,23 +1,30 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const secretKey = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "web",
},
];
const envs = [
`GLITCHTIP_HOST=${randomDomain}`,
`GLITCHTIP_HOST=${mainDomain}`,
"GLITCHTIP_PORT=8000",
`SECRET_KEY=${secretKey}`,
`HASH=${mainServiceHash}`,
];
return {
envs,
domains,
};
}

View File

@@ -1,20 +1,9 @@
version: "3.8"
services:
grafana:
networks:
- dokploy-network
image: grafana/grafana-enterprise:9.5.20
restart: unless-stopped
ports:
- ${GRAFANA_PORT}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${GRAFANA_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${GRAFANA_PORT}
volumes:
- grafana-storage:/var/lib/grafana
networks:
dokploy-network:
external: true
volumes:
grafana-storage: {}
grafana-storage: {}

View File

@@ -1,20 +1,19 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`GRAFANA_HOST=${randomDomain}`,
"GRAFANA_PORT=3000",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: generateRandomDomain(schema),
port: 3000,
serviceName: "grafana",
},
];
return {
envs,
domains,
};
}

View File

@@ -1,30 +1,19 @@
version: '3.8'
version: "3.8"
services:
jellyfin:
image: jellyfin/jellyfin:10
networks:
- dokploy-network
ports:
- ${JELLYFIN_PORT}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${JELLYFIN_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${JELLYFIN_PORT}"
volumes:
- config:/config
- cache:/cache
- media:/media
restart: 'unless-stopped'
restart: "unless-stopped"
# Optional - alternative address used for autodiscovery
environment:
- JELLYFIN_PublishedServerUrl=http://${JELLYFIN_HOST}
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- 'host.docker.internal:host-gateway'
- "host.docker.internal:host-gateway"
volumes:
config:
cache:
media:
networks:
dokploy-network:
external: true

View File

@@ -1,22 +1,25 @@
// EXAMPLE
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const port = 8096;
const envs = [
`JELLYFIN_HOST=${randomDomain}`,
`HASH=${mainServiceHash}`,
`JELLYFIN_PORT=${port}`,
const domain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: domain,
port: 8096,
serviceName: "jellyfin",
},
];
const envs = [`JELLYFIN_HOST=${domain}`];
return {
envs,
domains,
};
}

View File

@@ -36,10 +36,6 @@ services:
app:
restart: unless-stopped
image: listmonk/listmonk:v3.0.0
ports:
- "${LISTMONK_PORT}"
networks:
- dokploy-network
environment:
- TZ=Etc/UTC
depends_on:
@@ -47,15 +43,7 @@ services:
- setup
volumes:
- ../files/config.toml:/listmonk/config.toml
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${LISTMONK_PORT}"
volumes:
listmonk-data:
driver: local
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,24 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const adminPassword = generatePassword(32);
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 9000,
serviceName: "app",
},
];
const envs = [
`LISTMONK_HOST=${randomDomain}`,
"LISTMONK_PORT=9000",
`HASH=${mainServiceHash}`,
`# login with admin:${adminPassword}`,
"# check config.toml in Advanced / Volumes for more options",
];
@@ -48,5 +52,6 @@ params = ""
return {
envs,
mounts,
domains,
};
}

View File

@@ -1,25 +1,14 @@
version: '3.8'
version: "3.8"
services:
meilisearch:
networks:
- dokploy-network
image: getmeili/meilisearch:v1.8.3
ports:
- ${MEILISEARCH_PORT}
volumes:
- meili_data:/meili_data
environment:
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
MEILI_ENV: ${MEILI_ENV}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${MEILISEARCH_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${MEILISEARCH_PORT}
volumes:
meili_data:
driver: local
networks:
dokploy-network:
external: true

View File

@@ -1,24 +1,26 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const masterKey = generateBase64(32);
const envs = [
`MEILISEARCH_HOST=${randomDomain}`,
"MEILISEARCH_PORT=7700",
"MEILI_ENV=development",
`MEILI_MASTER_KEY=${masterKey}`,
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 7700,
serviceName: "meilisearch",
},
];
const envs = ["MEILI_ENV=development", `MEILI_MASTER_KEY=${masterKey}`];
return {
envs,
domains,
};
}

View File

@@ -4,8 +4,6 @@ services:
image: metabase/metabase:v0.50.8
volumes:
- /dev/urandom:/dev/random:ro
ports:
- ${METABASE_PORT}
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: metabaseappdb
@@ -13,17 +11,11 @@ services:
MB_DB_USER: metabase
MB_DB_PASS: mysecretpassword
MB_DB_HOST: postgres
networks:
- dokploy-network
healthcheck:
test: curl --fail -I http://localhost:3000/api/health || exit 1
interval: 15s
timeout: 5s
retries: 5
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${METABASE_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${METABASE_PORT}
postgres:
image: postgres:14
environment:
@@ -32,7 +24,3 @@ services:
POSTGRES_PASSWORD: mysecretpassword
networks:
- dokploy-network
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,22 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`METABASE_HOST=${randomDomain}`,
"METABASE_PORT=3000",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 3000,
serviceName: "metabase",
},
];
return {
envs,
domains,
};
}

View File

@@ -1,31 +1,13 @@
version: '3.8'
version: "3.8"
services:
minio:
image: minio/minio
ports:
- ${MINIO_API_PORT}
- ${MINIO_DASHBOARD_PORT}
volumes:
- minio-data:/data
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin123
command: server /data --console-address ":9001"
networks:
- dokploy-network
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.service=${HASH}
- traefik.http.routers.${HASH}.rule=Host(`${MINIO_DASHBOARD_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${MINIO_DASHBOARD_PORT}
# API router and service
- traefik.http.routers.${HASH}-api.service=${HASH}-api
- traefik.http.routers.${HASH}-api.rule=Host(`${MINIO_API_HOST}`)
- traefik.http.services.${HASH}-api.loadbalancer.server.port=${MINIO_API_PORT}
volumes:
minio-data:
networks:
dokploy-network:
external: true

View File

@@ -1,23 +1,28 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const apiDomain = generateRandomDomain(schema);
const envs = [
`MINIO_DASHBOARD_HOST=${randomDomain}`,
"MINIO_DASHBOARD_PORT=9001",
`MINIO_API_HOST=${apiDomain}`,
"MINIO_API_PORT=9000",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 9001,
serviceName: "minio",
},
{
host: apiDomain,
port: 9000,
serviceName: "minio",
},
];
return {
envs,
domains,
};
}

View File

@@ -3,17 +3,9 @@ services:
n8n:
image: docker.n8n.io/n8nio/n8n:1.48.1
restart: always
networks:
- dokploy-network
ports:
- ${N8N_PORT}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${N8N_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${N8N_PORT}
environment:
- N8N_HOST=${N8N_HOST}
- N8N_PORT=5678
- N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=http
- NODE_ENV=production
- WEBHOOK_URL=https://${N8N_HOST}/
@@ -23,7 +15,4 @@ services:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
networks:
dokploy-network:
external: true
n8n_data:

View File

@@ -1,21 +1,28 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 5678,
serviceName: "n8n",
},
];
const envs = [
`N8N_HOST=${randomDomain}`,
`N8N_HOST=${mainDomain}`,
"N8N_PORT=5678",
`HASH=${mainServiceHash}`,
"GENERIC_TIMEZONE=Europe/Berlin",
];
return {
envs,
domains,
};
}

View File

@@ -3,18 +3,10 @@ services:
nocodb:
image: nocodb/nocodb:0.251.1
restart: always
networks:
- dokploy-network
ports:
- ${NOCODB_PORT}
environment:
NC_DB : "pg://root_db?u=postgres&p=password&d=root_db"
PORT : ${NOCODB_PORT}
NC_DB: "pg://root_db?u=postgres&p=password&d=root_db"
PORT: ${NOCODB_PORT}
NC_REDIS_URL: ${NC_REDIS_URL}
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${NOCODB_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${NOCODB_PORT}
volumes:
- nc_data:/usr/app/data
@@ -30,15 +22,11 @@ services:
healthcheck:
interval: 10s
retries: 10
test: "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""
test: 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'
timeout: 2s
volumes:
- "db_data:/var/lib/postgresql/data"
networks:
dokploy-network:
external: true
volumes:
db_data: {}
nc_data: {}
nc_data: {}

View File

@@ -1,26 +1,28 @@
// EXAMPLE
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const secretBase = generateBase64(64);
const toptKeyBase = generateBase64(32);
const envs = [
`NOCODB_HOST=${randomDomain}`,
"NOCODB_PORT=8000",
`NC_AUTH_JWT_SECRET=${secretBase}`,
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 8000,
serviceName: "nocodb",
},
];
const envs = ["NOCODB_PORT=8000", `NC_AUTH_JWT_SECRET=${secretBase}`];
return {
envs,
domains,
};
}

View File

@@ -2,20 +2,12 @@ version: "3.8"
services:
web:
image: odoo:16.0
networks:
- dokploy-network
depends_on:
- db
ports:
- ${ODOO_PORT}
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${ODOO_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${ODOO_PORT}"
volumes:
- odoo-web-data:/var/lib/odoo
- ../files/config:/etc/odoo
@@ -35,7 +27,3 @@ services:
volumes:
odoo-web-data:
odoo-db-data:
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,22 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`ODOO_HOST=${randomDomain}`,
"ODOO_PORT=8069",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 8069,
serviceName: "web",
},
];
return {
envs,
domains,
};
}

View File

@@ -1,6 +1,5 @@
version: '3.8'
version: "3.8"
services:
ollama:
volumes:
- ollama:/root/.ollama
@@ -13,24 +12,14 @@ services:
open-webui:
image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
networks:
- dokploy-network
volumes:
- open-webui:/app/backend/data
depends_on:
- ollama
environment:
- 'OLLAMA_BASE_URL=http://ollama:11434'
- 'WEBUI_SECRET_KEY='
- "OLLAMA_BASE_URL=http://ollama:11434"
- "WEBUI_SECRET_KEY="
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${OPEN_WEBUI_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${OPEN_WEBUI_PORT}"
networks:
dokploy-network:
external: true
volumes:
ollama: {}

View File

@@ -1,22 +1,24 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`OPEN_WEBUI_HOST=${randomDomain}`,
"OPEN_WEBUI_PORT=8080",
`HASH=${mainServiceHash}`,
"OLLAMA_DOCKER_TAG=0.1.47",
"WEBUI_DOCKER_TAG=0.3.7",
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 8080,
serviceName: "open-webui",
},
];
const envs = ["OLLAMA_DOCKER_TAG=0.1.47", "WEBUI_DOCKER_TAG=0.3.7"];
return {
envs,
domains,
};
}

View File

@@ -1,4 +1,4 @@
version: '3.8'
version: "3.8"
services:
db:
@@ -20,21 +20,9 @@ services:
PMA_USER: ${MYSQL_USER}
PMA_PASSWORD: ${MYSQL_PASSWORD}
PMA_ARBITRARY: 1
ports:
- ${PHPMYADMIN_PORT}
depends_on:
- db
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${PHPMYADMIN_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${PHPMYADMIN_PORT}
networks:
- dokploy-network
volumes:
db_data:
driver: local
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,24 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const rootPassword = generatePassword(32);
const password = generatePassword(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 80,
serviceName: "phpmyadmin",
},
];
const envs = [
`PHPMYADMIN_HOST=${randomDomain}`,
"PHPMYADMIN_PORT=80",
`HASH=${mainServiceHash}`,
`MYSQL_ROOT_PASSWORD=${rootPassword}`,
"MYSQL_DATABASE=mysql",
"MYSQL_USER=phpmyadmin",
@@ -23,5 +27,6 @@ export function generate(schema: Schema): Template {
return {
envs,
domains,
};
}

View File

@@ -32,17 +32,9 @@ services:
depends_on:
- plausible_db
- plausible_events_db
ports:
- ${PLAUSIBLE_PORT}
networks:
- dokploy-network
env_file:
- .env
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${PLAUSIBLE_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${PLAUSIBLE_PORT}"
volumes:
db-data:
driver: local
@@ -50,7 +42,3 @@ volumes:
driver: local
event-logs:
driver: local
networks:
dokploy-network:
external: true

View File

@@ -1,24 +1,28 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const secretBase = generateBase64(64);
const toptKeyBase = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "plausible",
},
];
const envs = [
`PLAUSIBLE_HOST=${randomDomain}`,
"PLAUSIBLE_PORT=8000",
`BASE_URL=http://${randomDomain}`,
`BASE_URL=http://${mainDomain}`,
`SECRET_KEY_BASE=${secretBase}`,
`TOTP_VAULT_KEY=${toptKeyBase}`,
`HASH=${mainServiceHash}`,
];
const mounts: Template["mounts"] = [
@@ -62,5 +66,6 @@ export function generate(schema: Schema): Template {
return {
envs,
mounts,
domains,
};
}

View File

@@ -3,19 +3,7 @@ services:
pocketbase:
image: spectado/pocketbase:0.22.12
restart: unless-stopped
ports:
- ${POCKETBASE_PORT}
networks:
- dokploy-network
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${POCKETBASE_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${POCKETBASE_PORT}
volumes:
- /etc/dokploy/templates/${HASH}/data:/pb_data
- /etc/dokploy/templates/${HASH}/public:/pb_public
- /etc/dokploy/templates/${HASH}/migrations:/pb_migrations
networks:
dokploy-network:
external: true

View File

@@ -1,21 +1,22 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const envs = [
`POCKETBASE_HOST=${randomDomain}`,
"POCKETBASE_PORT=80",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 80,
serviceName: "pocketbase",
},
];
return {
envs,
domains,
};
}

View File

@@ -9,18 +9,10 @@ services:
ROOT_URL: ${ROOT_URL:-http://${ROCKETCHAT_HOST}:${ROCKETCHAT_PORT}}
PORT: ${ROCKETCHAT_PORT}
DEPLOY_METHOD: docker
DEPLOY_PLATFORM:
REG_TOKEN:
DEPLOY_PLATFORM:
REG_TOKEN:
depends_on:
- mongodb
ports:
- ${ROCKETCHAT_PORT}
networks:
- dokploy-network
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${ROCKETCHAT_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${ROCKETCHAT_PORT}
mongodb:
image: docker.io/bitnami/mongodb:5.0
@@ -41,8 +33,3 @@ services:
volumes:
mongodb_data: { driver: local }
networks:
dokploy-network:
external: true

View File

@@ -1,21 +1,25 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const envs = [
`ROCKETCHAT_HOST=${randomDomain}`,
"ROCKETCHAT_PORT=3000",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "rocketchat",
},
];
const envs = [`ROCKETCHAT_HOST=${mainDomain}`, "ROCKETCHAT_PORT=3000"];
return {
envs,
domains,
};
}

View File

@@ -0,0 +1,12 @@
version: "3"
services:
soketi:
image: quay.io/soketi/soketi:1.4-16-debian
container_name: soketi
environment:
SOKETI_DEBUG: "1"
SOKETI_HOST: "0.0.0.0"
SOKETI_PORT: "6001"
SOKETI_METRICS_SERVER_PORT: "9601"
restart: unless-stopped

View File

@@ -0,0 +1,28 @@
import {
type DomainSchema,
type Schema,
type Template,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const metricsDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 6001,
serviceName: "soketi",
},
{
host: metricsDomain,
port: 9601,
serviceName: "soketi",
},
];
return {
domains,
};
}

View File

@@ -0,0 +1,437 @@
# Usage
# Start: docker compose up
# With helpers: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml up
# Stop: docker compose down
# Destroy: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml down -v --remove-orphans
name: supabase
version: "3.8"
services:
studio:
container_name: supabase-studio
image: supabase/studio:20240729-ce42139
networks:
- dokploy-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ]
timeout: 5s
interval: 5s
retries: 3
depends_on:
analytics:
condition: service_healthy
environment:
STUDIO_PG_META_URL: http://meta:8080
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION}
DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT}
SUPABASE_URL: http://kong:8000
SUPABASE_PUBLIC_URL: http://${SUPABASE_HOST}
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
AUTH_JWT_SECRET: ${JWT_SECRET}
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
LOGFLARE_URL: http://analytics:4000
NEXT_PUBLIC_ENABLE_LOGS: true
# Comment to use Big Query backend for analytics
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
# Uncomment to use Big Query backend for analytics
# NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
kong:
container_name: supabase-kong
image: kong:2.8.1
restart: unless-stopped
networks:
- dokploy-network
# https://unix.stackexchange.com/a/294837
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
#ports:
# - ${KONG_HTTP_PORT}:8000/tcp
# - ${KONG_HTTPS_PORT}:8443/tcp
expose:
- 8000
- 8443
labels:
- traefik.enable=true
- traefik.http.routers.frontend-app.rule=Host(`${SUPABASE_HOST}`)
- traefik.http.routers.frontend-app.entrypoints=web
- traefik.http.services.frontend-app.loadbalancer.server.port=${KONG_HTTP_PORT}
depends_on:
analytics:
condition: service_healthy
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
# https://github.com/supabase/cli/issues/14
KONG_DNS_ORDER: LAST,A,CNAME
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
volumes:
# https://github.com/supabase/supabase/issues/12661
- ../files/volumes/api/kong.yml:/home/kong/temp.yml:ro
auth:
container_name: supabase-auth
image: supabase/gotrue:v2.158.1
networks:
- dokploy-network
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
GOTRUE_API_HOST: 0.0.0.0
GOTRUE_API_PORT: 9999
API_EXTERNAL_URL: http://${SUPABASE_HOST}
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
GOTRUE_SITE_URL: http://${SUPABASE_HOST}
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
GOTRUE_JWT_ADMIN_ROLES: service_role
GOTRUE_JWT_AUD: authenticated
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_JWT_EXP: ${JWT_EXPIRY}
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
# GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
# GOTRUE_SMTP_MAX_FREQUENCY: 1s
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
GOTRUE_SMTP_HOST: ${SMTP_HOSTNAME}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
# Uncomment to enable custom access token hook. You'll need to create a public.custom_access_token_hook function and grant necessary permissions.
# See: https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token for details
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED="true"
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="pg-functions://postgres/public/custom_access_token_hook"
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED="true"
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/mfa_verification_attempt"
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true"
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt"
rest:
container_name: supabase-rest
image: postgrest/postgrest:v12.2.0
networks:
- dokploy-network
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
restart: unless-stopped
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
PGRST_DB_USE_LEGACY_GUCS: "false"
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY}
command: "postgrest"
realtime:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
container_name: realtime-dev.supabase-realtime
image: supabase/realtime:v2.30.23
networks:
- dokploy-network
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
healthcheck:
test: [ "CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "-H", "Authorization: Bearer ${ANON_KEY}", "http://localhost:4000/api/tenants/realtime-dev/health" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
PORT: 4000
DB_HOST: ${POSTGRES_HOSTNAME}
DB_PORT: ${POSTGRES_PORT}
DB_USER: supabase_admin
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_NAME: ${POSTGRES_DB}
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
DB_ENC_KEY: supabaserealtime
API_JWT_SECRET: ${JWT_SECRET}
SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
ERL_AFLAGS: -proto_dist inet_tcp
DNS_NODES: "''"
RLIMIT_NOFILE: "10000"
APP_NAME: realtime
SEED_SELF_HOST: true
# To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.0.6
networks:
- dokploy-network
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
rest:
condition: service_started
imgproxy:
condition: service_started
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ]
timeout: 5s
interval: 5s
retries: 3
restart: unless-stopped
environment:
ANON_KEY: ${ANON_KEY}
SERVICE_KEY: ${SERVICE_ROLE_KEY}
POSTGREST_URL: http://rest:3000
PGRST_JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
TENANT_ID: stub
# TODO: https://github.com/supabase/storage-api/issues/55
REGION: stub
GLOBAL_S3_BUCKET: stub
ENABLE_IMAGE_TRANSFORMATION: "true"
IMGPROXY_URL: http://imgproxy:5001
volumes:
- ../files/volumes/storage:/var/lib/storage:z
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.8.0
networks:
- dokploy-network
healthcheck:
test: [ "CMD", "imgproxy", "health" ]
timeout: 5s
interval: 5s
retries: 3
environment:
IMGPROXY_BIND: ":5001"
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
IMGPROXY_USE_ETAG: "true"
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
volumes:
- ../files/volumes/storage:/var/lib/storage:z
meta:
container_name: supabase-meta
image: supabase/postgres-meta:v0.83.2
networks:
- dokploy-network
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
analytics:
condition: service_healthy
restart: unless-stopped
environment:
PG_META_PORT: 8080
PG_META_DB_HOST: ${POSTGRES_HOSTNAME}
PG_META_DB_PORT: ${POSTGRES_PORT}
PG_META_DB_NAME: ${POSTGRES_DB}
PG_META_DB_USER: supabase_admin
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
functions:
container_name: supabase-edge-functions
image: supabase/edge-runtime:v1.56.0
restart: unless-stopped
networks:
- dokploy-network
depends_on:
analytics:
condition: service_healthy
environment:
JWT_SECRET: ${JWT_SECRET}
SUPABASE_URL: http://kong:8000
SUPABASE_ANON_KEY: ${ANON_KEY}
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
volumes:
- ../files/volumes/functions:/home/deno/functions:Z
command:
- start
- --main-service
- /home/deno/functions/main
analytics:
container_name: supabase-analytics
image: supabase/logflare:1.4.0
networks:
- dokploy-network
healthcheck:
test: [ "CMD", "curl", "http://localhost:4000/health" ]
timeout: 5s
interval: 5s
retries: 10
restart: unless-stopped
depends_on:
db:
# Disable this if you are using an external Postgres database
condition: service_healthy
# Uncomment to use Big Query backend for analytics
# volumes:
# - type: bind
# source: ${PWD}/gcloud.json
# target: /opt/app/rel/logflare/bin/gcloud.json
# read_only: true
environment:
LOGFLARE_NODE_HOST: 127.0.0.1
DB_USERNAME: supabase_admin
DB_DATABASE: ${POSTGRES_DB}
DB_HOSTNAME: ${POSTGRES_HOSTNAME}
DB_PORT: ${POSTGRES_PORT}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_SCHEMA: _analytics
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
LOGFLARE_SINGLE_TENANT: true
LOGFLARE_SUPABASE_MODE: true
LOGFLARE_MIN_CLUSTER_SIZE: 1
# Comment variables to use Big Query backend for analytics
POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
POSTGRES_BACKEND_SCHEMA: _analytics
LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
# Uncomment to use Big Query backend for analytics
# GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID}
# GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER}
#ports:
# - 4000:4000
expose:
- 4000
# Comment out everything below this point if you are using an external Postgres database
db:
container_name: supabase-db
image: supabase/postgres:15.1.1.78
networks:
- dokploy-network
healthcheck:
test: pg_isready -U postgres -h localhost
interval: 5s
timeout: 5s
retries: 10
depends_on:
vector:
condition: service_healthy
command:
- postgres
- -c
- config_file=/etc/postgresql/postgresql.conf
- -c
- log_min_messages=fatal # prevents Realtime polling queries from appearing in logs
restart: unless-stopped
#ports:
# # Pass down internal port because it's set dynamically by other services
# - ${POSTGRES_PORT}:${POSTGRES_PORT}
expose:
- ${POSTGRES_PORT}
environment:
POSTGRES_HOST: /var/run/postgresql
PGPORT: ${POSTGRES_PORT}
POSTGRES_PORT: ${POSTGRES_PORT}
PGPASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATABASE: ${POSTGRES_DB}
POSTGRES_DB: ${POSTGRES_DB}
JWT_SECRET: ${JWT_SECRET}
JWT_EXP: ${JWT_EXPIRY}
volumes:
- ../files/volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
# Must be superuser to create event trigger
- ../files/volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
# Must be superuser to alter reserved role
- ../files/volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
# Initialize the database settings with JWT_SECRET and JWT_EXP
- ../files/volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
# PGDATA directory is persisted between restarts
- ../files/volumes/db/data:/var/lib/postgresql/data:Z
# Changes required for Analytics support
- ../files/volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
# Use named volume to persist pgsodium decryption key between restarts
- db-config:/etc/postgresql-custom
vector:
container_name: supabase-vector
image: timberio/vector:0.28.1-alpine
networks:
- dokploy-network
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://vector:9001/health"
]
timeout: 5s
interval: 5s
retries: 3
volumes:
- ../files/volumes/logs/vector.yml:/etc/vector/vector.yml:ro
- ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
environment:
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
command: [ "--config", "etc/vector/vector.yml" ]
volumes:
db-config:
networks:
dokploy-network:
external: true

View File

@@ -0,0 +1,989 @@
import { createHmac, randomBytes } from "node:crypto";
import {
type Schema,
type Template,
generateBase64,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
interface JWTPayload {
role: "anon" | "service_role";
iss: string;
iat: number;
exp: number;
}
function base64UrlEncode(str: string): string {
return Buffer.from(str)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
function generateJWT(payload: JWTPayload, secret: string): string {
const header = { alg: "HS256", typ: "JWT" };
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
const signature = createHmac("sha256", secret)
.update(`${encodedHeader}.${encodedPayload}`)
.digest("base64url");
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
export function generateSupabaseAnonJWT(secret: string): string {
const now = Math.floor(Date.now() / 1000);
const payload: JWTPayload = {
role: "anon",
iss: "supabase",
iat: now,
exp: now + 100 * 365 * 24 * 60 * 60, // 100 years
};
return generateJWT(payload, secret);
}
export function generateSupabaseServiceJWT(secret: string): string {
const now = Math.floor(Date.now() / 1000);
const payload: JWTPayload = {
role: "service_role",
iss: "supabase",
iat: now,
exp: now + 100 * 365 * 24 * 60 * 60, // 100 years
};
return generateJWT(payload, secret);
}
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const postgresPassword = generatePassword(32);
const jwtSecret = generateBase64(32);
const dashboardPassword = generatePassword(32);
const logflareApiKey = generatePassword(32);
const annonKey = generateSupabaseAnonJWT(jwtSecret);
const serviceRoleKey = generateSupabaseServiceJWT(jwtSecret);
const envs = [
`SUPABASE_HOST=${randomDomain}`,
`POSTGRES_PASSWORD=${postgresPassword}`,
`JWT_SECRET=${jwtSecret}`,
`ANON_KEY=${annonKey}`,
`SERVICE_ROLE_KEY=${serviceRoleKey}`,
"DASHBOARD_USERNAME=supabase",
`DASHBOARD_PASSWORD=${dashboardPassword}`,
"POSTGRES_HOSTNAME=db",
"POSTGRES_DB=postgres",
"POSTGRES_PORT=5432",
"KONG_HTTP_PORT=8000",
"KONG_HTTPS_PORT=8443",
"PGRST_DB_SCHEMAS=public,storage,graphql_public",
"ADDITIONAL_REDIRECT_URLS=",
"JWT_EXPIRY=3600",
"DISABLE_SIGNUP=false",
`MAILER_URLPATHS_CONFIRMATION=\"/auth/v1/verify\"`,
`MAILER_URLPATHS_INVITE=\"/auth/v1/verify\"`,
`MAILER_URLPATHS_RECOVERY=\"/auth/v1/verify\"`,
`MAILER_URLPATHS_EMAIL_CHANGE=\"/auth/v1/verify\"`,
"ENABLE_EMAIL_SIGNUP=true",
"ENABLE_EMAIL_AUTOCONFIRM=false",
"SMTP_ADMIN_EMAIL=admin@example.com",
"SMTP_HOSTNAME=supabase-mail",
"SMTP_PORT=2500",
"SMTP_USER=fake_mail_user",
"SMTP_PASS=fake_mail_password",
"SMTP_SENDER_NAME=fake_sender",
"ENABLE_ANONYMOUS_USERS=false",
"ENABLE_PHONE_SIGNUP=true",
"ENABLE_PHONE_AUTOCONFIRM=true",
"STUDIO_DEFAULT_ORGANIZATION=Default Organization",
"STUDIO_DEFAULT_PROJECT=Default Project",
"STUDIO_PORT=3000",
"IMGPROXY_ENABLE_WEBP_DETECTION=true",
"FUNCTIONS_VERIFY_JWT=false",
`LOGFLARE_LOGGER_BACKEND_API_KEY=${logflareApiKey}`,
`LOGFLARE_API_KEY=${logflareApiKey}`,
"DOCKER_SOCKET_LOCATION=/var/run/docker.sock",
"GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID",
"GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER",
`HASH=${mainServiceHash}`,
];
const mounts: Template["mounts"] = [
{
filePath: "/volumes/api/kong.yml",
content: `
_format_version: '2.1'
_transform: true
###
### Consumers / Users
###
consumers:
- username: DASHBOARD
- username: anon
keyauth_credentials:
- key: $SUPABASE_ANON_KEY
- username: service_role
keyauth_credentials:
- key: $SUPABASE_SERVICE_KEY
###
### Access Control List
###
acls:
- consumer: anon
group: anon
- consumer: service_role
group: admin
###
### Dashboard credentials
###
basicauth_credentials:
- consumer: DASHBOARD
username: $DASHBOARD_USERNAME
password: $DASHBOARD_PASSWORD
###
### API Routes
###
services:
## Open Auth routes
- name: auth-v1-open
url: http://auth:9999/verify
routes:
- name: auth-v1-open
strip_path: true
paths:
- /auth/v1/verify
plugins:
- name: cors
- name: auth-v1-open-callback
url: http://auth:9999/callback
routes:
- name: auth-v1-open-callback
strip_path: true
paths:
- /auth/v1/callback
plugins:
- name: cors
- name: auth-v1-open-authorize
url: http://auth:9999/authorize
routes:
- name: auth-v1-open-authorize
strip_path: true
paths:
- /auth/v1/authorize
plugins:
- name: cors
## Secure Auth routes
- name: auth-v1
_comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'
url: http://auth:9999/
routes:
- name: auth-v1-all
strip_path: true
paths:
- /auth/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Secure REST routes
- name: rest-v1
_comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'
url: http://rest:3000/
routes:
- name: rest-v1-all
strip_path: true
paths:
- /rest/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Secure GraphQL routes
- name: graphql-v1
_comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'
url: http://rest:3000/rpc/graphql
routes:
- name: graphql-v1-all
strip_path: true
paths:
- /graphql/v1
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: true
- name: request-transformer
config:
add:
headers:
- Content-Profile:graphql_public
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Secure Realtime routes
- name: realtime-v1-ws
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/socket
protocol: ws
routes:
- name: realtime-v1-ws
strip_path: true
paths:
- /realtime/v1/
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
- name: realtime-v1-rest
_comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'
url: http://realtime-dev.supabase-realtime:4000/api
protocol: http
routes:
- name: realtime-v1-rest
strip_path: true
paths:
- /realtime/v1/api
plugins:
- name: cors
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
- anon
## Storage routes: the storage server manages its own auth
- name: storage-v1
_comment: 'Storage: /storage/v1/* -> http://storage:5000/*'
url: http://storage:5000/
routes:
- name: storage-v1-all
strip_path: true
paths:
- /storage/v1/
plugins:
- name: cors
## Edge Functions routes
- name: functions-v1
_comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'
url: http://functions:9000/
routes:
- name: functions-v1-all
strip_path: true
paths:
- /functions/v1/
plugins:
- name: cors
## Analytics routes
- name: analytics-v1
_comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'
url: http://analytics:4000/
routes:
- name: analytics-v1-all
strip_path: true
paths:
- /analytics/v1/
## Secure Database routes
- name: meta
_comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'
url: http://meta:8080/
routes:
- name: meta-all
strip_path: true
paths:
- /pg/
plugins:
- name: key-auth
config:
hide_credentials: false
- name: acl
config:
hide_groups_header: true
allow:
- admin
## Protected Dashboard - catch all remaining routes
- name: dashboard
_comment: 'Studio: /* -> http://studio:3000/*'
url: http://studio:3000/
routes:
- name: dashboard-all
strip_path: true
paths:
- /
plugins:
- name: cors
- name: basic-auth
config:
hide_credentials: true
`,
},
{
filePath: "/volumes/db/init/data.sql",
content: `
`,
},
{
filePath: "/volumes/db/jwt.sql",
content: `
\\set jwt_secret \`echo "$JWT_SECRET"\`
\\set jwt_exp \`echo "$JWT_EXP"\`
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
`,
},
{
filePath: "/volumes/db/logs.sql",
content: `
\\set pguser \`echo "$POSTGRES_USER"\`
create schema if not exists _analytics;
alter schema _analytics owner to :pguser;
`,
},
{
filePath: "/volumes/db/realtime.sql",
content: `
\\set pguser \`echo "$POSTGRES_USER"\`
create schema if not exists _realtime;
alter schema _realtime owner to :pguser;
`,
},
{
filePath: "/volumes/db/roles.sql",
content: `
-- NOTE: change to your own passwords for production environments
\\set pgpass \`echo "$POSTGRES_PASSWORD"\`
ALTER USER authenticator WITH PASSWORD :'pgpass';
ALTER USER pgbouncer WITH PASSWORD :'pgpass';
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
`,
},
{
filePath: "/volumes/db/webhooks.sql",
content: `
BEGIN;
-- Create pg_net extension
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
-- Create supabase_functions schema
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
-- supabase_functions.migrations definition
CREATE TABLE supabase_functions.migrations (
version text PRIMARY KEY,
inserted_at timestamptz NOT NULL DEFAULT NOW()
);
-- Initial supabase_functions migration
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
-- supabase_functions.hooks definition
CREATE TABLE supabase_functions.hooks (
id bigserial PRIMARY KEY,
hook_table_id integer NOT NULL,
hook_name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
request_id bigint
);
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
CREATE FUNCTION supabase_functions.http_request()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
request_id bigint;
payload jsonb;
url text := TG_ARGV[0]::text;
method text := TG_ARGV[1]::text;
headers jsonb DEFAULT '{}'::jsonb;
params jsonb DEFAULT '{}'::jsonb;
timeout_ms integer DEFAULT 1000;
BEGIN
IF url IS NULL OR url = 'null' THEN
RAISE EXCEPTION 'url argument is missing';
END IF;
IF method IS NULL OR method = 'null' THEN
RAISE EXCEPTION 'method argument is missing';
END IF;
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
headers = '{"Content-Type": "application/json"}'::jsonb;
ELSE
headers = TG_ARGV[2]::jsonb;
END IF;
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
params = '{}'::jsonb;
ELSE
params = TG_ARGV[3]::jsonb;
END IF;
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
timeout_ms = 1000;
ELSE
timeout_ms = TG_ARGV[4]::integer;
END IF;
CASE
WHEN method = 'GET' THEN
SELECT http_get INTO request_id FROM net.http_get(
url,
params,
headers,
timeout_ms
);
WHEN method = 'POST' THEN
payload = jsonb_build_object(
'old_record', OLD,
'record', NEW,
'type', TG_OP,
'table', TG_TABLE_NAME,
'schema', TG_TABLE_SCHEMA
);
SELECT http_post INTO request_id FROM net.http_post(
url,
payload,
params,
headers,
timeout_ms
);
ELSE
RAISE EXCEPTION 'method argument % is invalid', method;
END CASE;
INSERT INTO supabase_functions.hooks
(hook_table_id, hook_name, request_id)
VALUES
(TG_RELID, TG_NAME, request_id);
RETURN NEW;
END
$function$;
-- Supabase super admin
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_functions_admin'
)
THEN
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
END IF;
END
$$;
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
GRANT supabase_functions_admin TO postgres;
-- Remove unused supabase_pg_net_admin role
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_roles
WHERE rolname = 'supabase_pg_net_admin'
)
THEN
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
DROP OWNED BY supabase_pg_net_admin;
DROP ROLE supabase_pg_net_admin;
END IF;
END
$$;
-- pg_net grants when extension is already enabled
DO
$$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_extension
WHERE extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END
$$;
-- Event trigger for pg_net
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_event_trigger_ddl_commands() AS ev
JOIN pg_extension AS ext
ON ev.objid = ext.oid
WHERE ext.extname = 'pg_net'
)
THEN
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
END IF;
END;
$$;
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
DO
$$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_event_trigger
WHERE evtname = 'issue_pg_net_access'
) THEN
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
EXECUTE PROCEDURE extensions.grant_pg_net_access();
END IF;
END
$$;
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
ALTER function supabase_functions.http_request() SECURITY DEFINER;
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
COMMIT;
`,
},
{
filePath: "/volumes/functions/hello/index.ts",
content: `
// Follow this setup guide to integrate the Deno language server with your editor:
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.177.1/http/server.ts"
serve(async () => {
return new Response(
\`"Hello from Edge Functions!"\`,
{ headers: { "Content-Type": "application/json" } },
)
})
// To invoke:
// curl 'http://localhost:<KONG_HTTP_PORT>/functions/v1/hello' \\
// --header 'Authorization: Bearer <anon/service_role API key>'
`,
},
{
filePath: "/volumes/functions/main/index.ts",
content: `
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
console.log('main function started')
const JWT_SECRET = Deno.env.get('JWT_SECRET')
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
function getAuthToken(req: Request) {
const authHeader = req.headers.get('authorization')
if (!authHeader) {
throw new Error('Missing authorization header')
}
const [bearer, token] = authHeader.split(' ')
if (bearer !== 'Bearer') {
throw new Error(\`Auth header is not 'Bearer {token}'\`)
}
return token
}
async function verifyJWT(jwt: string): Promise<boolean> {
const encoder = new TextEncoder()
const secretKey = encoder.encode(JWT_SECRET)
try {
await jose.jwtVerify(jwt, secretKey)
} catch (err) {
console.error(err)
return false
}
return true
}
serve(async (req: Request) => {
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
try {
const token = getAuthToken(req)
const isValidJWT = await verifyJWT(token)
if (!isValidJWT) {
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
} catch (e) {
console.error(e)
return new Response(JSON.stringify({ msg: e.toString() }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
})
}
}
const url = new URL(req.url)
const { pathname } = url
const path_parts = pathname.split('/')
const service_name = path_parts[1]
if (!service_name || service_name === '') {
const error = { msg: 'missing function name in request' }
return new Response(JSON.stringify(error), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const servicePath = \`/home/deno/functions/\${service_name}\`
console.error(\`serving the request with \${servicePath}\`)
const memoryLimitMb = 150
const workerTimeoutMs = 1 * 60 * 1000
const noModuleCache = false
const importMapPath = null
const envVarsObj = Deno.env.toObject()
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
try {
const worker = await EdgeRuntime.userWorkers.create({
servicePath,
memoryLimitMb,
workerTimeoutMs,
noModuleCache,
importMapPath,
envVars,
})
return await worker.fetch(req)
} catch (e) {
const error = { msg: e.toString() }
return new Response(JSON.stringify(error), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
})
`,
},
{
filePath: "/volumes/logs/vector.yml",
content: `
api:
enabled: true
address: 0.0.0.0:9001
sources:
docker_host:
type: docker_logs
exclude_containers:
- supabase-vector
transforms:
project_logs:
type: remap
inputs:
- docker_host
source: |-
.project = "default"
.event_message = del(.message)
.appname = del(.container_name)
del(.container_created_at)
del(.container_id)
del(.source_type)
del(.stream)
del(.label)
del(.image)
del(.host)
del(.stream)
router:
type: route
inputs:
- project_logs
route:
kong: '.appname == "supabase-kong"'
auth: '.appname == "supabase-auth"'
rest: '.appname == "supabase-rest"'
realtime: '.appname == "supabase-realtime"'
storage: '.appname == "supabase-storage"'
functions: '.appname == "supabase-functions"'
db: '.appname == "supabase-db"'
# Ignores non nginx errors since they are related with kong booting up
kong_logs:
type: remap
inputs:
- router.kong
source: |-
req, err = parse_nginx_log(.event_message, "combined")
if err == null {
.timestamp = req.timestamp
.metadata.request.headers.referer = req.referer
.metadata.request.headers.user_agent = req.agent
.metadata.request.headers.cf_connecting_ip = req.client
.metadata.request.method = req.method
.metadata.request.path = req.path
.metadata.request.protocol = req.protocol
.metadata.response.status_code = req.status
}
if err != null {
abort
}
# Ignores non nginx errors since they are related with kong booting up
kong_err:
type: remap
inputs:
- router.kong
source: |-
.metadata.request.method = "GET"
.metadata.response.status_code = 200
parsed, err = parse_nginx_log(.event_message, "error")
if err == null {
.timestamp = parsed.timestamp
.severity = parsed.severity
.metadata.request.host = parsed.host
.metadata.request.headers.cf_connecting_ip = parsed.client
url, err = split(parsed.request, " ")
if err == null {
.metadata.request.method = url[0]
.metadata.request.path = url[1]
.metadata.request.protocol = url[2]
}
}
if err != null {
abort
}
# Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.
auth_logs:
type: remap
inputs:
- router.auth
source: |-
parsed, err = parse_json(.event_message)
if err == null {
.metadata.timestamp = parsed.time
.metadata = merge!(.metadata, parsed)
}
# PostgREST logs are structured so we separate timestamp from message using regex
rest_logs:
type: remap
inputs:
- router.rest
source: |-
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.timestamp = to_timestamp!(parsed.time)
.metadata.host = .project
}
# Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)
realtime_logs:
type: remap
inputs:
- router.realtime
source: |-
.metadata.project = del(.project)
.metadata.external_id = .metadata.project
parsed, err = parse_regex(.event_message, r'^(?P<time>\\d+:\\d+:\\d+\\.\\d+) \\[(?P<level>\\w+)\\] (?P<msg>.*)$')
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
}
# Storage logs may contain json objects so we parse them for completeness
storage_logs:
type: remap
inputs:
- router.storage
source: |-
.metadata.project = del(.project)
.metadata.tenantId = .metadata.project
parsed, err = parse_json(.event_message)
if err == null {
.event_message = parsed.msg
.metadata.level = parsed.level
.metadata.timestamp = parsed.time
.metadata.context[0].host = parsed.hostname
.metadata.context[0].pid = parsed.pid
}
# Postgres logs some messages to stderr which we map to warning severity level
db_logs:
type: remap
inputs:
- router.db
source: |-
.metadata.host = "db-default"
.metadata.parsed.timestamp = .timestamp
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
if err != null || parsed == null {
.metadata.parsed.error_severity = "info"
}
if parsed != null {
.metadata.parsed.error_severity = parsed.level
}
if .metadata.parsed.error_severity == "info" {
.metadata.parsed.error_severity = "log"
}
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
sinks:
logflare_auth:
type: 'http'
inputs:
- auth_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_realtime:
type: 'http'
inputs:
- realtime_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_rest:
type: 'http'
inputs:
- rest_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_db:
type: 'http'
inputs:
- db_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
# We must route the sink through kong because ingesting logs before logflare is fully initialised will
# lead to broken queries from studio. This works by the assumption that containers are started in the
# following order: vector > db > logflare > kong
uri: 'http://kong:8000/analytics/v1/api/logs?source_name=postgres.logs&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_functions:
type: 'http'
inputs:
- router.functions
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_storage:
type: 'http'
inputs:
- storage_logs
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
logflare_kong:
type: 'http'
inputs:
- kong_logs
- kong_err
encoding:
codec: 'json'
method: 'post'
request:
retry_max_duration_secs: 10
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod&api_key=\${LOGFLARE_API_KEY?LOGFLARE_API_KEY is required}'
`,
},
];
return {
envs,
mounts,
};
}

View File

@@ -4,8 +4,6 @@ services:
teable:
image: ghcr.io/teableio/teable:1.3.1-alpha-build.460
restart: always
ports:
- ${TEABLE_PORT}
volumes:
- teable-data:/app/.assets
# you may use a bind-mounted host directory instead,
@@ -24,12 +22,6 @@ services:
- BACKEND_MAIL_SENDER_NAME=${BACKEND_MAIL_SENDER_NAME}
- BACKEND_MAIL_AUTH_USER=${BACKEND_MAIL_AUTH_USER}
- BACKEND_MAIL_AUTH_PASS=${BACKEND_MAIL_AUTH_PASS}
networks:
- dokploy-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${TEABLE_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${TEABLE_PORT}"
depends_on:
teable-db-migrate:
condition: service_completed_successfully
@@ -72,10 +64,6 @@ services:
teable-db:
condition: service_healthy
networks:
dokploy-network:
external: true
volumes:
teable-data: {}
teable-db: {}

View File

@@ -1,25 +1,30 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const password = generatePassword();
const randomDomain = generateRandomDomain(schema);
const mainDomain = generateRandomDomain(schema);
const publicDbPort = ((min: number, max: number) => {
return Math.round(Math.random() * (max - min) + min);
})(32769, 65534);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "teable",
},
];
const envs = [
`TEABLE_HOST=${randomDomain}`,
"TEABLE_PORT=3000",
`TEABLE_HOST=${mainDomain}`,
`TEABLE_DB_PORT=${publicDbPort}`,
`HASH=${mainServiceHash}`,
"TIMEZONE=UTC",
"# Postgres",
"POSTGRES_HOST=teable-db",
@@ -44,5 +49,6 @@ export function generate(schema: Schema): Template {
return {
envs,
domains,
};
}

View File

@@ -1,6 +1,21 @@
import type { TemplateData } from "./types/templates-data.type";
export const templates: TemplateData[] = [
{
id: "supabase",
name: "SupaBase",
version: "1.24.07",
description:
"The open source Firebase alternative. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. ",
links: {
github: "https://github.com/supabase/supabase",
website: "https://supabase.com/",
docs: "https://supabase.com/docs/guides/self-hosting",
},
logo: "supabase.svg",
load: () => import("./supabase/index").then((m) => m.generate),
tags: ["database", "firebase", "postgres"],
},
{
id: "pocketbase",
name: "Pocketbase",
@@ -408,4 +423,34 @@ export const templates: TemplateData[] = [
tags: ["database", "spreadsheet", "low-code", "nocode"],
load: () => import("./teable/index").then((m) => m.generate),
},
{
id: "soketi",
name: "Soketi",
version: "v1.4-16",
description:
"Soketi is your simple, fast, and resilient open-source WebSockets server.",
logo: "soketi.png",
links: {
github: "https://github.com/soketi/soketi",
website: "https://soketi.app/",
docs: "https://docs.soketi.app/",
},
tags: ["chat"],
load: () => import("./soketi/index").then((m) => m.generate),
},
{
id: "aptabase",
name: "Aptabase",
version: "v1.0.0",
description:
"Aptabase is a self-hosted web analytics platform that lets you track website traffic and user behavior.",
logo: "aptabase.svg",
links: {
github: "https://github.com/aptabase/aptabase",
website: "https://aptabase.com/",
docs: "https://github.com/aptabase/aptabase/blob/main/README.md",
},
tags: ["analytics", "self-hosted"],
load: () => import("./aptabase/index").then((m) => m.generate),
},
];

View File

@@ -10,18 +10,10 @@ services:
depends_on:
db:
condition: service_healthy
ports:
- ${UMAMI_PORT}
networks:
- dokploy-network
environment:
DATABASE_URL: postgresql://umami:umami@db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: ${APP_SECRET}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${UMAMI_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${UMAMI_PORT}"
db:
image: postgres:15-alpine
restart: always
@@ -39,8 +31,5 @@ services:
POSTGRES_USER: umami
POSTGRES_PASSWORD: umami
networks:
dokploy-network:
external: true
volumes:
db-data:

View File

@@ -1,24 +1,27 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const randomSecret = generateBase64();
const envs = [
`UMAMI_HOST=${randomDomain}`,
"UMAMI_PORT=3000",
`APP_SECRET=${randomSecret}`,
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 3000,
serviceName: "umami",
},
];
const envs = [`APP_SECRET=${randomSecret}`];
return {
envs,
domains,
};
}

View File

@@ -1,21 +1,10 @@
version: "3.8"
services:
uptime-kuma:
networks:
- dokploy-network
image: louislam/uptime-kuma:1
restart: always
ports:
- ${UPTIME_KUMA_PORT}
volumes:
- uptime-kuma-data:/app/data
labels:
- traefik.enable=true
- traefik.http.routers.${HASH}.rule=Host(`${UPTIME_KUMA_HOST}`)
- traefik.http.services.${HASH}.loadbalancer.server.port=${UPTIME_KUMA_PORT}
volumes:
uptime-kuma-data:
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,22 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`UPTIME_KUMA_HOST=${randomDomain}`,
"UPTIME_KUMA_PORT=3001",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 3001,
serviceName: "uptime-kuma",
},
];
return {
envs,
domains,
};
}

View File

@@ -1,6 +1,7 @@
import { randomBytes } from "node:crypto";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import type { Domain } from "@/server/api/services/domain";
import { TRPCError } from "@trpc/server";
import { templates } from "../templates";
import type { TemplatesKeys } from "../types/templates-data.type";
@@ -10,12 +11,15 @@ export interface Schema {
projectName: string;
}
export type DomainSchema = Pick<Domain, "host" | "port" | "serviceName">;
export interface Template {
envs: string[];
envs?: string[];
mounts?: {
filePath: string;
content?: string;
}[];
domains?: DomainSchema[];
}
export const generateRandomDomain = ({

View File

@@ -1,20 +1,12 @@
version: '3.8'
version: "3.8"
services:
wordpress:
image: wordpress:5.8.3
networks:
- dokploy-network
ports:
- ${WORDPRESS_PORT}
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${WORDPRESS_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${WORDPRESS_PORT}"
volumes:
- wordpress_data:/var/www/html
@@ -33,7 +25,3 @@ services:
volumes:
wordpress_data:
db_data:
networks:
dokploy-network:
external: true

View File

@@ -1,20 +1,22 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const randomDomain = generateRandomDomain(schema);
const envs = [
`WORDPRESS_HOST=${randomDomain}`,
"WORDPRESS_PORT=80",
`HASH=${mainServiceHash}`,
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 80,
serviceName: "wordpress",
},
];
return {
envs,
domains,
};
}