mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(docker-cleanup): clean after a resource stop only when the disk needs it
A stop or delete no longer queues a full cleanup each time. It skips the cleanup when one finished on the server in the last hour, and the queued run only cleans when disk usage is at or above the server cleanup threshold. The docker_cleanup API flag keeps its default of true.
This commit is contained in:
@@ -58,7 +58,7 @@ class StopApplication
|
||||
}
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::dispatch($server, false, false);
|
||||
CleanupDocker::dispatchAfterStop($server);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = $e->getMessage();
|
||||
|
||||
@@ -41,7 +41,7 @@ class StopDatabase
|
||||
}
|
||||
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::dispatch($server, false, false);
|
||||
CleanupDocker::dispatchAfterStop($server);
|
||||
}
|
||||
|
||||
if ($database->is_public) {
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Support\Actions\UniqueUntilProcessingJobDecorator;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Lorisleiva\Actions\Decorators\JobDecorator;
|
||||
use Lorisleiva\Actions\Decorators\UniqueJobDecorator;
|
||||
@@ -34,6 +35,12 @@ class CleanupDocker implements ShouldBeUnique
|
||||
*/
|
||||
public const QUEUED_DELAY = 60;
|
||||
|
||||
/**
|
||||
* A resource stop does not queue a cleanup when a cleanup finished on the server in this
|
||||
* window. The scheduled cleanup still runs.
|
||||
*/
|
||||
public const STOP_CLEANUP_COOLDOWN = 3600;
|
||||
|
||||
public int $jobTries = 1;
|
||||
|
||||
/**
|
||||
@@ -71,6 +78,24 @@ class CleanupDocker implements ShouldBeUnique
|
||||
return new UniqueUntilProcessingJobDecorator(static::class, ...$arguments);
|
||||
}
|
||||
|
||||
public static function lastRunCacheKey(Server $server): string
|
||||
{
|
||||
return 'docker-cleanup-last-run-'.$server->uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup after a resource stop: skipped when a cleanup ran recently, and the queued run
|
||||
* only cleans when the disk usage is at or above the server cleanup threshold.
|
||||
*/
|
||||
public static function dispatchAfterStop(Server $server): void
|
||||
{
|
||||
if (Cache::has(self::lastRunCacheKey($server))) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::dispatch($server, false, false, true);
|
||||
}
|
||||
|
||||
public function getJobUniqueId(Server $server): string
|
||||
{
|
||||
return $server->uuid;
|
||||
@@ -95,9 +120,16 @@ class CleanupDocker implements ShouldBeUnique
|
||||
];
|
||||
}
|
||||
|
||||
public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false)
|
||||
public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false, bool $onlyAboveThreshold = false)
|
||||
{
|
||||
$this->deadline = now()->getTimestamp() + self::REMOTE_COMMANDS_DEADLINE;
|
||||
|
||||
if ($onlyAboveThreshold) {
|
||||
$diskUsage = $server->getDiskUsage();
|
||||
if (is_numeric($diskUsage) && (int) $diskUsage < $server->settings->docker_cleanup_threshold) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
$helperImageVersion = getHelperVersion();
|
||||
$helperImage = coolifyHelperImage();
|
||||
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||
@@ -155,6 +187,8 @@ class CleanupDocker implements ShouldBeUnique
|
||||
}
|
||||
}
|
||||
|
||||
Cache::put(self::lastRunCacheKey($server), true, self::STOP_CLEANUP_COOLDOWN);
|
||||
|
||||
return $cleanupLog;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class StopService
|
||||
$service->deleteConnectedNetworks();
|
||||
}
|
||||
if ($dockerCleanup) {
|
||||
CleanupDocker::dispatch($server, false, false);
|
||||
CleanupDocker::dispatchAfterStop($server);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
|
||||
@@ -2926,7 +2926,7 @@ class ApplicationsController extends Controller
|
||||
),
|
||||
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
],
|
||||
responses: [
|
||||
@@ -4553,7 +4553,7 @@ class ApplicationsController extends Controller
|
||||
new OA\Parameter(
|
||||
name: 'docker_cleanup',
|
||||
in: 'query',
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).',
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.',
|
||||
schema: new OA\Schema(
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
|
||||
@@ -2738,7 +2738,7 @@ class DatabasesController extends Controller
|
||||
),
|
||||
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
],
|
||||
responses: [
|
||||
@@ -3428,7 +3428,7 @@ class DatabasesController extends Controller
|
||||
new OA\Parameter(
|
||||
name: 'docker_cleanup',
|
||||
in: 'query',
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).',
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.',
|
||||
schema: new OA\Schema(
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
|
||||
@@ -1005,7 +1005,7 @@ class ServicesController extends Controller
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
|
||||
new OA\Parameter(name: 'delete_from_coolify_only', in: 'query', required: false, description: 'Remove only Coolify metadata without deleting Docker resources.', schema: new OA\Schema(type: 'boolean', default: false)),
|
||||
],
|
||||
@@ -2184,7 +2184,7 @@ class ServicesController extends Controller
|
||||
new OA\Parameter(
|
||||
name: 'docker_cleanup',
|
||||
in: 'query',
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).',
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.',
|
||||
schema: new OA\Schema(
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
|
||||
+6
-6
@@ -2919,7 +2919,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Run docker cleanup.",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
@@ -4359,7 +4359,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Perform docker cleanup (prune networks, volumes, etc.).",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
@@ -6512,7 +6512,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Run docker cleanup.",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
@@ -8768,7 +8768,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Perform docker cleanup (prune networks, volumes, etc.).",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
@@ -18942,7 +18942,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Run docker cleanup.",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
@@ -19895,7 +19895,7 @@
|
||||
{
|
||||
"name": "docker_cleanup",
|
||||
"in": "query",
|
||||
"description": "Perform docker cleanup (prune networks, volumes, etc.).",
|
||||
"description": "Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
|
||||
+6
-6
@@ -1931,7 +1931,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Run docker cleanup.'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -2841,7 +2841,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
@@ -4203,7 +4203,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Run docker cleanup.'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -5765,7 +5765,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
@@ -11971,7 +11971,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Run docker cleanup.'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -12547,7 +12547,7 @@ paths:
|
||||
-
|
||||
name: docker_cleanup
|
||||
in: query
|
||||
description: 'Perform docker cleanup (prune networks, volumes, etc.).'
|
||||
description: 'Run docker cleanup when the server disk usage is at or above its cleanup threshold. Skipped when a cleanup ran on the server in the last hour.'
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
@@ -292,6 +292,40 @@ it('accepts a new queued cleanup once the waiting one is lost', function () {
|
||||
Queue::assertPushed(UniqueUntilProcessingJobDecorator::class, 2);
|
||||
});
|
||||
|
||||
it('does not queue a cleanup after a stop when a cleanup ran on the server recently', function () {
|
||||
Queue::fake();
|
||||
$cleanedServer = dockerCleanupReachableServer();
|
||||
$otherServer = dockerCleanupReachableServer();
|
||||
Cache::put(CleanupDocker::lastRunCacheKey($cleanedServer), true, CleanupDocker::STOP_CLEANUP_COOLDOWN);
|
||||
|
||||
CleanupDocker::dispatchAfterStop($cleanedServer);
|
||||
CleanupDocker::dispatchAfterStop($otherServer);
|
||||
|
||||
Queue::assertPushed(UniqueUntilProcessingJobDecorator::class, 1);
|
||||
Queue::assertPushed(UniqueUntilProcessingJobDecorator::class, fn (JobDecorator $job) => $job->getParameters()[0]->is($otherServer));
|
||||
});
|
||||
|
||||
it('cleans after a stop only when the disk usage reaches the server threshold', function (string $diskUsage, bool $cleans) {
|
||||
$server = dockerCleanupReachableServer();
|
||||
$server->settings()->update(['docker_cleanup_threshold' => 80]);
|
||||
$commands = collect();
|
||||
Process::fake(function ($process) use ($commands, $diskUsage) {
|
||||
$command = is_array($process->command) ? implode(' ', $process->command) : $process->command;
|
||||
$commands->push($command);
|
||||
|
||||
return Process::result(output: str_contains($command, 'df / --output=pcent') ? $diskUsage : '');
|
||||
});
|
||||
|
||||
CleanupDocker::run($server->fresh(), false, false, true);
|
||||
|
||||
expect($commands->contains(fn (string $command) => str_contains($command, 'docker builder prune')))->toBe($cleans)
|
||||
->and(Cache::has(CleanupDocker::lastRunCacheKey($server)))->toBe($cleans);
|
||||
})->with([
|
||||
'below threshold' => ['79', false],
|
||||
'at threshold' => ['80', true],
|
||||
'unknown usage' => ['', true],
|
||||
]);
|
||||
|
||||
it('scans application images with a fixed number of remote commands and keeps the retention rules', function () {
|
||||
$server = dockerCleanupReachableServer();
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
|
||||
|
||||
Reference in New Issue
Block a user