To Reproduce
- Open an application's Logs tab in Dokploy.
- Redeploy or restart that same application while the Logs tab is open.
- Close the Logs tab after the new container has replaced the old container.
- Monitor the host's processes with
pidstat, top, or htop.
This was observed twice on the same single-node Docker Swarm host. It may depend on JSON log rotation/container replacement timing.
Current vs. Expected behavior
Current behavior
After the container is replaced, one or more Docker log requests started by Dokploy remain active even after the browser Logs tab is closed. The Docker daemon then consumes most of the host CPU indefinitely.
In the latest occurrence:
dockerd sustained approximately 314% CPU on a four-core host.
- The affected application was using approximately 1-3% CPU during direct sampling.
- Other containers were mostly idle.
- Closing the browser tab did not recover the CPU.
- Restarting only the Dokploy service immediately reduced
dockerd to approximately 18% CPU average, without restarting application containers.
A SIGUSR1 Docker daemon stack dump showed many log-reader goroutines, including:
github.com/docker/docker/api/server/httputils.WriteLogStream
github.com/docker/docker/daemon/logger/loggerutils.tailFiles
github.com/docker/docker/daemon/logger/jsonfilelog.getTailReader
github.com/docker/docker/pkg/tailfile.(*scanner).Scan
Several hot goroutines were repeatedly executing pread while resolving the last 300 lines across current and rotated *-json.log files. Some offsets were already beyond the current file size.
Expected behavior
Closing or navigating away from the Logs tab, and replacing the selected container, should cancel and terminate all associated server-side log commands. No Docker log reader should remain indefinitely or saturate the Docker daemon.
Provide environment information
Dokploy: v0.29.12
Docker Engine: 28.5.0
Docker API: 1.51
Kernel: Linux 6.8.0-134-generic x86_64
Deployment: Self-hosted, single-node Docker Swarm
Dokploy and applications: Same server
Logging driver: json-file
Which area(s) are affected?
Docker, Application, Docker Compose, Logs UI
Additional context
Dokploy's current getContainerLogs() implementation executes docker container logs --timestamps --tail ... and awaits the command:
|
export const getContainerLogs = async ( |
|
appNameOrId: string, |
|
tail = 100, |
|
since = "all", |
|
search?: string, |
|
serverId?: string | null, |
|
useContainerIdDirectly = false, |
|
): Promise<string> => { |
|
const exec = (cmd: string) => |
|
serverId ? execAsyncRemote(serverId, cmd) : execAsync(cmd); |
|
|
|
let target = appNameOrId; |
|
let isService = false; |
|
|
|
if (!useContainerIdDirectly) { |
|
// Find the real container ID by appName filter |
|
const findResult = await exec( |
|
`docker ps -q --filter "name=^${appNameOrId}" | head -1`, |
|
); |
|
const containerId = findResult.stdout.trim(); |
|
|
|
if (!containerId) { |
|
// Fallback: try as a swarm service |
|
const svcResult = await exec( |
|
`docker service ls -q --filter "name=${appNameOrId}" | head -1`, |
|
); |
|
const serviceId = svcResult.stdout.trim(); |
|
if (!serviceId) { |
|
throw new Error(`No container or service found for: ${appNameOrId}`); |
|
} |
|
isService = true; |
|
} else { |
|
target = containerId; |
|
} |
|
} |
|
|
|
const sinceFlag = since === "all" ? "" : `--since ${since}`; |
|
const baseCommand = isService |
|
? `docker service logs --timestamps --raw --tail ${tail} ${sinceFlag} ${target}` |
|
: `docker container logs --timestamps --tail ${tail} ${sinceFlag} ${target}`; |
|
|
|
const escapedSearch = search?.replace(/'/g, "'\\''") ?? ""; |
|
const command = search |
|
? `${baseCommand} 2>&1 | grep -iF '${escapedSearch}'` |
|
: `${baseCommand} 2>&1`; |
|
|
|
try { |
|
const result = await exec(command); |
|
return result.stdout; |
|
} catch (error: unknown) { |
|
if ( |
|
error && |
|
typeof error === "object" && |
|
"stdout" in error && |
|
typeof (error as { stdout: string }).stdout === "string" && |
|
(error as { stdout: string }).stdout.length > 0 |
|
) { |
|
return (error as { stdout: string }).stdout; |
|
} |
|
throw error; |
|
} |
There does not appear to be a timeout or abort/cancellation path around this command. If Docker's tail scanner gets stuck during container replacement or log rotation, the in-flight command can survive after the UI stops requesting new logs.
Potential safeguards:
- Apply a bounded timeout to every
docker logs command.
- Terminate the child process when the HTTP/tRPC request is cancelled or disconnected.
- Cancel outstanding log requests when the selected container is removed/replaced.
- Avoid overlapping polling requests for the same container.
Workaround
- Close Dokploy Logs tabs before redeploying.
- If
dockerd remains hot, restart only the Dokploy service to terminate the orphaned log commands.
- A full Docker daemon restart was not required in the latest occurrence.
Related issues
Issue #4661 also reports high CPU, but appears unrelated: it concerns a TimeoutNegativeWarning loop in the Dokploy Node.js process. In this report, the hot process was dockerd, and the stack dump directly identified Docker log-tail readers.
To Reproduce
pidstat,top, orhtop.This was observed twice on the same single-node Docker Swarm host. It may depend on JSON log rotation/container replacement timing.
Current vs. Expected behavior
Current behavior
After the container is replaced, one or more Docker log requests started by Dokploy remain active even after the browser Logs tab is closed. The Docker daemon then consumes most of the host CPU indefinitely.
In the latest occurrence:
dockerdsustained approximately 314% CPU on a four-core host.dockerdto approximately 18% CPU average, without restarting application containers.A
SIGUSR1Docker daemon stack dump showed many log-reader goroutines, including:Several hot goroutines were repeatedly executing
preadwhile resolving the last 300 lines across current and rotated*-json.logfiles. Some offsets were already beyond the current file size.Expected behavior
Closing or navigating away from the Logs tab, and replacing the selected container, should cancel and terminate all associated server-side log commands. No Docker log reader should remain indefinitely or saturate the Docker daemon.
Provide environment information
Which area(s) are affected?
Docker, Application, Docker Compose, Logs UI
Additional context
Dokploy's current
getContainerLogs()implementation executesdocker container logs --timestamps --tail ...and awaits the command:dokploy/packages/server/src/services/docker.ts
Lines 357 to 417 in df3965a
There does not appear to be a timeout or abort/cancellation path around this command. If Docker's tail scanner gets stuck during container replacement or log rotation, the in-flight command can survive after the UI stops requesting new logs.
Potential safeguards:
docker logscommand.Workaround
dockerdremains hot, restart only the Dokploy service to terminate the orphaned log commands.Related issues
Issue #4661 also reports high CPU, but appears unrelated: it concerns a
TimeoutNegativeWarningloop in the Dokploy Node.js process. In this report, the hot process wasdockerd, and the stack dump directly identified Docker log-tail readers.