Merge pull request #1300 from Dokploy/canary

🚀 Release v0.18.3
This commit is contained in:
Mauricio Siu
2025-02-10 00:35:31 -06:00
committed by GitHub
86 changed files with 10315 additions and 515 deletions

BIN
.github/sponsors/openalternative.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -93,6 +93,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://startupfa.me/?ref=dokploy "><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
<a href="https://itsdb-center.com?ref=dokploy "><img src=".github/sponsors/its.png" width="65px" alt="Itsdb-center"/></a>
<a href="https://openalternative.co/?ref=dokploy "><img src=".github/sponsors/openalternative.png" width="65px" alt="Openalternative"/></a>
</div>
### Community Backers 🤝

View File

@@ -14,7 +14,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { validateAndFormatYAML } from "../../application/advanced/traefik/update-traefik-config";
import { RandomizeCompose } from "./randomize-compose";
import { ShowUtilities } from "./show-utilities";
interface Props {
composeId: string;
@@ -125,7 +125,7 @@ services:
</Form>
<div className="flex justify-between flex-col lg:flex-row gap-2">
<div className="w-full flex flex-col lg:flex-row gap-4 items-end">
<RandomizeCompose composeId={composeId} />
<ShowUtilities composeId={composeId} />
</div>
<Button
type="submit"

View File

@@ -0,0 +1,191 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
interface Props {
composeId: string;
}
const schema = z.object({
isolatedDeployment: z.boolean().optional(),
});
type Schema = z.infer<typeof schema>;
export const IsolatedDeployment = ({ composeId }: Props) => {
const utils = api.useUtils();
const [compose, setCompose] = useState<string>("");
const { mutateAsync, error, isError } =
api.compose.isolatedDeployment.useMutation();
const { mutateAsync: updateCompose } = api.compose.update.useMutation();
const { data, refetch } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
console.log(data);
const form = useForm<Schema>({
defaultValues: {
isolatedDeployment: false,
},
resolver: zodResolver(schema),
});
useEffect(() => {
randomizeCompose();
if (data) {
form.reset({
isolatedDeployment: data?.isolatedDeployment || false,
});
}
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (formData: Schema) => {
await updateCompose({
composeId,
isolatedDeployment: formData?.isolatedDeployment || false,
})
.then(async (data) => {
randomizeCompose();
refetch();
toast.success("Compose updated");
})
.catch(() => {
toast.error("Error updating the compose");
});
};
const randomizeCompose = async () => {
await mutateAsync({
composeId,
suffix: data?.appName || "",
})
.then(async (data) => {
await utils.project.all.invalidate();
setCompose(data);
toast.success("Compose Isolated");
})
.catch(() => {
toast.error("Error isolating the compose");
});
};
return (
<>
<DialogHeader>
<DialogTitle>Isolate Deployment</DialogTitle>
<DialogDescription>
Use this option to isolate the deployment of this compose file.
</DialogDescription>
</DialogHeader>
<div className="text-sm text-muted-foreground flex flex-col gap-2">
<span>
This feature creates an isolated environment for your deployment by
adding unique prefixes to all resources. It establishes a dedicated
network based on your compose file's name, ensuring your services run
in isolation. This prevents conflicts when running multiple instances
of the same template or services with identical names.
</span>
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">
Resources that will be isolated:
</h4>
<ul className="list-disc list-inside">
<li>Docker volumes</li>
<li>Docker networks</li>
</ul>
</div>
</div>
</div>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-add-project"
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<div className="flex flex-col lg:flex-col gap-4 w-full ">
<div>
<FormField
control={form.control}
name="isolatedDeployment"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Enable Randomize ({data?.appName})</FormLabel>
<FormDescription>
Enable randomize to the compose file.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
form="hook-form-add-project"
type="submit"
className="lg:w-fit"
>
Save
</Button>
</div>
</div>
<div className="flex flex-col gap-4">
<Label>Preview</Label>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
/>
</pre>
</div>
</form>
</Form>
</>
);
};

View File

@@ -1,14 +1,10 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import { CardTitle } from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
@@ -20,11 +16,6 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from "@/components/ui/input-otp";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -70,6 +61,7 @@ export const RandomizeCompose = ({ composeId }: Props) => {
const suffix = form.watch("suffix");
useEffect(() => {
randomizeCompose();
if (data) {
form.reset({
suffix: data?.suffix || "",
@@ -110,126 +102,117 @@ export const RandomizeCompose = ({ composeId }: Props) => {
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild onClick={() => randomizeCompose()}>
<Button className="max-lg:w-full" variant="outline">
<Dices className="h-4 w-4" />
Randomize Compose
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-6xl max-h-[50rem] overflow-y-auto">
<DialogHeader>
<DialogTitle>Randomize Compose (Experimental)</DialogTitle>
<DialogDescription>
Use this in case you want to deploy the same compose file and you
have conflicts with some property like volumes, networks, etc.
</DialogDescription>
</DialogHeader>
<div className="text-sm text-muted-foreground flex flex-col gap-2">
<span>
This will randomize the compose file and will add a suffix to the
property to avoid conflicts
</span>
<ul className="list-disc list-inside">
<li>volumes</li>
<li>networks</li>
<li>services</li>
<li>configs</li>
<li>secrets</li>
</ul>
<AlertBlock type="info">
When you activate this option, we will include a env
`COMPOSE_PREFIX` variable to the compose file so you can use it in
your compose file.
</AlertBlock>
</div>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-add-project"
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
<div className="flex flex-col lg:flex-col gap-4 w-full ">
<div>
<FormField
control={form.control}
name="suffix"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center w-full">
<FormLabel>Suffix</FormLabel>
<FormControl>
<Input
placeholder="Enter a suffix (Optional, example: prod)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="randomize"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Apply Randomize</FormLabel>
<FormDescription>
Apply randomize to the compose file.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
form="hook-form-add-project"
type="submit"
className="lg:w-fit"
>
Save
</Button>
<Button
type="button"
variant="secondary"
onClick={async () => {
await randomizeCompose();
}}
className="lg:w-fit"
>
Random
</Button>
</div>
<div className="w-full">
<DialogHeader>
<DialogTitle>Randomize Compose (Experimental)</DialogTitle>
<DialogDescription>
Use this in case you want to deploy the same compose file and you have
conflicts with some property like volumes, networks, etc.
</DialogDescription>
</DialogHeader>
<div className="text-sm text-muted-foreground flex flex-col gap-2">
<span>
This will randomize the compose file and will add a suffix to the
property to avoid conflicts
</span>
<ul className="list-disc list-inside">
<li>volumes</li>
<li>networks</li>
<li>services</li>
<li>configs</li>
<li>secrets</li>
</ul>
<AlertBlock type="info">
When you activate this option, we will include a env `COMPOSE_PREFIX`
variable to the compose file so you can use it in your compose file.
</AlertBlock>
</div>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-add-project"
className="grid w-full gap-4"
>
{isError && (
<div className="flex flex-row gap-4 rounded-lg items-center bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
)}
<div className="flex flex-col lg:flex-col gap-4 w-full ">
<div>
<FormField
control={form.control}
name="suffix"
render={({ field }) => (
<FormItem className="flex flex-col justify-center max-sm:items-center w-full mt-4">
<FormLabel>Suffix</FormLabel>
<FormControl>
<Input
placeholder="Enter a suffix (Optional, example: prod)"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</pre>
</form>
</Form>
</DialogContent>
</Dialog>
<FormField
control={form.control}
name="randomize"
render={({ field }) => (
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Apply Randomize</FormLabel>
<FormDescription>
Apply randomize to the compose file.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
<div className="flex flex-col lg:flex-row gap-4 w-full items-end justify-end">
<Button
form="hook-form-add-project"
type="submit"
className="lg:w-fit"
>
Save
</Button>
<Button
type="button"
variant="secondary"
onClick={async () => {
await randomizeCompose();
}}
className="lg:w-fit"
>
Random
</Button>
</div>
</div>
<pre>
<CodeEditor
value={compose || ""}
language="yaml"
readOnly
height="50rem"
/>
</pre>
</form>
</Form>
</div>
);
};

View File

@@ -0,0 +1,46 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useState } from "react";
import { IsolatedDeployment } from "./isolated-deployment";
import { RandomizeCompose } from "./randomize-compose";
interface Props {
composeId: string;
}
export const ShowUtilities = ({ composeId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost">Show Utilities</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Utilities </DialogTitle>
<DialogDescription>Modify the application data</DialogDescription>
</DialogHeader>
<Tabs defaultValue="isolated">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="isolated">Isolated Deployment</TabsTrigger>
<TabsTrigger value="randomize">Randomize Compose</TabsTrigger>
</TabsList>
<TabsContent value="randomize" className="pt-5">
<RandomizeCompose composeId={composeId} />
</TabsContent>
<TabsContent value="isolated" className="pt-5">
<IsolatedDeployment composeId={composeId} />
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,5 +1,6 @@
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import {
AlertDialog,
AlertDialogAction,
@@ -176,8 +177,11 @@ export const ShowProjects = () => {
<div key={app.applicationId}>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="font-normal capitalize text-xs">
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
{app.name}
<StatusTooltip
status={app.applicationStatus}
/>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{app.domains.map((domain) => (
@@ -209,8 +213,11 @@ export const ShowProjects = () => {
<div key={comp.composeId}>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel className="font-normal capitalize text-xs">
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
{comp.name}
<StatusTooltip
status={comp.composeStatus}
/>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{comp.domains.map((domain) => (

View File

@@ -0,0 +1 @@
ALTER TABLE "compose" ADD COLUMN "deployable" boolean DEFAULT false NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "compose" RENAME COLUMN "deployable" TO "isolatedDeployment";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -449,6 +449,20 @@
"when": 1738522845992,
"tag": "0063_panoramic_dreadnoughts",
"breakpoints": true
},
{
"idx": 64,
"version": "7",
"when": 1738564387043,
"tag": "0064_previous_agent_brand",
"breakpoints": true
},
{
"idx": 65,
"version": "7",
"when": 1739087857244,
"tag": "0065_daily_zaladane",
"breakpoints": true
}
]
}

View File

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

View File

@@ -13,6 +13,7 @@ import {
import { ProjectLayout } from "@/components/layouts/project-layout";
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
import { DateTooltip } from "@/components/shared/date-tooltip";
import { DialogAction } from "@/components/shared/dialog-action";
import { StatusTooltip } from "@/components/shared/status-tooltip";
import { Button } from "@/components/ui/button";
import {
@@ -23,6 +24,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import {
Command,
CommandEmpty,
@@ -50,22 +52,27 @@ import type { findProjectById } from "@dokploy/server";
import { validateRequest } from "@dokploy/server";
import { createServerSideHelpers } from "@trpc/react-query/server";
import {
Ban,
Check,
CheckCircle2,
ChevronsUpDown,
CircuitBoard,
FolderInput,
GlobeIcon,
Loader2,
PlusIcon,
Search,
X,
} from "lucide-react";
import { Check, ChevronsUpDown, X } from "lucide-react";
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import React, { useMemo, useState, type ReactElement } from "react";
import { useMemo, useState, type ReactElement } from "react";
import superjson from "superjson";
import { toast } from "sonner";
export type Services = {
appName: string;
@@ -191,6 +198,7 @@ export const extractServices = (data: Project | undefined) => {
const Project = (
props: InferGetServerSidePropsType<typeof getServerSideProps>,
) => {
const [isBulkActionLoading, setIsBulkActionLoading] = useState(false);
const { projectId } = props;
const { data: auth } = api.auth.get.useQuery();
const { data: user } = api.user.byAuthId.useQuery(
@@ -201,7 +209,7 @@ const Project = (
enabled: !!auth?.id && auth?.rol === "user",
},
);
const { data, isLoading } = api.project.one.useQuery({ projectId });
const { data, isLoading, refetch } = api.project.one.useQuery({ projectId });
const router = useRouter();
const emptyServices =
@@ -228,6 +236,70 @@ const Project = (
const [selectedTypes, setSelectedTypes] = useState<string[]>([]);
const [openCombobox, setOpenCombobox] = useState(false);
const [selectedServices, setSelectedServices] = useState<string[]>([]);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const handleSelectAll = () => {
if (selectedServices.length === filteredServices.length) {
setSelectedServices([]);
} else {
setSelectedServices(filteredServices.map((service) => service.id));
}
};
const handleServiceSelect = (serviceId: string, event: React.MouseEvent) => {
event.stopPropagation();
setSelectedServices((prev) =>
prev.includes(serviceId)
? prev.filter((id) => id !== serviceId)
: [...prev, serviceId],
);
};
const composeActions = {
start: api.compose.start.useMutation(),
stop: api.compose.stop.useMutation(),
};
const handleBulkStart = async () => {
let success = 0;
setIsBulkActionLoading(true);
for (const serviceId of selectedServices) {
try {
await composeActions.start.mutateAsync({ composeId: serviceId });
success++;
} catch (error) {
toast.error(`Error starting service ${serviceId}`);
}
}
if (success > 0) {
toast.success(`${success} services started successfully`);
refetch();
}
setIsBulkActionLoading(false);
setSelectedServices([]);
setIsDropdownOpen(false);
};
const handleBulkStop = async () => {
let success = 0;
setIsBulkActionLoading(true);
for (const serviceId of selectedServices) {
try {
await composeActions.stop.mutateAsync({ composeId: serviceId });
success++;
} catch (error) {
toast.error(`Error stopping service ${serviceId}`);
}
}
if (success > 0) {
toast.success(`${success} services stopped successfully`);
refetch();
}
setSelectedServices([]);
setIsDropdownOpen(false);
setIsBulkActionLoading(false);
};
const filteredServices = useMemo(() => {
if (!applications) return [];
@@ -309,78 +381,151 @@ const Project = (
</div>
) : (
<>
<div className="flex flex-row gap-2 items-center">
<div className="w-full relative">
<Input
placeholder="Filter services..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pr-10"
/>
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Checkbox
checked={selectedServices.length > 0}
className={cn(
"data-[state=checked]:bg-primary",
selectedServices.length > 0 &&
selectedServices.length <
filteredServices.length &&
"bg-primary/50",
)}
onCheckedChange={handleSelectAll}
/>
<span className="text-sm">
Select All{" "}
{selectedServices.length > 0 &&
`(${selectedServices.length}/${filteredServices.length})`}
</span>
</div>
<DropdownMenu
open={isDropdownOpen}
onOpenChange={setIsDropdownOpen}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
disabled={selectedServices.length === 0}
isLoading={isBulkActionLoading}
>
Bulk Actions
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DialogAction
title="Start Services"
description={`Are you sure you want to start ${selectedServices.length} services?`}
type="default"
onClick={handleBulkStart}
>
<Button
variant="ghost"
className="w-full justify-start"
>
<CheckCircle2 className="mr-2 h-4 w-4" />
Start
</Button>
</DialogAction>
<DialogAction
title="Stop Services"
description={`Are you sure you want to stop ${selectedServices.length} services?`}
type="destructive"
onClick={handleBulkStop}
>
<Button
variant="ghost"
className="w-full justify-start text-destructive"
>
<Ban className="mr-2 h-4 w-4" />
Stop
</Button>
</DialogAction>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Popover open={openCombobox} onOpenChange={setOpenCombobox}>
<PopoverTrigger asChild>
<Button
variant="outline"
aria-expanded={openCombobox}
className="min-w-[200px] justify-between"
>
{selectedTypes.length === 0
? "Select types..."
: `${selectedTypes.length} selected`}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search type..." />
<CommandEmpty>No type found.</CommandEmpty>
<CommandGroup>
{serviceTypes.map((type) => (
<div className="flex flex-col gap-2 sm:flex-row sm:gap-4 sm:items-center">
<div className="w-full relative">
<Input
placeholder="Filter services..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pr-10"
/>
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
</div>
<Popover
open={openCombobox}
onOpenChange={setOpenCombobox}
>
<PopoverTrigger asChild>
<Button
variant="outline"
aria-expanded={openCombobox}
className="min-w-[200px] justify-between"
>
{selectedTypes.length === 0
? "Select types..."
: `${selectedTypes.length} selected`}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search type..." />
<CommandEmpty>No type found.</CommandEmpty>
<CommandGroup>
{serviceTypes.map((type) => (
<CommandItem
key={type.value}
onSelect={() => {
setSelectedTypes((prev) =>
prev.includes(type.value)
? prev.filter((t) => t !== type.value)
: [...prev, type.value],
);
setOpenCombobox(false);
}}
>
<div className="flex flex-row">
<Check
className={cn(
"mr-2 h-4 w-4",
selectedTypes.includes(type.value)
? "opacity-100"
: "opacity-0",
)}
/>
{type.icon && (
<type.icon className="mr-2 h-4 w-4" />
)}
{type.label}
</div>
</CommandItem>
))}
<CommandItem
key={type.value}
onSelect={() => {
setSelectedTypes((prev) =>
prev.includes(type.value)
? prev.filter((t) => t !== type.value)
: [...prev, type.value],
);
setSelectedTypes([]);
setOpenCombobox(false);
}}
className="border-t"
>
<div className="flex flex-row">
<Check
className={cn(
"mr-2 h-4 w-4",
selectedTypes.includes(type.value)
? "opacity-100"
: "opacity-0",
)}
/>
{type.icon && (
<type.icon className="mr-2 h-4 w-4" />
)}
{type.label}
<div className="flex flex-row items-center">
<X className="mr-2 h-4 w-4" />
Clear filters
</div>
</CommandItem>
))}
<CommandItem
onSelect={() => {
setSelectedTypes([]);
setOpenCombobox(false);
}}
className="border-t"
>
<div className="flex flex-row items-center">
<X className="mr-2 h-4 w-4" />
Clear filters
</div>
</CommandItem>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
<div className="flex w-full gap-8">
@@ -418,6 +563,27 @@ const Project = (
<StatusTooltip status={service.status} />
</div>
<div
className={cn(
"absolute -left-3 -bottom-3 size-9 translate-y-1 rounded-full p-0 transition-all duration-200 z-10 bg-background border",
selectedServices.includes(service.id)
? "opacity-100 translate-y-0"
: "opacity-0 group-hover:translate-y-0 group-hover:opacity-100",
)}
onClick={(e) =>
handleServiceSelect(service.id, e)
}
>
<div className="h-full w-full flex items-center justify-center">
<Checkbox
checked={selectedServices.includes(
service.id,
)}
className="data-[state=checked]:bg-primary"
/>
</div>
</div>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex flex-row items-center gap-2 justify-between w-full">

View File

@@ -0,0 +1,5 @@
<svg width="101" height="101" viewBox="0 0 101 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M72.2667 0.422852H29.4096C13.63 0.422852 0.838135 13.2147 0.838135 28.9943V71.8514C0.838135 87.631 13.63 100.423 29.4096 100.423H72.2667C88.0463 100.423 100.838 87.631 100.838 71.8514V28.9943C100.838 13.2147 88.0463 0.422852 72.2667 0.422852Z" fill="#06B58B"/>
<path d="M31.1592 78.9948L26.3379 73.7091C33.0879 67.602 41.7664 64.209 50.8021 64.209C59.8378 64.209 68.5522 67.5662 75.2665 73.7091L70.4449 78.9948C65.0164 74.0662 58.0521 71.3518 50.8021 71.3518C43.5521 71.3518 36.5523 74.0662 31.1237 78.9948H31.1592Z" fill="white"/>
<path d="M54.1236 21.8516H33.1948V28.9944H54.1236C58.0521 28.9944 61.2664 32.2087 61.2664 36.1373V42.7801C61.2664 46.7087 58.0521 49.9229 54.1236 49.9229H47.4805C43.552 49.9229 40.3377 46.7087 40.3377 42.7801V38.28H33.1948V42.7801C33.1948 50.6729 39.5877 57.0658 47.4805 57.0658H54.1236C62.0164 57.0658 68.4093 50.6729 68.4093 42.7801V36.1373C68.4093 28.2444 62.0164 21.8516 54.1236 21.8516Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -46,6 +46,7 @@ import {
findServerById,
loadServices,
randomizeComposeFile,
randomizeIsolatedDeploymentComposeFile,
removeCompose,
removeComposeDirectory,
removeDeploymentsByComposeId,
@@ -216,6 +217,21 @@ export const composeRouter = createTRPCRouter({
}
return await randomizeComposeFile(input.composeId, input.suffix);
}),
isolatedDeployment: protectedProcedure
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
if (compose.project.adminId !== ctx.user.adminId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
});
}
return await randomizeIsolatedDeploymentComposeFile(
input.composeId,
input.suffix,
);
}),
getConvertedCompose: protectedProcedure
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
@@ -399,6 +415,7 @@ export const composeRouter = createTRPCRouter({
name: input.id,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
isolatedDeployment: true,
});
if (ctx.user.rol === "user") {

View File

@@ -4,8 +4,7 @@ services:
activepieces:
image: activepieces/activepieces:0.35.0
restart: unless-stopped
networks:
- dokploy-network
depends_on:
postgres:
condition: service_healthy
@@ -35,8 +34,7 @@ services:
postgres:
image: postgres:14
restart: unless-stopped
networks:
- dokploy-network
environment:
POSTGRES_DB: activepieces
POSTGRES_PASSWORD: ${AP_POSTGRES_PASSWORD}
@@ -52,8 +50,7 @@ services:
redis:
image: redis:7
restart: unless-stopped
networks:
- dokploy-network
volumes:
- redis_data:/data
healthcheck:

View File

@@ -17,8 +17,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
networks:
- dokploy-network
volumes:
- db-data:/var/lib/postgresql/data
environment:

View File

@@ -7,8 +7,7 @@ services:
environment:
POSTGRES_USER: aptabase
POSTGRES_PASSWORD: sTr0NGp4ssw0rd
networks:
- dokploy-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aptabase"]
interval: 10s
@@ -27,8 +26,7 @@ services:
nofile:
soft: 262144
hard: 262144
networks:
- dokploy-network
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8123 || exit 1"]
interval: 10s

View File

@@ -2,8 +2,7 @@ services:
apps:
image: budibase.docker.scarf.sh/budibase/apps:3.2.25
restart: unless-stopped
networks:
- dokploy-network
environment:
SELF_HOSTED: 1
LOG_LEVEL: info
@@ -43,8 +42,7 @@ services:
worker:
image: budibase.docker.scarf.sh/budibase/worker:3.2.25
restart: unless-stopped
networks:
- dokploy-network
environment:
SELF_HOSTED: 1
LOG_LEVEL: info
@@ -83,8 +81,7 @@ services:
minio:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
restart: unless-stopped
networks:
- dokploy-network
volumes:
- 'minio_data:/data'
environment:
@@ -104,8 +101,7 @@ services:
proxy:
image: budibase/proxy:3.2.25
restart: unless-stopped
networks:
- dokploy-network
environment:
PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND: 10
PROXY_RATE_LIMIT_API_PER_SECOND: 20
@@ -137,8 +133,7 @@ services:
couchdb:
image: budibase/couchdb:v3.3.3
restart: unless-stopped
networks:
- dokploy-network
environment:
COUCHDB_USER: budibase
COUCHDB_PASSWORD: ${BB_COUCHDB_PASSWORD}
@@ -157,8 +152,7 @@ services:
- 'couchdb3_data:/opt/couchdb/data'
redis:
image: redis:7.2-alpine
networks:
- dokploy-network
restart: unless-stopped
command: 'redis-server --requirepass "${BB_REDIS_PASSWORD}"'
volumes:
@@ -176,8 +170,7 @@ services:
start_period: 10s
watchtower:
restart: unless-stopped
networks:
- dokploy-network
image: containrrr/watchtower:1.7.1
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'

View File

@@ -1,8 +1,7 @@
services:
postgres:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- calcom-data:/var/lib/postgresql/data
environment:

View File

@@ -51,8 +51,7 @@ services:
restart: always
volumes:
- chatwoot-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
@@ -63,8 +62,7 @@ services:
restart: always
volumes:
- chatwoot-redis-data:/data
networks:
- dokploy-network
networks:
dokploy-network:

View File

@@ -9,8 +9,7 @@ services:
- 443
depends_on:
- server
networks:
- dokploy-network
server:
image: bluewaveuptime/uptime_server:latest
restart: always
@@ -22,8 +21,7 @@ services:
environment:
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- REDIS_HOST=redis
networks:
- dokploy-network
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
redis:
@@ -33,8 +31,7 @@ services:
- 6379
volumes:
- ../files/redis/data:/data
networks:
- dokploy-network
mongodb:
image: bluewaveuptime/uptime_database_mongo:latest
restart: always
@@ -43,5 +40,3 @@ services:
command: ["mongod", "--quiet"]
ports:
- 27017
networks:
- dokploy-network

View File

@@ -1,8 +1,7 @@
services:
coder:
image: ghcr.io/coder/coder:v2.15.3
networks:
- dokploy-network
volumes:
- /var/run/docker.sock:/var/run/docker.sock
group_add:
@@ -17,8 +16,7 @@ services:
db:
image: postgres:17
networks:
- dokploy-network
environment:
- POSTGRES_PASSWORD
- POSTGRES_USER

View File

@@ -3,8 +3,7 @@ services:
image: postgis/postgis:13-master
volumes:
- directus_database:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
POSTGRES_USER: "directus"
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
@@ -26,8 +25,7 @@ services:
retries: 5
start_interval: 5s
start_period: 30s
networks:
- dokploy-network
directus:
image: directus/directus:11.0.2

View File

@@ -4,8 +4,7 @@ services:
tickets-postgres:
image: mysql:8
restart: unless-stopped
networks:
- dokploy-network
volumes:
- tickets-mysql-data:/var/lib/mysql
environment:
@@ -25,8 +24,7 @@ services:
tickets-postgres:
condition: service_healthy
restart: unless-stopped
networks:
- dokploy-network
volumes:
- tickets-app-data:/home/container/user
- /etc/timezone:/etc/timezone:ro

View File

@@ -3,8 +3,7 @@ version: '3.7'
services:
discourse-db:
image: docker.io/bitnami/postgresql:17
networks:
- dokploy-network
volumes:
- discourse-postgresql-data:/bitnami/postgresql
environment:
@@ -20,8 +19,7 @@ services:
discourse-redis:
image: docker.io/bitnami/redis:7.4
networks:
- dokploy-network
volumes:
- discourse-redis-data:/bitnami/redis
environment:
@@ -35,8 +33,7 @@ services:
discourse-app:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-data:/bitnami/discourse
depends_on:
@@ -63,8 +60,7 @@ services:
discourse-sidekiq:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-sidekiq-data:/bitnami/discourse
depends_on:

View File

@@ -12,8 +12,7 @@ services:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
- REDIS_URL=redis://redis:6379
restart: unless-stopped
networks:
- dokploy-network
volumes:
- docmost:/app/data/storage
@@ -24,16 +23,14 @@ services:
- POSTGRES_USER
- POSTGRES_PASSWORD
restart: unless-stopped
networks:
- dokploy-network
volumes:
- db_docmost_data:/var/lib/postgresql/data
redis:
image: redis:7.2-alpine
restart: unless-stopped
networks:
- dokploy-network
volumes:
- redis_docmost_data:/data

View File

@@ -2,8 +2,7 @@ version: "3.8"
services:
postgres:
image: postgres:16
networks:
- dokploy-network
volumes:
- documenso-data:/var/lib/postgresql/data
environment:

View File

@@ -4,16 +4,14 @@ services:
image: plantuml/plantuml-server
ports:
- "8080"
networks:
- dokploy-network
volumes:
- fonts_volume:/usr/share/fonts/drawio
image-export:
image: jgraph/export-server
ports:
- "8000"
networks:
- dokploy-network
volumes:
- fonts_volume:/usr/share/fonts/drawio
environment:
@@ -28,8 +26,7 @@ services:
depends_on:
- plantuml-server
- image-export
networks:
- dokploy-network
environment:
RAWIO_SELF_CONTAINED: 1
DRAWIO_USE_HTTP: 1

View File

@@ -189,7 +189,7 @@ services:
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
bench set-config -gp socketio_port $$SOCKETIO_PORT;
environment:
DB_HOST: db
DB_HOST: "${DB_HOST:-db}"
DB_PORT: "3306"
REDIS_CACHE: redis-cache:6379
REDIS_QUEUE: redis-queue:6379
@@ -210,7 +210,7 @@ services:
entrypoint: ["bash", "-c"]
command:
- >
wait-for-it -t 120 db:3306;
wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
export start=`date +%s`;
@@ -231,10 +231,12 @@ services:
volumes:
- sites:/home/frappe/frappe-bench/sites
environment:
SITE_NAME: ${SITE_NAME}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DB_HOST: ${DB_HOST:-db}
DB_PORT: "${DB_PORT:-3306}"
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
SITE_NAME: ${SITE_NAME}
networks:
- bench-network
@@ -262,6 +264,8 @@ services:
db:
image: mariadb:10.6
deploy:
mode: replicated
replicas: ${ENABLE_DB:-0}
restart_policy:
condition: always
healthcheck:
@@ -341,6 +345,10 @@ volumes:
redis-queue-data:
redis-socketio-data:
sites:
driver_opts:
type: "${SITE_VOLUME_TYPE}"
o: "${SITE_VOLUME_OPTS}"
device: "${SITE_VOLUME_DEV}"
networks:
bench-network:

View File

@@ -24,6 +24,8 @@ export function generate(schema: Schema): Template {
`ADMIN_PASSWORD=${adminPassword}`,
`DB_ROOT_PASSWORD=${dbRootPassword}`,
"MIGRATE=1",
"ENABLE_DB=1",
"DB_HOST=db",
"CREATE_SITE=1",
"CONFIGURE=1",
"REGENERATE_APPS_TXT=1",

View File

@@ -4,8 +4,7 @@ services:
restart: always
volumes:
- evolution-instances:/evolution/instances
networks:
- dokploy-network
environment:
- SERVER_URL=${SERVER_URL}
- AUTHENTICATION_TYPE=${AUTHENTICATION_TYPE}
@@ -36,8 +35,7 @@ services:
restart: always
volumes:
- evolution-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
@@ -48,8 +46,7 @@ services:
restart: always
volumes:
- evolution-redis-data:/data
networks:
- dokploy-network
networks:
dokploy-network:

View File

@@ -2,6 +2,5 @@ version: "3.8"
services:
excalidraw:
networks:
- dokploy-network
image: excalidraw/excalidraw:latest

View File

@@ -18,8 +18,7 @@ services:
- postgres:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres
networks:
- dokploy-network
formbricks:
restart: always

View File

@@ -0,0 +1,354 @@
x-custom-image: &custom_image
image: ${IMAGE_NAME:-ghcr.io/frappe/hrms}:${VERSION:-version-15}
pull_policy: ${PULL_POLICY:-always}
deploy:
restart_policy:
condition: always
services:
backend:
<<: *custom_image
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:8000'
interval: 2s
timeout: 10s
retries: 30
frontend:
<<: *custom_image
command:
- nginx-entrypoint.sh
depends_on:
backend:
condition: service_started
required: true
websocket:
condition: service_started
required: true
environment:
BACKEND: backend:8000
FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
SOCKETIO: websocket:9000
UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
UPSTREAM_REAL_IP_RECURSIVE: "off"
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:8080'
interval: 2s
timeout: 30s
retries: 30
queue-default:
<<: *custom_image
command:
- bench
- worker
- --queue
- default
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
queue-long:
<<: *custom_image
command:
- bench
- worker
- --queue
- long
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
queue-short:
<<: *custom_image
command:
- bench
- worker
- --queue
- short
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
depends_on:
configurator:
condition: service_completed_successfully
required: true
scheduler:
<<: *custom_image
healthcheck:
test:
- CMD
- wait-for-it
- 'redis-queue:6379'
interval: 2s
timeout: 10s
retries: 30
command:
- bench
- schedule
depends_on:
configurator:
condition: service_completed_successfully
required: true
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
websocket:
<<: *custom_image
healthcheck:
test:
- CMD
- wait-for-it
- '0.0.0.0:9000'
interval: 2s
timeout: 10s
retries: 30
command:
- node
- /home/frappe/frappe-bench/apps/frappe/socketio.js
depends_on:
configurator:
condition: service_completed_successfully
required: true
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
configurator:
<<: *custom_image
deploy:
mode: replicated
replicas: ${CONFIGURE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
[[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt;
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0;
bench set-config -g db_host $$DB_HOST;
bench set-config -gp db_port $$DB_PORT;
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
bench set-config -gp socketio_port $$SOCKETIO_PORT;
environment:
DB_HOST: "${DB_HOST:-db}"
DB_PORT: "3306"
REDIS_CACHE: redis-cache:6379
REDIS_QUEUE: redis-queue:6379
SOCKETIO_PORT: "9000"
REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
create-site:
<<: *custom_image
deploy:
mode: replicated
replicas: ${CREATE_SITE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
export start=`date +%s`;
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
do
echo "Waiting for sites/common_site_config.json to be created";
sleep 5;
if (( `date +%s`-start > 120 )); then
echo "could not find sites/common_site_config.json with required keys";
exit 1
fi
done;
echo "sites/common_site_config.json found";
[[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0;
bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME};
volumes:
- sites:/home/frappe/frappe-bench/sites
environment:
SITE_NAME: ${SITE_NAME}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DB_HOST: ${DB_HOST:-db}
DB_PORT: "${DB_PORT:-3306}"
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
networks:
- bench-network
migration:
<<: *custom_image
deploy:
mode: replicated
replicas: ${MIGRATE:-0}
restart_policy:
condition: none
entrypoint: ["bash", "-c"]
command:
- >
curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0;
bench --site all set-config -p maintenance_mode 1;
bench --site all set-config -p pause_scheduler 1;
bench --site all migrate;
bench --site all set-config -p maintenance_mode 0;
bench --site all set-config -p pause_scheduler 0;
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- bench-network
db:
image: mariadb:10.6
deploy:
mode: replicated
replicas: ${ENABLE_DB:-0}
restart_policy:
condition: always
healthcheck:
test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD}
interval: 1s
retries: 20
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- db-data:/var/lib/mysql
networks:
- bench-network
redis-cache:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
volumes:
- redis-cache-data:/data
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
redis-queue:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
volumes:
- redis-queue-data:/data
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
redis-socketio:
deploy:
restart_policy:
condition: always
image: redis:6.2-alpine
volumes:
- redis-socketio-data:/data
networks:
- bench-network
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 5s
retries: 3
volumes:
db-data:
redis-cache-data:
redis-queue-data:
redis-socketio-data:
sites:
driver_opts:
type: "${SITE_VOLUME_TYPE}"
o: "${SITE_VOLUME_OPTS}"
device: "${SITE_VOLUME_DEV}"
networks:
bench-network:

View File

@@ -0,0 +1,39 @@
import {
type DomainSchema,
type Schema,
type Template,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const dbRootPassword = generatePassword(32);
const adminPassword = generatePassword(32);
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8080,
serviceName: "frontend",
},
];
const envs = [
`SITE_NAME=${mainDomain}`,
`ADMIN_PASSWORD=${adminPassword}`,
`DB_ROOT_PASSWORD=${dbRootPassword}`,
"MIGRATE=1",
"ENABLE_DB=1",
"DB_HOST=db",
"CREATE_SITE=1",
"CONFIGURE=1",
"REGENERATE_APPS_TXT=1",
"INSTALL_APP_ARGS=--install-app hrms",
"IMAGE_NAME=ghcr.io/frappe/hrms",
"VERSION=version-15",
"FRAPPE_SITE_NAME_HEADER=",
];
return { envs, domains };
}

View File

@@ -17,8 +17,7 @@ services:
db:
image: mysql:8.0
restart: always
networks:
- dokploy-network
environment:
MYSQL_ROOT_PASSWORD: example
volumes:

View File

@@ -11,8 +11,7 @@ services:
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea
restart: always
networks:
- dokploy-network
volumes:
- gitea_server:/data
- /etc/timezone:/etc/timezone:ro
@@ -27,8 +26,7 @@ services:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea
networks:
- dokploy-network
volumes:
- gitea_db:/var/lib/postgresql/data

View File

@@ -20,13 +20,11 @@ services:
restart: unless-stopped
volumes:
- pg-data:/var/lib/postgresql/data
networks:
- dokploy-network
redis:
image: redis
restart: unless-stopped
networks:
- dokploy-network
web:
image: glitchtip/glitchtip:v4.0
depends_on: *default-depends_on
@@ -44,15 +42,13 @@ services:
restart: unless-stopped
volumes:
- uploads:/code/uploads
networks:
- dokploy-network
migrate:
image: glitchtip/glitchtip:v4.0
depends_on: *default-depends_on
command: "./manage.py migrate"
environment: *default-environment
networks:
- dokploy-network
volumes:
pg-data:

View File

@@ -4,8 +4,7 @@ services:
restart: always
volumes:
- glpi-mysql-data:/var/lib/mysql
networks:
- dokploy-network
glpi-web:
image: elestio/glpi:10.0.16
@@ -16,8 +15,7 @@ services:
- glpi-www-data:/var/www/html/glpi
environment:
- TIMEZONE=Europe/Brussels
networks:
- dokploy-network
volumes:
glpi-mysql-data:

View File

@@ -28,8 +28,7 @@ services:
postgres:
image: elestio/postgres:16
restart: always
networks:
- dokploy-network
environment:
- POSTGRES_DB
- POSTGRES_USER

View File

@@ -2,8 +2,7 @@ name: ${DOCKER_NAME}
version: "3"
services:
nginx:
networks:
- dokploy-network
image: "nginx:1.21.3"
ports:
- 80
@@ -12,8 +11,7 @@ services:
restart: unless-stopped
mongodb:
networks:
- dokploy-network
image: "mongo:7-jammy"
environment:
- PUID=1000
@@ -23,8 +21,7 @@ services:
restart: unless-stopped
minio:
networks:
- dokploy-network
image: "minio/minio:RELEASE.2024-11-07T00-52-20Z"
command: server /data --address ":9000" --console-address ":9001"
volumes:
@@ -32,8 +29,7 @@ services:
restart: unless-stopped
elastic:
networks:
- dokploy-network
image: "elasticsearch:7.14.2"
command: |
/bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment;
@@ -54,8 +50,7 @@ services:
restart: unless-stopped
rekoni:
networks:
- dokploy-network
image: hardcoreeng/rekoni-service:${HULY_VERSION}
environment:
- SECRET=${SECRET}
@@ -66,8 +61,7 @@ services:
restart: unless-stopped
transactor:
networks:
- dokploy-network
image: hardcoreeng/transactor:${HULY_VERSION}
environment:
- SERVER_PORT=3333
@@ -84,8 +78,7 @@ services:
restart: unless-stopped
collaborator:
networks:
- dokploy-network
image: hardcoreeng/collaborator:${HULY_VERSION}
environment:
- COLLABORATOR_PORT=3078
@@ -97,8 +90,7 @@ services:
restart: unless-stopped
account:
networks:
- dokploy-network
image: hardcoreeng/account:${HULY_VERSION}
environment:
- SERVER_PORT=3000
@@ -115,8 +107,7 @@ services:
restart: unless-stopped
workspace:
networks:
- dokploy-network
image: hardcoreeng/workspace:${HULY_VERSION}
environment:
- SERVER_SECRET=${SECRET}
@@ -130,8 +121,7 @@ services:
restart: unless-stopped
front:
networks:
- dokploy-network
image: hardcoreeng/front:${HULY_VERSION}
environment:
- SERVER_PORT=8080
@@ -156,8 +146,7 @@ services:
restart: unless-stopped
fulltext:
networks:
- dokploy-network
image: hardcoreeng/fulltext:${HULY_VERSION}
environment:
- SERVER_SECRET=${SECRET}
@@ -171,8 +160,7 @@ services:
restart: unless-stopped
stats:
networks:
- dokploy-network
image: hardcoreeng/stats:${HULY_VERSION}
environment:
- PORT=4900

View File

@@ -3,8 +3,7 @@ version: "3.9"
services:
immich-server:
image: ghcr.io/immich-app/immich-server:v1.121.0
networks:
- dokploy-network
volumes:
- immich-library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
@@ -38,8 +37,7 @@ services:
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:v1.121.0
networks:
- dokploy-network
volumes:
- immich-model-cache:/cache
environment:
@@ -55,8 +53,7 @@ services:
immich-redis:
image: redis:6.2-alpine
networks:
- dokploy-network
volumes:
- immich-redis-data:/data
healthcheck:
@@ -68,8 +65,7 @@ services:
immich-database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
networks:
- dokploy-network
volumes:
- immich-postgres:/var/lib/postgresql/data
environment:

View File

@@ -19,8 +19,7 @@ services:
- SMTP_SECURE=true
command: npm run migration:latest
pull_policy: always
networks:
- dokploy-network
backend:
restart: unless-stopped
@@ -46,8 +45,7 @@ services:
- SMTP_USERNAME
- SMTP_PASSWORD
- SMTP_SECURE=true
networks:
- dokploy-network
redis:
image: redis:7.4.1
@@ -55,8 +53,7 @@ services:
restart: always
environment:
- ALLOW_EMPTY_PASSWORD=yes
networks:
- dokploy-network
volumes:
- redis_infisical_data:/data
@@ -69,8 +66,7 @@ services:
- POSTGRES_DB
volumes:
- pg_infisical_data:/var/lib/postgresql/data
networks:
- dokploy-network
healthcheck:
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
interval: 5s

View File

@@ -3,8 +3,7 @@ version: "3.8"
services:
invoiceshelf-postgres:
image: postgres:15
networks:
- dokploy-network
volumes:
- invoiceshelf-postgres-data:/var/lib/postgresql/data
environment:
@@ -19,8 +18,7 @@ services:
invoiceshelf-app:
image: invoiceshelf/invoiceshelf:latest
networks:
- dokploy-network
volumes:
- invoiceshelf-app-data:/data
- invoiceshelf-app-conf:/conf

View File

@@ -16,8 +16,7 @@ services:
depends_on:
db:
condition: service_healthy
networks:
- dokploy-network
db:
image: mariadb:10.11
restart: unless-stopped
@@ -39,8 +38,7 @@ services:
timeout: 5s
retries: 5
start_period: 30s
networks:
- dokploy-network
networks:
dokploy-network:

View File

@@ -12,8 +12,7 @@ services:
# This variable defines where the logs, file storage, monitor data and secret keys are stored.
volumes:
- langflow-data:/app/langflow
networks:
- dokploy-network
postgres-langflow:
image: postgres:16
@@ -25,8 +24,7 @@ services:
- 5432
volumes:
- langflow-postgres:/var/lib/postgresql/data
networks:
- dokploy-network
volumes:
langflow-postgres:

View File

@@ -3,8 +3,7 @@ services:
image: postgres:17-alpine
ports:
- 5432
networks:
- dokploy-network
environment:
- POSTGRES_PASSWORD=listmonk
- POSTGRES_USER=listmonk
@@ -20,8 +19,7 @@ services:
setup:
image: listmonk/listmonk:v4.1.0
networks:
- dokploy-network
volumes:
- ../files/config.toml:/listmonk/config.toml
depends_on:

View File

@@ -8,8 +8,7 @@ services:
ports:
- 3001
- 3002
networks:
- dokploy-network
environment:
TRUST_PROXY_HEADER: 1
DB_URL: postgres://logto:${LOGTO_POSTGRES_PASSWORD}@postgres:5432/logto
@@ -20,8 +19,7 @@ services:
postgres:
image: postgres:17-alpine
user: postgres
networks:
- dokploy-network
environment:
POSTGRES_USER: logto
POSTGRES_PASSWORD: ${LOGTO_POSTGRES_PASSWORD}

View File

@@ -24,8 +24,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
networks:
- dokploy-network
volumes:
- db-data:/var/lib/postgresql/data
environment:

View File

@@ -22,5 +22,4 @@ services:
POSTGRES_USER: metabase
POSTGRES_DB: metabaseappdb
POSTGRES_PASSWORD: mysecretpassword
networks:
- dokploy-network

View File

@@ -2,8 +2,7 @@ services:
nextcloud:
image: nextcloud:30.0.2
restart: always
networks:
- dokploy-network
ports:
- 80
volumes:
@@ -19,8 +18,7 @@ services:
nextcloud_db:
image: mariadb
restart: always
networks:
- dokploy-network
volumes:
- nextcloud_db_data:/var/lib/mysql
environment:

View File

@@ -13,8 +13,7 @@ services:
root_db:
image: postgres:17
restart: always
networks:
- dokploy-network
environment:
POSTGRES_DB: root_db
POSTGRES_PASSWORD: password

View File

@@ -15,8 +15,7 @@ services:
db:
image: postgres:13
networks:
- dokploy-network
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo

View File

@@ -3,8 +3,7 @@ services:
ollama:
volumes:
- ollama:/root/.ollama
networks:
- dokploy-network
pull_policy: always
tty: true
restart: unless-stopped

View File

@@ -46,8 +46,7 @@ services:
- penpot-backend
- penpot-exporter
networks:
- dokploy-network
environment:
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies
@@ -63,8 +62,7 @@ services:
- penpot-postgres
- penpot-redis
networks:
- dokploy-network
## Configuration envronment variables for the backend
## container.
@@ -143,8 +141,7 @@ services:
penpot-exporter:
image: "penpotapp/exporter:2.3.2"
restart: always
networks:
- dokploy-network
environment:
# Don't touch it; this uses an internal docker network to
@@ -162,8 +159,7 @@ services:
volumes:
- penpot_postgres_v15:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
@@ -174,8 +170,7 @@ services:
penpot-redis:
image: redis:7.2
restart: always
networks:
- dokploy-network
## A mailcatch service, used as temporal SMTP server. You can access via HTTP to the
## port 1080 for read all emails the penpot platform has sent. Should be only used as a
@@ -188,8 +183,7 @@ services:
- '1025'
ports:
- 1080
networks:
- dokploy-network
## Example configuration of MiniIO (S3 compatible object storage service); If you don't
## have preference, then just use filesystem, this is here just for the completeness.

View File

@@ -4,8 +4,7 @@ services:
peppermint-postgres:
image: postgres:latest
restart: always
networks:
- dokploy-network
volumes:
- peppermint-postgres-data:/var/lib/postgresql/data
environment:
@@ -21,8 +20,7 @@ services:
peppermint-app:
image: pepperlabs/peppermint:latest
restart: always
networks:
- dokploy-network
depends_on:
peppermint-postgres:
condition: service_healthy

View File

@@ -7,8 +7,7 @@ services:
security_opt:
- seccomp:unconfined
- apparmor:unconfined
networks:
- dokploy-network
environment:
PHOTOPRISM_ADMIN_USER: "admin"
PHOTOPRISM_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
@@ -57,8 +56,7 @@ services:
image: mariadb:11
restart: unless-stopped
stop_grace_period: 5s
networks:
- dokploy-network
security_opt:
- seccomp:unconfined
- apparmor:unconfined

View File

@@ -10,8 +10,7 @@ services:
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- dokploy-network
phpmyadmin:
image: phpmyadmin/phpmyadmin:5.2.1

View File

@@ -3,8 +3,7 @@ services:
plausible_db:
image: postgres:16-alpine
restart: always
networks:
- dokploy-network
volumes:
- db-data:/var/lib/postgresql/data
environment:
@@ -13,8 +12,7 @@ services:
plausible_events_db:
image: clickhouse/clickhouse-server:24.3.3.102-alpine
restart: always
networks:
- dokploy-network
volumes:
- event-data:/var/lib/clickhouse
- event-logs:/var/log/clickhouse-server

View File

@@ -6,8 +6,7 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
networks:
- dokploy-network
deploy:
mode: global

View File

@@ -4,8 +4,7 @@ services:
postiz-app:
image: ghcr.io/gitroomhq/postiz-app:latest
restart: always
networks:
- dokploy-network
environment:
MAIN_URL: "https://${POSTIZ_HOST}"
FRONTEND_URL: "https://${POSTIZ_HOST}"
@@ -30,8 +29,7 @@ services:
postiz-postgres:
image: postgres:17-alpine
restart: always
networks:
- dokploy-network
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER}
@@ -47,8 +45,7 @@ services:
postiz-redis:
image: redis:7.2
restart: always
networks:
- dokploy-network
healthcheck:
test: redis-cli ping
interval: 10s

View File

@@ -28,8 +28,7 @@ services:
MONGODB_ADVERTISED_HOSTNAME: mongodb
MONGODB_ENABLE_JOURNAL: true
ALLOW_EMPTY_PASSWORD: yes
networks:
- dokploy-network
volumes:
mongodb_data: { driver: local }

View File

@@ -9,8 +9,7 @@ services:
- ROUNDCUBEMAIL_SKIN=elastic
- ROUNDCUBEMAIL_DEFAULT_HOST=${DEFAULT_HOST}
- ROUNDCUBEMAIL_SMTP_SERVER=${SMTP_SERVER}
networks:
- dokploy-network
networks:
dokploy-network:

View File

@@ -3,8 +3,7 @@ version: '3.7'
services:
ryot-app:
image: ignisda/ryot:v7.10
networks:
- dokploy-network
environment:
- DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@ryot-db:5432/postgres
- SERVER_ADMIN_ACCESS_TOKEN=${ADMIN_ACCESS_TOKEN}
@@ -19,8 +18,7 @@ services:
ryot-db:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- ryot-postgres-data:/var/lib/postgresql/data
environment:

View File

@@ -3,8 +3,7 @@ version: "3.8"
services:
slash-app:
image: yourselfhosted/slash:latest
networks:
- dokploy-network
volumes:
- slash-app-data:/var/opt/slash
environment:
@@ -17,8 +16,7 @@ services:
slash-postgres:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- slash-postgres-data:/var/lib/postgresql/data
environment:

View File

@@ -11,8 +11,7 @@ services:
studio:
container_name: supabase-studio
image: supabase/studio:20240729-ce42139
networks:
- dokploy-network
restart: unless-stopped
healthcheck:
test:
@@ -53,8 +52,7 @@ services:
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:
@@ -85,8 +83,7 @@ services:
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
@@ -157,8 +154,7 @@ services:
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
@@ -180,8 +176,7 @@ services:
# 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
@@ -226,8 +221,7 @@ services:
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
@@ -271,8 +265,7 @@ services:
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.8.0
networks:
- dokploy-network
healthcheck:
test: ["CMD", "imgproxy", "health"]
timeout: 5s
@@ -289,8 +282,7 @@ services:
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
@@ -310,8 +302,7 @@ services:
container_name: supabase-edge-functions
image: supabase/edge-runtime:v1.56.0
restart: unless-stopped
networks:
- dokploy-network
depends_on:
analytics:
condition: service_healthy
@@ -333,8 +324,7 @@ services:
analytics:
container_name: supabase-analytics
image: supabase/logflare:1.4.0
networks:
- dokploy-network
healthcheck:
test: ["CMD", "curl", "http://localhost:4000/health"]
timeout: 5s
@@ -380,8 +370,7 @@ services:
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
@@ -430,8 +419,7 @@ services:
vector:
container_name: supabase-vector
image: timberio/vector:0.28.1-alpine
networks:
- dokploy-network
healthcheck:
test:
[

View File

@@ -43,8 +43,7 @@ services:
interval: 30s
timeout: 10s
retries: 3
networks:
- dokploy-network
superset_redis:
image: redis
@@ -57,8 +56,7 @@ services:
interval: 30s
timeout: 10s
retries: 3
networks:
- dokploy-network
volumes:
superset_postgres_data:

View File

@@ -41,8 +41,7 @@ services:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
networks:
- dokploy-network
healthcheck:
test:
[
@@ -58,8 +57,7 @@ services:
environment:
- TZ=${TIMEZONE}
- PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
networks:
- dokploy-network
depends_on:
teable-db:
condition: service_healthy

View File

@@ -1095,7 +1095,7 @@ export const templates: TemplateData[] = [
{
id: "unsend",
name: "Unsend",
version: "v1.2.4",
version: "v1.3.2",
description: "Open source alternative to Resend,Sendgrid, Postmark etc. ",
logo: "unsend.png",
links: {
@@ -1453,6 +1453,21 @@ export const templates: TemplateData[] = [
tags: ["sharing", "shortener", "url"],
load: () => import("./shlink/index").then((m) => m.generate),
},
{
id: "frappe-hr",
name: "Frappe HR",
version: "version-15",
description:
"Feature rich HR & Payroll software. 100% FOSS and customizable.",
logo: "frappe-hr.svg",
links: {
github: "https://github.com/frappe/hrms",
docs: "https://docs.frappe.io/hr",
website: "https://frappe.io/hr",
},
tags: ["hrms", "payroll", "leaves", "expenses", "attendance", "performace"],
load: () => import("./frappe-hr/index").then((m) => m.generate),
},
{
id: "formbricks",
name: "Formbricks",

View File

@@ -4,8 +4,7 @@ services:
twenty-change-vol-ownership:
image: ubuntu
user: root
networks:
- dokploy-network
volumes:
- twenty-server-local-data:/tmp/server-local-data
- twenty-docker-data:/tmp/docker-data
@@ -16,8 +15,7 @@ services:
twenty-server:
image: twentycrm/twenty:latest
networks:
- dokploy-network
volumes:
- twenty-server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
- twenty-docker-data:/app/docker-data
@@ -45,8 +43,7 @@ services:
twenty-worker:
image: twentycrm/twenty:latest
networks:
- dokploy-network
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@twenty-postgres:5432/twenty
@@ -65,8 +62,7 @@ services:
twenty-postgres:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- twenty-postgres-data:/var/lib/postgresql/data
environment:
@@ -82,8 +78,7 @@ services:
twenty-redis:
image: redis:latest
networks:
- dokploy-network
volumes:
- twenty-redis-data:/data
healthcheck:

View File

@@ -13,8 +13,7 @@ services:
POSTGRES_USER: typebot
POSTGRES_DB: typebot
POSTGRES_PASSWORD: typebot
networks:
- dokploy-network
typebot-builder:
image: baptistearno/typebot-builder:2.27

View File

@@ -22,8 +22,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
networks:
- dokploy-network
volumes:
- db-data:/var/lib/postgresql/data
environment:

View File

@@ -29,8 +29,7 @@ services:
restart: unless-stopped
depends_on:
- unifi-db
networks:
- dokploy-network
unifi-db:
image: mongo:4.4
@@ -40,8 +39,7 @@ services:
ports:
- 27017
restart: unless-stopped
networks:
- dokploy-network
networks:
dokploy-network:

View File

@@ -3,8 +3,7 @@ name: unsend-prod
services:
unsend-db-prod:
image: postgres:16
networks:
- dokploy-network
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER:?err}
@@ -22,8 +21,7 @@ services:
unsend-redis-prod:
image: redis:7
networks:
- dokploy-network
restart: always
# ports:
# - "6379:6379"
@@ -33,8 +31,7 @@ services:
unsend-storage-prod:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
networks:
- dokploy-network
ports:
- 9002
- 9001
@@ -47,9 +44,7 @@ services:
command: -c 'mkdir -p /data/unsend && minio server /data --console-address ":9001" --address ":9002"'
unsend:
image: unsend/unsend:v1.2.5
networks:
- dokploy-network
image: unsend/unsend:v1.3.2
restart: always
ports:
- ${PORT:-3000}

View File

@@ -7,8 +7,7 @@ services:
restart: unless-stopped
volumes:
- windmill-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: windmill
@@ -20,8 +19,7 @@ services:
windmill-server:
image: ghcr.io/windmill-labs/windmill:main
networks:
- dokploy-network
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
@@ -42,8 +40,7 @@ services:
cpus: "1"
memory: 2048M
restart: unless-stopped
networks:
- dokploy-network
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
@@ -65,8 +62,7 @@ services:
cpus: "0.1"
memory: 128M
restart: unless-stopped
networks:
- dokploy-network
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
@@ -82,16 +78,14 @@ services:
windmill-lsp:
image: ghcr.io/windmill-labs/windmill-lsp:latest
restart: unless-stopped
networks:
- dokploy-network
volumes:
- windmill-lsp-cache:/root/.cache
windmill-caddy:
image: ghcr.io/windmill-labs/caddy-l4:latest
restart: unless-stopped
networks:
- dokploy-network
volumes:
- ../files/Caddyfile:/etc/caddy/Caddyfile
environment:

View File

@@ -12,8 +12,6 @@ services:
db:
image: mysql:5.7.34
networks:
- dokploy-network
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser

View File

@@ -3,8 +3,7 @@ version: '3.7'
services:
yourls-app:
image: yourls:1.9.2
networks:
- dokploy-network
environment:
YOURLS_SITE: https://${YOURLS_HOST}
YOURLS_USER: ${YOURLS_ADMIN_USER}
@@ -22,8 +21,7 @@ services:
yourls-mysql:
image: mysql:5.7
networks:
- dokploy-network
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: yourls

View File

@@ -2,8 +2,7 @@ version: "3"
services:
postgres:
image: postgres:15
networks:
- dokploy-network
restart: unless-stopped
environment:
- POSTGRES_USER=postgres

View File

@@ -69,6 +69,7 @@ export const compose = pgTable("compose", {
composePath: text("composePath").notNull().default("./docker-compose.yml"),
suffix: text("suffix").notNull().default(""),
randomize: boolean("randomize").notNull().default(false),
isolatedDeployment: boolean("isolatedDeployment").notNull().default(false),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId")
.notNull()

View File

@@ -73,6 +73,7 @@ export * from "./utils/builders/utils";
export * from "./utils/cluster/upload";
export * from "./utils/docker/compose";
export * from "./utils/docker/collision";
export * from "./utils/docker/domain";
export * from "./utils/docker/utils";
export * from "./utils/docker/types";

View File

@@ -34,6 +34,12 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
if (compose.isolatedDeployment) {
await execAsync(
`docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}`,
);
}
const logContent = `
App Name: ${appName}
Build Compose 🐳
@@ -73,6 +79,12 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
},
);
if (compose.isolatedDeployment) {
await execAsync(
`docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`,
).catch(() => {});
}
writeStream.write("Docker Compose Deployed: ✅");
} catch (error) {
writeStream.write(`Error ❌ ${(error as Error).message}`);
@@ -128,9 +140,10 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
${exportEnvCommand}
${exportEnvCommand}
${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}` : ""}
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
echo "Docker Compose Deployed: ✅" >> "${logPath}"
} || {

View File

@@ -0,0 +1,46 @@
import { findComposeById } from "@dokploy/server/services/compose";
import { dump, load } from "js-yaml";
import { addAppNameToAllServiceNames } from "./collision/root-network";
import { generateRandomHash } from "./compose";
import { addSuffixToAllVolumes } from "./compose/volume";
import type { ComposeSpecification } from "./types";
export const addAppNameToPreventCollision = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
let updatedComposeData = { ...composeData };
updatedComposeData = addAppNameToAllServiceNames(updatedComposeData, appName);
updatedComposeData = addSuffixToAllVolumes(updatedComposeData, appName);
return updatedComposeData;
};
export const randomizeIsolatedDeploymentComposeFile = async (
composeId: string,
suffix?: string,
) => {
const compose = await findComposeById(composeId);
const composeFile = compose.composeFile;
const composeData = load(composeFile) as ComposeSpecification;
const randomSuffix = suffix || compose.appName || generateRandomHash();
const newComposeFile = addAppNameToPreventCollision(
composeData,
randomSuffix,
);
return dump(newComposeFile);
};
export const randomizeDeployableSpecificationFile = (
composeSpec: ComposeSpecification,
suffix?: string,
) => {
if (!suffix) {
return composeSpec;
}
const newComposeFile = addAppNameToPreventCollision(composeSpec, suffix);
return newComposeFile;
};

View File

@@ -0,0 +1,62 @@
import _ from "lodash";
import type { ComposeSpecification, DefinitionsService } from "../types";
export const addAppNameToRootNetwork = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
const updatedComposeData = { ...composeData };
// Initialize networks if it doesn't exist
if (!updatedComposeData.networks) {
updatedComposeData.networks = {};
}
// Add the new network with the app name
updatedComposeData.networks[appName] = {
name: appName,
external: true,
};
return updatedComposeData;
};
export const addAppNameToServiceNetworks = (
services: { [key: string]: DefinitionsService },
appName: string,
): { [key: string]: DefinitionsService } => {
return _.mapValues(services, (service) => {
if (!service.networks) {
service.networks = [appName];
return service;
}
if (Array.isArray(service.networks)) {
if (!service.networks.includes(appName)) {
service.networks.push(appName);
}
} else {
service.networks[appName] = {};
}
return service;
});
};
export const addAppNameToAllServiceNames = (
composeData: ComposeSpecification,
appName: string,
): ComposeSpecification => {
let updatedComposeData = { ...composeData };
updatedComposeData = addAppNameToRootNetwork(updatedComposeData, appName);
if (updatedComposeData.services) {
updatedComposeData.services = addAppNameToServiceNetworks(
updatedComposeData.services,
appName,
);
}
return updatedComposeData;
};

View File

@@ -26,6 +26,7 @@ import {
createComposeFileRaw,
createComposeFileRawRemote,
} from "../providers/raw";
import { randomizeDeployableSpecificationFile } from "./collision";
import { randomizeSpecificationFile } from "./compose";
import type {
ComposeSpecification,
@@ -190,7 +191,13 @@ export const addDomainToCompose = async (
return null;
}
if (compose.randomize) {
if (compose.isolatedDeployment) {
const randomized = randomizeDeployableSpecificationFile(
result,
compose.suffix || compose.appName,
);
result = randomized;
} else if (compose.randomize) {
const randomized = randomizeSpecificationFile(result, compose.suffix);
result = randomized;
}
@@ -240,14 +247,18 @@ export const addDomainToCompose = async (
labels.push(...httpLabels);
}
// Add the dokploy-network to the service
result.services[serviceName].networks = addDokployNetworkToService(
result.services[serviceName].networks,
);
if (!compose.isolatedDeployment) {
// Add the dokploy-network to the service
result.services[serviceName].networks = addDokployNetworkToService(
result.services[serviceName].networks,
);
}
}
// Add dokploy-network to the root of the compose file
result.networks = addDokployNetworkToRoot(result.networks);
if (!compose.isolatedDeployment) {
result.networks = addDokployNetworkToRoot(result.networks);
}
return result;
};