Skip to content

feat(backup): additional options for database backups#3445

Open
paulwer wants to merge 8 commits intoDokploy:canaryfrom
paulwer:feat-backup-additional-options
Open

feat(backup): additional options for database backups#3445
paulwer wants to merge 8 commits intoDokploy:canaryfrom
paulwer:feat-backup-additional-options

Conversation

@paulwer
Copy link

@paulwer paulwer commented Jan 12, 2026

What is this PR about?

Adds a new optional field additionalOptions that allows users to pass extra arguments to the dump commands for databases.

We also have created a fix for successfully running dokploy:setup on windows systems

Checklist

Before submitting this PR, please make sure that:

Issues related (if applicable)

closes #3191

Screenshots (if applicable)

image

Notes

@Siumauricio I tested the ui/api changes for creating / restoring backups, which works fine, but I was not able to test backup jobs for all databases. please validate while reviewing this PR please :)

Please also write suggestion/feedback for UI-Component created by me for ArrayLists, if it matches your style-guide or do necessary changes yourself :)

We also have created a fix for successfully running dokploy:setup on windows systems.

Greptile Summary

This PR adds an additionalOptions field to database backups, allowing users to pass extra CLI arguments to dump/restore commands. It also includes a Windows compatibility fix for the setup script by replacing sleep 5 (Unix-only) with a Node.js setTimeout.

Key issues found:

  • Shell injection risk in backup command generation: The additionalOptions are escaped with single-quote wrapping, but the entire command sits inside a double-quoted bash -c "..." argument. Characters like ", $, and backticks in user input can break out of the double-quoted context and execute arbitrary commands on the host or remote server via SSH. Server-side validation should enforce a strict allowlist pattern for option strings.
  • additionalOptions unused in restore commands: The parameter is threaded through the entire restore code path (getRestoreCommandgenerateRestoreCommand → individual restore functions) but never incorporated into the actual restore command strings. This is either an incomplete implementation or dead code that should be cleaned up.
  • Setup delay bug: The 5-second delay (moved from shell sleep 5 to setTimeout) is placed after exit(0) in the success path, meaning it never executes on successful setup. The migration command may fail if Postgres hasn't finished initializing.

Confidence Score: 2/5

  • This PR has a command injection vulnerability in the backup command generation and a logic bug in the setup delay that should be resolved before merging.
  • Score of 2 reflects a shell injection risk in how user-supplied additionalOptions are interpolated into shell commands executed via child_process.exec() and SSH, combined with an incomplete restore implementation (options accepted but unused) and a setup timing bug.
  • Pay close attention to packages/server/src/utils/backups/utils.ts (shell injection), packages/server/src/utils/restore/utils.ts (unused parameter), and apps/dokploy/setup.ts (delay bug).

Last reviewed commit: d3e32d2

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

@paulwer paulwer changed the title initial changes feat(backup): additional options for database backups Jan 13, 2026
@michakfromparis
Copy link

Yes. I second that. This is key to restore a db with acl or RLS. Totally necessary for logto for example

@paulwer
Copy link
Author

paulwer commented Feb 8, 2026

@Siumauricio I am very sorry, but I am not able to do testing right now and for the next few weeks.
Would it be possible to overhand you the current status, as I am likly not able to work on this for the next 2 weeks.

@paulwer paulwer marked this pull request as ready for review February 18, 2026 15:52
@paulwer paulwer requested a review from Siumauricio as a code owner February 18, 2026 15:52
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

16 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +86 to +96
'docker exec -i $CONTAINER_ID bash -c "set -o pipefail; pg_dump',
'-Fc',
'--no-acl',
'--no-owner',
additionalOptions?.length ? additionalOptions.map(opt => `'${opt.trim().replace(/'/g, `'\\''`)}'`).join(' ') : '',
'-h localhost',
`-U ${databaseUser}`,
'--no-password',
database,
'| gzip"',
].filter(Boolean).join(' ');
Copy link
Contributor

Choose a reason for hiding this comment

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

Shell injection via double-quote breakout

The additionalOptions are escaped with single-quote wrapping ('...'), but this entire command string sits inside a double-quoted bash -c "..." argument (line 86). Within the outer double-quote context, single quotes are treated as literal characters and do not prevent shell interpretation. A user-supplied option containing a " character (e.g., " ; malicious_command ; echo ") would terminate the bash -c argument and allow arbitrary command execution on the host or remote server.

The single-quote escaping on line 90 only handles ' characters but does not escape ", `, $, or \, all of which are interpreted within double quotes by the outer shell.

Since this command is executed via child_process.exec() or SSH, this is a command injection risk. Consider either:

  1. Escaping double quotes and other shell metacharacters (", $, `, \) in addition to single quotes, or
  2. Validating additionalOptions against a strict allowlist pattern (e.g., /^--?[\w-]+(=[\w\-\/.]+)?$/) on the server side before they reach command generation

Comment on lines 6 to 12
export const getPostgresRestoreCommand = (
database: string,
databaseUser: string,
additionalOptions?: string[],
) => {
return `docker exec -i $CONTAINER_ID sh -c "pg_restore -U '${databaseUser}' -d ${database} -O --clean --if-exists"`;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

additionalOptions parameter accepted but never used

All four restore command functions (getPostgresRestoreCommand, getMariadbRestoreCommand, getMysqlRestoreCommand, getMongoRestoreCommand) accept additionalOptions as a parameter but never include it in the generated command string. The parameter is threaded through generateRestoreCommandgetRestoreCommand → the individual restore files, but has no effect on the actual restore commands.

This appears to be an incomplete implementation. If the intent is to support additional options for restore commands, the options need to be incorporated into the command strings. If only backup commands should support this feature, the parameter should be removed from the restore path to avoid confusion.

Comment on lines +142 to 153
return [
'docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mongodump',
`-d ${database}`,
`-u ${databaseUser}`,
`-p ${databasePassword}`,
'--archive',
'--authenticationDatabase admin',
'--gzip',
additionalOptions?.length ? additionalOptions.map(opt => `'${opt.trim().replace(/'/g, `'\\''`)}'`).join(' ') : '',
'"',
].filter(Boolean).join(' ');
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Mongo backup command has a stray trailing "

For the other database commands (postgres, mysql, mariadb), the command ends with | gzip" which correctly closes the bash -c "..." double quote. But for mongo, the closing " is a separate array element on line 151. When additionalOptions is empty, filter(Boolean) removes the empty string, and the command ends correctly as ...--gzip ". However, when additionalOptions has values, the result is ...--gzip '--opt1' " — the additional options are placed between --gzip and the closing ", meaning the closing " is separated by a space from the last option. While this technically still works (the " closes the bash -c argument), it's inconsistent with the other commands where options are placed before the pipe. This is a minor structural inconsistency but worth being aware of.

HTMLInputElement,
InputArrayProps
>(({ className, errorMessage, value, onChange, disabled, ...props }, ref) => {
if (!value) value = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Reassigning a const parameter breaks immutability

Reassigning the value parameter (if (!value) value = [];) mutates a const-declared function parameter from the destructuring. While this works at runtime because the parameter is function-scoped, it's an unconventional pattern. A local variable would be clearer:

Suggested change
if (!value) value = [];
const items = value ?? [];

Then use items in place of value throughout the component.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Additional Comments (1)

apps/dokploy/setup.ts
5-second delay never executes on success

The sleep 5 was moved from the shell command (package.json) into the Node.js script, but exit(0) on line 34 terminates the process before reaching the setTimeout on line 39. The delay only runs when an error occurs (when the catch block completes without exiting).

The original sleep 5 in package.json was placed between setup.ts and migration:run to ensure services like Postgres are ready. With this change, on a successful setup, the migration runs immediately after setup completes with no delay, which may cause the migration to fail if Postgres hasn't finished initializing.

To fix, either move the delay before exit(0):

		console.log("Dokploy setup completed");
		await new Promise<void>((res) => setTimeout(() => res(), 5000));
		exit(0);
	} catch (e) {
		console.error("Error in dokploy setup", e);
	}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to customize postgres backup command

3 participants

Comments