Show live progress while pruning networks, and ask first (0.63.0)
CI / check (push) Successful in 13m10s
CI / build-and-push (push) Successful in 1m57s

The network prune joins the image and volume prunes on the shared
PruneJob: it runs in the background, the Networks page shows a progress
bar and a toast with the outcome, and the button now asks before deleting
instead of pruning on click. Docker has no -a for networks and they hold
no data, so there is no option and no space figure.

Candidates are stricter than docker network prune, which goes by active
endpoints and so removes a stopped container's network, after which that
container fails to start ("network not found"). A network any container,
running or stopped, is attached to (by name or id) is kept, as are
bridge/host/none, ingress/docker_gwbridge and swarm-scoped networks.
Removal is per network, so one the daemon refuses does not stop the rest.

- POST /api/networks/prune takes background=; without it it still waits
  and returns {NetworksDeleted}. GET /api/networks/prune-status is
  admin-only and registered before /{network_id}, which would otherwise
  take "prune-status" for an id.
- The long-timeout test for network prune in test_docker_timeouts.py is
  superseded by test_network_prune.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
menzeljandClaude Opus 5 committed 2026-09-18 10:49:29 +02:00
1 parent 57231e4a67
commit ee98d0da6d
12 files changed
+293 -61

No files matched your search

+65 -3
View File
@@ -1,15 +1,18 @@
"""Docker network management."""
from __future__ import annotations
from typing import Optional
from typing import Callable, Optional
from docker.errors import APIError
from docker.types import IPAMConfig, IPAMPool
from docker_client import DockerError, get_client, get_long_client, safe_call
from services.prune_job import PruneJob, PrunePlan
COMPOSE_PROJECT_LABEL = "com.docker.compose.project"
DEFAULT_NETWORKS = {"bridge", "host", "none"}
#: Networks the daemon manages for swarm; never a prune's to remove.
_SWARM_NETWORKS = {"ingress", "docker_gwbridge"}
def _summary(net) -> dict:
@@ -132,6 +135,65 @@ def delete_network(network_id: str) -> None:
safe_call(net.remove)
def prune_networks() -> dict:
_prune = PruneJob("network")
def prune_status() -> dict:
return _prune.status()
def _candidates(client) -> list[dict]:
"""The networks no container, running or stopped, is attached to.
Stricter than ``docker network prune``, which goes by active endpoints and
so removes a stopped container's network, after which that container no
longer starts ("network not found").
"""
in_use: set[str] = set()
for c in safe_call(client.api.containers, all=True):
networks = (c.get("NetworkSettings") or {}).get("Networks") or {}
for name, settings in networks.items():
in_use.add(name)
if (settings or {}).get("NetworkID"):
in_use.add(settings["NetworkID"])
return [
n
for n in safe_call(client.api.networks)
if n["Name"] not in DEFAULT_NETWORKS | _SWARM_NETWORKS
and n.get("Scope", "local") == "local"
and n["Name"] not in in_use
and n["Id"] not in in_use
]
def _plan() -> PrunePlan:
client = get_long_client()
return safe_call(client.networks.prune)
def remove(network: dict) -> tuple[list, bool]:
# The daemon refuses if a container was attached since the listing.
safe_call(client.api.remove_network, network["Id"])
return [network["Name"]], True
def finish(names: list) -> tuple[dict, Optional[int]]:
# Networks hold no data: nothing to measure.
return {"NetworksDeleted": names}, None
return PrunePlan(
all=False,
items=_candidates(client),
label=lambda network: network["Name"],
remove=remove,
finish=finish,
)
def prune_networks() -> dict:
"""Remove unused networks and wait for the result. Progress is visible
through prune_status() while it runs."""
return _prune.run(_plan)
def start_prune(on_done: Optional[Callable[[dict], None]] = None) -> dict:
"""Start a prune in the background and return its initial status; the UI
polls prune_status(). ``on_done`` gets what prune_networks() would return."""
return _prune.start(_plan, on_done)
+1 -1
View File
@@ -1,4 +1,4 @@
"""Prunes with visible progress, shared by images and volumes.
"""Prunes with visible progress, shared by images, volumes and networks.
``docker image prune`` / ``docker volume prune`` are single blocking calls that
report nothing until they are done, which on a host with a lot to delete means