Merge pull request #3545 from peaklabs-dev/improve-cleanup

Feat: Improve Docker cleanup
This commit is contained in:
Andras Bacsai 2024-09-27 16:41:03 +02:00 committed by GitHub
commit 6726964f18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 135 additions and 33 deletions

View file

@ -12,28 +12,29 @@ class CleanupDocker
public function handle(Server $server)
{
$settings = InstanceSettings::get();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";
$commands = $this->getCommands();
$commands = [
'docker container prune -f --filter "label=coolify.managed=true"',
'docker image prune -af --filter "label!=coolify.managed=true"',
'docker builder prune -af',
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
];
$serverSettings = $server->settings;
if ($serverSettings->delete_unused_volumes) {
$commands[] = 'docker volume prune -af';
}
if ($serverSettings->delete_unused_networks) {
$commands[] = 'docker network prune -f';
}
foreach ($commands as $command) {
instant_remote_process([$command], $server, false);
}
}
private function getCommands(): array
{
$settings = InstanceSettings::get();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
$commonCommands = [
'docker container prune -f --filter "label=coolify.managed=true"',
'docker image prune -af --filter "label!=coolify.managed=true"',
'docker builder prune -af',
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
];
return $commonCommands;
}
}

View file

@ -23,7 +23,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public ?string $usageBefore = null;
public function __construct(public Server $server) {}
public function __construct(public Server $server, public bool $manualCleanup = false) {}
public function handle(): void
{
@ -31,10 +31,10 @@ public function handle(): void
if (! $this->server->isFunctional()) {
return;
}
if ($this->server->settings->force_docker_cleanup) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server);
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
Log::info('DockerCleanupJob ' . ($this->manualCleanup ? 'manual' : 'force') . ' cleanup on ' . $this->server->name);
CleanupDocker::run(server: $this->server);
return;
}

View file

@ -7,6 +7,7 @@
use App\Jobs\PullSentinelImageJob;
use App\Models\Server;
use Livewire\Component;
use App\Jobs\DockerCleanupJob;
class Form extends Component
{
@ -24,6 +25,9 @@ class Form extends Component
public $timezones;
public $delete_unused_volumes = false;
public $delete_unused_networks = false;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
@ -58,6 +62,8 @@ public function getListeners()
'server.settings.force_docker_cleanup' => 'required|boolean',
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
'server.settings.delete_unused_volumes' => 'boolean',
'server.settings.delete_unused_networks' => 'boolean',
];
protected $validationAttributes = [
@ -79,6 +85,8 @@ public function getListeners()
'server.settings.metrics_history_days' => 'Metrics History',
'server.settings.is_server_api_enabled' => 'Server API',
'server.settings.server_timezone' => 'Server Timezone',
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
];
public function mount(Server $server)
@ -88,6 +96,8 @@ public function mount(Server $server)
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
}
public function updated($field)
@ -137,6 +147,7 @@ public function instantSave()
try {
refresh_server_connection($this->server->privateKey);
$this->validateServer(false);
$this->server->settings->save();
$this->server->save();
$this->dispatch('success', 'Server updated.');
@ -154,6 +165,7 @@ public function instantSave()
ray('Sentinel is not enabled');
StopSentinel::dispatch($this->server);
}
$this->server->settings->save();
// $this->checkPortForServerApi();
} catch (\Throwable $e) {
@ -234,9 +246,9 @@ public function submit()
$this->server->settings->server_timezone = $newTimezone;
$this->server->settings->save();
}
$this->server->settings->save();
$this->server->save();
$this->dispatch('success', 'Server updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
@ -250,6 +262,15 @@ public function updatedServerSettingsServerTimezone($value)
$this->dispatch('success', 'Server timezone updated.');
}
public function manualCleanup()
{
try {
DockerCleanupJob::dispatch($this->server, true);
$this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function manualCloudflareConfig()
{
$this->server->settings->is_cloudflare_tunnel = true;

View file

@ -36,6 +36,8 @@
'validation_logs' => ['type' => 'string'],
'log_drain_notification_sent' => ['type' => 'boolean'],
'swarm_cluster' => ['type' => 'string'],
'delete_unused_volumes' => ['type' => 'boolean'],
'delete_unused_networks' => ['type' => 'boolean'],
]
)]
@ -105,6 +107,8 @@ protected static function booted()
'proxy' => SchemalessAttributes::class,
'logdrain_axiom_api_key' => 'encrypted',
'logdrain_newrelic_license_key' => 'encrypted',
'delete_unused_volumes' => 'boolean',
'delete_unused_networks' => 'boolean',
];
protected $schemalessAttributes = [

View file

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('server_settings', function (Blueprint $table) {
$table->boolean('delete_unused_volumes')->default(false);
$table->boolean('delete_unused_networks')->default(false);
});
}
public function down()
{
Schema::table('server_settings', function (Blueprint $table) {
$table->dropColumn('delete_unused_volumes');
$table->dropColumn('delete_unused_networks');
});
}
};

View file

@ -194,26 +194,78 @@ class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-co
@if ($server->isFunctional())
<h3 class="pt-4">Settings</h3>
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
<div class="flex flex-wrap items-center gap-4">
<div class="w-64">
<x-forms.checkbox
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
helper="Enabling Force Docker Cleanup or manually triggering a cleanup will perform the following actions:
<ul class='list-disc pl-4 mt-2'>
<li>Removes stopped containers manged by Coolify (as containers are none persistent, no data will be lost).</li>
<li>Deletes unused images.</li>
<li>Clears build cache.</li>
<li>Removes old versions of the Coolify helper image.</li>
<li>Optionally delete unused volumes (if enabled in advanced options).</li>
<li>Optionally remove unused networks (if enabled in advanced options).</li>
</ul>"
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
</div>
@if ($server->settings->force_docker_cleanup)
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
label="Docker cleanup frequency" required
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
@else
<x-modal-confirmation
title="Confirm Docker Cleanup?"
buttonTitle="Trigger Docker Cleanup"
submitAction="manualCleanup"
:actions="[
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
'Permanently deletes all unused images',
'Clears build cache',
'Removes old versions of the Coolify helper image',
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
'Optionally permanently deletes all unused networks (if enabled in advanced options).'
]"
:confirmWithText="false"
:confirmWithPassword="false"
step2ButtonText="Trigger Docker Cleanup"
/>
</div>
@if ($server->settings->force_docker_cleanup)
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
label="Docker cleanup frequency" required
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
@else
<x-forms.input id="server.settings.docker_cleanup_threshold"
label="Docker cleanup threshold (%)" required
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
@endif
@endif
<div x-data="{ open: false }" class="mt-4 max-w-md">
<button @click="open = !open" type="button" class="flex items-center justify-between w-full text-left text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
<span>Advanced Options</span>
<svg :class="{'rotate-180': open}" class="w-5 h-5 transition-transform duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
<div x-show="open" class="mt-2 space-y-2">
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2"><strong>Warning: Enable these options only if you fully understand their implications and consequences!</strong><br>Improper use will result in data loss and could cause functional issues.</p>
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes" label="Delete Unused Volumes"
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
<ul class='list-disc pl-4 mt-2'>
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
<li>Data from stopped containers volumes will be permanently lost.</li>
<li>No way to recover deleted volume data.</li>
</ul>"
/>
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks" label="Delete Unused Networks"
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
<ul class='list-disc pl-4 mt-2'>
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
<li>Custom networks for stopped containers will be permanently deleted.</li>
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
</ul>"
/>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<div class="flex flex-wrap gap-4 sm:flex-nowrap">
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required