From 3f6afd63b6099b7f582cadfa64643da75dd62b9c Mon Sep 17 00:00:00 2001 From: Monika Kairaityte Date: Mon, 20 Jul 2026 19:34:05 +0300 Subject: [PATCH] Stop remaining containers on `--abort-on-container-exit` Before this change, when using `--abort-on-container-exit` or `--exit-code-from`, exiting one container would leave the remaining containers running in the background. Now, remaining containers are properly stopped when one container exits. Signed-off-by: Monika Kairaityte --- ...t_on_container_exit_stop_containers.bugfix | 1 + podman_compose.py | 9 ++++- .../test_podman_compose_exit_from.py | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 newsfragments/abort_on_container_exit_stop_containers.bugfix diff --git a/newsfragments/abort_on_container_exit_stop_containers.bugfix b/newsfragments/abort_on_container_exit_stop_containers.bugfix new file mode 100644 index 00000000..1b064c34 --- /dev/null +++ b/newsfragments/abort_on_container_exit_stop_containers.bugfix @@ -0,0 +1 @@ +Fixed `--abort-on-container-exit` and `--exit-code-from` so that remaining containers are explicitly stopped when one container exits, instead of being left running in the background. diff --git a/podman_compose.py b/podman_compose.py index 3a90cc0a..2001c77e 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -1936,6 +1936,7 @@ async def run( # pylint: disable=dangerous-default-value log_formatter: str | None = None, *, suppress_output: bool = False, + container_name: str | None = None, # Intentionally mutable default argument to hold references to tasks task_reference: set[asyncio.Task] = set(), ) -> int | None: @@ -1988,6 +1989,12 @@ async def run( # pylint: disable=dangerous-default-value except asyncio.CancelledError: log.info("Sending termination signal") p.terminate() + if container_name: + log.info("Stopping container %s", container_name) + try: + await self.run([], "stop", ["-t", "10", container_name]) + except Exception as e: # pylint: disable=broad-exception-caught + log.warning("Error stopping container %s: %s", container_name, e) try: exit_code = await wait_with_timeout(p.wait(), 10) except TimeoutError: @@ -3893,7 +3900,7 @@ async def run_container( # start the container log.debug("Starting task for container %s", name) return await compose.podman.run( # type: ignore[misc] - *command, log_formatter=log_formatter, suppress_output=suppress_output + *command, log_formatter=log_formatter, suppress_output=suppress_output, container_name=name ) diff --git a/tests/integration/exit_from/test_podman_compose_exit_from.py b/tests/integration/exit_from/test_podman_compose_exit_from.py index 326c0813..fb247e09 100644 --- a/tests/integration/exit_from/test_podman_compose_exit_from.py +++ b/tests/integration/exit_from/test_podman_compose_exit_from.py @@ -65,3 +65,43 @@ def test_podman_compose_exit_from(self) -> None: self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh1"], 1) self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh2"], 2) + + def test_abort_all_containers_on_container_exit(self) -> None: + try: + out, err, returncode = self.run_subprocess( + [ + podman_compose_path(), + "-f", + compose_yaml_path(), + "up", + "--abort-on-container-exit", + ], + timeout=30, + ) + # After one container exits, the others should be stopped. + # The command should complete within the timeout (all containers stop). + self.assertNotEqual( + returncode, -9, "Command was killed by timeout, containers were not stopped" + ) + + # Verify all containers are stopped + out2, _, _ = self.run_subprocess([ + "podman", + "ps", + "-a", + "--filter", + "label=io.podman.compose.project=exit_from", + "--format", + "{{.Names}} {{.Status}}", + ]) + self.assertEqual( + out2.decode("utf-8").strip().count("Exited"), + 2, + ) + finally: + self.run_subprocess_assert_returncode([ + podman_compose_path(), + "-f", + compose_yaml_path(), + "down", + ])