From ef332b9af488a6249aa0ee5021d1653d9ac8f9a4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 27 Nov 2025 14:00:07 +0100 Subject: [PATCH 1/2] fix: add support for nixpacks plan variables in buildtime environment --- app/Jobs/ApplicationDeploymentJob.php | 33 ++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 13938675a..5aa405aac 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1409,6 +1409,35 @@ private function generate_buildtime_environment_variables() $envs->push($key.'='.escapeBashEnvValue($item)); }); + // Add all variables from nixpacks plan if this is a nixpacks build + // These include NIXPACKS_* variables and other build-time variables detected by nixpacks + 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_* as they're already added + if (str_starts_with($key, 'COOLIFY_') || str_starts_with($key, 'SERVICE_')) { + continue; + } + + $escapedValue = escapeBashEnvValue($value); + $envs->push($key.'='.$escapedValue); + + if (isDev()) { + $this->application_deployment_queue->addLogEntry("[DEBUG] Nixpacks var: {$key}={$escapedValue}"); + } + } + } + } + // Add SERVICE_NAME variables for Docker Compose builds if ($this->build_pack === 'dockercompose') { if ($this->pull_request_id === 0) { @@ -1462,10 +1491,9 @@ private function generate_buildtime_environment_variables() } } - // Add build-time user variables only + // Add user-defined build-time variables (these override nixpacks plan values if there's a conflict) 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(); @@ -1507,7 +1535,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(); From be2b01786ac08b69f40646d4dac4f168b04e5197 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 28 Nov 2025 08:36:00 +0100 Subject: [PATCH 2/2] fix: prevent duplicate environment variables in buildtime.env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors generate_buildtime_environment_variables() to use an associative array (dictionary) approach instead of sequential push() calls. This prevents duplicate variable declarations in the buildtime.env file. **Problem:** After adding nixpacks plan variables to buildtime.env, the same variable could appear twice in the file: - Once from nixpacks plan (e.g., NIXPACKS_NODE_VERSION='22') - Once from user-defined variables (e.g., NIXPACKS_NODE_VERSION="22") This caused shell errors and undefined behavior during Docker builds. **Root Cause:** The push() method adds items sequentially without checking for duplicate keys. When a variable existed in both nixpacks plan AND user-defined vars, both would be written to the file. **Solution:** - Use associative array ($envs_dict) for automatic deduplication - Establish clear override precedence: 1. Nixpacks plan variables (lowest priority) 2. COOLIFY_* variables (medium priority) 3. SERVICE_* variables (medium priority) 4. User-defined variables (highest priority - can override everything) - Convert to collection format at the end - Add debug logging when user variables override plan variables **Benefits:** - Automatic deduplication (array keys are unique by nature) - User variables properly override nixpacks plan values - Clear, explicit precedence order - No breaking changes to existing functionality Fixes #7114 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Jobs/ApplicationDeploymentJob.php | 72 ++++++++++++++++++--------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 5aa405aac..06a0ad328 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1401,16 +1401,10 @@ private function generate_buildtime_environment_variables() $this->application_deployment_queue->addLogEntry('[DEBUG] ========================================'); } - $envs = collect([]); - $coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true); + // Use associative array for automatic deduplication + $envs_dict = []; - // Add COOLIFY variables - $coolify_envs->each(function ($item, $key) use ($envs) { - $envs->push($key.'='.escapeBashEnvValue($item)); - }); - - // Add all variables from nixpacks plan if this is a nixpacks build - // These include NIXPACKS_* variables and other build-time variables detected by nixpacks + // 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()) { @@ -1423,13 +1417,13 @@ private function generate_buildtime_environment_variables() } foreach ($planVariables as $key => $value) { - // Skip COOLIFY_* and SERVICE_* as they're already added + // 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->push($key.'='.$escapedValue); + $envs_dict[$key] = $escapedValue; if (isDev()) { $this->application_deployment_queue->addLogEntry("[DEBUG] Nixpacks var: {$key}={$escapedValue}"); @@ -1438,7 +1432,13 @@ private function generate_buildtime_environment_variables() } } - // Add SERVICE_NAME variables for Docker Compose builds + // 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); + } + + // 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 @@ -1449,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 @@ -1462,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 { @@ -1471,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 @@ -1484,14 +1484,14 @@ 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 user-defined build-time variables (these override nixpacks plan values if there's a conflict) + // 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('is_buildtime', true) // ONLY build-time variables @@ -1511,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}"); @@ -1523,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}"); @@ -1552,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}"); @@ -1564,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}"); @@ -1576,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] ========================================');