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
+12 -5
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
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.
+1
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 🤝
@@ -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");
}}
>
@@ -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");
}}
>
@@ -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">
@@ -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>
);
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.7.0",
"version": "v0.7.2",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -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

@@ -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

+9
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(
+15 -6
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,
};
}),
});
+14 -3
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;
}),
+38 -1
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;
}),
});
+10 -1
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",
@@ -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
+9 -6
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,
};
}
@@ -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
+27
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,
};
}
@@ -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
+11 -7
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,
};
}
@@ -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:
+12 -7
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,
};
}
@@ -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:
+8 -8
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,
};
}
@@ -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:
+12 -6
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,
};
}
@@ -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
+12 -5
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,
};
}
@@ -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
+10 -7
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,
};
}
@@ -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
+11 -6
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,
};
}
@@ -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
+12 -5
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,
};
}
@@ -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: {}
+8 -9
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,
};
}
@@ -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
+11 -8
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,
};
}
@@ -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
+10 -5
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,
};
}
@@ -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
+11 -9
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,
};
}
@@ -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
+9 -7
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,
};
}
@@ -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
+15 -10
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,
};
}
+2 -13
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:
+12 -5
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,
};
}
@@ -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: {}
+10 -8
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,
};
}
@@ -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
+9 -7
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,
};
}
@@ -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: {}
+10 -8
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,
};
}
@@ -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
+11 -6
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,
};
}
@@ -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
+12 -7
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,
};
}
@@ -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
+9 -8
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,
};
}
@@ -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
+11 -7
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,
};
}
@@ -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
+28
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,
};
}
@@ -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
+989
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,
};
}
@@ -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: {}
+12 -6
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,
};
}
+45
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),
},
];
@@ -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:
+10 -7
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,
};
}
@@ -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
+9 -7
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,
};
}
+5 -1
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 = ({
@@ -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
+9 -7
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,
};
}