Skip to content

feat: add SFTP, FTP, and Google Drive backup destinations#4096

Closed
marcoberutti wants to merge 1 commit intoDokploy:canaryfrom
marcoberutti:feat/backup-destinations-sftp-ftp-gdrive
Closed

feat: add SFTP, FTP, and Google Drive backup destinations#4096
marcoberutti wants to merge 1 commit intoDokploy:canaryfrom
marcoberutti:feat/backup-destinations-sftp-ftp-gdrive

Conversation

@marcoberutti
Copy link
Copy Markdown

@marcoberutti marcoberutti commented Mar 29, 2026

What is this PR about?

Adds SFTP, FTP, and Google Drive as backup destination types alongside existing S3 support. Previously only S3 was supported. This allows users to backup databases and volumes to SFTP servers, FTP servers, or Google Drive using a service account.

Checklist

  • Created a dedicated branch based on the canary branch.
  • Read the CONTRIBUTING.md suggestions.
  • Tested in local instance.

Issues related

closes #416

Changes

  • New destinationType (text, default s3) and providerConfig (jsonb) columns on destination table
  • Migration: 0155_backup_multi_destination.sql
  • New getRcloneConfig(destination) utility returning {preamble, flags, remotePath} for SFTP/FTP/GDrive/S3
  • All backup + restore executors migrated from getS3Credentials to getRcloneConfig
  • testConnection mutation supports all 4 provider types
  • apiCreateDestination/apiUpdateDestination are now discriminated unions on destinationType
  • handle-destinations.tsx rewritten with dynamic per-provider form fields

/claim #416

Greptile Summary

This PR adds SFTP, FTP, and Google Drive as backup destinations alongside existing S3 support. The core architecture is sound, but three P1 defects were found: shell injection via unescaped passwords in SFTP/FTP preamble commands, and missing preamble in all restore files and volume-backup cleanup (causing SFTP/FTP restores and cleanup to always fail).

Confidence Score: 4/5

Not safe to merge: SFTP/FTP restores are broken at runtime and passwords are vulnerable to shell injection

Three P1 issues requiring fixes before merge; the backup path is correct but restore and cleanup paths were overlooked

packages/server/src/utils/restore/ (all 7 files), packages/server/src/utils/volume-backups/utils.ts, packages/server/src/utils/backups/utils.ts lines 112+145, apps/dokploy/server/api/routers/destination.ts lines 60+66

Reviews (1): Last reviewed commit: "feat: add SFTP, FTP, and Google Drive ba..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

(5/5) You can turn off certain types of comments like style here!

Closes Dokploy#416

- Add destinationType (text, default s3) and providerConfig (jsonb) columns to destination table
- Migration: 0155_backup_multi_destination.sql with defaults on S3 credential columns
- New getRcloneConfig() utility returning {preamble, flags, remotePath} for all types
- All backup/restore executors migrated from getS3Credentials to getRcloneConfig
- testConnection router handler supports sftp/ftp/gdrive/s3
- handle-destinations.tsx UI rewritten with dynamic provider-specific form fields
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 29, 2026
@github-actions github-actions bot closed this Mar 29, 2026
@dosubot dosubot bot added the enhancement New feature or request label Mar 29, 2026
@dosubot
Copy link
Copy Markdown

dosubot bot commented Mar 29, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

Dokploy's Space

README /dokploy/blob/canary/README.md — ⏳ Awaiting Merge

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Comment on lines +38 to 39
`rclone copyto ${rclone.flags.join(" ")} "${backupPath}" "${tempDir}/${backupFile}"`,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing preamble causes SFTP/FTP restore to always fail

rclone.flags for SFTP includes --sftp-pass="$RCLONE_SFTP_PASS" and for FTP --ftp-pass="$RCLONE_FTP_PASS", but the preamble that defines those variables (RCLONE_SFTP_PASS=$(rclone obscure "...")) is never executed here. The shell variable will expand to an empty string and rclone will attempt authentication with an empty password, causing all SFTP/FTP restores to fail.

The same bug is present in every other restore file: restore/compose.ts, restore/libsql.ts, restore/mariadb.ts, restore/mongo.ts, restore/mysql.ts, and restore/postgres.ts — none of them prepend rclone.preamble before executing the rclone command.

Compare to the backup path in getBackupCommand (backups/utils.ts) which correctly injects the preamble into the shell script via the rclonePreamble parameter.

const sortAndPick = `sort -r | tail -n +$((${keepLatestCount}+1)) | xargs -I{}`;
const deleteCommand = `rclone delete ${rcloneFlags.join(" ")} ${backupFilesPath}{}`;
const deleteCommand = `rclone delete ${rclone.flags.join(" ")} ${backupFilesPath}{}`;
const fullCommand = `${listCommand} | ${sortAndPick} ${deleteCommand}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing preamble breaks SFTP/FTP volume-backup cleanup

fullCommand uses rclone.flags but never runs rclone.preamble first. For SFTP/FTP destinations the password environment variable (RCLONE_SFTP_PASS / RCLONE_FTP_PASS) will be unset when rclone lsf and rclone delete execute, causing the cleanup to fail silently (error is caught and swallowed). Note that keepLatestNBackups in backups/index.ts correctly prepends the preamble.

const port = cfg?.port ?? "22";
const remotePath = cfg?.remotePath ?? "/";
return {
preamble: `RCLONE_SFTP_PASS=$(rclone obscure "${cfg?.password ?? ""}")`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Shell injection via SFTP/FTP password

The password is interpolated directly into a double-quoted shell substitution. A password such as x"$(id)" would execute arbitrary commands. The same issue exists on line 145 for FTP, and in testConnection (destination.ts router lines 60 and 66). Use single-quoting with escaped single-quotes to prevent command substitution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Bounty claim enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to backup to more destination types

1 participant