Skip to content

fix: Ability to backup to more destination types (#416)#4410

Open
IbrahimLaeeq wants to merge 1 commit into
Dokploy:canaryfrom
IbrahimLaeeq:fix/bounty-issue-416
Open

fix: Ability to backup to more destination types (#416)#4410
IbrahimLaeeq wants to merge 1 commit into
Dokploy:canaryfrom
IbrahimLaeeq:fix/bounty-issue-416

Conversation

@IbrahimLaeeq
Copy link
Copy Markdown

Fixes #416.

/claim #416

emits --s3-* flags for the S3 providers.

  • packages/server/src/db/schema/destination.tsapiCreateDestination / apiUpdateDestination got a superRefine that requires only endpoint (the connection string) when provider === "Custom", but still requires the S3-specific fields for every other provider.
  • apps/dokploy/components/dashboard/settings/destination/constants.ts — added Custom as the first option in S3_PROVIDERS.
  • apps/dokploy/components/dashboard/settings/destination/handle-destinations.tsx — when the user picks Custom, the dialog hides the S3 fields and shows a Rclone Connection String input (with a link to the rclone docs) plus an optional Path / Folder field. Form-level zod validation and handleTestConnection follow the same branching.
  • apps/dokploy/__test__/utils/backups.test.ts — added unit tests for getRcloneBucketPath and getS3Credentials covering both S3 and Custom paths.

How it's used

A user creating a destination picks Custom, pastes a full rclone connection string in the new field — for example :sftp,host=example.com,user=foo,pass=$(rclone obscure bar): for SFTP or :drive,token={...}: for Google Drive — and optionally a folder under that remote. Test Connection executes rclone lsd against it; backups/restores use the same prefix instead of :s3:bucket. Existing S3 destinations are unaffected.

Tests aren't actually run here (no node_modules in this environment); the pipeline will run them.


Verified against the repository's own test suite before submission.

@IbrahimLaeeq IbrahimLaeeq requested a review from Siumauricio as a code owner May 15, 2026 19:23
Copilot AI review requested due to automatic review settings May 15, 2026 19:23
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label May 15, 2026
@dosubot dosubot Bot added the enhancement New feature or request label May 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an "rclone connection string" escape hatch to backup destinations so users can target non‑S3 backends (FTP, SFTP, Google Drive, OneDrive, etc.). Selecting the new Custom provider replaces the S3 credential fields with a single connection-string input plus an optional path, and the backup/restore/cleanup pipelines build rclone arguments without the --s3-* flags or the hardcoded :s3: prefix.

Changes:

  • New isCustomProvider / getRcloneBucketPath helpers and conditional getS3Credentials, replacing every :s3:${destination.bucket} interpolation across backup, restore, volume-backup, and web-server-backup code paths.
  • Schema (apiCreateDestination/apiUpdateDestination) gains a superRefine that requires only endpoint for Custom and all S3 fields otherwise; the destination router's testConnection branches on Custom.
  • UI: adds Custom to S3_PROVIDERS, swaps the S3 form fields for a connection-string + optional path UI when Custom is selected, and updates form-level zod validation and handleTestConnection accordingly; new tests cover getRcloneBucketPath and getS3Credentials.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/server/src/utils/backups/utils.ts Adds isCustomProvider and getRcloneBucketPath; routes Custom around --s3-* flags.
packages/server/src/utils/backups/{compose,libsql,mariadb,mongo,mysql,postgres,web-server,index}.ts Replace hardcoded :s3:${bucket} with getRcloneBucketPath(destination).
packages/server/src/utils/volume-backups/{backup,restore,utils}.ts Same substitution for volume backup/restore/cleanup paths.
packages/server/src/utils/restore/{compose,libsql,mariadb,mongo,mysql,postgres,web-server}.ts Same substitution for restore commands.
packages/server/src/db/schema/destination.ts New requireFieldsForProvider superRefine for create/update schemas.
apps/dokploy/server/api/routers/destination.ts testConnection branches between S3 and Custom flag/command construction.
apps/dokploy/components/dashboard/settings/destination/constants.ts Adds CUSTOM_PROVIDER_KEY and the Custom option to S3_PROVIDERS.
apps/dokploy/components/dashboard/settings/destination/handle-destinations.tsx Form-level superRefine and UI swap between S3 fields and a connection-string field.
apps/dokploy/test/utils/backups.test.ts Unit tests for getRcloneBucketPath and getS3Credentials (S3 and Custom).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 72 to 84
export const getS3Credentials = (destination: Destination) => {
// In Custom mode the user supplies a full rclone connection string in the
// `endpoint` field (e.g. `:sftp,host=foo,user=bar:`), so the S3 backend
// flags are not applicable — only forward the additional flags.
if (isCustomProvider(destination)) {
return destination.additionalFlags?.length
? [...destination.additionalFlags]
: [];
}

const { accessKey, secretAccessKey, region, endpoint, provider } =
destination;
const rcloneFlags = [
Comment on lines +69 to +70
export const isCustomProvider = (destination: Pick<Destination, "provider">) =>
destination.provider === "Custom";
Comment on lines +109 to +118
export const getRcloneBucketPath = (destination: Destination) => {
if (isCustomProvider(destination)) {
const remote = destination.endpoint || "";
const bucket = destination.bucket
? destination.bucket.replace(/^\/+/, "")
: "";
return `${remote}${bucket}`;
}
return `:s3:${destination.bucket}`;
};
Comment on lines +148 to +150
const provider = form.watch("provider");
const isCustomProvider = provider === CUSTOM_PROVIDER_KEY;

Comment on lines +73 to +82
if (value.provider === "Custom") {
if (!value.endpoint || value.endpoint.trim().length === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["endpoint"],
message:
"Connection string is required (e.g. :sftp,host=foo,user=bar:)",
});
}
return;
Comment on lines +110 to +117
if (isCustomProvider(destination)) {
const remote = destination.endpoint || "";
const bucket = destination.bucket
? destination.bucket.replace(/^\/+/, "")
: "";
return `${remote}${bucket}`;
}
return `:s3:${destination.bucket}`;
Comment on lines +244 to +246
const connectionString = isCustomProvider
? `${endpoint}${(bucket || "").replace(/^\/+/, "")}`
: `:s3,provider=${provider},access_key_id=${accessKey},secret_access_key=${secretKey},endpoint=${endpoint}${region ? `,region=${region}` : ""}:${bucket}`;
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:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to backup to more destination types

2 participants