Skip to content

feat(compose): transactional deployments with automatic rollback to last good release - #5182

Open
EngAbo3lia wants to merge 14 commits into
Dokploy:canaryfrom
EngAbo3lia:feat/compose-auto-rollback
Open

feat(compose): transactional deployments with automatic rollback to last good release#5182
EngAbo3lia wants to merge 14 commits into
Dokploy:canaryfrom
EngAbo3lia:feat/compose-auto-rollback

Conversation

@EngAbo3lia

@EngAbo3lia EngAbo3lia commented Aug 24, 2026

Copy link
Copy Markdown

What is this PR about?

Today, if a Docker Compose deployment fails (wrong image name, broken compose file, failed build), Dokploy can leave your running services down until you notice and manually redeploy. This PR fixes that: a failed deploy now rolls back automatically and your last working release keeps serving traffic, Vercel-style.

Before After
Deploy fails Services stop / run from broken files Last good release keeps running
Service status in UI Turns red on rollback success Stays green, only the deployment shows the error
Recovery Manual intervention needed Automatic, logged in the deploy log

How it works

  1. Snapshot first - before anything is touched, the current compose file and .env are backed up.
  2. Restore on failure - if any step fails, the backup is restored and docker compose up -d brings the previous release back up. Each step is visible in the deployment log.
  3. Remember the last good release - every successful deploy saves its compose file and env as a known-good snapshot. Rollbacks always prefer this snapshot.
  4. Honest status - after a successful rollback the service stays marked as live; only the failed deployment entry shows the error.

UI improvements included

  • Status badges in the service header: pulsing green Live, amber Deploying, red Deploy failed.
  • Clickable domain chips under the service name (disabled domains are shown struck-through).
  • Compose project name with click-to-copy.

Scope: applies to docker-compose type only. Stack type deployments keep their current behavior.

Testing

Verified end-to-end on a local instance:

  1. Deploy a working service - succeeds, known-good snapshot saved.
  2. Push a deploy with a broken image reference - deploy fails, log shows the restore steps, old containers keep answering HTTP requests during the whole process, service stays green.
  3. Fix the compose and redeploy - succeeds again and refreshes the snapshot.

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.

Greptile Summary

This PR adds transactional Docker Compose deployments that preserve the current deployment, restore it after failed updates, and retain known-good artifacts for later recovery. It also adds permission-aware deployment and domain status information to the Compose service header.

  • Backs up the active Compose definition and environment before deployment and rebuild mutations.
  • Restores the previous release after Docker Compose failures and keeps workload status live only when rollback succeeds.
  • Refreshes or removes known-good snapshots after successful deployments so stale artifacts are not preferred.
  • Adds live, deploying, and failed-deployment badges, domain links, and project-name copying to the Compose dashboard.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (8): Last reviewed commit: "fix(compose): drop stale last-good snaps..." | Re-trigger Greptile

Context used (3)

…eploy

When a docker compose deploy fails mid up, running services could be
left stopped or removed, taking the previous working deployment down.

Wrap the deploy in a transaction: snapshot the current compose file and
env file to .deploy-backup before applying changes; on failure, restore
both files and re-run up -d (without --build) so the last successful
deployment keeps serving traffic while the deployment itself still
reports as failed.
Failed docker compose deploys previously killed running services:
'docker compose up -d --build' mutated the live namespace in place and
a mid-deploy failure could leave containers stopped or removed.

This makes compose deploys transactional (Vercel-style guarantee for
stateful stacks):

- backupCurrentDeployment(): snapshot the compose file and .env to
  .deploy-backup before the deploy flow touches them (raw/git sources
  rewrite these on every deploy).
- getBuildComposeCommand(): on deploy failure, restore the snapshot and
  run 'up -d' without '--build' so the last working version keeps its
  containers up and serving; deployment still reports as failed.
- After each successful deploy, persist a 'last-good' snapshot;
  rollback prefers it, so the last SUCCESSFUL deployment (not the last
  attempt) is what gets reinstated.

Only applies to 'docker-compose' type (swarm handles its own rollback).
After a failed deploy that was auto-rolled-back, composeStatus was set
to 'error', showing a red dot even though the last-good deployment was
still running and serving traffic (Vercel shows the failed deployment
in history but the service stays online).

Emit __DOKPLOY_ROLLBACK_OK__ after a successful restore and set
composeStatus to 'done' instead of 'error' when the marker is found, so
the service card reflects the actually-served state. The failed
deployment still shows as error in the deployments tab.
The service header now shows Vercel-style status under the service name:
- green Live badge with the last successful deployment title
- amber 'Deploying...' badge while a deployment runs
- red 'Last deploy failed - live kept' badge when the latest deploy
  failed but the last successful one is still serving

Refresh the deployments list every 5s so badges update in real time.
A failed deploy must never flip the service status, even if the
rollback up-didn't run: as long as a previous successful deployment has
a last-good snapshot, the service is considered live (green). Only when
there has never been a successful deployment does the status go red.
- Status badges (Live/Deploying/failed) moved to the header's right
  column above server badge; cleaner labels ('Live', 'Last deploy
  failed') with tooltips instead of raw deployment titles.
- Domain URLs rendered as clickable chips under service name/appName:
  enabled = green link, disabled = greyed strikethrough.
- Identity column: icon, name, mono appName with click-to-copy
  (Copy/Check icons), truncated description.
- Status badges restyled with soft tinted borders; Live badge uses a
  pulsing emerald dot; 'Deploy failed' replaces verbose label.
- Server badge, edit and delete actions merged into a single row.
- Domain URLs moved to their own strip under the header with Globe/
  ArrowUpRight icons; disabled domains render muted strikethrough.
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 24, 2026
Comment thread packages/server/src/services/compose.ts Outdated
…, permission-gated polling

Greptile review fixes:

- didRollbackSucceed now only reports success when the rollback actually ran to
  completion (ROLLBACK_OK_MARKER present in the deployment log). A leftover
  last-good snapshot file no longer marks the service as live, so failed or
  skipped restorations surface as error instead of silently claiming green.
- rebuildCompose now snapshots the current compose file and .env before touching
  anything (backupCurrentDeployment), so a failed rebuild - even the first one
  after an upgrade, with no last-good snapshot - restores the pre-change files
  instead of leaving broken definitions on disk.
- Compose service header: deployment and domain queries are now gated on the
  user's deployment.read / domain.read permissions (enabled + refetchInterval),
  so members without those permissions no longer fire denied polling requests
  every 5/10s.

Also applies biome fixes (@ts-expect-error) to the kept @ts-ignore.
Comment thread packages/server/src/utils/builders/compose.ts Outdated
…ored

The restore path swallowed failed file copies (cp ... || true), so a deploy
whose rollback had nothing to restore could still emit
__DOKPLOY_ROLLBACK_OK__ and mark the service live with the broken
definition still on disk.

Track copy success in RESTORE_FILES_OK (1 = a compose file and an env file
were copied back) and require it for the marker: the marker is emitted only
when both the restored files exist and 'docker compose up -d' succeeds.
Comment thread packages/server/src/services/compose.ts Outdated
Comment thread packages/server/src/utils/builders/compose.ts Outdated
- didRollbackSucceed now greps the marker as a line-exact match
  (^__DOKPLOY_ROLLBACK_OK__$), so user build/startup output containing the
  string can no longer spoof rollback success.
- The .env restore is best-effort again: services with createEnvFile=false
  have no env backup, so an env copy failure no longer suppresses the
  marker when the compose definition was restored and 'up -d' succeeded.
- Marker constant is now defined once (exported from builders/compose)
  and reused by the deploy services instead of being duplicated.
Comment thread packages/server/src/services/compose.ts Outdated
Comment thread packages/server/src/utils/builders/compose.ts Outdated
…ore when snapshots exist

- The rollback marker is now echoed as __DOKPLOY_ROLLBACK_OK__:<deploymentId>
  and the log match is anchored to that exact line, so the constant alone
  printed by a compose build/container can no longer spoof a successful
  rollback (the deployment id is generated per deployment and unknown to
  service output). didRollbackSucceed and the build command thread the id.

- The .env restore is best-effort only when no env snapshot exists
  (createEnvFile=false services). When a previous env snapshot is present
  and the restore copy fails, the marker is suppressed so the service is
  not marked live with an environment that differs from the last good one.
Comment thread packages/server/src/utils/builders/compose.ts Outdated
createEnvFile=true services always consume a generated .env: when the
failed deploy overwrote it and no env snapshot is present, the rollback
must not be declared successful even if the compose definition was
restored. The marker is now gated whenever the service generates an env
file, regardless of snapshot presence; env-less services keep the
best-effort behavior.
Comment thread packages/server/src/services/compose.ts Outdated
backupCurrentDeployment previously converted a failed snapshot cp into a
successful echo (|| echo), so a rebuild could rewrite the live definition
with no guarantee that the previous release is recoverable. The backup now
copies only when the source exists (missing source = first deploy, allowed)
and exits with an error when a copy fails, aborting the deploy before any
file is rewritten.
Comment thread packages/server/src/utils/builders/compose.ts Outdated
persistLastGood previously ignored failed copies (|| true), so a stale
last-good could survive a persist failure and a later rollback would
prefer it over the fresh pre-deployment backup, restoring an older
definition than the last successful one. On persist failure the stale
snapshot is now removed, forcing rollback to the fresh backup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant