feat: add pre-deploy and post-deploy command hooks for applications#4240
Open
Alfredao wants to merge 3 commits intoDokploy:canaryfrom
Open
feat: add pre-deploy and post-deploy command hooks for applications#4240Alfredao wants to merge 3 commits intoDokploy:canaryfrom
Alfredao wants to merge 3 commits intoDokploy:canaryfrom
Conversation
14f258f to
16bf43c
Compare
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…er id from swarm wait, drop dead export
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this PR about?
Implements pre-deploy and post-deploy command hooks for Applications, closing a long-standing community request (issue #110, open ~2 years, 28+ comments).
Two new optional fields are exposed on the Application Advanced tab under a new Deployment Lifecycle Hooks card:
Both commands are executed via
docker exec <taskContainerId> sh -c "<cmd>"against the resolved swarm task container (local or remote via SSH). A non-zero exit aborts the deployment and marks it aserror, reusing the existing failure pipeline (sendBuildErrorNotifications+ log-line append). Hook output is streamed into the deployment log. The user's command string itself is never echoed — only a length-only header — so secrets embedded in the command don't leak into logs.Behaves identically in
deployApplicationandrebuildApplication. Compose services and preview deployments are out of scope here.Storage note — single
deployHookscolumnThe pre/post pair is stored in a single
textcolumn (deployHooks) as a JSON payload{ pre, post }, not two separate columns. Reason: theapplicationtable already has 99 columns oncanary, and Postgres capsjson_build_array()at 100 arguments. Drizzle's relationalwithquery on the project-listing page compiles every column of every nested relation into that call, so adding two columns would push the tree past the 100-arg limit and crash the home page. Stuffing both hooks into one column lands exactly at 100 and avoids the regression. Worth flagging as separate pressure for a future normalization pass onapplications— any additional column from any PR will re-trip this.Changes
packages/server/src/db/schema/application.ts— AddeddeployHooks: text(\"deployHooks\")next to the existingcommand. Drizzle-zod auto-picks it intoapiUpdateApplication(no router change needed).apps/dokploy/drizzle/0166_aromatic_jetstream.sql— Generated migration adding the column.packages/server/src/utils/docker/hooks.ts(new) —parseDeployHooks/serializeDeployHooksfor the JSON payload,runDeployHook({ kind, appName, serverId, command, logPath })that resolves the running task container via the existinggetServiceContainer, base64-encodes the user command (mirroring the pattern atutils/docker/utils.ts:720), and dispatches over localexecAsyncor remoteexecAsyncRemote. Pre-deploy is silently skipped when no previous container exists.waitForSwarmServiceRunning(appName, serverId, opts)is a remote-aware poll loop that waits for a task with `State === "running"` (default 120s / 2s interval) — needed because the existingcheckSwarmServiceRunningatutils/docker/utils.ts:796is local-only and unexported.packages/server/src/services/application.ts— CallsrunDeployHook({ kind: \"pre\" })between the build step andmechanizeDockerContainer, then (only when a post-deploy command is configured) awaitswaitForSwarmServiceRunning+runDeployHook({ kind: \"post\" })before marking the deployment done. Failures bubble through the existingtry/catchthat handles error notifications. Same placement insiderebuildApplication.apps/dokploy/components/dashboard/application/advanced/general/deploy-hooks.tsx(new) — NewDeployHookscard with two<Textarea>fields. Parses/serializes the JSON payload internally so the API surface stays a single field. Rendered next to the existing Run Command card in the Advanced tab.apps/dokploy/__test__/deploy/application.deploy-hooks.test.ts(new) — 7 Vitest cases covering pre-before-mechanize, post-after-wait, skip-when-empty, pre-fail / post-fail abort deployment, andserverIdpass-through.Notes for reviewers
apps/api/src/utils.ts) callsdeployApplicationfrom@dokploy/serverdirectly, so no parallel pipeline change was needed.docker exec ... sh -cassumesshexists in the image (doesn't work on scratch/distroless). This matches the industry pattern (Coolify works the same way).waitForSwarmServiceRunninghelper is technically a remote-aware duplicate of the unexportedcheckSwarmServiceRunningatutils/docker/utils.ts:796. Happy to consolidate the two in a follow-up if preferred.applicationis now the binding constraint for any future schema change — flagging so it's on maintainers' radar.Checklist
Issues related (if applicable)
Closes #110
Screenshots (if applicable)
Greptile Summary
This PR adds pre-deploy and post-deploy command hooks for applications, storing them as a JSON payload in a single new
deployHookstext column. The implementation uses base64 encoding to safely pass user commands todocker exec, with hook output streamed to the deployment log.deployApplicationandrebuildApplicationcomputeserverId = buildServerId || serverIdand pass it to bothrunDeployHookandwaitForSwarmServiceRunning. However,mechanizeDockerContainerusesapplication.serverIddirectly for Swarm operations. WhenbuildServerIdis set, hooks would target the build server — where the containers don't exist — causingdocker execto silently skip (pre) or throw (post). Both call sites should useapplication.serverIdinstead.serializeDeployHooksinhooks.tsis dead code: The function is exported but never imported; the frontend component carries its own copy of the same logic.Confidence Score: 4/5
Safe to merge for the common single-server setup; will silently misbehave for users with a dedicated build server configured.
One P1 defect: hooks use
buildServerId || serverIdinstead ofserverId, causing them to target the wrong host when a build server is configured. All other findings are P2 (dead code, minor race condition under non-default Swarm update order). The feature works correctly for the typical case where no separate build server is set.packages/server/src/services/application.ts — hook
serverIdargument in bothdeployApplicationandrebuildApplicationReviews (1): Last reviewed commit: "feat: add pre-deploy and post-deploy com..." | Re-trigger Greptile