mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-18 03:02:56 +00:00
Merge pull request #5008 from Dokploy/fix/compose-create-env-file-toggle
fix: add createEnvFile toggle to Compose, mirroring Application
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
import { getBuildComposeCommand } from "@dokploy/server/utils/builders/compose";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
// Compose now has a `createEnvFile` toggle (default true), mirroring the
|
||||||
|
// Application builder's flag: when disabled, Dokploy never writes `.env`,
|
||||||
|
// so a repo-tracked file survives untouched.
|
||||||
|
vi.mock("@dokploy/server/utils/docker/domain", () => ({
|
||||||
|
writeDomainsToCompose: vi.fn().mockResolvedValue(""),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const baseCompose = {
|
||||||
|
appName: "env-file-toggle",
|
||||||
|
sourceType: "raw",
|
||||||
|
command: "",
|
||||||
|
composePath: "docker-compose.yml",
|
||||||
|
composeType: "docker-compose",
|
||||||
|
isolatedDeployment: false,
|
||||||
|
randomize: false,
|
||||||
|
suffix: "",
|
||||||
|
serverId: null,
|
||||||
|
env: "FOO=bar",
|
||||||
|
mounts: [],
|
||||||
|
domains: [],
|
||||||
|
environment: { project: { env: "" }, env: "" },
|
||||||
|
} as unknown as Parameters<typeof getBuildComposeCommand>[0];
|
||||||
|
|
||||||
|
describe("getBuildComposeCommand createEnvFile toggle", () => {
|
||||||
|
it("createEnvFile: false never writes the .env file", async () => {
|
||||||
|
const command = await getBuildComposeCommand({
|
||||||
|
...baseCompose,
|
||||||
|
createEnvFile: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(command).not.toContain("base64 -d >");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("createEnvFile: true (default) writes Dokploy's vars", async () => {
|
||||||
|
const command = await getBuildComposeCommand({
|
||||||
|
...baseCompose,
|
||||||
|
createEnvFile: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(command).toContain("base64 -d >");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,16 +16,20 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import type { ServiceType } from "../advanced/show-resources";
|
import type { ServiceType } from "../advanced/show-resources";
|
||||||
|
|
||||||
const addEnvironmentSchema = z.object({
|
const addEnvironmentSchema = z.object({
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
|
createEnvFile: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
type EnvironmentSchema = z.infer<typeof addEnvironmentSchema>;
|
||||||
@@ -71,18 +75,32 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
const form = useForm<EnvironmentSchema>({
|
const form = useForm<EnvironmentSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
environment: "",
|
environment: "",
|
||||||
|
createEnvFile: true,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addEnvironmentSchema),
|
resolver: zodResolver(addEnvironmentSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch form value
|
// Watch form value
|
||||||
const currentEnvironment = form.watch("environment");
|
const currentEnvironment = form.watch("environment");
|
||||||
const hasChanges = currentEnvironment !== (data?.env || "");
|
const currentCreateEnvFile = form.watch("createEnvFile");
|
||||||
|
const composeData =
|
||||||
|
type === "compose"
|
||||||
|
? (data as { createEnvFile?: boolean; sourceType?: string } | undefined)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const showCreateEnvFileToggle =
|
||||||
|
type === "compose" &&
|
||||||
|
(composeData?.sourceType !== "raw" || composeData?.createEnvFile === false);
|
||||||
|
const hasChanges =
|
||||||
|
currentEnvironment !== (data?.env || "") ||
|
||||||
|
(showCreateEnvFileToggle &&
|
||||||
|
currentCreateEnvFile !== (composeData?.createEnvFile ?? true));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
environment: data.env || "",
|
environment: data.env || "",
|
||||||
|
createEnvFile: composeData?.createEnvFile ?? true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, form]);
|
}, [data, form]);
|
||||||
@@ -97,6 +115,9 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
postgresId: id || "",
|
postgresId: id || "",
|
||||||
redisId: id || "",
|
redisId: id || "",
|
||||||
env: formData.environment,
|
env: formData.environment,
|
||||||
|
...(type === "compose" && {
|
||||||
|
createEnvFile: formData.createEnvFile,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Environments Added");
|
toast.success("Environments Added");
|
||||||
@@ -110,6 +131,7 @@ export const ShowEnvironment = ({ id, type }: Props) => {
|
|||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
form.reset({
|
form.reset({
|
||||||
environment: data?.env || "",
|
environment: data?.env || "",
|
||||||
|
createEnvFile: composeData?.createEnvFile ?? true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -190,6 +212,34 @@ PORT=3000
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{showCreateEnvFileToggle && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="createEnvFile"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 border rounded-lg shadow-xs">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Create Environment File</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
When enabled, an .env file will be created in the same
|
||||||
|
directory as your compose file on every deploy.
|
||||||
|
Disable this to keep a repository-provided .env; the
|
||||||
|
variables above will then be ignored. Takes effect on
|
||||||
|
the next deploy.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
disabled={!canWrite}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{canWrite && (
|
{canWrite && (
|
||||||
<div className="flex flex-row justify-end gap-2">
|
<div className="flex flex-row justify-end gap-2">
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "compose" ADD COLUMN "createEnvFile" boolean DEFAULT true NOT NULL;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1261,6 +1261,13 @@
|
|||||||
"when": 1785800152143,
|
"when": 1785800152143,
|
||||||
"tag": "0179_foamy_marauders",
|
"tag": "0179_foamy_marauders",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 180,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1786132623336,
|
||||||
|
"tag": "0180_slippery_norrin_radd",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -212,6 +212,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
const updated = await updateCompose(input.composeId, {
|
const updated = await updateCompose(input.composeId, {
|
||||||
env: input.env,
|
env: input.env,
|
||||||
|
createEnvFile: input.createEnvFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ export const isDockerDesktop = async () => {
|
|||||||
/** Returns the Docker host IP address. */
|
/** Returns the Docker host IP address. */
|
||||||
export const getDockerHost = async (): Promise<string> => {
|
export const getDockerHost = async (): Promise<string> => {
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
const isLinux = process.platform === "linux"
|
const isLinux =
|
||||||
? (await Promise.all([isWSL(), isDockerDesktop()])).every((v) => !v)
|
process.platform === "linux"
|
||||||
: false;
|
? (await Promise.all([isWSL(), isDockerDesktop()])).every((v) => !v)
|
||||||
|
: false;
|
||||||
|
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export const compose = pgTable("compose", {
|
|||||||
),
|
),
|
||||||
command: text("command").notNull().default(""),
|
command: text("command").notNull().default(""),
|
||||||
//
|
//
|
||||||
|
createEnvFile: boolean("createEnvFile").notNull().default(true),
|
||||||
enableSubmodules: boolean("enableSubmodules").notNull().default(false),
|
enableSubmodules: boolean("enableSubmodules").notNull().default(false),
|
||||||
composePath: text("composePath").notNull().default("./docker-compose.yml"),
|
composePath: text("composePath").notNull().default("./docker-compose.yml"),
|
||||||
suffix: text("suffix").notNull().default(""),
|
suffix: text("suffix").notNull().default(""),
|
||||||
@@ -183,6 +184,7 @@ const createSchema = createInsertSchema(compose, {
|
|||||||
environmentId: z.string(),
|
environmentId: z.string(),
|
||||||
customGitSSHKeyId: z.string().optional(),
|
customGitSSHKeyId: z.string().optional(),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
|
createEnvFile: z.boolean().optional(),
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
||||||
watchPaths: z.array(z.string()).optional(),
|
watchPaths: z.array(z.string()).optional(),
|
||||||
@@ -266,7 +268,10 @@ export const apiSaveEnvironmentVariablesCompose = createSchema
|
|||||||
composeId: true,
|
composeId: true,
|
||||||
env: true,
|
env: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required()
|
||||||
|
.extend({
|
||||||
|
createEnvFile: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const apiRandomizeCompose = createSchema
|
export const apiRandomizeCompose = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ export const getBuildComposeCommand = async (compose: ComposeNested) => {
|
|||||||
const { COMPOSE_PATH } = paths(!!compose.serverId);
|
const { COMPOSE_PATH } = paths(!!compose.serverId);
|
||||||
const { sourceType, appName, mounts, composeType, domains } = compose;
|
const { sourceType, appName, mounts, composeType, domains } = compose;
|
||||||
const command = createCommand(compose);
|
const command = createCommand(compose);
|
||||||
const envCommand = getCreateEnvFileCommand(compose);
|
const envCommand = compose.createEnvFile
|
||||||
|
? getCreateEnvFileCommand(compose)
|
||||||
|
: "";
|
||||||
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
|
||||||
const exportEnvCommand = getExportEnvCommand(compose);
|
const exportEnvCommand = getExportEnvCommand(compose);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user