Skip to content

Commit d6044cb

Browse files
smackeseyDagster Devtools
authored andcommitted
Fix flaky docker compose teardown by force-disconnecting external containers (#21714)
## Summary & Motivation Fixes a flaky docker compose teardown in integration tests (e.g. `docker_dagster_cloud-docker_earliest_release`). The flake occurs when `docker compose down` fails because external containers (like the Buildkite agent container) are still connected to a compose-managed network. Two changes: 1. **New `_force_disconnect_external_containers` step before `docker compose down`**: inspects the compose network, identifies containers not managed by the compose file, and force-disconnects them. This ensures `docker compose down` can cleanly remove its networks. 2. **`disconnect_container_from_network` now uses `--force`**: the `--force` flag disconnects even if the container is in a transient/stopping state. ## Test Plan - This is a teardown-only change to test infrastructure; the fix addresses a race condition that is only reproducible in CI (Buildkite Docker environment). - Verified the logic by reading the Docker CLI docs for `docker network disconnect --force` and `docker network inspect`. Internal-RevId: b6bd757f44362f85b1723d890706f393b60b2863
1 parent e71845f commit d6044cb

1 file changed

Lines changed: 54 additions & 4 deletions

File tree

python_modules/dagster-test/dagster_test/fixtures/docker_compose.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,54 @@ def docker_compose_up(docker_compose_yml, context, service, env_file, no_build:
118118
subprocess.check_call(compose_command, env=docker_env)
119119

120120

121+
def _force_disconnect_external_containers(docker_compose_yml, docker_env):
122+
"""Force-disconnect any non-compose containers still attached to compose networks.
123+
124+
`docker compose down` fails if external containers (e.g. the Buildkite agent container)
125+
are still connected to a compose-managed network. This preemptively cleans those up so
126+
that `docker compose down` can remove networks without error.
127+
"""
128+
network_name = network_name_from_yml(docker_compose_yml)
129+
try:
130+
output = subprocess.check_output(
131+
[
132+
"docker",
133+
"network",
134+
"inspect",
135+
network_name,
136+
"--format",
137+
"{{range .Containers}}{{.Name}} {{end}}",
138+
],
139+
env=docker_env,
140+
stderr=subprocess.DEVNULL,
141+
)
142+
connected = output.decode().split()
143+
except subprocess.CalledProcessError:
144+
return # Network doesn't exist or already removed
145+
146+
# Get the list of containers managed by this compose file
147+
try:
148+
compose_output = subprocess.check_output(
149+
["docker", "compose", "--file", str(docker_compose_yml), "ps", "--format", "{{.Name}}"],
150+
env=docker_env,
151+
stderr=subprocess.DEVNULL,
152+
)
153+
compose_containers = set(compose_output.decode().splitlines())
154+
except subprocess.CalledProcessError:
155+
compose_containers = set()
156+
157+
for container in connected:
158+
if container and container not in compose_containers:
159+
logging.info(
160+
f"Force-disconnecting external container {container} from network {network_name}"
161+
)
162+
subprocess.run(
163+
["docker", "network", "disconnect", "--force", network_name, container],
164+
env=docker_env,
165+
check=False,
166+
)
167+
168+
121169
def docker_compose_down(docker_compose_yml, context, service, env_file):
122170
docker_env = os.environ.copy()
123171
if docker_env.get("BUILDKITE"):
@@ -132,17 +180,17 @@ def docker_compose_down(docker_compose_yml, context, service, env_file):
132180

133181
if service:
134182
compose_command += ["--file", str(docker_compose_yml), "down", "--volumes", service]
135-
subprocess.check_call(compose_command, env=docker_env)
136-
137183
else:
184+
_force_disconnect_external_containers(docker_compose_yml, docker_env)
138185
compose_command += [
139186
"--file",
140187
str(docker_compose_yml),
141188
"down",
142189
"--volumes",
143190
"--remove-orphans",
144191
]
145-
subprocess.check_call(compose_command, env=docker_env)
192+
193+
subprocess.check_call(compose_command, env=docker_env)
146194

147195

148196
def list_containers():
@@ -190,7 +238,9 @@ def disconnect_container_from_network(container, network):
190238
if env.get("BUILDKITE"):
191239
env["DOCKER_API_VERSION"] = "1.41"
192240
try:
193-
subprocess.check_call(["docker", "network", "disconnect", network, container], env=env)
241+
subprocess.check_call(
242+
["docker", "network", "disconnect", "--force", network, container], env=env
243+
)
194244
logging.info(f"Disconnected {container} from network {network}.")
195245
except subprocess.CalledProcessError:
196246
logging.warning(f"Unable to disconnect {container} from network {network}.")

0 commit comments

Comments
 (0)