+
+ {
+ await changePassword({
+ redisId,
+ password: newPassword,
+ });
+ toast.success("Password updated successfully");
+ utils.redis.one.invalidate({ redisId });
+ }}
+ />
diff --git a/apps/dokploy/components/shared/update-database-password.tsx b/apps/dokploy/components/shared/update-database-password.tsx
new file mode 100644
index 000000000..bc58c2f43
--- /dev/null
+++ b/apps/dokploy/components/shared/update-database-password.tsx
@@ -0,0 +1,126 @@
+import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
+import { PenBox } from "lucide-react";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+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,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+
+const updatePasswordSchema = z.object({
+ password: z.string().min(1, "Password is required"),
+});
+
+type UpdatePassword = z.infer
;
+
+interface Props {
+ label?: string;
+ onUpdatePassword: (newPassword: string) => Promise;
+}
+
+export const UpdateDatabasePassword = ({
+ label = "Password",
+ onUpdatePassword,
+}: Props) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [error, setError] = useState(null);
+ const [isPending, setIsPending] = useState(false);
+
+ const form = useForm({
+ defaultValues: { password: "" },
+ resolver: zodResolver(updatePasswordSchema),
+ });
+
+ const onSubmit = async (formData: UpdatePassword) => {
+ setIsPending(true);
+ setError(null);
+ try {
+ await onUpdatePassword(formData.password);
+ form.reset();
+ setIsOpen(false);
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Error updating password");
+ } finally {
+ setIsPending(false);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index bb739a43b..07dcb8c36 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -3,10 +3,13 @@ import {
createMariadb,
createMount,
deployMariadb,
+ execAsync,
+ execAsyncRemote,
findBackupsByDbId,
findEnvironmentById,
findMariadbById,
findProjectById,
+ getServiceContainerCommand,
IS_CLOUD,
rebuildDatabase,
removeMariadbById,
@@ -366,6 +369,56 @@ export const mariadbRouter = createTRPCRouter({
resourceId: mariadbId,
resourceName: service.appName,
});
+ return true;
+ }),
+ changePassword: protectedProcedure
+ .input(
+ z.object({
+ mariadbId: z.string().min(1),
+ password: z.string().min(1),
+ type: z.enum(["user", "root"]).default("user"),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { mariadbId, password, type } = input;
+ await checkServicePermissionAndAccess(ctx, mariadbId, {
+ service: ["create"],
+ });
+
+ const maria = await findMariadbById(mariadbId);
+ const { appName, serverId, databaseUser, databaseRootPassword } = maria;
+
+ const containerCmd = getServiceContainerCommand(appName);
+ const targetUser = type === "root" ? "root" : databaseUser;
+
+ const command = `
+ CONTAINER_ID=$(${containerCmd})
+ if [ -z "$CONTAINER_ID" ]; then
+ echo "No running container found for ${appName}" >&2
+ exit 1
+ fi
+ docker exec "$CONTAINER_ID" mariadb -u root -p'${databaseRootPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;"
+ `;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command, { shell: "/bin/bash" });
+ }
+
+ if (type === "root") {
+ await updateMariadbById(mariadbId, { databaseRootPassword: password });
+ } else {
+ await updateMariadbById(mariadbId, { databasePassword: password });
+ }
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "service",
+ resourceId: mariadbId,
+ resourceName: appName,
+ });
+
return true;
}),
move: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index de9e1f36f..abf1c2edf 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -3,10 +3,13 @@ import {
createMongo,
createMount,
deployMongo,
+ execAsync,
+ execAsyncRemote,
findBackupsByDbId,
findEnvironmentById,
findMongoById,
findProjectById,
+ getServiceContainerCommand,
IS_CLOUD,
rebuildDatabase,
removeMongoById,
@@ -388,6 +391,49 @@ export const mongoRouter = createTRPCRouter({
resourceId: mongoId,
resourceName: service.appName,
});
+ return true;
+ }),
+ changePassword: protectedProcedure
+ .input(
+ z.object({
+ mongoId: z.string().min(1),
+ password: z.string().min(1),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { mongoId, password } = input;
+ await checkServicePermissionAndAccess(ctx, mongoId, {
+ service: ["create"],
+ });
+
+ const mongo = await findMongoById(mongoId);
+ const { appName, serverId, databaseUser, databasePassword } = mongo;
+
+ const containerCmd = getServiceContainerCommand(appName);
+ const command = `
+ CONTAINER_ID=$(${containerCmd})
+ if [ -z "$CONTAINER_ID" ]; then
+ echo "No running container found for ${appName}" >&2
+ exit 1
+ fi
+ docker exec "$CONTAINER_ID" mongosh -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase admin --eval "db.getSiblingDB('admin').changeUserPassword('${databaseUser}', '${password}')"
+ `;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command, { shell: "/bin/bash" });
+ }
+
+ await updateMongoById(mongoId, { databasePassword: password });
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "service",
+ resourceId: mongoId,
+ resourceName: appName,
+ });
+
return true;
}),
move: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index b834f52c2..028ca6d6e 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -3,10 +3,13 @@ import {
createMount,
createMysql,
deployMySql,
+ execAsync,
+ execAsyncRemote,
findBackupsByDbId,
findEnvironmentById,
findMySqlById,
findProjectById,
+ getServiceContainerCommand,
IS_CLOUD,
rebuildDatabase,
removeMySqlById,
@@ -385,6 +388,58 @@ export const mysqlRouter = createTRPCRouter({
resourceId: mysqlId,
resourceName: service.appName,
});
+ return true;
+ }),
+ changePassword: protectedProcedure
+ .input(
+ z.object({
+ mysqlId: z.string().min(1),
+ password: z.string().min(1),
+ type: z.enum(["user", "root"]).default("user"),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { mysqlId, password, type } = input;
+ await checkServicePermissionAndAccess(ctx, mysqlId, {
+ service: ["create"],
+ });
+
+ const my = await findMySqlById(mysqlId);
+ const { appName, serverId, databaseUser, databaseRootPassword } = my;
+
+ const containerCmd = getServiceContainerCommand(appName);
+ const authPassword =
+ type === "root" ? databaseRootPassword : databaseRootPassword;
+ const targetUser = type === "root" ? "root" : databaseUser;
+
+ const command = `
+ CONTAINER_ID=$(${containerCmd})
+ if [ -z "$CONTAINER_ID" ]; then
+ echo "No running container found for ${appName}" >&2
+ exit 1
+ fi
+ docker exec "$CONTAINER_ID" mysql -u root -p'${authPassword}' -e "ALTER USER '${targetUser}'@'%' IDENTIFIED BY '${password}'; FLUSH PRIVILEGES;"
+ `;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command, { shell: "/bin/bash" });
+ }
+
+ if (type === "root") {
+ await updateMySqlById(mysqlId, { databaseRootPassword: password });
+ } else {
+ await updateMySqlById(mysqlId, { databasePassword: password });
+ }
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "service",
+ resourceId: mysqlId,
+ resourceName: appName,
+ });
+
return true;
}),
move: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index 78e0e1284..72485c882 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -3,11 +3,14 @@ import {
createMount,
createPostgres,
deployPostgres,
+ execAsync,
+ execAsyncRemote,
findBackupsByDbId,
findEnvironmentById,
findPostgresById,
findProjectById,
getMountPath,
+ getServiceContainerCommand,
IS_CLOUD,
rebuildDatabase,
removePostgresById,
@@ -394,6 +397,49 @@ export const postgresRouter = createTRPCRouter({
resourceId: postgresId,
resourceName: service.appName,
});
+ return true;
+ }),
+ changePassword: protectedProcedure
+ .input(
+ z.object({
+ postgresId: z.string().min(1),
+ password: z.string().min(1),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { postgresId, password } = input;
+ await checkServicePermissionAndAccess(ctx, postgresId, {
+ service: ["create"],
+ });
+
+ const pg = await findPostgresById(postgresId);
+ const { appName, serverId, databaseUser } = pg;
+
+ const containerCmd = getServiceContainerCommand(appName);
+ const command = `
+ CONTAINER_ID=$(${containerCmd})
+ if [ -z "$CONTAINER_ID" ]; then
+ echo "No running container found for ${appName}" >&2
+ exit 1
+ fi
+ docker exec "$CONTAINER_ID" psql -U ${databaseUser} -c "ALTER USER \\"${databaseUser}\\" WITH PASSWORD '${password}';"
+ `;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command, { shell: "/bin/bash" });
+ }
+
+ await updatePostgresById(postgresId, { databasePassword: password });
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "service",
+ resourceId: postgresId,
+ resourceName: appName,
+ });
+
return true;
}),
move: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts
index efc98bf77..75afe209a 100644
--- a/apps/dokploy/server/api/routers/redis.ts
+++ b/apps/dokploy/server/api/routers/redis.ts
@@ -3,9 +3,12 @@ import {
createMount,
createRedis,
deployRedis,
+ execAsync,
+ execAsyncRemote,
findEnvironmentById,
findProjectById,
findRedisById,
+ getServiceContainerCommand,
IS_CLOUD,
rebuildDatabase,
removeRedisById,
@@ -375,6 +378,49 @@ export const redisRouter = createTRPCRouter({
resourceId: redisId,
resourceName: redis.appName,
});
+ return true;
+ }),
+ changePassword: protectedProcedure
+ .input(
+ z.object({
+ redisId: z.string().min(1),
+ password: z.string().min(1),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const { redisId, password } = input;
+ await checkServicePermissionAndAccess(ctx, redisId, {
+ service: ["create"],
+ });
+
+ const rd = await findRedisById(redisId);
+ const { appName, serverId, databasePassword } = rd;
+
+ const containerCmd = getServiceContainerCommand(appName);
+ const command = `
+ CONTAINER_ID=$(${containerCmd})
+ if [ -z "$CONTAINER_ID" ]; then
+ echo "No running container found for ${appName}" >&2
+ exit 1
+ fi
+ docker exec "$CONTAINER_ID" redis-cli -a '${databasePassword}' CONFIG SET requirepass '${password}'
+ `;
+
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command, { shell: "/bin/bash" });
+ }
+
+ await updateRedisById(redisId, { databasePassword: password });
+
+ await audit(ctx, {
+ action: "update",
+ resourceType: "service",
+ resourceId: redisId,
+ resourceName: appName,
+ });
+
return true;
}),
move: protectedProcedure