Resource Cleanup and Lifecycle Management#
Overview#
Dokploy's teardown paths follow a common pattern: the database row is deleted first, sometimes triggering FK cascades that remove child rows, and then infrastructure cleanup (Docker service removal, Traefik config file deletion, source code removal) is attempted for the directly-deleted resource only. Cleanup steps run sequentially in individual try/catch blocks — failures are silently swallowed and never roll back the DB delete. FK cascades that remove child DB rows do not trigger infrastructure cleanup for those children, making parent-level deletions the primary source of orphaned Docker services and Traefik config files.
Application Deletion#
Entry point: application.delete tRPC mutation
The delete handler runs in this order:
- DB delete first — removes the
applicationsrow, which FK-cascades to childpreview_deployments, domains, and deployment records . Child infrastructure is not cleaned up by the cascade. - Queue drain (non-cloud only) —
cleanQueuesByApplication()clears pending/active jobs before infrastructure teardown . In cloud mode, in-flight builds are not cancelled. - Infrastructure cleanup chain — six operations, each independently
try/catch'd with errors silently swallowed :deleteAllMiddlewares— removes Traefik middleware config entriesremoveDeployments— clears deployment log recordsremoveDirectoryCode(appName, serverId)—rm -rf {APPLICATIONS_PATH}/{appName}removeMonitoringDirectory— removes monitoring dataremoveTraefikConfig(appName, serverId)— deletes{DYNAMIC_TRAEFIK_PATH}/{appName}.ymlremoveService(appName, serverId)—docker service rm <appName>
Orphan risk: Any preview deployments attached to the application have their DB rows cascade-deleted, but their Docker services and Traefik YAML files are not cleaned up because removePreviewDeployment() is never called for each child .
Preview Deployment Deletion#
Entry point: removePreviewDeployment(previewDeploymentId)
Triggered automatically by the GitHub pull_request.closed webhook, or callable directly. Five cleanup steps run sequentially with per-step try/catch :
removeService(appName, serverId)—docker service rmremoveDeploymentsByPreviewDeploymentId()— deployment log recordsremoveDirectoryCode(appName, serverId)— source code directoryremoveTraefikConfig(appName, serverId)— Traefik YAML file ({appName}.yml)- DB row delete — removes the
preview_deploymentsrecord (the associated domain record is removed via FK cascade)
The associated domain DB record has a previewDeploymentId FK with onDelete: "cascade" , so deleting the preview row also removes the domain row — but the Traefik YAML is only removed by step 4 above.
Parent application deletion gap: When a parent application is deleted, the DB cascade removes all child preview_deployments rows but removePreviewDeployment() is never called, leaving orphaned Docker services and Traefik YAML files for every preview that existed . Manual cleanup requires docker service rm <previewAppName> and deletion of /etc/dokploy/traefik/dynamic/<previewAppName>.yml on the host.
Domain Deletion#
Entry point: domain.delete tRPC mutation
The handler deletes the DB row via removeDomainById() , then conditionally cleans up Traefik config:
- Regular application domains — calls
removeDomain(application, domain.uniqueConfigKey), which loads the existing{appName}.yml, removes the specific router/service keys for this domain'suniqueConfigKey, and either rewrites the file (if other domains remain) or deletes it entirely (if this was the last router) . - Compose domains — no Traefik cleanup is triggered . Compose routing is injected as Docker labels into the compose YAML, not as a per-app YAML file; the label config persists until the next redeploy rewrites it.
- Preview domains — domain record is removed by FK cascade when the preview deployment is deleted; the
domain.deletemutation handles per-app removal only ifdomain.applicationIdis set.
ACME certificates: No Dokploy code path removes entries from Traefik's acme.json. When a domain is deleted, Traefik stops routing to it (once the YAML is updated) and eventually stops renewing the cert, but the entry in acme.json persists indefinitely . Manual pruning requires editing acme.json and restarting Traefik.
Project and Organization Deletion#
Both paths are DB-only — no infrastructure cleanup code runs at any level.
Project delete : Calls deleteProject() , which issues a simple DB delete. FK cascades propagate through environments → applications, databases, compose services, and project tags — removing all DB records. Running Docker services, Traefik config files, and source code directories for every service in the project are left untouched.
Organization delete : DB delete only, gated by a check that the user retains at least one owned organization. Cascades to projects, servers, members, and invitations. Same infrastructure orphan problem as project delete, compounded by also orphaning any servers registered to the org.
To avoid orphans, delete all services individually (triggering application.delete / compose.delete / database delete for each) before deleting the project or organization.
Orphaned Resource Reference#
| Deletion scenario | Docker service | Traefik YAML | Source code | ACME cert entry |
|---|---|---|---|---|
| Application deleted directly | ✅ cleaned | ✅ cleaned | ✅ cleaned | ❌ remains |
| Preview deleted via webhook / direct call | ✅ cleaned | ✅ cleaned | ✅ cleaned | ❌ remains |
| Parent application deleted (previews still active) | ❌ orphaned | ❌ orphaned | ❌ orphaned | ❌ remains |
| Domain deleted (regular app) | n/a | ✅ cleaned | n/a | ❌ remains |
| Domain deleted (compose) | n/a | ❌ labels persist | n/a | ❌ remains |
| Project or organization deleted | ❌ orphaned | ❌ orphaned | ❌ orphaned | ❌ remains |
Manual cleanup commands for orphaned resources:
- Docker service:
docker service rm <appName> - Traefik config:
rm /etc/dokploy/traefik/dynamic/<appName>.yml - Source code:
rm -rf /etc/dokploy/applications/<appName> - ACME: edit
/etc/dokploy/traefik/dynamic/acme.json, remove the domain entry, restart Traefik
Key Source Files#
| File | Role |
|---|---|
apps/dokploy/server/api/routers/application.ts | application.delete mutation |
packages/server/src/services/preview-deployment.ts | removePreviewDeployment() — preview teardown |
apps/dokploy/server/api/routers/domain.ts | domain.delete mutation |
packages/server/src/utils/traefik/domain.ts | removeDomain() — per-domain Traefik YAML update |
packages/server/src/utils/traefik/application.ts | removeTraefikConfig() — full YAML file deletion |
packages/server/src/utils/filesystem/directory.ts | removeDirectoryCode() — source code rm -rf |
packages/server/src/utils/docker/utils.ts | removeService() — docker service rm |
apps/dokploy/server/api/routers/organization.ts | Organization delete (DB-only, no infra cleanup) |
apps/dokploy/server/api/routers/project.ts | Project delete (DB-only, no infra cleanup) |