fix(deployment): use one COOLIFY_BRANCH value for preview deployments

A preview deployment set COOLIFY_BRANCH in three places with three
values: the Compose shell variables used the application branch
(main), the Compose parser used pull/<id>/head, and other build packs
used the fetched refspec (pull/<id>/head:pr-<id>-coolify) or the
application branch. A variable that refers to ${COOLIFY_BRANCH} then
got a different value than the container. All paths now use
Application::coolifyBranch(), which gives pull/<id>/head for previews
and git_branch for production deployments.

Closes #9572

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-11-03 20:25:28 +01:00
co-authored by Claude Opus 5.5
parent d3ef4d1c57
commit 71814b4541
5 changed files with 103 additions and 7 deletions
+1
View File
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Fixed
- Use the same `COOLIFY_BRANCH` value in all parts of a preview deployment: `pull/<pull request number>/head`. Before, a variable that refers to `${COOLIFY_BRANCH}` got the branch of the application (for example `main`), but the container got `pull/<number>/head`. Other build packs got `pull/<number>/head:pr-<number>-coolify`, and applications from a public repository got the branch of the application ([#9572](https://github.com/coollabsio/coolify/issues/9572))
- Send the **Resource status changes** notification when an application, preview, service, or database stops unexpectedly, for example after a crash or `docker kill`. Before, Coolify sent this notification only when it restarted the proxy or a database TCP proxy. Coolify waits 2 minutes and sends the notification only when the resource is still stopped, so a container that Docker restarts, or a deployment that replaces it, sends none. A stop, restart, or redeploy from Coolify sends no notification, and the same resource sends at most one notification in 15 minutes ([#9493](https://github.com/coollabsio/coolify/issues/9493))
- Fix the "Network error" when you sign in with email in AppFlowy Web. AppFlowy Web runs on its own domain and sends a `Redirect_to` header to GoTrue on the admin domain, and GoTrue blocked the CORS preflight because it did not allow that header. The AppFlowy service template now sets `GOTRUE_CORS_ALLOWED_HEADERS=redirect_to`. For an existing AppFlowy service, add this variable to the `gotrue` service in the Docker Compose file and restart the service ([#10545](https://github.com/coollabsio/coolify/issues/10545))
- List the commit images of Docker Compose applications on the **Rollback** page and in `GET /api/v1/applications/{uuid}/rollback-images`. Since v4.5.0 the list was always empty, because a Compose build names each service image `<uuid>_<service>`. The page also shows the running commit when the Compose file has services from other images, for example a database ([#12446](https://github.com/coollabsio/coolify/issues/12446))
+4 -3
View File
@@ -3380,8 +3380,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
}
if (isset($this->application->git_branch)) {
$this->coolify_variables .= 'COOLIFY_BRANCH='.escapeShellValue($this->application->git_branch).' ';
$branch = $this->application->coolifyBranch($this->pull_request_id);
if (isset($branch)) {
$this->coolify_variables .= 'COOLIFY_BRANCH='.escapeShellValue($branch).' ';
}
$this->coolify_variables .= 'COOLIFY_RESOURCE_UUID='.escapeShellValue($this->application->uuid).' ';
}
@@ -4298,7 +4299,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
protected function generate_coolify_env_variables(bool $forBuildTime = false): Collection
{
$coolify_envs = collect([]);
$local_branch = $this->branch;
$local_branch = $this->application->coolifyBranch($this->pull_request_id);
if ($this->pull_request_id !== 0) {
// Only add SOURCE_COMMIT for runtime OR when explicitly enabled for build-time
// SOURCE_COMMIT changes with each commit and breaks Docker cache if included in build
+12
View File
@@ -1962,6 +1962,18 @@ class Application extends BaseModel
return $commands;
}
/**
* The COOLIFY_BRANCH value of a deployment. Preview deployments use the pull request ref.
*/
public function coolifyBranch(int $pullRequestId = 0): ?string
{
if ($pullRequestId !== 0) {
return "pull/{$pullRequestId}/head";
}
return $this->git_branch;
}
public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
{
$branch = $this->git_branch;
+1 -4
View File
@@ -2002,10 +2002,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
}
}
}
$branch = $originalResource->git_branch;
if ($pullRequestId !== 0) {
$branch = "pull/{$pullRequestId}/head";
}
$branch = $originalResource->coolifyBranch($pullRequestId);
if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\"");
}
@@ -0,0 +1,85 @@
<?php
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
class TestableCoolifyBranchDeploymentJob extends ApplicationDeploymentJob
{
public function __construct() {}
public function execute_remote_command(...$commands): void {}
}
/**
* @return array{shell: string, env: array<string, mixed>}
*/
function coolifyBranchVariables(string $buildPack, int $pullRequestId, string $checkedOutBranch): array
{
$team = Team::create(['name' => 'Branch Team', 'personal_team' => false, 'show_boarding' => false]);
$project = Project::create(['name' => 'Branch Project', 'team_id' => $team->id]);
$environment = Environment::where('project_id', $project->id)->firstOrFail();
$server = Server::factory()->create(['team_id' => $team->id]);
$application = Application::factory()->create([
'environment_id' => $environment->id,
'destination_id' => $server->standaloneDockers()->firstOrFail()->id,
'destination_type' => StandaloneDocker::class,
'build_pack' => $buildPack,
'git_branch' => 'main',
]);
$preview = $pullRequestId === 0 ? null : ApplicationPreview::create([
'application_id' => $application->id,
'pull_request_id' => $pullRequestId,
'pull_request_html_url' => 'https://github.com/coollabsio/example/pull/'.$pullRequestId,
'fqdn' => 'https://pr.example.com',
]);
$job = new TestableCoolifyBranchDeploymentJob;
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
foreach ([
'application' => $application->fresh(),
'pull_request_id' => $pullRequestId,
'preview' => $preview,
'commit' => 'HEAD',
'branch' => $checkedOutBranch,
] as $property => $value) {
$reflection->getProperty($property)->setValue($job, $value);
}
$reflection->getMethod('set_coolify_variables')->invoke($job);
return [
'shell' => $reflection->getProperty('coolify_variables')->getValue($job),
'env' => $reflection->getMethod('generate_coolify_env_variables')->invoke($job, true)->all(),
];
}
it('uses the pull request ref for COOLIFY_BRANCH in compose variables of a preview deployment', function () {
$variables = coolifyBranchVariables('dockercompose', 7, 'main');
expect($variables['shell'])->toContain("COOLIFY_BRANCH='pull/7/head'");
});
it('uses the pull request ref for COOLIFY_BRANCH in env variables of a preview deployment', function () {
$variables = coolifyBranchVariables('dockerfile', 7, 'pull/7/head:pr-7-coolify');
expect($variables['env']['COOLIFY_BRANCH'])->toBe('pull/7/head')
->and($variables['shell'])->toContain("COOLIFY_BRANCH='pull/7/head'");
});
it('uses the application branch for COOLIFY_BRANCH of a production deployment', function () {
$variables = coolifyBranchVariables('dockerfile', 0, 'main');
expect($variables['env']['COOLIFY_BRANCH'])->toBe('main')
->and($variables['shell'])->toContain("COOLIFY_BRANCH='main'");
});