diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index ee3398b04..94651a3c1 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -39,7 +39,7 @@ public function handle(Application $application, bool $previewDeployments = fals foreach ($containersToStop as $containerName) { instant_remote_process(command: [ - "docker stop --time=30 $containerName", + "docker stop -t 30 $containerName", "docker rm -f $containerName", ], server: $server, throwError: false); } diff --git a/app/Actions/Application/StopApplicationOneServer.php b/app/Actions/Application/StopApplicationOneServer.php index 600b1cb9a..bf9fdee72 100644 --- a/app/Actions/Application/StopApplicationOneServer.php +++ b/app/Actions/Application/StopApplicationOneServer.php @@ -26,7 +26,7 @@ public function handle(Application $application, Server $server) if ($containerName) { instant_remote_process( [ - "docker stop --time=30 $containerName", + "docker stop -t 30 $containerName", "docker rm -f $containerName", ], $server diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 5c881e743..c024c14e1 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -49,7 +49,7 @@ private function stopContainer($database, string $containerName, int $timeout = { $server = $database->destination->server; instant_remote_process(command: [ - "docker stop --time=$timeout $containerName", + "docker stop -t $timeout $containerName", "docker rm -f $containerName", ], server: $server, throwError: false); } diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php index 8f1b8af1c..04d031ec6 100644 --- a/app/Actions/Proxy/StopProxy.php +++ b/app/Actions/Proxy/StopProxy.php @@ -24,7 +24,7 @@ public function handle(Server $server, bool $forceStop = true, int $timeout = 30 } instant_remote_process(command: [ - "docker stop --time=$timeout $containerName 2>/dev/null || true", + "docker stop -t=$timeout $containerName 2>/dev/null || true", "docker rm -f $containerName 2>/dev/null || true", '# Wait for container to be fully removed', 'for i in {1..10}; do', diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 3f4e96479..23b41e3f2 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -54,7 +54,7 @@ private function stopContainersInParallel(array $containersToStop, Server $serve $timeout = count($containersToStop) > 5 ? 10 : 30; $commands = []; $containerList = implode(' ', $containersToStop); - $commands[] = "docker stop --time=$timeout $containerList"; + $commands[] = "docker stop -t $timeout $containerList"; $commands[] = "docker rm -f $containerList"; instant_remote_process( command: $commands, diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 2c4d0d361..7440cc16a 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -351,7 +351,7 @@ public function create_service(Request $request) 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), ]; - if ($oneClickServiceName === 'cloudflared') { + if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { data_set($servicePayload, 'connect_to_docker_network', true); } $service = Service::create($servicePayload); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 13938675a..3c428cf5f 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1401,15 +1401,44 @@ private function generate_buildtime_environment_variables() $this->application_deployment_queue->addLogEntry('[DEBUG] ========================================'); } - $envs = collect([]); + // Use associative array for automatic deduplication + $envs_dict = []; + + // 1. Add nixpacks plan variables FIRST (lowest priority - can be overridden) + if ($this->build_pack === 'nixpacks' && + isset($this->nixpacks_plan_json) && + $this->nixpacks_plan_json->isNotEmpty()) { + + $planVariables = data_get($this->nixpacks_plan_json, 'variables', []); + + if (! empty($planVariables)) { + if (isDev()) { + $this->application_deployment_queue->addLogEntry('[DEBUG] Adding '.count($planVariables).' nixpacks plan variables to buildtime.env'); + } + + foreach ($planVariables as $key => $value) { + // Skip COOLIFY_* and SERVICE_* - they'll be added later with higher priority + if (str_starts_with($key, 'COOLIFY_') || str_starts_with($key, 'SERVICE_')) { + continue; + } + + $escapedValue = escapeBashEnvValue($value); + $envs_dict[$key] = $escapedValue; + + if (isDev()) { + $this->application_deployment_queue->addLogEntry("[DEBUG] Nixpacks var: {$key}={$escapedValue}"); + } + } + } + } + + // 2. Add COOLIFY variables (can override nixpacks, but shouldn't happen in practice) $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); + foreach ($coolify_envs as $key => $item) { + $envs_dict[$key] = escapeBashEnvValue($item); + } - // Add COOLIFY variables - $coolify_envs->each(function ($item, $key) use ($envs) { - $envs->push($key.'='.escapeBashEnvValue($item)); - }); - - // Add SERVICE_NAME variables for Docker Compose builds + // 3. Add SERVICE_NAME, SERVICE_FQDN, SERVICE_URL variables for Docker Compose builds if ($this->build_pack === 'dockercompose') { if ($this->pull_request_id === 0) { // Generate SERVICE_NAME for dockercompose services from processed compose @@ -1420,7 +1449,7 @@ private function generate_buildtime_environment_variables() } $services = data_get($dockerCompose, 'services', []); foreach ($services as $serviceName => $_) { - $envs->push('SERVICE_NAME_'.str($serviceName)->upper().'='.escapeBashEnvValue($serviceName)); + $envs_dict['SERVICE_NAME_'.str($serviceName)->upper()] = escapeBashEnvValue($serviceName); } // Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments @@ -1433,8 +1462,8 @@ private function generate_buildtime_environment_variables() $coolifyScheme = $coolifyUrl->getScheme(); $coolifyFqdn = $coolifyUrl->getHost(); $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); - $envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString())); - $envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn)); + $envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString()); + $envs_dict['SERVICE_FQDN_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyFqdn); } } } else { @@ -1442,7 +1471,7 @@ private function generate_buildtime_environment_variables() $rawDockerCompose = Yaml::parse($this->application->docker_compose_raw); $rawServices = data_get($rawDockerCompose, 'services', []); foreach ($rawServices as $rawServiceName => $_) { - $envs->push('SERVICE_NAME_'.str($rawServiceName)->upper().'='.escapeBashEnvValue(addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id))); + $envs_dict['SERVICE_NAME_'.str($rawServiceName)->upper()] = escapeBashEnvValue(addPreviewDeploymentSuffix($rawServiceName, $this->pull_request_id)); } // Generate SERVICE_FQDN & SERVICE_URL for preview deployments with PR-specific domains @@ -1455,17 +1484,16 @@ private function generate_buildtime_environment_variables() $coolifyScheme = $coolifyUrl->getScheme(); $coolifyFqdn = $coolifyUrl->getHost(); $coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null); - $envs->push('SERVICE_URL_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyUrl->__toString())); - $envs->push('SERVICE_FQDN_'.str($forServiceName)->upper().'='.escapeBashEnvValue($coolifyFqdn)); + $envs_dict['SERVICE_URL_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyUrl->__toString()); + $envs_dict['SERVICE_FQDN_'.str($forServiceName)->upper()] = escapeBashEnvValue($coolifyFqdn); } } } } - // Add build-time user variables only + // 4. Add user-defined build-time variables LAST (highest priority - can override everything) if ($this->pull_request_id === 0) { $sorted_environment_variables = $this->application->environment_variables() - ->where('key', 'not like', 'NIXPACKS_%') ->where('is_buildtime', true) // ONLY build-time variables ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') ->get(); @@ -1483,7 +1511,12 @@ private function generate_buildtime_environment_variables() // Strip outer quotes from real_value and apply proper bash escaping $value = trim($env->real_value, "'"); $escapedValue = escapeBashEnvValue($value); - $envs->push($env->key.'='.$escapedValue); + + if (isDev() && isset($envs_dict[$env->key])) { + $this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})"); + } + + $envs_dict[$env->key] = $escapedValue; if (isDev()) { $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); @@ -1495,7 +1528,12 @@ private function generate_buildtime_environment_variables() } else { // For normal vars, use double quotes to allow $VAR expansion $escapedValue = escapeBashDoubleQuoted($env->real_value); - $envs->push($env->key.'='.$escapedValue); + + if (isDev() && isset($envs_dict[$env->key])) { + $this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})"); + } + + $envs_dict[$env->key] = $escapedValue; if (isDev()) { $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); @@ -1507,7 +1545,6 @@ private function generate_buildtime_environment_variables() } } else { $sorted_environment_variables = $this->application->environment_variables_preview() - ->where('key', 'not like', 'NIXPACKS_%') ->where('is_buildtime', true) // ONLY build-time variables ->orderBy($this->application->settings->is_env_sorting_enabled ? 'key' : 'id') ->get(); @@ -1525,7 +1562,12 @@ private function generate_buildtime_environment_variables() // Strip outer quotes from real_value and apply proper bash escaping $value = trim($env->real_value, "'"); $escapedValue = escapeBashEnvValue($value); - $envs->push($env->key.'='.$escapedValue); + + if (isDev() && isset($envs_dict[$env->key])) { + $this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})"); + } + + $envs_dict[$env->key] = $escapedValue; if (isDev()) { $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); @@ -1537,7 +1579,12 @@ private function generate_buildtime_environment_variables() } else { // For normal vars, use double quotes to allow $VAR expansion $escapedValue = escapeBashDoubleQuoted($env->real_value); - $envs->push($env->key.'='.$escapedValue); + + if (isDev() && isset($envs_dict[$env->key])) { + $this->application_deployment_queue->addLogEntry("[DEBUG] User override: {$env->key} (was: {$envs_dict[$env->key]}, now: {$escapedValue})"); + } + + $envs_dict[$env->key] = $escapedValue; if (isDev()) { $this->application_deployment_queue->addLogEntry("[DEBUG] Build-time env: {$env->key}"); @@ -1549,6 +1596,12 @@ private function generate_buildtime_environment_variables() } } + // Convert dictionary back to collection in KEY=VALUE format + $envs = collect([]); + foreach ($envs_dict as $key => $value) { + $envs->push($key.'='.$value); + } + // Return the generated environment variables if (isDev()) { $this->application_deployment_queue->addLogEntry('[DEBUG] ========================================'); @@ -3090,7 +3143,7 @@ private function graceful_shutdown_container(string $containerName) try { $timeout = isDev() ? 1 : 30; $this->execute_remote_command( - ["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true], + ["docker stop -t $timeout $containerName", 'hidden' => true, 'ignore_errors' => true], ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true] ); } catch (Exception $error) { diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index c4358570e..825604910 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -191,7 +191,7 @@ private function stopPreviewContainers(array $containers, $server, int $timeout $containerList = implode(' ', array_map('escapeshellarg', $containerNames)); $commands = [ - "docker stop --time=$timeout $containerList", + "docker stop -t $timeout $containerList", "docker rm -f $containerList", ]; instant_remote_process( diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index e28c8142d..45371678b 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -278,7 +278,7 @@ private function stopContainers(array $containers, $server) foreach ($containersToStop as $containerName) { instant_remote_process(command: [ - "docker stop --time=30 $containerName", + "docker stop -t 30 $containerName", "docker rm -f $containerName", ], server: $server, throwError: false); } diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index cdf95d2e4..f4c5f81b0 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -81,7 +81,7 @@ public function mount() 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), ]; - if ($oneClickServiceName === 'cloudflared' || $oneClickServiceName === 'pgadmin') { + if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { data_set($service_payload, 'connect_to_docker_network', true); } $service = Service::create($service_payload); @@ -102,13 +102,33 @@ public function mount() } }); } - $service->parse(isNew: true); + $service->parse(isNew: true); - return redirect()->route('project.service.configuration', [ - 'service_uuid' => $service->uuid, - 'environment_uuid' => $environment->uuid, - 'project_uuid' => $project->uuid, - ]); + // For Beszel service disable gzip (fixes realtime not working issue) + if ($oneClickServiceName === 'beszel') { + $appService = $service->applications()->whereName('beszel')->first(); + if ($appService) { + $appService->is_gzip_enabled = false; + $appService->save(); + } + } + // For Appwrite services, disable strip prefix for services that handle domain requests + if ($oneClickServiceName === 'appwrite') { + $servicesToDisableStripPrefix = ['appwrite', 'appwrite-console', 'appwrite-realtime']; + foreach ($servicesToDisableStripPrefix as $serviceName) { + $appService = $service->applications()->whereName($serviceName)->first(); + if ($appService) { + $appService->is_stripprefix_enabled = false; + $appService->save(); + } + } + } + + return redirect()->route('project.service.configuration', [ + 'service_uuid' => $service->uuid, + 'environment_uuid' => $environment->uuid, + 'project_uuid' => $project->uuid, + ]); } } $this->type = $type->value(); diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index c92f73f17..49d872210 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -90,9 +90,9 @@ protected function getTraefikVersions(): ?array return is_array($traefikVersions) ? $traefikVersions : null; } - public function getConfigurationFilePathProperty() + public function getConfigurationFilePathProperty(): string { - return $this->server->proxyPath().'docker-compose.yml'; + return rtrim($this->server->proxyPath(), '/') . '/docker-compose.yml'; } public function changeProxy() diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index f588b6c00..6d9136b02 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -67,4 +67,8 @@ 'alpine', ]; +const NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK = [ + 'pgadmin', + 'postgresus', +]; const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; diff --git a/config/constants.php b/config/constants.php index b2c43f2b9..893fb11fd 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.451', + 'version' => '4.0.0-beta.452', 'helper_version' => '1.0.12', 'realtime_version' => '1.0.10', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/other/nightly/versions.json b/other/nightly/versions.json index fadd5580d..577fdfe18 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.451" + "version": "4.0.0-beta.452" }, "nightly": { - "version": "4.0.0-beta.452" + "version": "4.0.0-beta.453" }, "helper": { "version": "1.0.12" diff --git a/resources/views/livewire/project/database/heading.blade.php b/resources/views/livewire/project/database/heading.blade.php index b09adcc4e..0e67a3606 100644 --- a/resources/views/livewire/project/database/heading.blade.php +++ b/resources/views/livewire/project/database/heading.blade.php @@ -3,7 +3,9 @@ Database Startup - +
+ +