Skip to content

feat(application): add custom shell mode to run command - #5432

Open
medsabbar wants to merge 4 commits into
Dokploy:canaryfrom
medsabbar:feat/custom-shell-run-command
Open

medsabbar wants to merge 4 commits into
Dokploy:canaryfrom
medsabbar:feat/custom-shell-run-command

Conversation

@medsabbar

@medsabbar medsabbar commented Sep 12, 2026

Copy link
Copy Markdown

What is this PR about?

I add a Custom shell mode to the Application Run Command card. I pick sh or bash and I paste a startup script. Dokploy sets my container Command to [shell, -c, script] and skips Args in that mode. Single mode keeps its current behavior. I verified live: my service inspects as [sh, -c, <script>] and my script output leads the container logs.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Issues related (if applicable)

Fixes #5433

Screenshots (if applicable)

Redeploy recording to attach.

RetriggerConfidence Score: 4/5

Fix the rejection of valid shell-only partial updates before merging; whitespace argument validation also needs adjustment.

Summary

  • Persists custom command settings through a database migration and shares command construction between deployment and rollback.
  • Fixes the previously reported rollback behavior, inactive-field validation, and accessible Mode group name.
  • Adds rollback regression tests and guidance that the image must contain the selected shell.
  • The latest validation changes introduce a partial-update rejection and disallow legitimate whitespace-only Single-mode arguments.

Reviews (2) · Last reviewed commit: "fix(application): harden custom shell mo..."

args.length > 0 && {
Args: args,
}),
...buildContainerCommand(application),

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 Rollbacks lose custom shell commands

The rollback snapshot captures customCommand and customShell, but rollbackApplication still constructs its container command solely from the legacy command field. Rolling back a deployment configured with a startup script therefore runs the old Single command—or the image default when command is empty—instead of the saved script. This can skip required startup work and restore a different runtime configuration. Use buildContainerCommand when constructing the rollback ContainerSpec too, and add a rollback regression test.

Knowledge Base Used: Applications and deployments

Comment on lines +35 to +43
customCommand: z.string().max(20000).optional(),
customShell: z.enum(["sh", "bash"]).optional(),
args: z
.array(
z.object({
value: z.string().min(1, "Argument cannot be empty"),
}),
)
.optional(),

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.

P2 Hidden fields block saving

Click Add Argument in Single mode, leave it blank, then switch to Custom shell and enter a valid script: the retained empty argument fails min(1), but its error is hidden because SingleCommandFields is unmounted, so Save appears to do nothing. A script exceeding 20,000 characters similarly blocks saving after switching back to Single. Make validation conditional on commandMode while preserving stored values, and cover these mode-switch cases in a form test.

Comment on lines +146 to +151
<FormLabel>Mode</FormLabel>
<FormControl>
<Input placeholder="/bin/sh" {...field} />
<RadioGroup
onValueChange={field.onChange}
value={field.value}
className="flex flex-row gap-6"

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.

P2 Mode group lacks accessible name

FormLabel associates “Mode” through htmlFor, but the RadioGroup root is not a labelable control, and no aria-label or aria-labelledby is supplied. The individual options are labeled, but assistive technology cannot identify the group by its visible heading. Give the Mode label an ID and reference it with aria-labelledby on RadioGroup.

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!

Custom script runs through sh -c or bash -c. Legacy command plus args stays as fallback.
@medsabbar
medsabbar force-pushed the feat/custom-shell-run-command branch from 5df0d07 to ee6e61a Compare September 12, 2026 20:43
API rejects empty scripts and shell without script. Helper types shell as sh or bash. UI warns the image must include the shell.
Comment on lines +576 to +581
if (data.customShell !== undefined && data.customShell !== null) {
if (data.customCommand === undefined || data.customCommand === null) {
ctx.addIssue({
code: "custom",
path: ["customCommand"],
message: "Enter a script",

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-only updates are rejected

For an application with a saved script, { applicationId, customShell: "bash" } now fails with “Enter a script” because customCommand is absent from the request. application.update performs partial updates, so the existing script should remain unchanged, but this validation prevents changing just the shell. Validate the resulting stored configuration, or allow an omitted script when updating the shell.

}
} else {
data.args?.forEach((arg, i) => {
if (!arg.value.trim()) {

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.

P2 Whitespace arguments are rejected

Using arg.value.trim() rejects legitimate whitespace-only arguments in Single mode. For example, an application configured with --separator followed by a literal space can no longer save its command, although that argument was previously accepted and is passed unchanged to Docker. Keep validation conditional on the mode, but check the original string’s length rather than its trimmed length.

Suggested change
if (!arg.value.trim()) {
if (arg.value.length === 0) {

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!

Reject whitespace-only scripts. Length checks use raw length. Add regression tests for the apiUpdateApplication superRefine.
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.

Application Run Command cannot run chained startup scripts

1 participant