fix(cdn): fetch versions.json once per hour, spread evenly across instances

- Retry CDN requests only for transient errors and honor Retry-After
  (Cloudflare error 1200 sends 60 s), with jitter.
- Read helper and Sentinel versions from the local versions.json, which
  the hourly update check refreshes, instead of fetching per job/server.
- Replace the random 0-600 s delay with a stable per-instance offset
  inside the cron interval (max 1 h).
- Manual update checks and upgrades make one CDN attempt only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-10-21 18:10:48 +02:00
co-authored by Claude Opus 5.5
parent 44c6322259
commit dbc3fb91bf
12 changed files with 214 additions and 62 deletions
+2 -8
View File
@@ -703,14 +703,8 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string
{
try {
$response = Http::get(config('constants.coolify.versions_url'));
$versions = $response->json();
return data_get($versions, 'coolify.sentinel.version');
} catch (Throwable) {
return '0.0.0';
}
// Read the local versions.json, which CheckForUpdatesJob refreshes every hour. Do not ask the CDN for each server.
return data_get(get_versions_data(), 'coolify.sentinel.version') ?? '0.0.0';
}
function get_latest_version_of_coolify(): string
{
+49 -2
View File
@@ -1,5 +1,8 @@
<?php
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
@@ -54,6 +57,37 @@ function invalidate_versions_cache(): void
Cache::forget('coolify:versions:all');
}
/**
* HTTP client for the Coolify CDN. It retries only transient failures (connection errors, 429, 5xx)
* and does not throw for a failed response.
*/
function cdn_http(int $tries = 3): PendingRequest
{
return Http::timeout(10)
->connectTimeout(10)
->retry(
$tries,
fn (int $attempt, Throwable $exception) => cdn_retry_delay_ms($attempt, $exception),
fn (Throwable $exception) => $exception instanceof ConnectionException
|| ($exception instanceof RequestException
&& ($exception->response->status() === 429 || $exception->response->serverError())),
throw: false,
);
}
/**
* Wait as long as the Retry-After header says (Cloudflare sends 60 seconds with error 1200),
* or 2^attempt seconds without it. A random part of up to the same length is added, so instances
* that failed in the same burst do not retry in the same second.
*/
function cdn_retry_delay_ms(int $attempt, Throwable $exception): int
{
$retryAfter = $exception instanceof RequestException ? (int) $exception->response->header('Retry-After') : 0;
$seconds = $retryAfter > 0 ? min($retryAfter, 60) : 2 ** $attempt;
return ($seconds + random_int(0, $seconds)) * 1000;
}
/**
* Fetch versions.json from the CDN and write it to the local file of this node.
* The Coolify version never goes below the cached or the running version.
@@ -61,9 +95,9 @@ function invalidate_versions_cache(): void
*
* @return array|null The written versions data, or null if the CDN fetch failed
*/
function refresh_versions_file(): ?array
function refresh_versions_file(int $tries = 3): ?array
{
$response = Http::timeout(10)->retry(3, 1000, throw: false)->get(config('constants.coolify.versions_url'));
$response = cdn_http($tries)->get(config('constants.coolify.versions_url'));
$versions = $response->json();
if ($response->failed() || ! is_array($versions)) {
Log::warning('Could not fetch versions.json from the CDN, using the local file', ['status' => $response->status()]);
@@ -106,6 +140,19 @@ function refresh_versions_file(): ?array
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
invalidate_versions_cache();
update_helper_version($versions);
return $versions;
}
/**
* Store the helper image version of the versions data when it is newer than the stored one.
*/
function update_helper_version(?array $versions): void
{
$latestVersion = data_get($versions, 'coolify.helper.version');
$settings = instanceSettings();
if ($latestVersion && version_compare($latestVersion, (string) $settings->helper_version, '>')) {
$settings->update(['helper_version' => $latestVersion]);
}
}