diff --git a/php/public/index.php b/php/public/index.php
index 2bc8091b..b113d6b0 100644
--- a/php/public/index.php
+++ b/php/public/index.php
@@ -70,6 +70,7 @@ $app->post('/api/docker/backup-check-repair', AIO\Controller\DockerController::c
$app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest');
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
+$app->post('/api/docker/prune', AIO\Controller\DockerController::class . ':SystemPrune');
$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs');
$app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin');
$app->get('/api/auth/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin');
diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php
index 5942e093..835ed6d5 100644
--- a/php/src/Controller/DockerController.php
+++ b/php/src/Controller/DockerController.php
@@ -328,6 +328,22 @@ readonly class DockerController {
return $nonbufResp;
}
+ public function SystemPrune(Request $request, Response $response, array $args) : Response {
+ // Get streaming response start and closure
+ $nonbufResp = $this->startStreamingResponse($response);
+
+ $body = $nonbufResp->getBody();
+ $addToStreamingResponseBody = function (string $message) use ($body) : void {
+ $body->write("
$message
");
+ };
+
+ $this->dockerActionManager->SystemPrune($addToStreamingResponseBody);
+
+ // End streaming response
+ $this->finalizeStreamingResponse($nonbufResp);
+ return $nonbufResp;
+ }
+
public function stopTopContainer() : void {
$id = self::TOP_CONTAINER;
$this->PerformRecursiveContainerStop($id);
diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php
index d5afef81..0e19bcd8 100644
--- a/php/src/Docker/DockerActionManager.php
+++ b/php/src/Docker/DockerActionManager.php
@@ -997,4 +997,71 @@ readonly class DockerActionManager {
return $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag);
}
}
+
+ public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void {
+ $endpoints = [
+ // Remove stopped containers
+ 'containers/prune',
+ // Remove unused images
+ 'images/prune',
+ // Remove unused volumes
+ 'volumes/prune',
+ // Remove unused networks
+ 'networks/prune',
+ // Prune build cache
+ 'build/prune',
+ ];
+
+ foreach ($endpoints as $endpoint) {
+ // Special-case images prune to include the dangling filter as requested
+ if ($endpoint === 'images/prune') {
+ $filters = json_encode(['dangling' => ['false']]);
+ $url = $this->BuildApiUrl($endpoint . '?filters=' . urlencode((string) $filters));
+ } else {
+ $url = $this->BuildApiUrl($endpoint);
+ }
+
+ if ($addToStreamingResponseBody !== null) {
+ $addToStreamingResponseBody("Running $endpoint...");
+ }
+
+ try {
+ $response = $this->guzzleClient->post($url);
+ if ($addToStreamingResponseBody !== null) {
+ $data = json_decode((string)$response->getBody(), true);
+ $deleted = 0;
+ foreach (['ContainersDeleted', 'ImagesDeleted', 'VolumesDeleted', 'NetworksDeleted', 'CachesDeleted'] as $key) {
+ if (isset($data[$key]) && is_array($data[$key])) {
+ $deleted += count($data[$key]);
+ }
+ }
+ $reclaimed = $data['SpaceReclaimed'] ?? 0;
+ $parts = [];
+ if ($deleted > 0) {
+ $parts[] = "$deleted item(s) deleted";
+ }
+ if ($reclaimed > 0) {
+ $i = (int)floor(log($reclaimed, 1024));
+ $parts[] = 'Space reclaimed: ' . (string)round($reclaimed / (1024 ** $i), 2) . ' ' . ['B','KB','MB','GB'][$i];
+ }
+ $addToStreamingResponseBody(!empty($parts) ? implode('. ', $parts) . '.' : 'Nothing to prune.');
+ }
+ } catch (RequestException $e) {
+ error_log(sprintf('Docker prune (%s) failed: %s', $endpoint, $e->getMessage()));
+ if ($addToStreamingResponseBody !== null) {
+ $addToStreamingResponseBody('Error: ' . $e->getMessage());
+ }
+ // continue with next prune step
+ }
+ }
+
+ if ($addToStreamingResponseBody !== null) {
+ $addToStreamingResponseBody("Docker system prune completed.");
+ sleep(1);
+
+ // We automatically reload after 10s so that the output can be read or copied if necessary
+ $addToStreamingResponseBody("Automatically reloading the page after 10s.");
+ sleep(10);
+ }
+ }
}
diff --git a/php/templates/containers.twig b/php/templates/containers.twig
index 17405256..207cfceb 100644
--- a/php/templates/containers.twig
+++ b/php/templates/containers.twig
@@ -582,6 +582,18 @@
{% if is_backup_container_running == false %}
{% if isApacheStarting == false %}
+ {% if isAnyRunning == true %}
+ Docker System Prune
+
+ Click here to reveal a button to prune the docker system.
+ By clicking the button below you can run "docker system prune -a". This will remove unused images, containers, networks, volumes and build cache. It will not delete data of running containers.
+
+
+ {% endif %}
AIO passphrase change
Click here to change your AIO passphrase