Add server-level toggle to disable application image retention

Adds a new server-level setting that allows administrators to disable
per-application image retention globally for all applications on a server.
When enabled, Docker cleanup will only keep the currently running image
regardless of individual application retention settings.

Changes:
- Add migration for disable_application_image_retention boolean field
- Update ServerSetting model with cast
- Add checkbox in DockerCleanup page (Advanced section)
- Modify CleanupDocker action to check server-level setting
- Update Rollback page to show warning and disable inputs when server
  retention is disabled
- Add helper text noting server-level override capability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-12-05 12:22:20 +01:00
parent 0cc5973901
commit 511415770a
7 changed files with 48 additions and 4 deletions

View File

@@ -115,8 +115,10 @@ class CleanupDocker
$applications = $server->applications(); $applications = $server->applications();
} }
$disableRetention = $server->settings->disable_application_image_retention ?? false;
foreach ($applications as $application) { foreach ($applications as $application) {
$imagesToKeep = $application->settings->docker_images_to_keep ?? 2; $imagesToKeep = $disableRetention ? 0 : ($application->settings->docker_images_to_keep ?? 2);
$imageRepository = $application->docker_registry_image_name ?? $application->uuid; $imageRepository = $application->docker_registry_image_name ?? $application->uuid;
// Get the currently running image tag // Get the currently running image tag

View File

@@ -23,10 +23,14 @@ class Rollback extends Component
#[Validate(['integer', 'min:0', 'max:100'])] #[Validate(['integer', 'min:0', 'max:100'])]
public int $dockerImagesToKeep = 2; public int $dockerImagesToKeep = 2;
public bool $serverRetentionDisabled = false;
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->dockerImagesToKeep = $this->application->settings->docker_images_to_keep ?? 2; $this->dockerImagesToKeep = $this->application->settings->docker_images_to_keep ?? 2;
$server = $this->application->destination->server;
$this->serverRetentionDisabled = $server->settings->disable_application_image_retention ?? false;
} }
public function saveSettings() public function saveSettings()

View File

@@ -31,6 +31,9 @@ class DockerCleanup extends Component
#[Validate('boolean')] #[Validate('boolean')]
public bool $deleteUnusedNetworks = false; public bool $deleteUnusedNetworks = false;
#[Validate('boolean')]
public bool $disableApplicationImageRetention = false;
public function mount(string $server_uuid) public function mount(string $server_uuid)
{ {
try { try {
@@ -52,6 +55,7 @@ class DockerCleanup extends Component
$this->server->settings->docker_cleanup_threshold = $this->dockerCleanupThreshold; $this->server->settings->docker_cleanup_threshold = $this->dockerCleanupThreshold;
$this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes; $this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes;
$this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks; $this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks;
$this->server->settings->disable_application_image_retention = $this->disableApplicationImageRetention;
$this->server->settings->save(); $this->server->settings->save();
} else { } else {
$this->forceDockerCleanup = $this->server->settings->force_docker_cleanup; $this->forceDockerCleanup = $this->server->settings->force_docker_cleanup;
@@ -59,6 +63,7 @@ class DockerCleanup extends Component
$this->dockerCleanupThreshold = $this->server->settings->docker_cleanup_threshold; $this->dockerCleanupThreshold = $this->server->settings->docker_cleanup_threshold;
$this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes; $this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes;
$this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks; $this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks;
$this->disableApplicationImageRetention = $this->server->settings->disable_application_image_retention;
} }
} }

View File

@@ -61,6 +61,7 @@ class ServerSetting extends Model
'is_reachable' => 'boolean', 'is_reachable' => 'boolean',
'is_usable' => 'boolean', 'is_usable' => 'boolean',
'is_terminal_enabled' => 'boolean', 'is_terminal_enabled' => 'boolean',
'disable_application_image_retention' => 'boolean',
]; ];
protected static function booted() protected static function booted()

View File

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

View File

@@ -7,12 +7,18 @@
</div> </div>
<div class="pb-4">You can easily rollback to a previously built (local) images quickly.</div> <div class="pb-4">You can easily rollback to a previously built (local) images quickly.</div>
@if($serverRetentionDisabled)
<x-callout type="warning" class="mb-4">
Image retention is disabled at the server level. This setting has no effect until the server administrator enables it.
</x-callout>
@endif
<div class="pb-4"> <div class="pb-4">
<form wire:submit="saveSettings" class="flex items-end gap-2 w-96"> <form wire:submit="saveSettings" class="flex items-end gap-2 w-96">
<x-forms.input id="dockerImagesToKeep" type="number" min="0" max="100" label="Images to keep for rollback" <x-forms.input id="dockerImagesToKeep" type="number" min="0" max="100" label="Images to keep for rollback"
helper="Number of Docker images to keep for rollback during cleanup. Set to 0 to only keep the currently running image. PR images are always deleted during cleanup." helper="Number of Docker images to keep for rollback during cleanup. Set to 0 to only keep the currently running image. PR images are always deleted during cleanup.<br><br><strong>Note:</strong> Server administrators can disable image retention at the server level, which overrides this setting."
canGate="update" :canResource="$application" /> canGate="update" :canResource="$application" :disabled="$serverRetentionDisabled" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button> <x-forms.button canGate="update" :canResource="$application" type="submit" :disabled="$serverRetentionDisabled">Save</x-forms.button>
</form> </form>
</div> </div>
<div wire:target='loadImages' wire:loading.remove> <div wire:target='loadImages' wire:loading.remove>

View File

@@ -78,6 +78,10 @@
<li>Networks not attached to running containers will be permanently deleted (networks used by stopped containers are affected).</li> <li>Networks not attached to running containers will be permanently deleted (networks used by stopped containers are affected).</li>
<li>Containers may lose connectivity if required networks are removed.</li> <li>Containers may lose connectivity if required networks are removed.</li>
</ul>" /> </ul>" />
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
id="disableApplicationImageRetention"
label="Disable Application Image Retention"
helper="When enabled, Docker cleanup will delete all old application images regardless of per-application retention settings. Only the currently running image will be kept.<br><br><strong>Warning: This disables rollback capabilities for all applications on this server.</strong>" />
</div> </div>
</div> </div>
</form> </form>