Skip to content

Docker-image app on a floating tag (:latest): deploy pulls the new digest, reports success, but the Swarm task keeps running the old image #5496

Description

@yoavniran

To Reproduce

  1. Create an Application with sourceType = docker and set Docker Image to a floating tag — ghcr.io/<org>/<app>:latest.
  2. Deploy once. It works; the service starts on whatever :latest resolves to at that moment.
  3. Publish a new image to the same :latest tag from CI (new digest, same tag).
  4. Trigger a redeploy via the app's deploy webhook (POST /api/deploy/<refreshToken>).
  5. Dokploy pulls the new image successfully and marks the deployment Done.
  6. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions