Description
docker compose up -d --dry-run (with -d/--detach) does not stay dry
when it decides a service needs to be recreated: it performs a real
stop/remove of the old container, creates a new one, and renames it into
place — exactly as a real up -d would — despite the --dry-run flag.
No error, warning, or indication is given that a real change was applied.
The output itself also looks like normal dry-run output (Container <name> Recreate / Recreated), but crucially is missing the DRY-RUN MODE -
prefix that a correctly-dry run shows on every line (see the example
output in #13040 / PR #13042, which fixed a related build-path issue).
Dropping -d (running in the foreground) correctly stays dry for the same
scenario — the old container is left untouched. So the bug appears
specific to the -d/detached code path for the up/recreate step, not
dry-run handling in general.
This is a real-world data-loss risk: any tooling that uses docker compose up -d --dry-run to check whether an update is available (a common
pattern) will silently apply that update instead of just detecting it.
Steps To Reproduce
- Create a minimal compose project and bring it up for real:
# compose.yaml
services:
test:
image: nginx:alpine
container_name: dryrun-test
environment:
- FOO=bar
- Change a value that forces a Recreate (e.g.
FOO=bar -> FOO=baz).
- Run:
docker compose up -d --dry-run
- Check the container's actual state:
Expected behavior
The original container (age/ID) is unchanged — --dry-run should not
apply anything, per its name and documented purpose.
Actual behavior
Container dryrun-test Recreate
Container dryrun-test Recreated
Container 015cbd0f386c_dryrun-test Starting
Container 015cbd0f386c_dryrun-test Started
--- state immediately after dry-run ---
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
dryrun-test nginx:alpine "/docker-entrypoint.…" test 23 seconds ago Up 22 seconds 80/tcp
The container was genuinely swapped for a new one (new image ID, fresh
CREATED/uptime) — not left as-is. Running the same reproduction without
-d (docker compose up --dry-run, foreground) correctly leaves the
original container untouched, confirming the -d combination specifically
is what breaks dry-run's guarantee.
Compose Version
Docker Compose version v5.4.0
Docker Environment
Server: 29.7.2
Debian GNU/Linux 13 (trixie), kernel 6.12.96+deb13-amd64
docker-compose-plugin 5.4.0-1~debian.13~trixie (previously 5.3.1, upgraded
via the standard Docker apt repo)
Anything else?
Possibly related to #13040 ("build --dry-run actually builds the
image", fixed by PR #13042) — but that fix was scoped to the build/bake
path and its own example output correctly shows the DRY-RUN MODE -
prefix on every line, which is absent entirely from the output above,
suggesting the up -d/recreate path takes a different, still-broken code
path.
Description
docker compose up -d --dry-run(with-d/--detach) does not stay drywhen it decides a service needs to be recreated: it performs a real
stop/remove of the old container, creates a new one, and renames it into
place — exactly as a real
up -dwould — despite the--dry-runflag.No error, warning, or indication is given that a real change was applied.
The output itself also looks like normal dry-run output (
Container <name> Recreate/Recreated), but crucially is missing theDRY-RUN MODE -prefix that a correctly-dry run shows on every line (see the example
output in #13040 / PR #13042, which fixed a related build-path issue).
Dropping
-d(running in the foreground) correctly stays dry for the samescenario — the old container is left untouched. So the bug appears
specific to the
-d/detached code path for the up/recreate step, notdry-run handling in general.
This is a real-world data-loss risk: any tooling that uses
docker compose up -d --dry-runto check whether an update is available (a commonpattern) will silently apply that update instead of just detecting it.
Steps To Reproduce
FOO=bar->FOO=baz).Expected behavior
The original container (age/ID) is unchanged —
--dry-runshould notapply anything, per its name and documented purpose.
Actual behavior
The container was genuinely swapped for a new one (new image ID, fresh
CREATED/uptime) — not left as-is. Running the same reproduction without-d(docker compose up --dry-run, foreground) correctly leaves theoriginal container untouched, confirming the
-dcombination specificallyis what breaks dry-run's guarantee.
Compose Version
Docker Environment
Anything else?
Possibly related to #13040 ("
build --dry-runactually builds theimage", fixed by PR #13042) — but that fix was scoped to the build/bake
path and its own example output correctly shows the
DRY-RUN MODE -prefix on every line, which is absent entirely from the output above,
suggesting the
up -d/recreate path takes a different, still-broken codepath.