fix: inject backend network into compose file instead of docker network connect
The post-creation 'docker network connect' was failing silently for unknown reasons (race condition, container state, or Docker internals). Instead of fighting with this, we now inject the backend network directly into the compose file before 'docker compose up'. Docker Compose then attaches the container to the network atomically during creation. - apps/api/src/api/tool_instances.py: new _ensure_backend_network_in_compose() adds 'networks: [backend_name]' to the service and declares the network as external at the top level - apps/api/src/api/tool_instances.py: call _ensure_backend_network_in_compose() in both start_instance and restart_instance, right after _ensure_container_name_in_compose() - apps/api/src/api/tool_instances.py: removed connect_container_to_network import and call entirely - apps/api/src/api/tool_instances.py: import get_backend_network_name from docker module for use in the new helper Quality gates: ruff clean
This commit is contained in:
@@ -45,10 +45,10 @@ from src.services.config_profile_resolver import (
|
||||
resolve_profile,
|
||||
)
|
||||
from src.services.docker import (
|
||||
connect_container_to_network,
|
||||
ensure_instance_directory,
|
||||
execute_compose_command,
|
||||
find_free_port,
|
||||
get_backend_network_name,
|
||||
get_container_id,
|
||||
get_container_logs,
|
||||
get_container_status,
|
||||
@@ -750,6 +750,49 @@ def _ensure_web_bind_address(
|
||||
return
|
||||
|
||||
|
||||
def _ensure_backend_network_in_compose(compose_path: str) -> None:
|
||||
"""Inject the backend network into the compose file so compose up attaches it.
|
||||
|
||||
Instead of running 'docker network connect' after container creation (which
|
||||
is prone to race conditions and silent failures), we declare the network in
|
||||
the compose file itself. Docker Compose then connects the container to the
|
||||
network atomically during 'docker compose up'.
|
||||
"""
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
compose_file = Path(compose_path)
|
||||
if not compose_file.exists():
|
||||
return
|
||||
|
||||
content = compose_file.read_text()
|
||||
compose_data = yaml.safe_load(content)
|
||||
|
||||
if not compose_data or "services" not in compose_data:
|
||||
return
|
||||
|
||||
network_name = get_backend_network_name()
|
||||
modified = False
|
||||
|
||||
for svc_config in compose_data["services"].values():
|
||||
existing = svc_config.get("networks", [])
|
||||
if network_name not in existing:
|
||||
svc_config["networks"] = existing + [network_name]
|
||||
modified = True
|
||||
break # Only modify first service
|
||||
|
||||
# Declare the network as external at the top level
|
||||
if "networks" not in compose_data:
|
||||
compose_data["networks"] = {}
|
||||
if network_name not in compose_data["networks"]:
|
||||
compose_data["networks"][network_name] = {"external": True}
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
compose_file.write_text(yaml.dump(compose_data, default_flow_style=False))
|
||||
logger.info("Injected backend network '%s' into compose file", network_name)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/repositories/{repo_id}/instances",
|
||||
summary="Create tool instance",
|
||||
@@ -1690,6 +1733,7 @@ async def start_instance(
|
||||
|
||||
# Ensure predictable container name for tunnel connectivity
|
||||
_ensure_container_name_in_compose(instance.compose_path, instance.name)
|
||||
_ensure_backend_network_in_compose(instance.compose_path)
|
||||
|
||||
# Execute docker compose up with env file
|
||||
logger.debug(
|
||||
@@ -1729,20 +1773,6 @@ async def start_instance(
|
||||
"Container name for instance %s: %s", instance.id, expected_container_name
|
||||
)
|
||||
|
||||
# Connect container to backend network so API can reach it
|
||||
logger.debug(
|
||||
"Connecting container %s to backend network...", expected_container_name
|
||||
)
|
||||
connected = connect_container_to_network(expected_container_name)
|
||||
if connected:
|
||||
logger.debug(
|
||||
"Successfully connected %s to backend network", expected_container_name
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Failed to connect %s to backend network", expected_container_name
|
||||
)
|
||||
|
||||
# Verify container reached running state
|
||||
if instance.container_id:
|
||||
instance.status = "starting"
|
||||
@@ -2174,6 +2204,7 @@ async def restart_instance(
|
||||
instance.compose_path, tool_type.name, tool_type.default_port
|
||||
)
|
||||
_ensure_container_name_in_compose(instance.compose_path, instance.name)
|
||||
_ensure_backend_network_in_compose(instance.compose_path)
|
||||
|
||||
returncode, stdout, stderr = execute_compose_command(
|
||||
instance.compose_path, "restart"
|
||||
|
||||
Reference in New Issue
Block a user