mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213fa08210 | ||
|
|
1eed1a356d | ||
|
|
a8f21ad717 | ||
|
|
c1420bd6d8 | ||
|
|
74ea9debd5 | ||
|
|
1df9f1f4df | ||
|
|
f06ac587c9 | ||
|
|
c084cf84a0 | ||
|
|
5e5cbdeef9 | ||
|
|
24929d8a4d | ||
|
|
1e6e85ed5b | ||
|
|
137edf1250 | ||
|
|
b8741f1702 | ||
|
|
ac28aff022 | ||
|
|
217be3c6e9 | ||
|
|
27a0dc3770 | ||
|
|
53b24534a8 | ||
|
|
5a3d0f8288 | ||
|
|
ff3e3513ef | ||
|
|
7a1fba38b3 | ||
|
|
2b65a3c119 | ||
|
|
6d841497cc | ||
|
|
d37dc7c372 | ||
|
|
f3e0cf861f | ||
|
|
e2578e5794 | ||
|
|
16deec381c | ||
|
|
91dc35a138 | ||
|
|
acfa032e61 | ||
|
|
83ee4b2c59 | ||
|
|
d5c6a601d8 | ||
|
|
afbe42a577 | ||
|
|
866f700abf | ||
|
|
f2b6b33b1f | ||
|
|
813ffabb8c | ||
|
|
b5da9291b4 | ||
|
|
b0d604d12b | ||
|
|
e9f40e1644 | ||
|
|
61d520c239 | ||
|
|
124a884d2e | ||
|
|
b5e4b9af60 | ||
|
|
840c24e3ca | ||
|
|
d300eb73fb | ||
|
|
fb4e06116c | ||
|
|
2d3b903edc | ||
|
|
75c13df22f | ||
|
|
68b81cb48d | ||
|
|
9d6f2df25a | ||
|
|
724de2c1b9 | ||
|
|
86946b6b15 | ||
|
|
957bb3d3e6 | ||
|
|
7558029271 | ||
|
|
757c28dad1 | ||
|
|
8d3dc38816 | ||
|
|
9c8061a447 | ||
|
|
3a8b2867b6 | ||
|
|
389956d1a2 | ||
|
|
a84bdd1c8e | ||
|
|
eb219221be | ||
|
|
7b176bd877 | ||
|
|
6970923253 | ||
|
|
a3e23d54d8 | ||
|
|
8f11207d72 | ||
|
|
6bd98350d9 |
@@ -166,20 +166,26 @@ import {
|
||||
generateRandomDomain,
|
||||
type Template,
|
||||
type Schema,
|
||||
type DomainSchema,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const secretBase = generateBase64(64);
|
||||
const toptKeyBase = generateBase64(32);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 8000,
|
||||
serviceName: "plausible",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
// If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
||||
`PLAUSIBLE_HOST=${randomDomain}`,
|
||||
"PLAUSIBLE_PORT=8000",
|
||||
`BASE_URL=http://${randomDomain}`,
|
||||
`BASE_URL=http://${mainDomain}`,
|
||||
`SECRET_KEY_BASE=${secretBase}`,
|
||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
||||
`HASH=${mainServiceHash}`,
|
||||
@@ -195,6 +201,7 @@ export function generate(schema: Schema): Template {
|
||||
return {
|
||||
envs,
|
||||
mounts,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
26
LICENSE.MD
Normal file
26
LICENSE.MD
Normal 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.
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.7.0",
|
||||
"version": "v0.7.2",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
5
apps/dokploy/public/templates/aptabase.svg
Normal file
5
apps/dokploy/public/templates/aptabase.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg class="w-12 text-primary" viewBox="0 0 1000 760" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#1a61ff"
|
||||
d="M626.7 177.36c-55.8-98.4-197.59-98.4-253.39 0L112.97 636.44H500c0-51.67 41.88-93.55 93.55-93.55h22.09l57.82 93.55h213.57L626.69 177.37Zm-11.06 365.52-70.21-123.82c-20.01-35.28-70.84-35.28-90.85 0l-70.21 123.82H273.58l181.01-319.19c20.01-35.28 70.84-35.28 90.85 0l181.01 319.19H615.66Z"
|
||||
style="--darkreader-inline-fill:currentColor" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 465 B |
BIN
apps/dokploy/public/templates/soketi.png
Normal file
BIN
apps/dokploy/public/templates/soketi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
15
apps/dokploy/public/templates/supabase.svg
Normal file
15
apps/dokploy/public/templates/supabase.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="109" height="113" viewBox="0 0 109 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
|
||||
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="53.9738" y1="54.9738" x2="94.1635" y2="71.8293" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#249361"/>
|
||||
<stop offset="1" stop-color="#3ECF8E"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="36.1558" y1="30.5779" x2="54.4844" y2="65.0804" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
51
apps/dokploy/templates/aptabase/docker-compose.yml
Normal file
51
apps/dokploy/templates/aptabase/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
services:
|
||||
aptabase_db:
|
||||
image: postgres:15-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: aptabase
|
||||
POSTGRES_PASSWORD: sTr0NGp4ssw0rd
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U aptabase"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
aptabase_events_db:
|
||||
image: clickhouse/clickhouse-server:23.8.16.16-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- events-db-data:/var/lib/clickhouse
|
||||
environment:
|
||||
CLICKHOUSE_USER: aptabase
|
||||
CLICKHOUSE_PASSWORD: sTr0NGp4ssw0rd
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8123 || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
aptabase:
|
||||
image: ghcr.io/aptabase/aptabase:main
|
||||
restart: always
|
||||
environment:
|
||||
BASE_URL: http://${APTABASE_HOST}
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
DATABASE_URL: Server=aptabase_db;Port=5432;User Id=aptabase;Password=sTr0NGp4ssw0rd;Database=aptabase
|
||||
CLICKHOUSE_URL: Host=aptabase_events_db;Port=8123;Username=aptabase;Password=sTr0NGp4ssw0rd
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
driver: local
|
||||
events-db-data:
|
||||
driver: local
|
||||
27
apps/dokploy/templates/aptabase/index.ts
Normal file
27
apps/dokploy/templates/aptabase/index.ts
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
12
apps/dokploy/templates/soketi/docker-compose.yml
Normal file
12
apps/dokploy/templates/soketi/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
soketi:
|
||||
image: quay.io/soketi/soketi:1.4-16-debian
|
||||
container_name: soketi
|
||||
environment:
|
||||
SOKETI_DEBUG: "1"
|
||||
SOKETI_HOST: "0.0.0.0"
|
||||
SOKETI_PORT: "6001"
|
||||
SOKETI_METRICS_SERVER_PORT: "9601"
|
||||
restart: unless-stopped
|
||||
28
apps/dokploy/templates/soketi/index.ts
Normal file
28
apps/dokploy/templates/soketi/index.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
437
apps/dokploy/templates/supabase/docker-compose.yml
Normal file
437
apps/dokploy/templates/supabase/docker-compose.yml
Normal file
@@ -0,0 +1,437 @@
|
||||
# Usage
|
||||
# Start: docker compose up
|
||||
# With helpers: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml up
|
||||
# Stop: docker compose down
|
||||
# Destroy: docker compose -f docker-compose.yml -f ../files/dev/docker-compose.dev.yml down -v --remove-orphans
|
||||
|
||||
name: supabase
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
studio:
|
||||
container_name: supabase-studio
|
||||
image: supabase/studio:20240729-ce42139
|
||||
networks:
|
||||
- dokploy-network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
STUDIO_PG_META_URL: http://meta:8080
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION}
|
||||
DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT}
|
||||
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_PUBLIC_URL: http://${SUPABASE_HOST}
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
AUTH_JWT_SECRET: ${JWT_SECRET}
|
||||
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
LOGFLARE_URL: http://analytics:4000
|
||||
NEXT_PUBLIC_ENABLE_LOGS: true
|
||||
# Comment to use Big Query backend for analytics
|
||||
NEXT_ANALYTICS_BACKEND_PROVIDER: postgres
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery
|
||||
|
||||
kong:
|
||||
container_name: supabase-kong
|
||||
image: kong:2.8.1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
# https://unix.stackexchange.com/a/294837
|
||||
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
|
||||
#ports:
|
||||
# - ${KONG_HTTP_PORT}:8000/tcp
|
||||
# - ${KONG_HTTPS_PORT}:8443/tcp
|
||||
expose:
|
||||
- 8000
|
||||
- 8443
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.frontend-app.rule=Host(`${SUPABASE_HOST}`)
|
||||
- traefik.http.routers.frontend-app.entrypoints=web
|
||||
- traefik.http.services.frontend-app.loadbalancer.server.port=${KONG_HTTP_PORT}
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
KONG_DATABASE: "off"
|
||||
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
|
||||
# https://github.com/supabase/cli/issues/14
|
||||
KONG_DNS_ORDER: LAST,A,CNAME
|
||||
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
||||
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
|
||||
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
|
||||
DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
|
||||
volumes:
|
||||
# https://github.com/supabase/supabase/issues/12661
|
||||
- ../files/volumes/api/kong.yml:/home/kong/temp.yml:ro
|
||||
|
||||
auth:
|
||||
container_name: supabase-auth
|
||||
image: supabase/gotrue:v2.158.1
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GOTRUE_API_HOST: 0.0.0.0
|
||||
GOTRUE_API_PORT: 9999
|
||||
API_EXTERNAL_URL: http://${SUPABASE_HOST}
|
||||
|
||||
GOTRUE_DB_DRIVER: postgres
|
||||
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
|
||||
GOTRUE_SITE_URL: http://${SUPABASE_HOST}
|
||||
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS}
|
||||
GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}
|
||||
|
||||
GOTRUE_JWT_ADMIN_ROLES: service_role
|
||||
GOTRUE_JWT_AUD: authenticated
|
||||
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
||||
GOTRUE_JWT_EXP: ${JWT_EXPIRY}
|
||||
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||
|
||||
GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: ${ENABLE_ANONYMOUS_USERS}
|
||||
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
|
||||
# GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true
|
||||
# GOTRUE_SMTP_MAX_FREQUENCY: 1s
|
||||
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL}
|
||||
GOTRUE_SMTP_HOST: ${SMTP_HOSTNAME}
|
||||
GOTRUE_SMTP_PORT: ${SMTP_PORT}
|
||||
GOTRUE_SMTP_USER: ${SMTP_USER}
|
||||
GOTRUE_SMTP_PASS: ${SMTP_PASS}
|
||||
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME}
|
||||
GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE}
|
||||
GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION}
|
||||
GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY}
|
||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE}
|
||||
|
||||
GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP}
|
||||
GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM}
|
||||
# Uncomment to enable custom access token hook. You'll need to create a public.custom_access_token_hook function and grant necessary permissions.
|
||||
# See: https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token for details
|
||||
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED="true"
|
||||
# GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="pg-functions://postgres/public/custom_access_token_hook"
|
||||
|
||||
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED="true"
|
||||
# GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/mfa_verification_attempt"
|
||||
|
||||
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true"
|
||||
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt"
|
||||
|
||||
|
||||
|
||||
|
||||
rest:
|
||||
container_name: supabase-rest
|
||||
image: postgrest/postgrest:v12.2.0
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}
|
||||
PGRST_DB_ANON_ROLE: anon
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
||||
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY}
|
||||
command: "postgrest"
|
||||
|
||||
realtime:
|
||||
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
|
||||
container_name: realtime-dev.supabase-realtime
|
||||
image: supabase/realtime:v2.30.23
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "-H", "Authorization: Bearer ${ANON_KEY}", "http://localhost:4000/api/tenants/realtime-dev/health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 4000
|
||||
DB_HOST: ${POSTGRES_HOSTNAME}
|
||||
DB_PORT: ${POSTGRES_PORT}
|
||||
DB_USER: supabase_admin
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_NAME: ${POSTGRES_DB}
|
||||
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||
DB_ENC_KEY: supabaserealtime
|
||||
API_JWT_SECRET: ${JWT_SECRET}
|
||||
SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
|
||||
ERL_AFLAGS: -proto_dist inet_tcp
|
||||
DNS_NODES: "''"
|
||||
RLIMIT_NOFILE: "10000"
|
||||
APP_NAME: realtime
|
||||
SEED_SELF_HOST: true
|
||||
|
||||
# To use S3 backed storage: docker compose -f docker-compose.yml -f docker-compose.s3.yml up
|
||||
storage:
|
||||
container_name: supabase-storage
|
||||
image: supabase/storage-api:v1.0.6
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
rest:
|
||||
condition: service_started
|
||||
imgproxy:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ANON_KEY: ${ANON_KEY}
|
||||
SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
POSTGREST_URL: http://rest:3000
|
||||
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
FILE_SIZE_LIMIT: 52428800
|
||||
STORAGE_BACKEND: file
|
||||
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
|
||||
TENANT_ID: stub
|
||||
# TODO: https://github.com/supabase/storage-api/issues/55
|
||||
REGION: stub
|
||||
GLOBAL_S3_BUCKET: stub
|
||||
ENABLE_IMAGE_TRANSFORMATION: "true"
|
||||
IMGPROXY_URL: http://imgproxy:5001
|
||||
volumes:
|
||||
- ../files/volumes/storage:/var/lib/storage:z
|
||||
|
||||
imgproxy:
|
||||
container_name: supabase-imgproxy
|
||||
image: darthsim/imgproxy:v3.8.0
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: [ "CMD", "imgproxy", "health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
environment:
|
||||
IMGPROXY_BIND: ":5001"
|
||||
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
||||
IMGPROXY_USE_ETAG: "true"
|
||||
IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION}
|
||||
volumes:
|
||||
- ../files/volumes/storage:/var/lib/storage:z
|
||||
|
||||
meta:
|
||||
container_name: supabase-meta
|
||||
image: supabase/postgres-meta:v0.83.2
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PG_META_PORT: 8080
|
||||
PG_META_DB_HOST: ${POSTGRES_HOSTNAME}
|
||||
PG_META_DB_PORT: ${POSTGRES_PORT}
|
||||
PG_META_DB_NAME: ${POSTGRES_DB}
|
||||
PG_META_DB_USER: supabase_admin
|
||||
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
|
||||
functions:
|
||||
container_name: supabase-edge-functions
|
||||
image: supabase/edge-runtime:v1.56.0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
analytics:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
SUPABASE_URL: http://kong:8000
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
||||
SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
|
||||
VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}"
|
||||
volumes:
|
||||
- ../files/volumes/functions:/home/deno/functions:Z
|
||||
command:
|
||||
- start
|
||||
- --main-service
|
||||
- /home/deno/functions/main
|
||||
|
||||
analytics:
|
||||
container_name: supabase-analytics
|
||||
image: supabase/logflare:1.4.0
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "http://localhost:4000/health" ]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
# Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# volumes:
|
||||
# - type: bind
|
||||
# source: ${PWD}/gcloud.json
|
||||
# target: /opt/app/rel/logflare/bin/gcloud.json
|
||||
# read_only: true
|
||||
environment:
|
||||
LOGFLARE_NODE_HOST: 127.0.0.1
|
||||
DB_USERNAME: supabase_admin
|
||||
DB_DATABASE: ${POSTGRES_DB}
|
||||
DB_HOSTNAME: ${POSTGRES_HOSTNAME}
|
||||
DB_PORT: ${POSTGRES_PORT}
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_SCHEMA: _analytics
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
LOGFLARE_SINGLE_TENANT: true
|
||||
LOGFLARE_SUPABASE_MODE: true
|
||||
LOGFLARE_MIN_CLUSTER_SIZE: 1
|
||||
|
||||
# Comment variables to use Big Query backend for analytics
|
||||
POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
POSTGRES_BACKEND_SCHEMA: _analytics
|
||||
LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true
|
||||
# Uncomment to use Big Query backend for analytics
|
||||
# GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID}
|
||||
# GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER}
|
||||
#ports:
|
||||
# - 4000:4000
|
||||
expose:
|
||||
- 4000
|
||||
|
||||
# Comment out everything below this point if you are using an external Postgres database
|
||||
db:
|
||||
container_name: supabase-db
|
||||
image: supabase/postgres:15.1.1.78
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: pg_isready -U postgres -h localhost
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
depends_on:
|
||||
vector:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- postgres
|
||||
- -c
|
||||
- config_file=/etc/postgresql/postgresql.conf
|
||||
- -c
|
||||
- log_min_messages=fatal # prevents Realtime polling queries from appearing in logs
|
||||
restart: unless-stopped
|
||||
#ports:
|
||||
# # Pass down internal port because it's set dynamically by other services
|
||||
# - ${POSTGRES_PORT}:${POSTGRES_PORT}
|
||||
expose:
|
||||
- ${POSTGRES_PORT}
|
||||
environment:
|
||||
POSTGRES_HOST: /var/run/postgresql
|
||||
PGPORT: ${POSTGRES_PORT}
|
||||
POSTGRES_PORT: ${POSTGRES_PORT}
|
||||
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
PGDATABASE: ${POSTGRES_DB}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
JWT_EXP: ${JWT_EXPIRY}
|
||||
volumes:
|
||||
- ../files/volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z
|
||||
# Must be superuser to create event trigger
|
||||
- ../files/volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z
|
||||
# Must be superuser to alter reserved role
|
||||
- ../files/volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z
|
||||
# Initialize the database settings with JWT_SECRET and JWT_EXP
|
||||
- ../files/volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z
|
||||
# PGDATA directory is persisted between restarts
|
||||
- ../files/volumes/db/data:/var/lib/postgresql/data:Z
|
||||
# Changes required for Analytics support
|
||||
- ../files/volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z
|
||||
# Use named volume to persist pgsodium decryption key between restarts
|
||||
- db-config:/etc/postgresql-custom
|
||||
|
||||
vector:
|
||||
container_name: supabase-vector
|
||||
image: timberio/vector:0.28.1-alpine
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://vector:9001/health"
|
||||
]
|
||||
timeout: 5s
|
||||
interval: 5s
|
||||
retries: 3
|
||||
volumes:
|
||||
- ../files/volumes/logs/vector.yml:/etc/vector/vector.yml:ro
|
||||
- ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro
|
||||
environment:
|
||||
LOGFLARE_API_KEY: ${LOGFLARE_API_KEY}
|
||||
command: [ "--config", "etc/vector/vector.yml" ]
|
||||
|
||||
volumes:
|
||||
db-config:
|
||||
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
989
apps/dokploy/templates/supabase/index.ts
Normal file
989
apps/dokploy/templates/supabase/index.ts
Normal 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: {}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user