diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 0e2df8e43e..3a00704813 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2922,14 +2922,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue return; } - if ($this->application->custom_healthcheck_found) { - $this->application_deployment_queue->addLogEntry('Custom healthcheck found in Dockerfile.'); + if ($this->application->isHealthcheckDisabled()) { + $this->application_deployment_queue->addLogEntry('Using the custom healthcheck from the Dockerfile.'); } if ($this->container_name) { $escapedContainerName = escapeshellarg($this->container_name); $counter = 1; $this->application_deployment_queue->addLogEntry('Waiting for healthcheck to pass on the new container.'); - if ($this->full_healthcheck_url && ! $this->application->custom_healthcheck_found) { + if ($this->full_healthcheck_url) { $healthcheckLabel = $this->application->health_check_type === 'cmd' ? 'Healthcheck command' : 'Healthcheck URL'; $this->application_deployment_queue->addLogEntry("{$healthcheckLabel} (inside the container): {$this->full_healthcheck_url}"); } @@ -4445,10 +4445,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $docker_compose['services'][$this->container_name]['stop_grace_period'] = $this->application->settings->stopGracePeriodSeconds().'s'; } - // Only add Coolify healthcheck if no custom HEALTHCHECK found in Dockerfile - // If custom_healthcheck_found is true, the Dockerfile's HEALTHCHECK will be used - // If healthcheck is disabled, no healthcheck will be added - if (! $this->application->custom_healthcheck_found && ! $this->application->isHealthcheckDisabled()) { + // An enabled Coolify healthcheck replaces any HEALTHCHECK from the image or Dockerfile. + // When it is disabled, the container keeps the HEALTHCHECK of the image (if any). + if (! $this->application->isHealthcheckDisabled()) { $healthcheck_command = $this->generate_healthcheck_commands(); if ($healthcheck_command !== null) { $docker_compose['services'][$this->container_name]['healthcheck'] = [ diff --git a/app/Models/Application.php b/app/Models/Application.php index a183d412cc..f743be45f1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -292,6 +292,8 @@ class Application extends BaseModel 'container_present' => 'boolean', 'last_restart_at' => 'datetime', 'is_maintenance_enabled' => 'boolean', + 'health_check_enabled' => 'boolean', + 'custom_healthcheck_found' => 'boolean', ]; } diff --git a/tests/Unit/ApplicationDeploymentDockerfileHealthcheckOverrideTest.php b/tests/Unit/ApplicationDeploymentDockerfileHealthcheckOverrideTest.php new file mode 100644 index 0000000000..1b41182959 --- /dev/null +++ b/tests/Unit/ApplicationDeploymentDockerfileHealthcheckOverrideTest.php @@ -0,0 +1,116 @@ + 'Dockerfile Healthcheck Team', + 'personal_team' => false, + 'show_boarding' => false, + ]); + $project = Project::create([ + 'name' => 'Dockerfile Healthcheck Project', + 'team_id' => $team->id, + ]); + $environment = Environment::where('project_id', $project->id)->firstOrFail(); + $server = Server::factory()->create(['team_id' => $team->id]); + $destination = $server->standaloneDockers()->firstOrFail(); + $application = Application::factory()->create([ + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => StandaloneDocker::class, + 'build_pack' => 'dockerfile', + 'ports_exposes' => '4181', + 'custom_healthcheck_found' => true, + ...$applicationAttributes, + ]); + + $queue = Mockery::mock(ApplicationDeploymentQueue::class)->makePartial(); + $queue->status = 'queued'; + $queue->shouldReceive('refresh')->once()->andReturnSelf(); + $queue->shouldReceive('addLogEntry'); + + $job = new TestableDockerfileHealthcheckDeploymentJob; + $reflection = new ReflectionClass(ApplicationDeploymentJob::class); + + foreach ([ + 'application' => $application->fresh(), + 'application_deployment_queue' => $queue, + 'destination' => $destination, + 'server' => $server, + 'mainServer' => $server, + 'pull_request_id' => 0, + 'container_name' => 'dockerfile-healthcheck-app', + 'production_image_name' => 'example/app:latest', + 'deployment_uuid' => 'deployment-uuid', + 'workdir' => '/artifacts/dockerfile-healthcheck-app', + 'configuration_dir' => '/data/coolify/applications/test', + 'dockerfile_location' => '/Dockerfile', + 'saved_outputs' => new Collection([ + 'dockerfile_from_repo' => "FROM scratch\nHEALTHCHECK --interval=30s --timeout=3s CMD [\"/app\", \"healthcheck\"]\n", + ]), + ] as $property => $value) { + $reflection->getProperty($property)->setValue($job, $value); + } + + $reflection->getMethod('generate_compose_file')->invoke($job); + $compose = Yaml::parse($reflection->getProperty('docker_compose')->getValue($job)); + + return $compose['services']['dockerfile-healthcheck-app']; +} + +it('replaces the Dockerfile HEALTHCHECK with the enabled HTTP healthcheck', function () { + $service = generateComposeServiceWithDockerfileHealthcheck([ + 'health_check_enabled' => true, + 'health_check_type' => 'http', + 'health_check_port' => '4181', + 'health_check_path' => '/health', + 'health_check_interval' => 300, + 'health_check_timeout' => 3, + ]); + + expect($service['healthcheck']['interval'])->toBe('300s') + ->and($service['healthcheck']['timeout'])->toBe('3s') + ->and($service['healthcheck']['test'][1])->toContain("'http://localhost:4181/health'"); +}); + +it('replaces the Dockerfile HEALTHCHECK with the enabled command healthcheck', function () { + $service = generateComposeServiceWithDockerfileHealthcheck([ + 'health_check_enabled' => true, + 'health_check_type' => 'cmd', + 'health_check_command' => '/app healthcheck', + 'health_check_interval' => 300, + ]); + + expect($service['healthcheck']['test'])->toBe(['CMD-SHELL', '/app healthcheck']) + ->and($service['healthcheck']['interval'])->toBe('300s'); +}); + +it('keeps the Dockerfile HEALTHCHECK when the Coolify healthcheck is disabled', function () { + $service = generateComposeServiceWithDockerfileHealthcheck([ + 'health_check_enabled' => false, + ]); + + expect($service)->not->toHaveKey('healthcheck'); +});