Skip to content

fix: exit cleanly on fatal startup errors instead of crash-looping#69

Open
AminDhouib wants to merge 2 commits into
canaryfrom
port/upr-4601
Open

fix: exit cleanly on fatal startup errors instead of crash-looping#69
AminDhouib wants to merge 2 commits into
canaryfrom
port/upr-4601

Conversation

@AminDhouib

@AminDhouib AminDhouib commented Jul 12, 2026

Copy link
Copy Markdown
Member

Ports Dokploy#4601 by @apotema (1 commit, cherry-picked with -x, original authorship preserved).

Addresses Dokploy#4253 (verified: linked via the upstream PR's closing references; the diff directly targets the crash-loop-on-startup behavior described there).

What / why

A fatal error during startup (rejected app.prepare(), failed directory/Traefik init, EADDRINUSE on listen) previously left the process alive but unserviceable: background handles (Redis reconnect loop, sockets) kept the event loop spinning at ~100% CPU, the healthcheck never passed, and the container crash-looped with only an opaque ELIFECYCLE Command failed.

Changes in apps/dokploy/server/server.ts:

  • isServerListening flag distinguishes pre-listen fatal errors from post-listen runtime errors.
  • uncaughtException handler: log, then exit(1) (preserves Node default, adds cause logging).
  • unhandledRejection handler: exit(1) only before the server is listening; log-only afterwards.
  • Directory/Traefik init wrapped in try/catch with exit(1) on failure.
  • server.listen is now awaited via a promise that rejects on the error event, so bind failures exit cleanly.
  • .catch on app.prepare() chain: log + exit(1).
  • Main try/catch exits 1 when failure happens before listening.

Adaptations

One conflict in the deployment-worker block: the fork starts its worker via startDeploymentWorker() from ./queues/queueSetup (upstream uses deploymentWorker.run() from ./queues/deployments-queue). Resolved by keeping the fork's worker call inside upstream's new error-handling structure. No functional deviation otherwise.

Test evidence

  • pnpm --filter=dokploy run typecheck: clean.
  • No test files in the upstream PR; the change is bootstrap-path only (no unit-test surface in this repo's suites).

Overlap notes

No file overlap with open fork PRs #36/#58 or with the other salvo-2 M2 ports (only this item touches server.ts).

apotema and others added 2 commits July 12, 2026 10:45
…okploy#4253)

After "Migration complete", a fatal error in the server startup window (e.g. a
rejected `app.prepare()`) did not terminate the process. Background handles —
the ioredis reconnect loop, open sockets — kept the event loop alive, so instead
of exiting the process spun at high CPU, never passed the healthcheck, and Docker
Swarm crash-looped the container showing only "ELIFECYCLE  Command failed."

Reproduced with the real compiled bundle (node:24.4.0-slim + Postgres 16):
- before: fatal startup error -> process never exits, spins until killed
- after:  fatal startup error -> logs the cause, exits 1 in ~3-5s

Changes in apps/dokploy/server/server.ts:
- Phase-gated process handlers: before the HTTP server is listening, an uncaught
  exception or unhandled rejection logs the cause and exit(1)s so the orchestrator
  restarts cleanly. After it is listening, a stray rejection is only logged, so a
  healthy serving instance is never killed (verified: real post-listen docker.sock
  ENOENT rejection is survived).
- try/catch around the synchronous directory/Traefik init.
- await the listen() bind so a bind failure (e.g. EADDRINUSE) exits instead of
  spinning; only mark the server ready once actually listening.
- .catch() on app.prepare() with a labeled diagnostic.

(cherry picked from commit 9e122de)
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.

2 participants