Skip to content

Commit 67c8732

Browse files
authored
Merge branch 'next' into feat/prioritize-branch-selection
2 parents 3c2f6a5 + 8ba7533 commit 67c8732

File tree

118 files changed

+4562
-831
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+4562
-831
lines changed

.github/workflows/coolify-helper-next.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ jobs:
4444
uses: docker/login-action@v3
4545
with:
4646
registry: ${{ env.DOCKER_REGISTRY }}
47-
username: ${{ secrets.DOCKER_USERNAME }}
48-
password: ${{ secrets.DOCKER_TOKEN }}
47+
username: ${{ secrets.DOCKERHUB_USERNAME }}
48+
password: ${{ secrets.DOCKERHUB_TOKEN }}
4949

5050
- name: Get Version
5151
id: version
@@ -86,8 +86,8 @@ jobs:
8686
uses: docker/login-action@v3
8787
with:
8888
registry: ${{ env.DOCKER_REGISTRY }}
89-
username: ${{ secrets.DOCKER_USERNAME }}
90-
password: ${{ secrets.DOCKER_TOKEN }}
89+
username: ${{ secrets.DOCKERHUB_USERNAME }}
90+
password: ${{ secrets.DOCKERHUB_TOKEN }}
9191

9292
- name: Get Version
9393
id: version

.github/workflows/coolify-helper.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ jobs:
4444
uses: docker/login-action@v3
4545
with:
4646
registry: ${{ env.DOCKER_REGISTRY }}
47-
username: ${{ secrets.DOCKER_USERNAME }}
48-
password: ${{ secrets.DOCKER_TOKEN }}
47+
username: ${{ secrets.DOCKERHUB_USERNAME }}
48+
password: ${{ secrets.DOCKERHUB_TOKEN }}
4949

5050
- name: Get Version
5151
id: version
@@ -85,8 +85,8 @@ jobs:
8585
uses: docker/login-action@v3
8686
with:
8787
registry: ${{ env.DOCKER_REGISTRY }}
88-
username: ${{ secrets.DOCKER_USERNAME }}
89-
password: ${{ secrets.DOCKER_TOKEN }}
88+
username: ${{ secrets.DOCKERHUB_USERNAME }}
89+
password: ${{ secrets.DOCKERHUB_TOKEN }}
9090

9191
- name: Get Version
9292
id: version

.github/workflows/coolify-production-build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ jobs:
5151
uses: docker/login-action@v3
5252
with:
5353
registry: ${{ env.DOCKER_REGISTRY }}
54-
username: ${{ secrets.DOCKER_USERNAME }}
55-
password: ${{ secrets.DOCKER_TOKEN }}
54+
username: ${{ secrets.DOCKERHUB_USERNAME }}
55+
password: ${{ secrets.DOCKERHUB_TOKEN }}
5656

5757
- name: Get Version
5858
id: version
@@ -91,8 +91,8 @@ jobs:
9191
uses: docker/login-action@v3
9292
with:
9393
registry: ${{ env.DOCKER_REGISTRY }}
94-
username: ${{ secrets.DOCKER_USERNAME }}
95-
password: ${{ secrets.DOCKER_TOKEN }}
94+
username: ${{ secrets.DOCKERHUB_USERNAME }}
95+
password: ${{ secrets.DOCKERHUB_TOKEN }}
9696

9797
- name: Get Version
9898
id: version

.github/workflows/coolify-realtime-next.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ jobs:
4848
uses: docker/login-action@v3
4949
with:
5050
registry: ${{ env.DOCKER_REGISTRY }}
51-
username: ${{ secrets.DOCKER_USERNAME }}
52-
password: ${{ secrets.DOCKER_TOKEN }}
51+
username: ${{ secrets.DOCKERHUB_USERNAME }}
52+
password: ${{ secrets.DOCKERHUB_TOKEN }}
5353

5454
- name: Get Version
5555
id: version
@@ -90,8 +90,8 @@ jobs:
9090
uses: docker/login-action@v3
9191
with:
9292
registry: ${{ env.DOCKER_REGISTRY }}
93-
username: ${{ secrets.DOCKER_USERNAME }}
94-
password: ${{ secrets.DOCKER_TOKEN }}
93+
username: ${{ secrets.DOCKERHUB_USERNAME }}
94+
password: ${{ secrets.DOCKERHUB_TOKEN }}
9595

9696
- name: Get Version
9797
id: version

.github/workflows/coolify-realtime.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ jobs:
4848
uses: docker/login-action@v3
4949
with:
5050
registry: ${{ env.DOCKER_REGISTRY }}
51-
username: ${{ secrets.DOCKER_USERNAME }}
52-
password: ${{ secrets.DOCKER_TOKEN }}
51+
username: ${{ secrets.DOCKERHUB_USERNAME }}
52+
password: ${{ secrets.DOCKERHUB_TOKEN }}
5353

5454
- name: Get Version
5555
id: version
@@ -90,8 +90,8 @@ jobs:
9090
uses: docker/login-action@v3
9191
with:
9292
registry: ${{ env.DOCKER_REGISTRY }}
93-
username: ${{ secrets.DOCKER_USERNAME }}
94-
password: ${{ secrets.DOCKER_TOKEN }}
93+
username: ${{ secrets.DOCKERHUB_USERNAME }}
94+
password: ${{ secrets.DOCKERHUB_TOKEN }}
9595

9696
- name: Get Version
9797
id: version

.github/workflows/coolify-staging-build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ jobs:
6464
uses: docker/login-action@v3
6565
with:
6666
registry: ${{ env.DOCKER_REGISTRY }}
67-
username: ${{ secrets.DOCKER_USERNAME }}
68-
password: ${{ secrets.DOCKER_TOKEN }}
67+
username: ${{ secrets.DOCKERHUB_USERNAME }}
68+
password: ${{ secrets.DOCKERHUB_TOKEN }}
6969

7070
- name: Build and Push Image (${{ matrix.arch }})
7171
uses: docker/build-push-action@v6
@@ -110,8 +110,8 @@ jobs:
110110
uses: docker/login-action@v3
111111
with:
112112
registry: ${{ env.DOCKER_REGISTRY }}
113-
username: ${{ secrets.DOCKER_USERNAME }}
114-
password: ${{ secrets.DOCKER_TOKEN }}
113+
username: ${{ secrets.DOCKERHUB_USERNAME }}
114+
password: ${{ secrets.DOCKERHUB_TOKEN }}
115115

116116
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
117117
run: |

.github/workflows/coolify-testing-host.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ jobs:
4444
uses: docker/login-action@v3
4545
with:
4646
registry: ${{ env.DOCKER_REGISTRY }}
47-
username: ${{ secrets.DOCKER_USERNAME }}
48-
password: ${{ secrets.DOCKER_TOKEN }}
47+
username: ${{ secrets.DOCKERHUB_USERNAME }}
48+
password: ${{ secrets.DOCKERHUB_TOKEN }}
4949

5050
- name: Build and Push Image (${{ matrix.arch }})
5151
uses: docker/build-push-action@v6
@@ -81,8 +81,8 @@ jobs:
8181
uses: docker/login-action@v3
8282
with:
8383
registry: ${{ env.DOCKER_REGISTRY }}
84-
username: ${{ secrets.DOCKER_USERNAME }}
85-
password: ${{ secrets.DOCKER_TOKEN }}
84+
username: ${{ secrets.DOCKERHUB_USERNAME }}
85+
password: ${{ secrets.DOCKERHUB_TOKEN }}
8686

8787
- name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }}
8888
run: |

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5389,7 +5389,6 @@ All notable changes to this project will be documented in this file.
53895389
- Add static ipv4 ipv6 support
53905390
- Server disabled by overflow
53915391
- Preview deployment logs
5392-
- Collect webhooks during maintenance
53935392
- Logs and execute commands with several servers
53945393

53955394
### 🐛 Bug Fixes

app/Actions/Docker/GetContainersStatus.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,9 +461,10 @@ private function aggregateApplicationStatus($application, Collection $containerS
461461
}
462462

463463
// Use ContainerStatusAggregator service for state machine logic
464+
// Use preserveRestarting: true so applications show "Restarting" instead of "Degraded"
464465
$aggregator = new ContainerStatusAggregator;
465466

466-
return $aggregator->aggregateFromStrings($relevantStatuses, $maxRestartCount);
467+
return $aggregator->aggregateFromStrings($relevantStatuses, $maxRestartCount, preserveRestarting: true);
467468
}
468469

469470
private function aggregateServiceContainerStatuses($services)
@@ -518,8 +519,9 @@ private function aggregateServiceContainerStatuses($services)
518519
}
519520

520521
// Use ContainerStatusAggregator service for state machine logic
522+
// Use preserveRestarting: true so individual sub-resources show "Restarting" instead of "Degraded"
521523
$aggregator = new ContainerStatusAggregator;
522-
$aggregatedStatus = $aggregator->aggregateFromStrings($relevantStatuses);
524+
$aggregatedStatus = $aggregator->aggregateFromStrings($relevantStatuses, preserveRestarting: true);
523525

524526
// Update service sub-resource status with aggregated result
525527
if ($aggregatedStatus) {

app/Actions/Server/CleanupDocker.php

Lines changed: 135 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class CleanupDocker
1313

1414
public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $deleteUnusedNetworks = false)
1515
{
16-
$settings = instanceSettings();
1716
$realtimeImage = config('constants.coolify.realtime_image');
1817
$realtimeImageVersion = config('constants.coolify.realtime_version');
1918
$realtimeImageWithVersion = "$realtimeImage:$realtimeImageVersion";
@@ -26,9 +25,25 @@ public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $
2625
$helperImageWithoutPrefix = 'coollabsio/coolify-helper';
2726
$helperImageWithoutPrefixVersion = "coollabsio/coolify-helper:$helperImageVersion";
2827

28+
$cleanupLog = [];
29+
30+
// Get all application image repositories to exclude from prune
31+
$applications = $server->applications();
32+
$applicationImageRepos = collect($applications)->map(function ($app) {
33+
return $app->docker_registry_image_name ?? $app->uuid;
34+
})->unique()->values();
35+
36+
// Clean up old application images while preserving N most recent for rollback
37+
$applicationCleanupLog = $this->cleanupApplicationImages($server, $applications);
38+
$cleanupLog = array_merge($cleanupLog, $applicationCleanupLog);
39+
40+
// Build image prune command that excludes application images
41+
// This ensures we clean up non-Coolify images while preserving rollback images
42+
$imagePruneCmd = $this->buildImagePruneCommand($applicationImageRepos);
43+
2944
$commands = [
3045
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
31-
'docker image prune -af --filter "label!=coolify.managed=true"',
46+
$imagePruneCmd,
3247
'docker builder prune -af',
3348
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
3449
"docker images --filter before=$realtimeImageWithVersion --filter reference=$realtimeImage | grep $realtimeImage | awk '{print $3}' | xargs -r docker rmi -f",
@@ -44,7 +59,6 @@ public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $
4459
$commands[] = 'docker network prune -f';
4560
}
4661

47-
$cleanupLog = [];
4862
foreach ($commands as $command) {
4963
$commandOutput = instant_remote_process([$command], $server, false);
5064
if ($commandOutput !== null) {
@@ -57,4 +71,122 @@ public function handle(Server $server, bool $deleteUnusedVolumes = false, bool $
5771

5872
return $cleanupLog;
5973
}
74+
75+
/**
76+
* Build a docker image prune command that excludes application image repositories.
77+
*
78+
* Since docker image prune doesn't support excluding by repository name directly,
79+
* we use a shell script approach to delete unused images while preserving application images.
80+
*/
81+
private function buildImagePruneCommand($applicationImageRepos): string
82+
{
83+
// Step 1: Always prune dangling images (untagged)
84+
$commands = ['docker image prune -f'];
85+
86+
if ($applicationImageRepos->isEmpty()) {
87+
// No applications, add original prune command for all unused images
88+
$commands[] = 'docker image prune -af --filter "label!=coolify.managed=true"';
89+
} else {
90+
// Build grep pattern to exclude application image repositories
91+
$excludePatterns = $applicationImageRepos->map(function ($repo) {
92+
// Escape special characters for grep extended regex (ERE)
93+
// ERE special chars: . \ + * ? [ ^ ] $ ( ) { } |
94+
return preg_replace('/([.\\\\+*?\[\]^$(){}|])/', '\\\\$1', $repo);
95+
})->implode('|');
96+
97+
// Delete unused images that:
98+
// - Are not application images (don't match app repos)
99+
// - Don't have coolify.managed=true label
100+
// Images in use by containers will fail silently with docker rmi
101+
// Pattern matches both uuid:tag and uuid_servicename:tag (Docker Compose with build)
102+
$commands[] = "docker images --format '{{.Repository}}:{{.Tag}}' | ".
103+
"grep -v -E '^({$excludePatterns})[_:].+' | ".
104+
"grep -v '<none>' | ".
105+
"xargs -r -I {} sh -c 'docker inspect --format \"{{{{index .Config.Labels \\\"coolify.managed\\\"}}}}\" \"{}\" 2>/dev/null | grep -q true || docker rmi \"{}\" 2>/dev/null' || true";
106+
}
107+
108+
return implode(' && ', $commands);
109+
}
110+
111+
private function cleanupApplicationImages(Server $server, $applications = null): array
112+
{
113+
$cleanupLog = [];
114+
115+
if ($applications === null) {
116+
$applications = $server->applications();
117+
}
118+
119+
$disableRetention = $server->settings->disable_application_image_retention ?? false;
120+
121+
foreach ($applications as $application) {
122+
$imagesToKeep = $disableRetention ? 0 : ($application->settings->docker_images_to_keep ?? 2);
123+
$imageRepository = $application->docker_registry_image_name ?? $application->uuid;
124+
125+
// Get the currently running image tag
126+
$currentTagCommand = "docker inspect --format='{{.Config.Image}}' {$application->uuid} 2>/dev/null | grep -oP '(?<=:)[^:]+$' || true";
127+
$currentTag = instant_remote_process([$currentTagCommand], $server, false);
128+
$currentTag = trim($currentTag ?? '');
129+
130+
// List all images for this application with their creation timestamps
131+
// Use wildcard to match both uuid:tag and uuid_servicename:tag (Docker Compose with build)
132+
$listCommand = "docker images --format '{{.Repository}}:{{.Tag}}#{{.CreatedAt}}' --filter reference='{$imageRepository}*' 2>/dev/null || true";
133+
$output = instant_remote_process([$listCommand], $server, false);
134+
135+
if (empty($output)) {
136+
continue;
137+
}
138+
139+
$images = collect(explode("\n", trim($output)))
140+
->filter()
141+
->map(function ($line) {
142+
$parts = explode('#', $line);
143+
$imageRef = $parts[0] ?? '';
144+
$tagParts = explode(':', $imageRef);
145+
146+
return [
147+
'repository' => $tagParts[0] ?? '',
148+
'tag' => $tagParts[1] ?? '',
149+
'created_at' => $parts[1] ?? '',
150+
'image_ref' => $imageRef,
151+
];
152+
})
153+
->filter(fn ($image) => ! empty($image['tag']));
154+
155+
// Separate images into categories
156+
// PR images (pr-*) and build images (*-build) are excluded from retention
157+
// Build images will be cleaned up by docker image prune -af
158+
$prImages = $images->filter(fn ($image) => str_starts_with($image['tag'], 'pr-'));
159+
$regularImages = $images->filter(fn ($image) => ! str_starts_with($image['tag'], 'pr-') && ! str_ends_with($image['tag'], '-build'));
160+
161+
// Always delete all PR images
162+
foreach ($prImages as $image) {
163+
$deleteCommand = "docker rmi {$image['image_ref']} 2>/dev/null || true";
164+
$deleteOutput = instant_remote_process([$deleteCommand], $server, false);
165+
$cleanupLog[] = [
166+
'command' => $deleteCommand,
167+
'output' => $deleteOutput ?? 'PR image removed or was in use',
168+
];
169+
}
170+
171+
// Filter out current running image from regular images and sort by creation date
172+
$sortedRegularImages = $regularImages
173+
->filter(fn ($image) => $image['tag'] !== $currentTag)
174+
->sortByDesc('created_at')
175+
->values();
176+
177+
// Keep only N images (imagesToKeep), delete the rest
178+
$imagesToDelete = $sortedRegularImages->skip($imagesToKeep);
179+
180+
foreach ($imagesToDelete as $image) {
181+
$deleteCommand = "docker rmi {$image['image_ref']} 2>/dev/null || true";
182+
$deleteOutput = instant_remote_process([$deleteCommand], $server, false);
183+
$cleanupLog[] = [
184+
'command' => $deleteCommand,
185+
'output' => $deleteOutput ?? 'Image removed or was in use',
186+
];
187+
}
188+
}
189+
190+
return $cleanupLog;
191+
}
60192
}

0 commit comments

Comments
 (0)