From 922b4d58f18994a65641641b2abaa69c50e35383 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 7 Mar 2026 23:32:41 -0600 Subject: [PATCH 1/2] refactor: enhance backup functionality by incorporating appName and serviceName for S3 bucket paths --- packages/server/src/utils/backups/compose.ts | 7 ++++--- packages/server/src/utils/backups/index.ts | 17 ++++++++++++++- packages/server/src/utils/backups/mariadb.ts | 4 ++-- packages/server/src/utils/backups/mongo.ts | 4 ++-- packages/server/src/utils/backups/mysql.ts | 4 ++-- packages/server/src/utils/backups/postgres.ts | 4 ++-- .../server/src/utils/volume-backups/backup.ts | 21 ++++++++++++++++++- .../server/src/utils/volume-backups/utils.ts | 5 +++-- 8 files changed, 51 insertions(+), 15 deletions(-) diff --git a/packages/server/src/utils/backups/compose.ts b/packages/server/src/utils/backups/compose.ts index 28124f809..34f6d2a9b 100644 --- a/packages/server/src/utils/backups/compose.ts +++ b/packages/server/src/utils/backups/compose.ts @@ -14,13 +14,14 @@ export const runComposeBackup = async ( compose: Compose, backup: BackupSchedule, ) => { - const { environmentId, name } = compose; + const { environmentId, name, appName } = compose; const environment = await findEnvironmentById(environmentId); const project = await findProjectById(environment.projectId); - const { prefix, databaseType } = backup; + const { prefix, databaseType, serviceName } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = `${backup.appName}/${normalizeS3Path(prefix)}${backupFileName}`; + const s3AppName = serviceName ? `${appName}_${serviceName}` : appName; + const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, title: "Compose Backup", diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts index c747c8656..71eeda7ea 100644 --- a/packages/server/src/utils/backups/index.ts +++ b/packages/server/src/utils/backups/index.ts @@ -106,6 +106,20 @@ export const initCronJobs = async () => { } }; +const getServiceAppName = (backup: BackupSchedule): string => { + if (backup.compose?.appName) { + return backup.serviceName + ? `${backup.compose.appName}_${backup.serviceName}` + : backup.compose.appName; + } + const serviceAppName = + backup.postgres?.appName || + backup.mysql?.appName || + backup.mariadb?.appName || + backup.mongo?.appName; + return serviceAppName || backup.appName; +}; + export const keepLatestNBackups = async ( backup: BackupSchedule, serverId?: string | null, @@ -116,7 +130,8 @@ export const keepLatestNBackups = async ( try { const rcloneFlags = getS3Credentials(backup.destination); - const backupFilesPath = `:s3:${backup.destination.bucket}/${backup.appName}/${normalizeS3Path(backup.prefix)}`; + const appName = getServiceAppName(backup); + const backupFilesPath = `:s3:${backup.destination.bucket}/${appName}/${normalizeS3Path(backup.prefix)}`; // --include "*.sql.gz" or "*.zip" ensures nothing else other than the dokploy backup files are touched by rclone const rcloneList = `rclone lsf ${rcloneFlags.join(" ")} --include "*${backup.databaseType === "web-server" ? ".zip" : ".sql.gz"}" ${backupFilesPath}`; diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts index 292b08cc8..089b3cb04 100644 --- a/packages/server/src/utils/backups/mariadb.ts +++ b/packages/server/src/utils/backups/mariadb.ts @@ -14,13 +14,13 @@ export const runMariadbBackup = async ( mariadb: Mariadb, backup: BackupSchedule, ) => { - const { environmentId, name } = mariadb; + const { environmentId, name, appName } = mariadb; const environment = await findEnvironmentById(environmentId); const project = await findProjectById(environment.projectId); const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = `${backup.appName}/${normalizeS3Path(prefix)}${backupFileName}`; + const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, title: "MariaDB Backup", diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts index 10a434560..d1b04e68b 100644 --- a/packages/server/src/utils/backups/mongo.ts +++ b/packages/server/src/utils/backups/mongo.ts @@ -11,13 +11,13 @@ import { execAsync, execAsyncRemote } from "../process/execAsync"; import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { - const { environmentId, name } = mongo; + const { environmentId, name, appName } = mongo; const environment = await findEnvironmentById(environmentId); const project = await findProjectById(environment.projectId); const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = `${backup.appName}/${normalizeS3Path(prefix)}${backupFileName}`; + const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, title: "MongoDB Backup", diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts index 26c3105a4..461a17bf9 100644 --- a/packages/server/src/utils/backups/mysql.ts +++ b/packages/server/src/utils/backups/mysql.ts @@ -11,13 +11,13 @@ import { execAsync, execAsyncRemote } from "../process/execAsync"; import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils"; export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { - const { environmentId, name } = mysql; + const { environmentId, name, appName } = mysql; const environment = await findEnvironmentById(environmentId); const project = await findProjectById(environment.projectId); const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = `${backup.appName}/${normalizeS3Path(prefix)}${backupFileName}`; + const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; const deployment = await createDeploymentBackup({ backupId: backup.backupId, title: "MySQL Backup", diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts index de493f0bd..3371b0cf9 100644 --- a/packages/server/src/utils/backups/postgres.ts +++ b/packages/server/src/utils/backups/postgres.ts @@ -14,7 +14,7 @@ export const runPostgresBackup = async ( postgres: Postgres, backup: BackupSchedule, ) => { - const { name, environmentId } = postgres; + const { name, environmentId, appName } = postgres; const environment = await findEnvironmentById(environmentId); const project = await findProjectById(environment.projectId); @@ -26,7 +26,7 @@ export const runPostgresBackup = async ( const { prefix } = backup; const destination = backup.destination; const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = `${backup.appName}/${normalizeS3Path(prefix)}${backupFileName}`; + const bucketDestination = `${appName}/${normalizeS3Path(prefix)}${backupFileName}`; try { const rcloneFlags = getS3Credentials(destination); const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; diff --git a/packages/server/src/utils/volume-backups/backup.ts b/packages/server/src/utils/volume-backups/backup.ts index dd1575563..e192fd698 100644 --- a/packages/server/src/utils/volume-backups/backup.ts +++ b/packages/server/src/utils/volume-backups/backup.ts @@ -4,6 +4,24 @@ import { findComposeById } from "@dokploy/server/services/compose"; import type { findVolumeBackupById } from "@dokploy/server/services/volume-backups"; import { getS3Credentials, normalizeS3Path } from "../backups/utils"; +export const getVolumeServiceAppName = ( + volumeBackup: Awaited>, +): string => { + if (volumeBackup.compose?.appName) { + return volumeBackup.serviceName + ? `${volumeBackup.compose.appName}_${volumeBackup.serviceName}` + : volumeBackup.compose.appName; + } + const serviceAppName = + volumeBackup.application?.appName || + volumeBackup.postgres?.appName || + volumeBackup.mysql?.appName || + volumeBackup.mariadb?.appName || + volumeBackup.mongo?.appName || + volumeBackup.redis?.appName; + return serviceAppName || volumeBackup.appName; +}; + export const backupVolume = async ( volumeBackup: Awaited>, ) => { @@ -12,8 +30,9 @@ export const backupVolume = async ( volumeBackup.application?.serverId || volumeBackup.compose?.serverId; const { VOLUME_BACKUPS_PATH, VOLUME_BACKUP_LOCK_PATH } = paths(!!serverId); const destination = volumeBackup.destination; + const s3AppName = getVolumeServiceAppName(volumeBackup); const backupFileName = `${volumeName}-${new Date().toISOString()}.tar`; - const bucketDestination = `${volumeBackup.appName}/${normalizeS3Path(prefix || "")}${backupFileName}`; + const bucketDestination = `${s3AppName}/${normalizeS3Path(prefix || "")}${backupFileName}`; const rcloneFlags = getS3Credentials(volumeBackup.destination); const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; const volumeBackupPath = path.join(VOLUME_BACKUPS_PATH, volumeBackup.appName); diff --git a/packages/server/src/utils/volume-backups/utils.ts b/packages/server/src/utils/volume-backups/utils.ts index dc2a4504f..6a51e765d 100644 --- a/packages/server/src/utils/volume-backups/utils.ts +++ b/packages/server/src/utils/volume-backups/utils.ts @@ -12,7 +12,7 @@ import { import { scheduledJobs, scheduleJob } from "node-schedule"; import { getS3Credentials, normalizeS3Path } from "../backups/utils"; import { sendVolumeBackupNotifications } from "../notifications/volume-backup"; -import { backupVolume } from "./backup"; +import { backupVolume, getVolumeServiceAppName } from "./backup"; // Helper functions to extract project info from volume backup const getProjectName = ( @@ -81,7 +81,8 @@ const cleanupOldVolumeBackups = async ( try { const rcloneFlags = getS3Credentials(destination); - const backupFilesPath = `:s3:${destination.bucket}/${volumeBackup.appName}/${normalizeS3Path(prefix || "")}`; + const s3AppName = getVolumeServiceAppName(volumeBackup); + const backupFilesPath = `:s3:${destination.bucket}/${s3AppName}/${normalizeS3Path(prefix || "")}`; const listCommand = `rclone lsf ${rcloneFlags.join(" ")} --include \"${volumeName}-*.tar\" ${backupFilesPath}`; const sortAndPick = `sort -r | tail -n +$((${keepLatestCount}+1)) | xargs -I{}`; const deleteCommand = `rclone delete ${rcloneFlags.join(" ")} ${backupFilesPath}{}`; From b419294b0968b2799587a45896d0ee0770429c57 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Sat, 7 Mar 2026 23:38:58 -0600 Subject: [PATCH 2/2] fix: add --drop option to mongorestore command for improved data restoration https://github.com/Dokploy/dokploy/issues/2713 --- packages/server/src/utils/restore/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/restore/utils.ts b/packages/server/src/utils/restore/utils.ts index 23052e642..7300ca479 100644 --- a/packages/server/src/utils/restore/utils.ts +++ b/packages/server/src/utils/restore/utils.ts @@ -30,7 +30,7 @@ export const getMongoRestoreCommand = ( databaseUser: string, databasePassword: string, ) => { - return `docker exec -i $CONTAINER_ID sh -c "mongorestore --username '${databaseUser}' --password '${databasePassword}' --authenticationDatabase admin --db ${database} --archive"`; + return `docker exec -i $CONTAINER_ID sh -c "mongorestore --username '${databaseUser}' --password '${databasePassword}' --authenticationDatabase admin --db ${database} --archive --drop"`; }; export const getComposeSearchCommand = (