mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into feat/shared-ssh
This commit is contained in:
@@ -28,84 +28,103 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { useEffect, useState } from "react";
|
||||||
import { PlusIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
// const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
import { domain } from "@/server/db/validations";
|
||||||
// .regex(hostnameRegex
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
const addDomain = z.object({
|
import type z from "zod";
|
||||||
host: z.string().min(1, "Hostname is required"),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z.number(),
|
|
||||||
https: z.boolean(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
type AddDomain = z.infer<typeof addDomain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
children?: React.ReactNode;
|
domainId?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddDomain = ({
|
export const AddDomain = ({
|
||||||
applicationId,
|
applicationId,
|
||||||
children = <PlusIcon className="h-4 w-4" />,
|
domainId = "",
|
||||||
|
children,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
const { data, refetch } = api.domain.one.useQuery(
|
||||||
const { mutateAsync, isError, error } = api.domain.create.useMutation();
|
{
|
||||||
|
domainId,
|
||||||
const form = useForm<AddDomain>({
|
|
||||||
defaultValues: {
|
|
||||||
host: "",
|
|
||||||
https: false,
|
|
||||||
path: "/",
|
|
||||||
port: 3000,
|
|
||||||
certificateType: "none",
|
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addDomain),
|
{
|
||||||
|
enabled: !!domainId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutateAsync, isError, error, isLoading } = domainId
|
||||||
|
? api.domain.update.useMutation()
|
||||||
|
: api.domain.create.useMutation();
|
||||||
|
|
||||||
|
const form = useForm<Domain>({
|
||||||
|
resolver: zodResolver(domain),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset();
|
if (data) {
|
||||||
}, [form, form.reset, form.formState.isSubmitSuccessful]);
|
form.reset({
|
||||||
|
...data,
|
||||||
|
/* Convert null to undefined */
|
||||||
|
path: data?.path || undefined,
|
||||||
|
port: data?.port || undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async (data: AddDomain) => {
|
if (!domainId) {
|
||||||
|
form.reset({});
|
||||||
|
}
|
||||||
|
}, [form, form.reset, data, isLoading]);
|
||||||
|
|
||||||
|
const dictionary = {
|
||||||
|
success: domainId ? "Domain Updated" : "Domain Created",
|
||||||
|
error: domainId
|
||||||
|
? "Error to update the domain"
|
||||||
|
: "Error to create the domain",
|
||||||
|
submit: domainId ? "Update" : "Create",
|
||||||
|
dialogDescription: domainId
|
||||||
|
? "In this section you can edit a domain"
|
||||||
|
: "In this section you can add domains",
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (data: Domain) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
|
domainId,
|
||||||
applicationId,
|
applicationId,
|
||||||
host: data.host,
|
...data,
|
||||||
https: data.https,
|
|
||||||
path: data.path,
|
|
||||||
port: data.port,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Domain Created");
|
toast.success(dictionary.success);
|
||||||
await utils.domain.byApplicationId.invalidate({
|
await utils.domain.byApplicationId.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
await utils.application.readTraefikConfig.invalidate({ applicationId });
|
||||||
|
|
||||||
|
if (domainId) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to create the domain");
|
toast.error(dictionary.error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button>{children}</Button>
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
In this section you can add custom domains
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
@@ -169,33 +188,36 @@ export const AddDomain = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{form.getValues().https && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="certificateType"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="certificateType"
|
||||||
<FormItem className="col-span-2">
|
render={({ field }) => (
|
||||||
<FormLabel>Certificate</FormLabel>
|
<FormItem className="col-span-2">
|
||||||
<Select
|
<FormLabel>Certificate</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<Select
|
||||||
defaultValue={field.value || ""}
|
onValueChange={field.onChange}
|
||||||
>
|
defaultValue={field.value || ""}
|
||||||
<FormControl>
|
>
|
||||||
<SelectTrigger>
|
<FormControl>
|
||||||
<SelectValue placeholder="Select a certificate" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a certificate" />
|
||||||
</FormControl>
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">None</SelectItem>
|
||||||
|
<SelectItem value={"letsencrypt"}>
|
||||||
|
Letsencrypt (Default)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">None</SelectItem>
|
|
||||||
<SelectItem value={"letsencrypt"}>
|
|
||||||
Letsencrypt (Default)
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="https"
|
name="https"
|
||||||
@@ -206,6 +228,7 @@ export const AddDomain = ({
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
Automatically provision SSL Certificate.
|
Automatically provision SSL Certificate.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -226,7 +249,7 @@ export const AddDomain = ({
|
|||||||
form="hook-form"
|
form="hook-form"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Create
|
{dictionary.submit}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
|
import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React from "react";
|
|
||||||
import { AddDomain } from "./add-domain";
|
import { AddDomain } from "./add-domain";
|
||||||
import { DeleteDomain } from "./delete-domain";
|
import { DeleteDomain } from "./delete-domain";
|
||||||
import { GenerateDomain } from "./generate-domain";
|
import { GenerateDomain } from "./generate-domain";
|
||||||
import { UpdateDomain } from "./update-domain";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
@@ -43,7 +41,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-row gap-4 flex-wrap">
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
{data && data?.length > 0 && (
|
{data && data?.length > 0 && (
|
||||||
<AddDomain applicationId={applicationId}>
|
<AddDomain applicationId={applicationId}>
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<Button>
|
||||||
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
|
</Button>
|
||||||
</AddDomain>
|
</AddDomain>
|
||||||
)}
|
)}
|
||||||
{data && data?.length > 0 && (
|
{data && data?.length > 0 && (
|
||||||
@@ -61,7 +61,9 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex flex-row gap-4 flex-wrap">
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
<AddDomain applicationId={applicationId}>
|
<AddDomain applicationId={applicationId}>
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<Button>
|
||||||
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
|
</Button>
|
||||||
</AddDomain>
|
</AddDomain>
|
||||||
|
|
||||||
<GenerateDomain applicationId={applicationId} />
|
<GenerateDomain applicationId={applicationId} />
|
||||||
@@ -90,7 +92,14 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
{item.https ? "HTTPS" : "HTTP"}
|
{item.https ? "HTTPS" : "HTTP"}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex flex-row gap-1">
|
<div className="flex flex-row gap-1">
|
||||||
<UpdateDomain domainId={item.domainId} />
|
<AddDomain
|
||||||
|
applicationId={applicationId}
|
||||||
|
domainId={item.domainId}
|
||||||
|
>
|
||||||
|
<Button variant="ghost">
|
||||||
|
<PenBoxIcon className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</AddDomain>
|
||||||
<DeleteDomain domainId={item.domainId} />
|
<DeleteDomain domainId={item.domainId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { PenBoxIcon } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
|
||||||
|
|
||||||
const updateDomain = z.object({
|
|
||||||
host: z.string().regex(hostnameRegex, { message: "Invalid hostname" }),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z
|
|
||||||
.number()
|
|
||||||
.min(1, { message: "Port must be at least 1" })
|
|
||||||
.max(65535, { message: "Port must be 65535 or below" }),
|
|
||||||
https: z.boolean(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
type UpdateDomain = z.infer<typeof updateDomain>;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
domainId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UpdateDomain = ({ domainId }: Props) => {
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { data, refetch } = api.domain.one.useQuery(
|
|
||||||
{
|
|
||||||
domainId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!domainId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const { mutateAsync, isError, error } = api.domain.update.useMutation();
|
|
||||||
|
|
||||||
const form = useForm<UpdateDomain>({
|
|
||||||
defaultValues: {
|
|
||||||
host: "",
|
|
||||||
https: true,
|
|
||||||
path: "/",
|
|
||||||
port: 3000,
|
|
||||||
certificateType: "none",
|
|
||||||
},
|
|
||||||
resolver: zodResolver(updateDomain),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
host: data.host || "",
|
|
||||||
port: data.port || 3000,
|
|
||||||
path: data.path || "/",
|
|
||||||
https: data.https,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [form, form.reset, data]);
|
|
||||||
|
|
||||||
const onSubmit = async (data: UpdateDomain) => {
|
|
||||||
await mutateAsync({
|
|
||||||
domainId,
|
|
||||||
host: data.host,
|
|
||||||
https: data.https,
|
|
||||||
path: data.path,
|
|
||||||
port: data.port,
|
|
||||||
certificateType: data.certificateType,
|
|
||||||
})
|
|
||||||
.then(async (data) => {
|
|
||||||
toast.success("Domain Updated");
|
|
||||||
await refetch();
|
|
||||||
await utils.domain.byApplicationId.invalidate({
|
|
||||||
applicationId: data?.applicationId,
|
|
||||||
});
|
|
||||||
await utils.application.readTraefikConfig.invalidate({
|
|
||||||
applicationId: data?.applicationId,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error to update the domain");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger className="" asChild>
|
|
||||||
<Button variant="ghost">
|
|
||||||
<PenBoxIcon className="size-4 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Domain</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
In this section you can add custom domains
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
id="hook-form"
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="grid w-full gap-8 "
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="host"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Host</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="api.dokploy.com" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="path"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Path</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder={"/"} {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="port"
|
|
||||||
render={({ field }) => {
|
|
||||||
return (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Container Port</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder={"3000"}
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(Number.parseInt(e.target.value));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="certificateType"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="col-span-2">
|
|
||||||
<FormLabel>Certificate</FormLabel>
|
|
||||||
<Select
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
value={field.value}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select a certificate" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value={"none"}>None</SelectItem>
|
|
||||||
<SelectItem value={"letsencrypt"}>
|
|
||||||
Letsencrypt
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
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>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
isLoading={form.formState.isSubmitting}
|
|
||||||
form="hook-form"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -23,8 +23,11 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
|
|||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
cols: 80,
|
cols: 80,
|
||||||
rows: 30,
|
rows: 30,
|
||||||
lineHeight: 1.4,
|
lineHeight: 1.25,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily:
|
||||||
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||||
|
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
form,
|
form,
|
||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
|
ip,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
data?.databasePassword,
|
data?.databasePassword,
|
||||||
form,
|
form,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
|
ip,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
data?.databaseUser,
|
data?.databaseUser,
|
||||||
form,
|
form,
|
||||||
|
ip,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
data?.databasePassword,
|
data?.databasePassword,
|
||||||
form,
|
form,
|
||||||
data?.databaseName,
|
data?.databaseName,
|
||||||
|
ip,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setConnectionUrl(buildConnectionUrl());
|
setConnectionUrl(buildConnectionUrl());
|
||||||
}, [data?.appName, data?.externalPort, data?.databasePassword, form]);
|
}, [data?.appName, data?.externalPort, data?.databasePassword, form, ip]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex w-full flex-col gap-5 ">
|
<div className="flex w-full flex-col gap-5 ">
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ if ss -tulnp | grep ':443 ' >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
command_exists() {
|
command_exists() {
|
||||||
command -v "$@" > /dev/null 2>&1
|
command -v "$@" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
@@ -46,7 +41,25 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
docker swarm leave --force 2>/dev/null
|
docker swarm leave --force 2>/dev/null
|
||||||
docker swarm init;
|
|
||||||
|
get_ip() {
|
||||||
|
# Try to get IPv4
|
||||||
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$ipv4" ]; then
|
||||||
|
echo "$ipv4"
|
||||||
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_addr=$(get_ip)
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Swarm initialized"
|
||||||
|
|
||||||
@@ -71,19 +84,28 @@ docker service create \
|
|||||||
--publish published=3000,target=3000,mode=host \
|
--publish published=3000,target=3000,mode=host \
|
||||||
--update-parallelism 1 \
|
--update-parallelism 1 \
|
||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
|
--constraint 'node.role == manager' \
|
||||||
dokploy/dokploy:latest
|
dokploy/dokploy:latest
|
||||||
|
|
||||||
|
|
||||||
public_ip=$(hostname -I | awk '{print $1}')
|
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
GREEN="\033[0;32m"
|
||||||
YELLOW="\033[1;33m"
|
YELLOW="\033[1;33m"
|
||||||
BLUE="\033[0;34m"
|
BLUE="\033[0;34m"
|
||||||
NC="\033[0m" # No Color
|
NC="\033[0m" # No Color
|
||||||
|
|
||||||
|
format_ip_for_url() {
|
||||||
|
local ip="$1"
|
||||||
|
if echo "$ip" | grep -q ':'; then
|
||||||
|
# IPv6
|
||||||
|
echo "[${ip}]"
|
||||||
|
else
|
||||||
|
# IPv4
|
||||||
|
echo "${ip}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
formatted_addr=$(format_ip_for_url "$advertise_addr")
|
||||||
echo ""
|
echo ""
|
||||||
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
printf "${GREEN}Congratulations, Dokploy is installed!${NC}\n"
|
||||||
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
printf "${BLUE}Wait 15 seconds for the server to start${NC}\n"
|
||||||
printf "${YELLOW}Please go to http://${public_ip}:3000${NC}\n\n"
|
printf "${YELLOW}Please go to http://${formatted_addr}:3000${NC}\n\n"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { domain } from "@/server/db/validations";
|
||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
import { boolean, integer, pgTable, serial, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { certificateType } from "./shared";
|
import { certificateType } from "./shared";
|
||||||
|
|
||||||
@@ -31,27 +31,17 @@ export const domainsRelations = relations(domains, ({ one }) => ({
|
|||||||
references: [applications.applicationId],
|
references: [applications.applicationId],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
const hostnameRegex = /^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/;
|
|
||||||
const createSchema = createInsertSchema(domains, {
|
|
||||||
domainId: z.string().min(1),
|
|
||||||
host: z.string().min(1),
|
|
||||||
path: z.string().min(1),
|
|
||||||
port: z.number(),
|
|
||||||
https: z.boolean(),
|
|
||||||
applicationId: z.string(),
|
|
||||||
certificateType: z.enum(["letsencrypt", "none"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiCreateDomain = createSchema
|
const createSchema = createInsertSchema(domains, domain._def.schema.shape);
|
||||||
.pick({
|
|
||||||
host: true,
|
export const apiCreateDomain = createSchema.pick({
|
||||||
path: true,
|
host: true,
|
||||||
port: true,
|
path: true,
|
||||||
https: true,
|
port: true,
|
||||||
applicationId: true,
|
https: true,
|
||||||
certificateType: true,
|
applicationId: true,
|
||||||
})
|
certificateType: true,
|
||||||
.required();
|
});
|
||||||
|
|
||||||
export const apiFindDomain = createSchema
|
export const apiFindDomain = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
@@ -59,19 +49,16 @@ export const apiFindDomain = createSchema
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiFindDomainByApplication = createSchema
|
export const apiFindDomainByApplication = createSchema.pick({
|
||||||
.pick({
|
applicationId: true,
|
||||||
applicationId: true,
|
});
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export const apiUpdateDomain = createSchema
|
export const apiUpdateDomain = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
domainId: true,
|
|
||||||
host: true,
|
host: true,
|
||||||
path: true,
|
path: true,
|
||||||
port: true,
|
port: true,
|
||||||
https: true,
|
https: true,
|
||||||
certificateType: true,
|
certificateType: true,
|
||||||
})
|
})
|
||||||
.required();
|
.merge(createSchema.pick({ domainId: true }).required());
|
||||||
|
|||||||
@@ -33,3 +33,28 @@ export const sshKeyUpdate = sshKeyCreate.pick({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const sshKeyType = z.enum(["rsa", "ed25519"]).optional();
|
export const sshKeyType = z.enum(["rsa", "ed25519"]).optional();
|
||||||
|
|
||||||
|
export const domain = z
|
||||||
|
.object({
|
||||||
|
host: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9\.-]*\.[a-zA-Z]{2,}$/, {
|
||||||
|
message: "Invalid hostname",
|
||||||
|
}),
|
||||||
|
path: z.string().min(1).optional(),
|
||||||
|
port: z
|
||||||
|
.number()
|
||||||
|
.min(1, { message: "Port must be at least 1" })
|
||||||
|
.max(65535, { message: "Port must be 65535 or below" })
|
||||||
|
.optional(),
|
||||||
|
https: z.boolean().optional(),
|
||||||
|
certificateType: z.enum(["letsencrypt", "none"]).optional(),
|
||||||
|
})
|
||||||
|
.superRefine((input, ctx) => {
|
||||||
|
if (input.https && !input.certificateType) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["certificateType"],
|
||||||
|
message: "Required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, mkdirSync } from "node:fs";
|
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
||||||
import {
|
import {
|
||||||
APPLICATIONS_PATH,
|
APPLICATIONS_PATH,
|
||||||
BASE_PATH,
|
BASE_PATH,
|
||||||
@@ -32,6 +32,9 @@ export const setupDirectories = () => {
|
|||||||
for (const dir of directories) {
|
for (const dir of directories) {
|
||||||
try {
|
try {
|
||||||
createDirectoryIfNotExist(dir);
|
createDirectoryIfNotExist(dir);
|
||||||
|
if (dir === SSH_PATH) {
|
||||||
|
chmodSync(SSH_PATH, "600");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error, " On path: ", dir);
|
console.log(error, " On path: ", dir);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { CreateServiceOptions } from "dockerode";
|
import type { CreateServiceOptions } from "dockerode";
|
||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
@@ -86,6 +86,7 @@ export const initializeTraefik = async () => {
|
|||||||
|
|
||||||
export const createDefaultServerTraefikConfig = () => {
|
export const createDefaultServerTraefikConfig = () => {
|
||||||
const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml");
|
const configFilePath = path.join(DYNAMIC_TRAEFIK_PATH, "dokploy.yml");
|
||||||
|
|
||||||
if (existsSync(configFilePath)) {
|
if (existsSync(configFilePath)) {
|
||||||
console.log("Default traefik config already exists");
|
console.log("Default traefik config already exists");
|
||||||
return;
|
return;
|
||||||
@@ -125,6 +126,11 @@ export const createDefaultServerTraefikConfig = () => {
|
|||||||
|
|
||||||
export const createDefaultTraefikConfig = () => {
|
export const createDefaultTraefikConfig = () => {
|
||||||
const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
const mainConfig = path.join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
||||||
|
const acmeJsonPath = path.join(DYNAMIC_TRAEFIK_PATH, "acme.json");
|
||||||
|
|
||||||
|
if (existsSync(acmeJsonPath)) {
|
||||||
|
chmodSync(acmeJsonPath, "600");
|
||||||
|
}
|
||||||
if (existsSync(mainConfig)) {
|
if (existsSync(mainConfig)) {
|
||||||
console.log("Main config already exists");
|
console.log("Main config already exists");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import { docker } from "@/server/constants";
|
import { prepareEnvironmentVariables } from "@/server/utils/docker/utils";
|
||||||
import { prepareBuildArgs } from "@/server/utils/docker/utils";
|
|
||||||
import * as tar from "tar-fs";
|
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
import { createEnvFile } from "./utils";
|
import { createEnvFile } from "./utils";
|
||||||
|
|
||||||
export const buildCustomDocker = async (
|
export const buildCustomDocker = async (
|
||||||
@@ -14,29 +13,30 @@ export const buildCustomDocker = async (
|
|||||||
const dockerFilePath = getBuildAppDirectory(application);
|
const dockerFilePath = getBuildAppDirectory(application);
|
||||||
try {
|
try {
|
||||||
const image = `${appName}`;
|
const image = `${appName}`;
|
||||||
|
|
||||||
const contextPath =
|
const contextPath =
|
||||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||||
const tarStream = tar.pack(contextPath);
|
const args = prepareEnvironmentVariables(buildArgs);
|
||||||
|
|
||||||
|
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
commandArgs.push("--build-arg", arg);
|
||||||
|
}
|
||||||
|
|
||||||
createEnvFile(dockerFilePath, env);
|
createEnvFile(dockerFilePath, env);
|
||||||
|
await spawnAsync(
|
||||||
const stream = await docker.buildImage(tarStream, {
|
"docker",
|
||||||
t: image,
|
commandArgs,
|
||||||
buildargs: prepareBuildArgs(buildArgs),
|
(data) => {
|
||||||
dockerfile: dockerFilePath.substring(dockerFilePath.lastIndexOf("/") + 1),
|
if (writeStream.writable) {
|
||||||
});
|
writeStream.write(data);
|
||||||
|
}
|
||||||
await new Promise((resolve, reject) => {
|
},
|
||||||
docker.modem.followProgress(
|
{
|
||||||
stream,
|
cwd: contextPath,
|
||||||
(err, res) => (err ? reject(err) : resolve(res)),
|
},
|
||||||
(event) => {
|
);
|
||||||
if (event.stream) {
|
|
||||||
writeStream.write(event.stream);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,12 +102,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#terminal span {
|
|
||||||
font-family: "Inter", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
letter-spacing: 0px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Codemirror */
|
/* Codemirror */
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply w-full h-full rounded-md overflow-hidden border border-solid border-border outline-none;
|
@apply w-full h-full rounded-md overflow-hidden border border-solid border-border outline-none;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
appsmith:
|
appsmith:
|
||||||
image: index.docker.io/appsmith/appsmith-ee:v1.29
|
image: index.docker.io/appsmith/appsmith-ee:v1.29
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
ports:
|
ports:
|
||||||
- ${APP_SMITH_PORT}
|
- ${APP_SMITH_PORT}
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)"
|
- "traefik.http.routers.${HASH}.rule=Host(`${APP_SMITH_HOST}`)"
|
||||||
- "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}"
|
- "traefik.http.services.${HASH}.loadbalancer.server.port=${APP_SMITH_PORT}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./stacks:/appsmith-stacks
|
- ../files/stacks:/appsmith-stacks
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dokploy-network:
|
dokploy-network:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8055
|
- 8055
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/directus/uploads
|
- ../files/uploads:/directus/uploads
|
||||||
- ./extensions:/directus/extensions
|
- ../files/extensions:/directus/extensions
|
||||||
depends_on:
|
depends_on:
|
||||||
- cache
|
- cache
|
||||||
- database
|
- database
|
||||||
@@ -53,4 +53,4 @@ networks:
|
|||||||
dokploy-network:
|
dokploy-network:
|
||||||
external: true
|
external: true
|
||||||
volumes:
|
volumes:
|
||||||
directus:
|
directus:
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.toml:/listmonk/config.toml
|
- ../files/config.toml:/listmonk/config.toml
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
command: [sh, -c, "sleep 3 && ./listmonk --install --idempotent --yes --config config.toml"]
|
command:
|
||||||
|
[
|
||||||
|
sh,
|
||||||
|
-c,
|
||||||
|
"sleep 3 && ./listmonk --install --idempotent --yes --config config.toml",
|
||||||
|
]
|
||||||
|
|
||||||
app:
|
app:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -41,7 +46,7 @@ services:
|
|||||||
- db
|
- db
|
||||||
- setup
|
- setup
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.toml:/listmonk/config.toml
|
- ../files/config.toml:/listmonk/config.toml
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)"
|
- "traefik.http.routers.${HASH}.rule=Host(`${LISTMONK_HOST}`)"
|
||||||
@@ -50,7 +55,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
listmonk-data:
|
listmonk-data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dokploy-network:
|
dokploy-network:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3.8'
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: odoo:16.0
|
image: odoo:16.0
|
||||||
@@ -18,8 +18,8 @@ services:
|
|||||||
- "traefik.http.services.${HASH}.loadbalancer.server.port=${ODOO_PORT}"
|
- "traefik.http.services.${HASH}.loadbalancer.server.port=${ODOO_PORT}"
|
||||||
volumes:
|
volumes:
|
||||||
- odoo-web-data:/var/lib/odoo
|
- odoo-web-data:/var/lib/odoo
|
||||||
- ./config:/etc/odoo
|
- ../files/config:/etc/odoo
|
||||||
- ./addons:/mnt/extra-addons
|
- ../files/addons:/mnt/extra-addons
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
@@ -36,7 +36,6 @@ volumes:
|
|||||||
odoo-web-data:
|
odoo-web-data:
|
||||||
odoo-db-data:
|
odoo-db-data:
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dokploy-network:
|
dokploy-network:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- event-data:/var/lib/clickhouse
|
- event-data:/var/lib/clickhouse
|
||||||
- event-logs:/var/log/clickhouse-server
|
- event-logs:/var/log/clickhouse-server
|
||||||
- ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
|
- ../files/clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
|
||||||
- ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
|
- ../files/clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
|
||||||
ulimits:
|
ulimits:
|
||||||
nofile:
|
nofile:
|
||||||
soft: 262144
|
soft: 262144
|
||||||
@@ -50,7 +50,7 @@ volumes:
|
|||||||
driver: local
|
driver: local
|
||||||
event-logs:
|
event-logs:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
dokploy-network:
|
dokploy-network:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
Reference in New Issue
Block a user