mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #297 from lorenzomigliorero/feat/static-build-phase
feat: new publish directory flag
This commit is contained in:
commit
6299385bb4
@ -40,6 +40,7 @@ const baseApp: ApplicationNested = {
|
|||||||
placementSwarm: null,
|
placementSwarm: null,
|
||||||
ports: [],
|
ports: [],
|
||||||
projectId: "",
|
projectId: "",
|
||||||
|
publishDirectory: null,
|
||||||
redirects: [],
|
redirects: [],
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
registry: null,
|
registry: null,
|
||||||
|
@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -43,6 +44,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal("nixpacks"),
|
buildType: z.literal("nixpacks"),
|
||||||
|
publishDirectory: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -84,6 +86,7 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
} else {
|
} else {
|
||||||
form.reset({
|
form.reset({
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
|
publishDirectory: data.publishDirectory || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +96,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
applicationId,
|
applicationId,
|
||||||
buildType: data.buildType,
|
buildType: data.buildType,
|
||||||
|
publishDirectory:
|
||||||
|
data.buildType === "nixpacks" ? data.publishDirectory : null,
|
||||||
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@ -200,6 +205,36 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{buildType === "nixpacks" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="publishDirectory"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Publish Directory</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Allows you to serve a single directory via NGINX after
|
||||||
|
the build phase. Useful if the final build assets
|
||||||
|
should be served as a static site.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={"Publish Directory"}
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button isLoading={isLoading} type="submit">
|
<Button isLoading={isLoading} type="submit">
|
||||||
Save
|
Save
|
||||||
|
1
apps/dokploy/drizzle/0027_red_lady_bullseye.sql
Normal file
1
apps/dokploy/drizzle/0027_red_lady_bullseye.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "publishDirectory" text;
|
3016
apps/dokploy/drizzle/meta/0027_snapshot.json
Normal file
3016
apps/dokploy/drizzle/meta/0027_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -190,6 +190,13 @@
|
|||||||
"when": 1721979220929,
|
"when": 1721979220929,
|
||||||
"tag": "0026_known_dormammu",
|
"tag": "0026_known_dormammu",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 27,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1722445099203,
|
||||||
|
"tag": "0027_red_lady_bullseye",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -191,6 +191,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
await updateApplication(input.applicationId, {
|
await updateApplication(input.applicationId, {
|
||||||
buildType: input.buildType,
|
buildType: input.buildType,
|
||||||
dockerfile: input.dockerfile,
|
dockerfile: input.dockerfile,
|
||||||
|
publishDirectory: input.publishDirectory,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -157,6 +157,7 @@ export const applications = pgTable("application", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.default("idle"),
|
.default("idle"),
|
||||||
buildType: buildType("buildType").notNull().default("nixpacks"),
|
buildType: buildType("buildType").notNull().default("nixpacks"),
|
||||||
|
publishDirectory: text("publishDirectory"),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@ -316,6 +317,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
"paketo_buildpacks",
|
"paketo_buildpacks",
|
||||||
"nixpacks",
|
"nixpacks",
|
||||||
]),
|
]),
|
||||||
|
publishDirectory: z.string().optional(),
|
||||||
owner: z.string(),
|
owner: z.string(),
|
||||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||||
@ -353,7 +355,8 @@ export const apiSaveBuildType = createSchema
|
|||||||
buildType: true,
|
buildType: true,
|
||||||
dockerfile: true,
|
dockerfile: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required()
|
||||||
|
.merge(createSchema.pick({ publishDirectory: true }));
|
||||||
|
|
||||||
export const apiSaveGithubProvider = createSchema
|
export const apiSaveGithubProvider = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
|
@ -9,7 +9,7 @@ export const buildCustomDocker = async (
|
|||||||
application: ApplicationNested,
|
application: ApplicationNested,
|
||||||
writeStream: WriteStream,
|
writeStream: WriteStream,
|
||||||
) => {
|
) => {
|
||||||
const { appName, env, buildArgs } = application;
|
const { appName, env, publishDirectory, buildArgs } = application;
|
||||||
const dockerFilePath = getBuildAppDirectory(application);
|
const dockerFilePath = getBuildAppDirectory(application);
|
||||||
try {
|
try {
|
||||||
const image = `${appName}`;
|
const image = `${appName}`;
|
||||||
@ -24,7 +24,15 @@ export const buildCustomDocker = async (
|
|||||||
commandArgs.push("--build-arg", arg);
|
commandArgs.push("--build-arg", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
createEnvFile(dockerFilePath, env);
|
/*
|
||||||
|
Do not generate an environment file when publishDirectory is specified,
|
||||||
|
as it could be publicly exposed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!publishDirectory) {
|
||||||
|
createEnvFile(dockerFilePath, env);
|
||||||
|
}
|
||||||
|
|
||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"docker",
|
"docker",
|
||||||
commandArgs,
|
commandArgs,
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { buildStatic } from "@/server/utils/builders/static";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
import { prepareEnvironmentVariables } from "../docker/utils";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
|
|
||||||
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
|
|
||||||
export const buildNixpacks = async (
|
export const buildNixpacks = async (
|
||||||
application: ApplicationNested,
|
application: ApplicationNested,
|
||||||
writeStream: WriteStream,
|
writeStream: WriteStream,
|
||||||
) => {
|
) => {
|
||||||
const { env, appName } = application;
|
const { env, appName, publishDirectory } = application;
|
||||||
const buildAppDirectory = getBuildAppDirectory(application);
|
|
||||||
|
|
||||||
|
const buildAppDirectory = getBuildAppDirectory(application);
|
||||||
|
const buildContainerId = `${appName}-${nanoid(10)}`;
|
||||||
const envVariables = prepareEnvironmentVariables(env);
|
const envVariables = prepareEnvironmentVariables(env);
|
||||||
|
|
||||||
|
const writeToStream = (data: string) => {
|
||||||
|
if (writeStream.writable) {
|
||||||
|
writeStream.write(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const args = ["build", buildAppDirectory, "--name", appName];
|
const args = ["build", buildAppDirectory, "--name", appName];
|
||||||
|
|
||||||
@ -20,13 +30,44 @@ export const buildNixpacks = async (
|
|||||||
args.push("--env", env);
|
args.push("--env", env);
|
||||||
}
|
}
|
||||||
|
|
||||||
await spawnAsync("nixpacks", args, (data) => {
|
if (publishDirectory) {
|
||||||
if (writeStream.writable) {
|
/* No need for any start command, since we'll use nginx later on */
|
||||||
writeStream.write(data);
|
args.push("--no-error-without-start");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
await spawnAsync("nixpacks", args, writeToStream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Run the container with the image created by nixpacks,
|
||||||
|
and copy the artifacts on the host filesystem.
|
||||||
|
Then, remove the container and create a static build.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (publishDirectory) {
|
||||||
|
await spawnAsync(
|
||||||
|
"docker",
|
||||||
|
["create", "--name", buildContainerId, appName],
|
||||||
|
writeToStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
await spawnAsync(
|
||||||
|
"docker",
|
||||||
|
[
|
||||||
|
"cp",
|
||||||
|
`${buildContainerId}:/app/${publishDirectory}`,
|
||||||
|
path.join(buildAppDirectory, publishDirectory),
|
||||||
|
],
|
||||||
|
writeToStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||||
|
|
||||||
|
await buildStatic(application, writeStream);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
38
apps/dokploy/server/utils/builders/static.ts
Normal file
38
apps/dokploy/server/utils/builders/static.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { WriteStream } from "node:fs";
|
||||||
|
import { buildCustomDocker } from "@/server/utils/builders/docker-file";
|
||||||
|
import type { ApplicationNested } from ".";
|
||||||
|
import { createFile } from "../docker/utils";
|
||||||
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
|
|
||||||
|
export const buildStatic = async (
|
||||||
|
application: ApplicationNested,
|
||||||
|
writeStream: WriteStream,
|
||||||
|
) => {
|
||||||
|
const { publishDirectory } = application;
|
||||||
|
const buildAppDirectory = getBuildAppDirectory(application);
|
||||||
|
|
||||||
|
try {
|
||||||
|
createFile(
|
||||||
|
buildAppDirectory,
|
||||||
|
"Dockerfile",
|
||||||
|
[
|
||||||
|
"FROM nginx:alpine",
|
||||||
|
"WORKDIR /usr/share/nginx/html/",
|
||||||
|
`COPY ${publishDirectory || "."} .`,
|
||||||
|
].join("\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
await buildCustomDocker(
|
||||||
|
{
|
||||||
|
...application,
|
||||||
|
buildType: "dockerfile",
|
||||||
|
dockerfile: "Dockerfile",
|
||||||
|
},
|
||||||
|
writeStream,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user