Skip to content

Commit 6726964

Browse files
authored
Merge pull request coollabsio#3545 from peaklabs-dev/improve-cleanup
Feat: Improve Docker cleanup
2 parents 96b8ddf + 9a766ae commit 6726964

File tree

6 files changed

+131
-29
lines changed

6 files changed

+131
-29
lines changed

app/Actions/Server/CleanupDocker.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,30 @@ class CleanupDocker
1111
use AsAction;
1212

1313
public function handle(Server $server)
14-
{
15-
16-
$commands = $this->getCommands();
17-
18-
foreach ($commands as $command) {
19-
instant_remote_process([$command], $server, false);
20-
}
21-
}
22-
23-
private function getCommands(): array
2414
{
2515
$settings = InstanceSettings::get();
2616
$helperImageVersion = data_get($settings, 'helper_version');
2717
$helperImage = config('coolify.helper_image');
28-
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
18+
$helperImageWithVersion = "$helperImage:$helperImageVersion";
2919

30-
$commonCommands = [
20+
$commands = [
3121
'docker container prune -f --filter "label=coolify.managed=true"',
3222
'docker image prune -af --filter "label!=coolify.managed=true"',
3323
'docker builder prune -af',
34-
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
24+
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
3525
];
3626

37-
return $commonCommands;
27+
$serverSettings = $server->settings;
28+
if ($serverSettings->delete_unused_volumes) {
29+
$commands[] = 'docker volume prune -af';
30+
}
31+
32+
if ($serverSettings->delete_unused_networks) {
33+
$commands[] = 'docker network prune -f';
34+
}
35+
36+
foreach ($commands as $command) {
37+
instant_remote_process([$command], $server, false);
38+
}
3839
}
3940
}

app/Jobs/DockerCleanupJob.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
2323

2424
public ?string $usageBefore = null;
2525

26-
public function __construct(public Server $server) {}
26+
public function __construct(public Server $server, public bool $manualCleanup = false) {}
2727

2828
public function handle(): void
2929
{
3030
try {
3131
if (! $this->server->isFunctional()) {
3232
return;
3333
}
34-
if ($this->server->settings->force_docker_cleanup) {
35-
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
36-
CleanupDocker::run(server: $this->server);
3734

35+
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
36+
Log::info('DockerCleanupJob ' . ($this->manualCleanup ? 'manual' : 'force') . ' cleanup on ' . $this->server->name);
37+
CleanupDocker::run(server: $this->server);
3838
return;
3939
}
4040

app/Livewire/Server/Form.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Jobs\PullSentinelImageJob;
88
use App\Models\Server;
99
use Livewire\Component;
10+
use App\Jobs\DockerCleanupJob;
1011

1112
class Form extends Component
1213
{
@@ -24,6 +25,9 @@ class Form extends Component
2425

2526
public $timezones;
2627

28+
public $delete_unused_volumes = false;
29+
public $delete_unused_networks = false;
30+
2731
public function getListeners()
2832
{
2933
$teamId = auth()->user()->currentTeam()->id;
@@ -58,6 +62,8 @@ public function getListeners()
5862
'server.settings.force_docker_cleanup' => 'required|boolean',
5963
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
6064
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
65+
'server.settings.delete_unused_volumes' => 'boolean',
66+
'server.settings.delete_unused_networks' => 'boolean',
6167
];
6268

6369
protected $validationAttributes = [
@@ -79,6 +85,8 @@ public function getListeners()
7985
'server.settings.metrics_history_days' => 'Metrics History',
8086
'server.settings.is_server_api_enabled' => 'Server API',
8187
'server.settings.server_timezone' => 'Server Timezone',
88+
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
89+
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
8290
];
8391

8492
public function mount(Server $server)
@@ -88,6 +96,8 @@ public function mount(Server $server)
8896
$this->wildcard_domain = $this->server->settings->wildcard_domain;
8997
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
9098
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
99+
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
100+
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
91101
}
92102

93103
public function updated($field)
@@ -137,6 +147,7 @@ public function instantSave()
137147
try {
138148
refresh_server_connection($this->server->privateKey);
139149
$this->validateServer(false);
150+
140151
$this->server->settings->save();
141152
$this->server->save();
142153
$this->dispatch('success', 'Server updated.');
@@ -154,6 +165,7 @@ public function instantSave()
154165
ray('Sentinel is not enabled');
155166
StopSentinel::dispatch($this->server);
156167
}
168+
$this->server->settings->save();
157169
// $this->checkPortForServerApi();
158170

159171
} catch (\Throwable $e) {
@@ -234,9 +246,9 @@ public function submit()
234246
$this->server->settings->server_timezone = $newTimezone;
235247
$this->server->settings->save();
236248
}
237-
238249
$this->server->settings->save();
239250
$this->server->save();
251+
240252
$this->dispatch('success', 'Server updated.');
241253
} catch (\Throwable $e) {
242254
return handleError($e, $this);
@@ -250,6 +262,15 @@ public function updatedServerSettingsServerTimezone($value)
250262
$this->dispatch('success', 'Server timezone updated.');
251263
}
252264

265+
public function manualCleanup()
266+
{
267+
try {
268+
DockerCleanupJob::dispatch($this->server, true);
269+
$this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
270+
} catch (\Throwable $e) {
271+
return handleError($e, $this);
272+
}
273+
}
253274
public function manualCloudflareConfig()
254275
{
255276
$this->server->settings->is_cloudflare_tunnel = true;

app/Models/Server.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
'validation_logs' => ['type' => 'string'],
3737
'log_drain_notification_sent' => ['type' => 'boolean'],
3838
'swarm_cluster' => ['type' => 'string'],
39+
'delete_unused_volumes' => ['type' => 'boolean'],
40+
'delete_unused_networks' => ['type' => 'boolean'],
3941
]
4042
)]
4143

@@ -105,6 +107,8 @@ protected static function booted()
105107
'proxy' => SchemalessAttributes::class,
106108
'logdrain_axiom_api_key' => 'encrypted',
107109
'logdrain_newrelic_license_key' => 'encrypted',
110+
'delete_unused_volumes' => 'boolean',
111+
'delete_unused_networks' => 'boolean',
108112
];
109113

110114
protected $schemalessAttributes = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up()
10+
{
11+
Schema::table('server_settings', function (Blueprint $table) {
12+
$table->boolean('delete_unused_volumes')->default(false);
13+
$table->boolean('delete_unused_networks')->default(false);
14+
});
15+
}
16+
17+
public function down()
18+
{
19+
Schema::table('server_settings', function (Blueprint $table) {
20+
$table->dropColumn('delete_unused_volumes');
21+
$table->dropColumn('delete_unused_networks');
22+
});
23+
}
24+
};

resources/views/livewire/server/form.blade.php

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,26 +194,78 @@ class="px-4 py-2 text-gray-800 cursor-pointer hover:bg-gray-100 dark:hover:bg-co
194194

195195
@if ($server->isFunctional())
196196
<h3 class="pt-4">Settings</h3>
197-
<div class="flex flex-col gap-1">
197+
<div class="flex flex-col gap-4">
198198
<div class="flex flex-col gap-2">
199-
<div class="flex flex-col flex-wrap gap-2 sm:flex-nowrap">
199+
<div class="flex flex-wrap items-center gap-4">
200200
<div class="w-64">
201201
<x-forms.checkbox
202-
helper="Enable force Docker Cleanup. This will cleanup build caches / unused images / etc."
202+
helper="Enabling Force Docker Cleanup or manually triggering a cleanup will perform the following actions:
203+
<ul class='list-disc pl-4 mt-2'>
204+
<li>Removes stopped containers manged by Coolify (as containers are none persistent, no data will be lost).</li>
205+
<li>Deletes unused images.</li>
206+
<li>Clears build cache.</li>
207+
<li>Removes old versions of the Coolify helper image.</li>
208+
<li>Optionally delete unused volumes (if enabled in advanced options).</li>
209+
<li>Optionally remove unused networks (if enabled in advanced options).</li>
210+
</ul>"
203211
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
204212
</div>
205-
@if ($server->settings->force_docker_cleanup)
206-
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
207-
label="Docker cleanup frequency" required
208-
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." />
209-
@else
213+
<x-modal-confirmation
214+
title="Confirm Docker Cleanup?"
215+
buttonTitle="Trigger Docker Cleanup"
216+
submitAction="manualCleanup"
217+
:actions="[
218+
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
219+
'Permanently deletes all unused images',
220+
'Clears build cache',
221+
'Removes old versions of the Coolify helper image',
222+
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
223+
'Optionally permanently deletes all unused networks (if enabled in advanced options).'
224+
]"
225+
:confirmWithText="false"
226+
:confirmWithPassword="false"
227+
step2ButtonText="Trigger Docker Cleanup"
228+
/>
229+
</div>
230+
@if ($server->settings->force_docker_cleanup)
231+
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
232+
label="Docker cleanup frequency" required
233+
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." />
234+
@else
210235
<x-forms.input id="server.settings.docker_cleanup_threshold"
211236
label="Docker cleanup threshold (%)" required
212237
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
213-
@endif
238+
@endif
239+
<div x-data="{ open: false }" class="mt-4 max-w-md">
240+
<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">
241+
<span>Advanced Options</span>
242+
<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">
243+
<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" />
244+
</svg>
245+
</button>
246+
<div x-show="open" class="mt-2 space-y-2">
247+
<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>
248+
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes" label="Delete Unused Volumes"
249+
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>
250+
<ul class='list-disc pl-4 mt-2'>
251+
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
252+
<li>Data from stopped containers volumes will be permanently lost.</li>
253+
<li>No way to recover deleted volume data.</li>
254+
</ul>"
255+
/>
256+
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks" label="Delete Unused Networks"
257+
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>
258+
<ul class='list-disc pl-4 mt-2'>
259+
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
260+
<li>Custom networks for stopped containers will be permanently deleted.</li>
261+
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
262+
</ul>"
263+
/>
264+
</div>
214265
</div>
215266
</div>
216-
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
267+
268+
<div class="flex flex-wrap gap-4 sm:flex-nowrap">
217269
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
218270
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
219271
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required

0 commit comments

Comments
 (0)