Skip to content

Commit 362416a

Browse files
authored
Merge pull request #3138 from Dokploy/711-custom-build-server
711 custom build server
2 parents cc1620b + 035f883 commit 362416a

23 files changed

Lines changed: 7477 additions & 152 deletions

File tree

apps/dokploy/__test__/drop/drop.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const baseApp: ApplicationNested = {
3030
previewLabels: [],
3131
herokuVersion: "",
3232
giteaBranch: "",
33+
buildServerId: "",
34+
buildRegistryId: "",
35+
buildRegistry: null,
3336
giteaBuildPath: "",
3437
previewRequireCollaboratorPermissions: false,
3538
giteaId: "",

apps/dokploy/__test__/traefik/traefik.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const baseApp: ApplicationNested = {
1111
giteaRepository: "",
1212
giteaOwner: "",
1313
giteaBranch: "",
14+
buildServerId: "",
15+
buildRegistryId: "",
16+
buildRegistry: null,
1417
giteaBuildPath: "",
1518
giteaId: "",
1619
cleanCache: false,
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { zodResolver } from "@hookform/resolvers/zod";
2+
import { Server } from "lucide-react";
3+
import Link from "next/link";
4+
import { useEffect } from "react";
5+
import { useForm } from "react-hook-form";
6+
import { toast } from "sonner";
7+
import { z } from "zod";
8+
import { AlertBlock } from "@/components/shared/alert-block";
9+
import { Button } from "@/components/ui/button";
10+
import {
11+
Card,
12+
CardContent,
13+
CardDescription,
14+
CardHeader,
15+
CardTitle,
16+
} from "@/components/ui/card";
17+
import {
18+
Form,
19+
FormControl,
20+
FormDescription,
21+
FormField,
22+
FormItem,
23+
FormLabel,
24+
FormMessage,
25+
} from "@/components/ui/form";
26+
import {
27+
Select,
28+
SelectContent,
29+
SelectGroup,
30+
SelectItem,
31+
SelectLabel,
32+
SelectTrigger,
33+
SelectValue,
34+
} from "@/components/ui/select";
35+
import { api } from "@/utils/api";
36+
37+
interface Props {
38+
applicationId: string;
39+
}
40+
41+
const schema = z.object({
42+
buildServerId: z.string().min(1, "Build server is required"),
43+
buildRegistryId: z.string().min(1, "Build registry is required"),
44+
});
45+
46+
type Schema = z.infer<typeof schema>;
47+
48+
export const ShowBuildServer = ({ applicationId }: Props) => {
49+
const { data, refetch } = api.application.one.useQuery(
50+
{ applicationId },
51+
{ enabled: !!applicationId },
52+
);
53+
const { data: buildServers } = api.server.buildServers.useQuery();
54+
const { data: registries } = api.registry.all.useQuery();
55+
56+
const { mutateAsync, isLoading } = api.application.update.useMutation();
57+
58+
const form = useForm<Schema>({
59+
defaultValues: {
60+
buildServerId: data?.buildServerId || "",
61+
buildRegistryId: data?.buildRegistryId || "",
62+
},
63+
resolver: zodResolver(schema),
64+
});
65+
66+
useEffect(() => {
67+
if (data) {
68+
form.reset({
69+
buildServerId: data?.buildServerId || "",
70+
buildRegistryId: data?.buildRegistryId || "",
71+
});
72+
}
73+
}, [form, form.reset, data]);
74+
75+
const onSubmit = async (formData: Schema) => {
76+
await mutateAsync({
77+
applicationId,
78+
buildServerId:
79+
formData?.buildServerId === "none" || !formData?.buildServerId
80+
? null
81+
: formData?.buildServerId,
82+
buildRegistryId:
83+
formData?.buildRegistryId === "none" || !formData?.buildRegistryId
84+
? null
85+
: formData?.buildRegistryId,
86+
})
87+
.then(async () => {
88+
toast.success("Build Server Settings Updated");
89+
await refetch();
90+
})
91+
.catch(() => {
92+
toast.error("Error updating build server settings");
93+
});
94+
};
95+
96+
return (
97+
<Card className="bg-background">
98+
<CardHeader>
99+
<div className="flex flex-row items-center gap-2">
100+
<Server className="size-6 text-muted-foreground" />
101+
<div>
102+
<CardTitle className="text-xl">Build Server</CardTitle>
103+
<CardDescription>
104+
Configure a dedicated server for building your application.
105+
</CardDescription>
106+
</div>
107+
</div>
108+
</CardHeader>
109+
<CardContent className="flex flex-col gap-4">
110+
<AlertBlock type="info">
111+
Build servers offload the build process from your deployment servers.
112+
Select a build server and registry to use for building your
113+
application.
114+
</AlertBlock>
115+
116+
<AlertBlock type="info">
117+
📊 <strong>Important:</strong> Once the build finishes, you'll need to
118+
wait a few seconds for the deployment server to download the image.
119+
These download logs will <strong>NOT</strong> appear in the build
120+
deployment logs. Check the <strong>Logs</strong> tab to see when the
121+
container starts running.
122+
</AlertBlock>
123+
124+
{!registries || registries.length === 0 ? (
125+
<AlertBlock type="warning">
126+
You need to add at least one registry to use build servers. Please
127+
go to{" "}
128+
<Link
129+
href="/dashboard/settings/registry"
130+
className="text-primary underline"
131+
>
132+
Settings
133+
</Link>{" "}
134+
to add a registry.
135+
</AlertBlock>
136+
) : null}
137+
138+
<Form {...form}>
139+
<form
140+
onSubmit={form.handleSubmit(onSubmit)}
141+
className="grid w-full gap-4"
142+
>
143+
<FormField
144+
control={form.control}
145+
name="buildServerId"
146+
render={({ field }) => (
147+
<FormItem>
148+
<FormLabel>Build Server</FormLabel>
149+
<Select
150+
onValueChange={field.onChange}
151+
value={field.value || "none"}
152+
>
153+
<FormControl>
154+
<SelectTrigger>
155+
<SelectValue placeholder="Select a build server" />
156+
</SelectTrigger>
157+
</FormControl>
158+
<SelectContent>
159+
<SelectGroup>
160+
<SelectItem value="none">
161+
<span className="flex items-center gap-2">
162+
<span>None</span>
163+
</span>
164+
</SelectItem>
165+
{buildServers?.map((server) => (
166+
<SelectItem
167+
key={server.serverId}
168+
value={server.serverId}
169+
>
170+
<span className="flex items-center gap-2 justify-between w-full">
171+
<span>{server.name}</span>
172+
<span className="text-muted-foreground text-xs">
173+
{server.ipAddress}
174+
</span>
175+
</span>
176+
</SelectItem>
177+
))}
178+
<SelectLabel>
179+
Build Servers ({buildServers?.length || 0})
180+
</SelectLabel>
181+
</SelectGroup>
182+
</SelectContent>
183+
</Select>
184+
<FormDescription>
185+
Select a build server to handle the build process for this
186+
application.
187+
</FormDescription>
188+
<FormMessage />
189+
</FormItem>
190+
)}
191+
/>
192+
193+
<FormField
194+
control={form.control}
195+
name="buildRegistryId"
196+
render={({ field }) => (
197+
<FormItem>
198+
<FormLabel>Build Registry</FormLabel>
199+
<Select
200+
onValueChange={field.onChange}
201+
value={field.value || "none"}
202+
>
203+
<FormControl>
204+
<SelectTrigger>
205+
<SelectValue placeholder="Select a registry" />
206+
</SelectTrigger>
207+
</FormControl>
208+
<SelectContent>
209+
<SelectGroup>
210+
<SelectItem value="none">
211+
<span className="flex items-center gap-2">
212+
<span>None</span>
213+
</span>
214+
</SelectItem>
215+
{registries?.map((registry) => (
216+
<SelectItem
217+
key={registry.registryId}
218+
value={registry.registryId}
219+
>
220+
{registry.registryName}
221+
</SelectItem>
222+
))}
223+
<SelectLabel>
224+
Registries ({registries?.length || 0})
225+
</SelectLabel>
226+
</SelectGroup>
227+
</SelectContent>
228+
</Select>
229+
<FormDescription>
230+
Select a registry to store the built images from the build
231+
server.
232+
</FormDescription>
233+
<FormMessage />
234+
</FormItem>
235+
)}
236+
/>
237+
238+
<div className="flex w-full justify-end">
239+
<Button isLoading={isLoading} type="submit">
240+
Save
241+
</Button>
242+
</div>
243+
</form>
244+
</Form>
245+
</CardContent>
246+
</Card>
247+
);
248+
};

apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ export const ShowDeployments = ({
407407
</div>
408408
)}
409409
<ShowDeployment
410-
serverId={serverId}
410+
serverId={activeLog?.buildServerId || serverId}
411411
open={Boolean(activeLog && activeLog.logPath !== null)}
412412
onClose={() => setActiveLog(null)}
413413
logPath={activeLog?.logPath || ""}

apps/dokploy/components/dashboard/settings/servers/handle-servers.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const Schema = z.object({
5252
sshKeyId: z.string().min(1, {
5353
message: "SSH Key is required",
5454
}),
55+
serverType: z.enum(["deploy", "build"]).default("deploy"),
5556
});
5657

5758
type Schema = z.infer<typeof Schema>;
@@ -89,6 +90,7 @@ export const HandleServers = ({ serverId }: Props) => {
8990
port: 22,
9091
username: "root",
9192
sshKeyId: "",
93+
serverType: "deploy",
9294
},
9395
resolver: zodResolver(Schema),
9496
});
@@ -101,6 +103,7 @@ export const HandleServers = ({ serverId }: Props) => {
101103
port: data?.port || 22,
102104
username: data?.username || "root",
103105
sshKeyId: data?.sshKeyId || "",
106+
serverType: data?.serverType || "deploy",
104107
});
105108
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
106109

@@ -116,6 +119,7 @@ export const HandleServers = ({ serverId }: Props) => {
116119
port: data.port || 22,
117120
username: data.username || "root",
118121
sshKeyId: data.sshKeyId || "",
122+
serverType: data.serverType || "deploy",
119123
serverId: serverId || "",
120124
})
121125
.then(async (_data) => {
@@ -266,6 +270,50 @@ export const HandleServers = ({ serverId }: Props) => {
266270
</FormItem>
267271
)}
268272
/>
273+
<FormField
274+
control={form.control}
275+
name="serverType"
276+
render={({ field }) => {
277+
const serverTypeValue = form.watch("serverType");
278+
return (
279+
<FormItem>
280+
<FormLabel>Server Type</FormLabel>
281+
<Select
282+
onValueChange={field.onChange}
283+
defaultValue={field.value}
284+
>
285+
<SelectTrigger>
286+
<SelectValue placeholder="Select a server type" />
287+
</SelectTrigger>
288+
<SelectContent>
289+
<SelectGroup>
290+
<SelectItem value="deploy">Deploy Server</SelectItem>
291+
<SelectItem value="build">Build Server</SelectItem>
292+
<SelectLabel>Server Type</SelectLabel>
293+
</SelectGroup>
294+
</SelectContent>
295+
</Select>
296+
<FormMessage />
297+
{serverTypeValue === "deploy" && (
298+
<AlertBlock type="info" className="mt-2">
299+
Deploy servers are used to run your applications,
300+
databases, and services. They handle the deployment and
301+
execution of your projects.
302+
</AlertBlock>
303+
)}
304+
{serverTypeValue === "build" && (
305+
<AlertBlock type="info" className="mt-2">
306+
Build servers are dedicated to building your
307+
applications. They handle the compilation and build
308+
process, offloading this work from your deployment
309+
servers. Build servers won't appear in deployment
310+
options.
311+
</AlertBlock>
312+
)}
313+
</FormItem>
314+
);
315+
}}
316+
/>
269317
<FormField
270318
control={form.control}
271319
name="sshKeyId"

0 commit comments

Comments
 (0)