To Reproduce
- Create an Application with
sourceType = docker and set Docker Image to a floating tag — ghcr.io/<org>/<app>:latest.
- Deploy once. It works; the service starts on whatever
:latest resolves to at that moment.
- Publish a new image to the same
:latest tag from CI (new digest, same tag).
- Trigger a redeploy via the app's deploy webhook (
POST /api/deploy/<refreshToken>).
- Dokploy pulls the new image successfully and marks the deployment Done.
- The running container is still the old image.
Repeat steps 3–6 for a second release: still the old image. In my case production served build 1.2.0 across two subsequent releases (1.3.0 and 1.3.1), with every deployment reporting success.
Current vs. Expected behavior
Current: the deploy pulls the new digest onto the host, reports success, and the Swarm task is never replaced — the app keeps serving the previous build indefinitely. There is no error, no warning, and nothing in the deployment log that indicates the new image did not take effect.
Expected: after successfully pulling a newer digest for the configured tag, the deployed task runs that image. A deployment marked "Done" should mean the new image is running.
Provide environment information
- Dokploy **v0.30.5**
- Docker Swarm mode
- Application: `sourceType = docker`, image `ghcr.io/<org>/<app>:latest` (private GHCR, registry credentials configured)
- Deploys triggered by the per-app deploy webhook from GitHub Actions
- Host OS / arch: Ubuntu 24.04.4 LTS / x86_64
- Nodes: 1 managers / 1 workers (deployed on Manager)
- `docker version`: 29.8.0
Which area(s) are affected? (Select all that apply)
Application
Are you deploying the applications where Dokploy is installed or on a remote server?
Same server where Dokploy is installed
Additional context
The deployment log shows the pull working:
Digest: sha256:5a12fdf4f54f9bd48ff113490b367147e3a7d022cd22de3b6ad2c84f71d2b6b7
Status: Downloaded newer image for ghcr.io/<org>/<app>:latest
✅ Pulling image completed.
The new image really is on the host:
$ docker image inspect <image-id> -f '{{json .RepoDigests}}'
[
"ghcr.io/<org>/<app>@sha256:5a12fdf4f54f..."
]
But the running container is still the old build (the app bakes its version into a file at build time):
$ docker exec <container-id> cat /app/dist/build-revision.txt
1.2.0 # expected 1.3.1
…confirmed over HTTP from outside as well:
$ curl -s https://<app-domain>/build-revision.txt
1.2.0
And the service spec pins nothing — it carries the bare floating tag, with no @sha256: digest:
$ docker service inspect <app>-<suffix> --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'
ghcr.io/<org>/<app>:latest
Suspected root cause
mechanizeDockerContainer (packages/server/src/utils/builders/index.ts) builds the service spec with Image taken from getImageName(), which for sourceType === "docker" returns the configured dockerImage verbatim:
const getImageName = async (application: ApplicationNested) => {
const { appName, sourceType, dockerImage, registry, buildRegistry } = ...
if (sourceType === "docker") {
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
}
...
So when the configured image is a floating tag, the desired state is byte-identical on every deploy. The update does increment the force counter:
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
…but in practice the task is not being replaced with the newly pulled image. Because the spec stores an unresolved tag, nothing in the desired state ever references the digest that was just pulled.
Suggested fix: resolve the configured tag to an immutable digest at deploy time (after the pull) and write image@sha256:... into ContainerSpec.Image. That makes every deploy of a new build a genuine spec change that Swarm cannot treat as a no-op, and it has the side benefit of making docker service inspect show exactly which build is live instead of an opaque :latest. Deploying by digest is also the standard recommendation for Swarm services on mutable tags.
Will you send a PR to fix it?
No
To Reproduce
sourceType = dockerand set Docker Image to a floating tag —ghcr.io/<org>/<app>:latest.:latestresolves to at that moment.:latesttag from CI (new digest, same tag).POST /api/deploy/<refreshToken>).Repeat steps 3–6 for a second release: still the old image. In my case production served build
1.2.0across two subsequent releases (1.3.0and1.3.1), with every deployment reporting success.Current vs. Expected behavior
Current: the deploy pulls the new digest onto the host, reports success, and the Swarm task is never replaced — the app keeps serving the previous build indefinitely. There is no error, no warning, and nothing in the deployment log that indicates the new image did not take effect.
Expected: after successfully pulling a newer digest for the configured tag, the deployed task runs that image. A deployment marked "Done" should mean the new image is running.
Provide environment information
Which area(s) are affected? (Select all that apply)
Application
Are you deploying the applications where Dokploy is installed or on a remote server?
Same server where Dokploy is installed
Additional context
The deployment log shows the pull working:
The new image really is on the host:
But the running container is still the old build (the app bakes its version into a file at build time):
…confirmed over HTTP from outside as well:
And the service spec pins nothing — it carries the bare floating tag, with no
@sha256:digest:Suspected root cause
mechanizeDockerContainer(packages/server/src/utils/builders/index.ts) builds the service spec withImagetaken fromgetImageName(), which forsourceType === "docker"returns the configureddockerImageverbatim:So when the configured image is a floating tag, the desired state is byte-identical on every deploy. The update does increment the force counter:
…but in practice the task is not being replaced with the newly pulled image. Because the spec stores an unresolved tag, nothing in the desired state ever references the digest that was just pulled.
Suggested fix: resolve the configured tag to an immutable digest at deploy time (after the pull) and write
image@sha256:...intoContainerSpec.Image. That makes every deploy of a new build a genuine spec change that Swarm cannot treat as a no-op, and it has the side benefit of makingdocker service inspectshow exactly which build is live instead of an opaque:latest. Deploying by digest is also the standard recommendation for Swarm services on mutable tags.Will you send a PR to fix it?
No