mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-12 15:05:19 +00:00
feat(docker): Build-time Secrets
This commit is contained in:
@@ -12,6 +12,7 @@ import { api } from "@/utils/api";
|
|||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
env: z.string(),
|
env: z.string(),
|
||||||
buildArgs: z.string(),
|
buildArgs: z.string(),
|
||||||
|
buildSecrets: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
||||||
@@ -37,6 +38,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
env: "",
|
env: "",
|
||||||
buildArgs: "",
|
buildArgs: "",
|
||||||
|
buildSecrets: "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addEnvironmentSchema),
|
resolver: zodResolver(addEnvironmentSchema),
|
||||||
});
|
});
|
||||||
@@ -44,15 +46,18 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
// Watch form values
|
// Watch form values
|
||||||
const currentEnv = form.watch("env");
|
const currentEnv = form.watch("env");
|
||||||
const currentBuildArgs = form.watch("buildArgs");
|
const currentBuildArgs = form.watch("buildArgs");
|
||||||
|
const currentBuildSecrets = form.watch("buildSecrets");
|
||||||
const hasChanges =
|
const hasChanges =
|
||||||
currentEnv !== (data?.env || "") ||
|
currentEnv !== (data?.env || "") ||
|
||||||
currentBuildArgs !== (data?.buildArgs || "");
|
currentBuildArgs !== (data?.buildArgs || "") ||
|
||||||
|
currentBuildSecrets !== (data?.buildSecrets || "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
env: data.env || "",
|
env: data.env || "",
|
||||||
buildArgs: data.buildArgs || "",
|
buildArgs: data.buildArgs || "",
|
||||||
|
buildSecrets: data.buildSecrets || "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, form]);
|
}, [data, form]);
|
||||||
@@ -61,6 +66,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
mutateAsync({
|
mutateAsync({
|
||||||
env: formData.env,
|
env: formData.env,
|
||||||
buildArgs: formData.buildArgs,
|
buildArgs: formData.buildArgs,
|
||||||
|
buildSecrets: formData.buildSecrets,
|
||||||
applicationId,
|
applicationId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -76,6 +82,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
env: data?.env || "",
|
env: data?.env || "",
|
||||||
buildArgs: data?.buildArgs || "",
|
buildArgs: data?.buildArgs || "",
|
||||||
|
buildSecrets: data?.buildSecrets || "",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,13 +111,36 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
{data?.buildType === "dockerfile" && (
|
{data?.buildType === "dockerfile" && (
|
||||||
<Secrets
|
<Secrets
|
||||||
name="buildArgs"
|
name="buildArgs"
|
||||||
title="Build-time Variables"
|
title="Build-time Arguments"
|
||||||
description={
|
description={
|
||||||
<span>
|
<span>
|
||||||
Available only at build-time. See documentation
|
Arguments are available only at build-time. See
|
||||||
|
documentation
|
||||||
<a
|
<a
|
||||||
className="text-primary"
|
className="text-primary"
|
||||||
href="https://docs.docker.com/build/guide/build-args/"
|
href="https://docs.docker.com/build/building/variables/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placeholder="NPM_TOKEN=xyz"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{data?.buildType === "dockerfile" && (
|
||||||
|
<Secrets
|
||||||
|
name="buildSecrets"
|
||||||
|
title="Build-time Secrets"
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
Secrets are specially designed for sensitive information and
|
||||||
|
are only available at build-time. See documentation
|
||||||
|
<a
|
||||||
|
className="text-primary"
|
||||||
|
href="https://docs.docker.com/build/building/secrets/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
+30
-3
@@ -46,6 +46,7 @@ const schema = z
|
|||||||
.object({
|
.object({
|
||||||
env: z.string(),
|
env: z.string(),
|
||||||
buildArgs: z.string(),
|
buildArgs: z.string(),
|
||||||
|
buildSecrets: z.string(),
|
||||||
wildcardDomain: z.string(),
|
wildcardDomain: z.string(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
previewLimit: z.number(),
|
previewLimit: z.number(),
|
||||||
@@ -109,6 +110,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
form.reset({
|
form.reset({
|
||||||
env: data.previewEnv || "",
|
env: data.previewEnv || "",
|
||||||
buildArgs: data.previewBuildArgs || "",
|
buildArgs: data.previewBuildArgs || "",
|
||||||
|
buildSecrets: data.previewBuildSecrets || "",
|
||||||
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
wildcardDomain: data.previewWildcard || "*.traefik.me",
|
||||||
port: data.previewPort || 3000,
|
port: data.previewPort || 3000,
|
||||||
previewLabels: data.previewLabels || [],
|
previewLabels: data.previewLabels || [],
|
||||||
@@ -127,6 +129,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
updateApplication({
|
updateApplication({
|
||||||
previewEnv: formData.env,
|
previewEnv: formData.env,
|
||||||
previewBuildArgs: formData.buildArgs,
|
previewBuildArgs: formData.buildArgs,
|
||||||
|
previewBuildSecrets: formData.buildSecrets,
|
||||||
previewWildcard: formData.wildcardDomain,
|
previewWildcard: formData.wildcardDomain,
|
||||||
previewPort: formData.port,
|
previewPort: formData.port,
|
||||||
previewLabels: formData.previewLabels,
|
previewLabels: formData.previewLabels,
|
||||||
@@ -467,13 +470,37 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
{data?.buildType === "dockerfile" && (
|
{data?.buildType === "dockerfile" && (
|
||||||
<Secrets
|
<Secrets
|
||||||
name="buildArgs"
|
name="buildArgs"
|
||||||
title="Build-time Variables"
|
title="Build-time Arguments"
|
||||||
description={
|
description={
|
||||||
<span>
|
<span>
|
||||||
Available only at build-time. See documentation
|
Arguments are available only at build-time. See
|
||||||
|
documentation
|
||||||
<a
|
<a
|
||||||
className="text-primary"
|
className="text-primary"
|
||||||
href="https://docs.docker.com/build/guide/build-args/"
|
href="https://docs.docker.com/build/building/variables/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
placeholder="NPM_TOKEN=xyz"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{data?.buildType === "dockerfile" && (
|
||||||
|
<Secrets
|
||||||
|
name="buildSecrets"
|
||||||
|
title="Build-time Secrets"
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
Secrets are specially designed for sensitive information
|
||||||
|
and are only available at build-time. See
|
||||||
|
documentation
|
||||||
|
<a
|
||||||
|
className="text-primary"
|
||||||
|
href="https://docs.docker.com/build/building/secrets/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "application" ADD COLUMN "previewBuildSecrets" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "application" ADD COLUMN "buildSecrets" text;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -799,6 +799,13 @@
|
|||||||
"when": 1758960816504,
|
"when": 1758960816504,
|
||||||
"tag": "0113_complete_rafael_vega",
|
"tag": "0113_complete_rafael_vega",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 114,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1759360386227,
|
||||||
|
"tag": "0114_left_smasher",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -360,6 +360,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
await updateApplication(input.applicationId, {
|
await updateApplication(input.applicationId, {
|
||||||
env: input.env,
|
env: input.env,
|
||||||
buildArgs: input.buildArgs,
|
buildArgs: input.buildArgs,
|
||||||
|
buildSecrets: input.buildSecrets,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const applications = pgTable("application", {
|
|||||||
previewEnv: text("previewEnv"),
|
previewEnv: text("previewEnv"),
|
||||||
watchPaths: text("watchPaths").array(),
|
watchPaths: text("watchPaths").array(),
|
||||||
previewBuildArgs: text("previewBuildArgs"),
|
previewBuildArgs: text("previewBuildArgs"),
|
||||||
|
previewBuildSecrets: text("previewBuildSecrets"),
|
||||||
previewLabels: text("previewLabels").array(),
|
previewLabels: text("previewLabels").array(),
|
||||||
previewWildcard: text("previewWildcard"),
|
previewWildcard: text("previewWildcard"),
|
||||||
previewPort: integer("previewPort").default(3000),
|
previewPort: integer("previewPort").default(3000),
|
||||||
@@ -99,6 +100,7 @@ export const applications = pgTable("application", {
|
|||||||
).default(true),
|
).default(true),
|
||||||
rollbackActive: boolean("rollbackActive").default(false),
|
rollbackActive: boolean("rollbackActive").default(false),
|
||||||
buildArgs: text("buildArgs"),
|
buildArgs: text("buildArgs"),
|
||||||
|
buildSecrets: text("buildSecrets"),
|
||||||
memoryReservation: text("memoryReservation"),
|
memoryReservation: text("memoryReservation"),
|
||||||
memoryLimit: text("memoryLimit"),
|
memoryLimit: text("memoryLimit"),
|
||||||
cpuReservation: text("cpuReservation"),
|
cpuReservation: text("cpuReservation"),
|
||||||
@@ -252,6 +254,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
autoDeploy: z.boolean(),
|
autoDeploy: z.boolean(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
buildArgs: z.string().optional(),
|
buildArgs: z.string().optional(),
|
||||||
|
buildSecrets: z.string().optional(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
memoryReservation: z.string().optional(),
|
memoryReservation: z.string().optional(),
|
||||||
@@ -303,6 +306,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
previewPort: z.number().optional(),
|
previewPort: z.number().optional(),
|
||||||
previewEnv: z.string().optional(),
|
previewEnv: z.string().optional(),
|
||||||
previewBuildArgs: z.string().optional(),
|
previewBuildArgs: z.string().optional(),
|
||||||
|
previewBuildSecrets: z.string().optional(),
|
||||||
previewWildcard: z.string().optional(),
|
previewWildcard: z.string().optional(),
|
||||||
previewLimit: z.number().optional(),
|
previewLimit: z.number().optional(),
|
||||||
previewHttps: z.boolean().optional(),
|
previewHttps: z.boolean().optional(),
|
||||||
@@ -456,6 +460,7 @@ export const apiSaveEnvironmentVariables = createSchema
|
|||||||
applicationId: true,
|
applicationId: true,
|
||||||
env: true,
|
env: true,
|
||||||
buildArgs: true,
|
buildArgs: true,
|
||||||
|
buildSecrets: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ export const deployPreviewApplication = async ({
|
|||||||
application.appName = previewDeployment.appName;
|
application.appName = previewDeployment.appName;
|
||||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||||
application.buildArgs = application.previewBuildArgs;
|
application.buildArgs = application.previewBuildArgs;
|
||||||
|
application.buildSecrets = application.previewBuildSecrets;
|
||||||
|
|
||||||
if (application.sourceType === "github") {
|
if (application.sourceType === "github") {
|
||||||
await cloneGithubRepository({
|
await cloneGithubRepository({
|
||||||
@@ -580,6 +581,7 @@ export const deployRemotePreviewApplication = async ({
|
|||||||
application.appName = previewDeployment.appName;
|
application.appName = previewDeployment.appName;
|
||||||
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
|
||||||
application.buildArgs = application.previewBuildArgs;
|
application.buildArgs = application.previewBuildArgs;
|
||||||
|
application.buildSecrets = application.previewBuildSecrets;
|
||||||
|
|
||||||
if (application.serverId) {
|
if (application.serverId) {
|
||||||
let command = "set -e;";
|
let command = "set -e;";
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import { prepareEnvironmentVariables } from "@dokploy/server/utils/docker/utils";
|
import {
|
||||||
|
getEnviromentVariablesObject,
|
||||||
|
prepareEnvironmentVariables,
|
||||||
|
} from "@dokploy/server/utils/docker/utils";
|
||||||
import {
|
import {
|
||||||
getBuildAppDirectory,
|
getBuildAppDirectory,
|
||||||
getDockerContextPath,
|
getDockerContextPath,
|
||||||
@@ -17,6 +20,7 @@ export const buildCustomDocker = async (
|
|||||||
env,
|
env,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
buildArgs,
|
buildArgs,
|
||||||
|
buildSecrets,
|
||||||
dockerBuildStage,
|
dockerBuildStage,
|
||||||
cleanCache,
|
cleanCache,
|
||||||
} = application;
|
} = application;
|
||||||
@@ -26,11 +30,6 @@ export const buildCustomDocker = async (
|
|||||||
|
|
||||||
const defaultContextPath =
|
const defaultContextPath =
|
||||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||||
const args = prepareEnvironmentVariables(
|
|
||||||
buildArgs,
|
|
||||||
application.environment.project.env,
|
|
||||||
application.environment.env,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dockerContextPath = getDockerContextPath(application);
|
const dockerContextPath = getDockerContextPath(application);
|
||||||
|
|
||||||
@@ -44,9 +43,29 @@ export const buildCustomDocker = async (
|
|||||||
commandArgs.push("--target", dockerBuildStage);
|
commandArgs.push("--target", dockerBuildStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args = prepareEnvironmentVariables(
|
||||||
|
buildArgs,
|
||||||
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
|
);
|
||||||
|
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
commandArgs.push("--build-arg", arg);
|
commandArgs.push("--build-arg", arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secrets = getEnviromentVariablesObject(
|
||||||
|
buildSecrets,
|
||||||
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const key in secrets) {
|
||||||
|
// Although buildx is smart enough to know we may be referring to an environment variable name,
|
||||||
|
// we still make sure it doesn't fall back to type=file.
|
||||||
|
// See: https://docs.docker.com/reference/cli/docker/buildx/build/#secret
|
||||||
|
commandArgs.push("--secret", `type=env,id=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Do not generate an environment file when publishDirectory is specified,
|
Do not generate an environment file when publishDirectory is specified,
|
||||||
as it could be publicly exposed.
|
as it could be publicly exposed.
|
||||||
@@ -70,6 +89,10 @@ export const buildCustomDocker = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
cwd: dockerContextPath || defaultContextPath,
|
cwd: dockerContextPath || defaultContextPath,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...secrets,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -86,6 +109,7 @@ export const getDockerCommand = (
|
|||||||
env,
|
env,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
buildArgs,
|
buildArgs,
|
||||||
|
buildSecrets,
|
||||||
dockerBuildStage,
|
dockerBuildStage,
|
||||||
cleanCache,
|
cleanCache,
|
||||||
} = application;
|
} = application;
|
||||||
@@ -96,11 +120,6 @@ export const getDockerCommand = (
|
|||||||
|
|
||||||
const defaultContextPath =
|
const defaultContextPath =
|
||||||
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
|
||||||
const args = prepareEnvironmentVariables(
|
|
||||||
buildArgs,
|
|
||||||
application.environment.project.env,
|
|
||||||
application.environment.env,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dockerContextPath =
|
const dockerContextPath =
|
||||||
getDockerContextPath(application) || defaultContextPath;
|
getDockerContextPath(application) || defaultContextPath;
|
||||||
@@ -115,10 +134,33 @@ export const getDockerCommand = (
|
|||||||
commandArgs.push("--no-cache");
|
commandArgs.push("--no-cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const args = prepareEnvironmentVariables(
|
||||||
|
buildArgs,
|
||||||
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
|
);
|
||||||
|
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
commandArgs.push("--build-arg", `'${arg}'`);
|
commandArgs.push("--build-arg", `'${arg}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secrets = getEnviromentVariablesObject(
|
||||||
|
buildSecrets,
|
||||||
|
application.environment.project.env,
|
||||||
|
application.environment.env,
|
||||||
|
);
|
||||||
|
|
||||||
|
const joinedSecrets = Object.entries(secrets)
|
||||||
|
.map(([key, value]) => `${key}='${value}'`)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
for (const key in secrets) {
|
||||||
|
// Although buildx is smart enough to know we may be referring to an environment variable name,
|
||||||
|
// we still make sure it doesn't fall back to `type=file`.
|
||||||
|
// See: https://docs.docker.com/reference/cli/docker/buildx/build/#secret
|
||||||
|
commandArgs.push("--secret", `type=env,id=${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Do not generate an environment file when publishDirectory is specified,
|
Do not generate an environment file when publishDirectory is specified,
|
||||||
as it could be publicly exposed.
|
as it could be publicly exposed.
|
||||||
@@ -140,7 +182,7 @@ cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || {
|
|||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
|
${joinedSecrets} docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
|
||||||
echo "❌ Docker build failed" >> ${logPath};
|
echo "❌ Docker build failed" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user