Free monitoring resolved containers via a local Dockerode listContainers()
call, which only sees containers scheduled on the host running Dokploy.
For Stack services with tasks on other Swarm nodes, this always fell into
the "Container not running" branch even when the task was healthy
elsewhere, since local docker.listContainers() can't see remote-node
containers.
Now falls back to `docker service ps` (Swarm-aggregated, works from any
manager regardless of task placement) to tell a genuinely stopped
container apart from one running on another node, and reports that
distinction in the WS close reason instead of the misleading message.
Also fixes a client-side race where the stats websocket connected with
an empty appName on first mount (before the container selector settled),
and surfaces the close reason as a toast instead of only logging it.
Closes#5134. The container list queries in the Logs tab only fetched
once on mount, so if the underlying container was replaced (restart,
reschedule, redeploy) while the page stayed open, the native log
WebSocket kept streaming from the stale id and either froze or failed
with 'No such container'.
Poll the container list every 5s and reconcile the selected id against
it, matching the pattern already used for automatic selection.
Attaching Basic Auth or a Redirect to an application with no domains
wrote an empty routers/services block to the app's dynamic config
file. Traefik's file provider rejects that as invalid and aborts its
watcher, blocking config updates for every other application.
Fixes#5189
Compose deployments using a git provider (GitHub/GitLab/Gitea/Bitbucket/
custom Git) clone the repo verbatim, so composePath can point into a
subdirectory (e.g. deploy/docker-compose.yml). Docker Compose resolves
relative bind mounts (../files) against the compose file's own directory,
not against the code/ dir where file mounts are actually written, so the
mount silently binds an empty directory instead of the configured content.
Pin --project-directory to code/ when building the docker compose command
so relative mounts always resolve the same way raw deployments already do.
Fixes#5181
Network chart showed values labeled as KB/s, but the underlying data
is MB totals accumulated since boot. Convert to GB and relabel as
Network Total (since Boot).
Fixes#5115
Network sync compared only by name, so a network deleted and
recreated with the same name but different attributes (driver,
attachable, etc.) was reported as already in sync.
Store Docker's network Id (dockerId column) and compare it against
the live Id during sync. A name match with a dockerId mismatch is
now surfaced as a new "changed" state, with an Update action to
refresh the DB row from the live network.
Fixes#5179
Escaping in prepareEnvironmentVariablesForFile escaped every $,
including inside a deliberate ${VAR} reference, breaking Compose's
documented .env variable interpolation. Only escape $ that isn't
part of a ${IDENTIFIER} sequence.
Fixes#5151
Reaching a server implied being able to open an SSH root shell on it: the
/terminal websocket only checked that the server was in the caller's accessible
set, so granting a server to a developer necessarily granted root on it.
Add a server.terminal action, assignable on custom roles, and require it on the
remote-server terminal websocket on top of server access. Owner/admin keep it
through the enterprise bypass; the local host terminal stays owner/admin only.
A migration grants server.terminal to existing custom roles that already have
server.read, which is the permission that surfaces the terminal today, so
current setups keep working. Roles without a server entry are left alone.
Note: server.create still implies root execution (server.update persists
server.command and server.setup runs it over SSH), reflected in the Create
description in the role editor.
The swarm migration in 3848fa9c0 dropped the container.remove({force:true})
that the standalone deploy path used to run. Swarm tasks are named
dokploy-monitoring.<slot>.<id>, so there is no name collision and the
pre-v0.30.0 container survives every redeploy. It also stays pinned to an
orphaned image ID once pullRemoteImage moves the latest tag, so neither a
pull nor a Save clears it and it restarts forever.
Cloud setup spread metricsConfig straight from the row, shipping
cronJob: "" to the agent. robfig/cron rejects an empty spec, so the Go
binary exits before Fiber binds 4500 and Docker restarts it every ~60s.
- remove the legacy container in deployMonitoringService, which covers both
setupMonitoring and setupWebMonitoring. Cleanup is best effort: a failure
is logged and the deploy continues, matching the pre-migration behaviour
- default cronJob when configuring monitoring for cloud
- on build servers, clean up the legacy container but deploy no service.
They never join the swarm, yet cloud setup did create the standalone
container there before v0.30.0, and the monitoring form has always been
hidden for them, so those agents are all stuck with an empty cronJob
- cover the above with real-docker tests
The tag dialog's form is nested inside the project dialog's form in the
React tree, and React propagates events along the React tree rather than
the DOM tree, so submitting the tag form also submitted the project form.
The project was created immediately with no tags and the dialog closed
before more than one tag could be picked.
Stop propagation in the tag form's onSubmit, and select the new tag in
the selector so it is included when the project is saved.
Fixes#5117
Fixes#5108. The changePassword mutations for postgres/mysql/mariadb/mongo/redis
detected the running container via a shell one-liner (docker ps -q --filter
status=running | head -n1) executed through execAsync/execAsyncRemote. When that
shell command failed for any reason the CONTAINER_ID ended up empty and the
mutation incorrectly reported the container as not running even though it was.
Replaced it with getServiceContainer(), the existing dockerode-based lookup
already used by mount.ts and schedules/utils.ts for the same purpose.
Also fixes the postgres ALTER USER command, which never selected a database
and defaulted to a database named after the user, causing a false
'database "<user>" does not exist' failure whenever the db name differs
from the username.
Fixes#5092 — typing in the database/docker terminal overwrote text
instead of continuing on new lines. The xterm.js client never told the
backend PTY its actual size (node-pty/ssh2 always defaulted to 80x24
and were never resized), so whenever the rendered terminal width
differed from that, the remote shell's cursor math diverged from what
the client displayed.
- docker-container-terminal.ts / terminal.ts: pass the client's initial
cols/rows into node-pty spawn and ssh2 exec/shell, and resize the PTY
on a {type:"resize"} control message sent over the same ws channel.
- docker-terminal.tsx / settings/web-server/terminal.tsx: send cols/rows
on connect and on every term.onResize, and watch the container with a
ResizeObserver to refit on dialog/window resize.
Also fixes two related terminal/logs bugs surfaced while testing this:
- docker-terminal.tsx no longer opens a connection with the
"select-a-container" placeholder ID before a real container is
selected, and defers terminal creation a frame so React StrictMode's
dev-only phantom mount doesn't hit a known xterm.js dispose race
(xtermjs/xterm.js#5011, "Cannot read properties of undefined
(reading 'dimensions')").
- docker-logs-id.tsx (shared by application/compose/compose-stack logs)
had the same placeholder-ID issue, surfacing Docker's raw
"No such container: select-a-container" daemon error instead of a
proper empty state.