diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index b3f9793c7..965eab68e 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -1526,7 +1526,6 @@ private function prepare_builder_image()
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
$env_flags = $this->generate_docker_env_flags_for_secrets();
-
if ($this->use_build_server) {
if ($this->dockerConfigFileExists === 'NOK') {
throw new RuntimeException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.');
@@ -1553,6 +1552,18 @@ private function prepare_builder_image()
$this->run_pre_deployment_command();
}
+ private function restart_builder_container_with_actual_commit()
+ {
+ // Stop and remove the current helper container
+ $this->graceful_shutdown_container($this->deployment_uuid);
+
+ // Clear cached env_args to force regeneration with actual SOURCE_COMMIT value
+ $this->env_args = null;
+
+ // Restart the helper container with updated environment variables (including actual SOURCE_COMMIT)
+ $this->prepare_builder_image();
+ }
+
private function deploy_to_additional_destinations()
{
if ($this->application->additional_networks->count() === 0) {
@@ -1621,6 +1632,8 @@ private function set_coolify_variables()
if (isset($this->application->git_branch)) {
$this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} ";
}
+ $this->coolify_variables .= "COOLIFY_RESOURCE_UUID={$this->application->uuid} ";
+ $this->coolify_variables .= "COOLIFY_CONTAINER_NAME={$this->container_name} ";
}
private function check_git_if_build_needed()
@@ -1688,6 +1701,12 @@ private function check_git_if_build_needed()
$this->application_deployment_queue->save();
}
$this->set_coolify_variables();
+
+ // Restart helper container with actual SOURCE_COMMIT value
+ if ($this->application->settings->use_build_secrets && $this->commit !== 'HEAD') {
+ $this->application_deployment_queue->addLogEntry('Restarting helper container with actual SOURCE_COMMIT value.');
+ $this->restart_builder_container_with_actual_commit();
+ }
}
private function clone_repository()
@@ -1936,7 +1955,6 @@ private function generate_env_variables()
$this->env_args = collect([]);
$this->env_args->put('SOURCE_COMMIT', $this->commit);
$coolify_envs = $this->generate_coolify_env_variables();
-
// For build process, include only environment variables where is_buildtime = true
if ($this->pull_request_id === 0) {
// Get environment variables that are marked as available during build
@@ -1991,6 +2009,12 @@ private function generate_env_variables()
}
}
}
+
+ // Merge COOLIFY_* variables into env_args for build process
+ // This ensures they're available for both build args and build secrets
+ $coolify_envs->each(function ($value, $key) {
+ $this->env_args->put($key, $value);
+ });
}
private function generate_compose_file()
@@ -2610,6 +2634,8 @@ private function build_image()
} else {
// Dockerfile buildpack
if ($this->dockerBuildkitSupported) {
+ // Modify the Dockerfile to use build secrets
+ $this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
// Use BuildKit with secrets
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
if ($this->force_rebuild) {
@@ -2764,7 +2790,6 @@ private function generate_build_env_variables()
$this->generate_env_variables();
$variables = collect([])->merge($this->env_args);
}
-
// Analyze build variables for potential issues
if ($variables->isNotEmpty()) {
$this->analyzeBuildTimeVariables($variables);
@@ -2779,12 +2804,23 @@ private function generate_build_env_variables()
$secrets_hash = $this->generate_secrets_hash($variables);
}
- $this->build_args = $variables->map(function ($value, $key) {
- $value = escapeshellarg($value);
+ $env_vars = $this->pull_request_id === 0
+ ? $this->application->environment_variables()->where('is_buildtime', true)->get()
+ : $this->application->environment_variables_preview()->where('is_buildtime', true)->get();
- return "--build-arg {$key}={$value}";
+ // Map variables to include is_multiline flag
+ $vars_with_metadata = $variables->map(function ($value, $key) use ($env_vars) {
+ $env = $env_vars->firstWhere('key', $key);
+
+ return [
+ 'key' => $key,
+ 'value' => $value,
+ 'is_multiline' => $env ? $env->is_multiline : false,
+ ];
});
+ $this->build_args = generateDockerBuildArgs($vars_with_metadata);
+
if ($secrets_hash) {
$this->build_args->push("--build-arg COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}");
}
@@ -2798,23 +2834,37 @@ private function generate_docker_env_flags_for_secrets()
return '';
}
- $variables = $this->pull_request_id === 0
- ? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get()
- : $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get();
+ // Generate env variables if not already done
+ // This populates $this->env_args with both user-defined and COOLIFY_* variables
+ if (! $this->env_args || $this->env_args->isEmpty()) {
+ $this->generate_env_variables();
+ }
+
+ $variables = $this->env_args;
if ($variables->isEmpty()) {
return '';
}
$secrets_hash = $this->generate_secrets_hash($variables);
- $env_flags = $variables
- ->map(function ($env) {
- $escaped_value = escapeshellarg($env->real_value);
- return "-e {$env->key}={$escaped_value}";
- })
- ->implode(' ');
+ // Get database env vars to check for multiline flag
+ $env_vars = $this->pull_request_id === 0
+ ? $this->application->environment_variables()->where('is_buildtime', true)->get()
+ : $this->application->environment_variables_preview()->where('is_buildtime', true)->get();
+ // Map to simple array format for the helper function
+ $vars_array = $variables->map(function ($value, $key) use ($env_vars) {
+ $env = $env_vars->firstWhere('key', $key);
+
+ return [
+ 'key' => $key,
+ 'value' => $value,
+ 'is_multiline' => $env ? $env->is_multiline : false,
+ ];
+ });
+
+ $env_flags = generateDockerEnvFlags($vars_array);
$env_flags .= " -e COOLIFY_BUILD_SECRETS_HASH={$secrets_hash}";
return $env_flags;
@@ -2876,7 +2926,6 @@ private function add_build_env_variables_to_dockerfile()
'save' => 'dockerfile',
]);
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
-
if ($this->pull_request_id === 0) {
// Only add environment variables that are available during build
$envs = $this->application->environment_variables()
@@ -2961,16 +3010,19 @@ private function modify_dockerfile_for_secrets($dockerfile_path)
$dockerfile->prepend('# syntax=docker/dockerfile:1');
}
- // Get environment variables for secrets
- $variables = $this->pull_request_id === 0
- ? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get()
- : $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->where('is_buildtime', true)->get();
+ // Generate env variables if not already done
+ // This populates $this->env_args with both user-defined and COOLIFY_* variables
+ if (! $this->env_args || $this->env_args->isEmpty()) {
+ $this->generate_env_variables();
+ }
+
+ $variables = $this->env_args;
if ($variables->isEmpty()) {
return;
}
// Generate mount strings for all secrets
- $mountStrings = $variables->map(fn ($env) => "--mount=type=secret,id={$env->key},env={$env->key}")->implode(' ');
+ $mountStrings = $variables->map(fn ($value, $key) => "--mount=type=secret,id={$key},env={$key}")->implode(' ');
// Add mount for the secrets hash to ensure cache invalidation
$mountStrings .= ' --mount=type=secret,id=COOLIFY_BUILD_SECRETS_HASH,env=COOLIFY_BUILD_SECRETS_HASH';
@@ -2998,8 +3050,6 @@ private function modify_dockerfile_for_secrets($dockerfile_path)
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$dockerfile_path} > /dev/null"),
'hidden' => true,
]);
-
- $this->application_deployment_queue->addLogEntry('Modified Dockerfile to use build secrets.');
}
}
@@ -3009,15 +3059,13 @@ private function modify_dockerfiles_for_compose($composeFile)
return;
}
- $variables = $this->pull_request_id === 0
- ? $this->application->environment_variables()
- ->where('key', 'not like', 'NIXPACKS_%')
- ->where('is_buildtime', true)
- ->get()
- : $this->application->environment_variables_preview()
- ->where('key', 'not like', 'NIXPACKS_%')
- ->where('is_buildtime', true)
- ->get();
+ // Generate env variables if not already done
+ // This populates $this->env_args with both user-defined and COOLIFY_* variables
+ if (! $this->env_args || $this->env_args->isEmpty()) {
+ $this->generate_env_variables();
+ }
+
+ $variables = $this->env_args;
if ($variables->isEmpty()) {
$this->application_deployment_queue->addLogEntry('No build-time variables to add to Dockerfiles.');
@@ -3091,8 +3139,8 @@ private function modify_dockerfiles_for_compose($composeFile)
$isMultiStage = count($fromIndices) > 1;
$argsToAdd = collect([]);
- foreach ($variables as $env) {
- $argsToAdd->push("ARG {$env->key}");
+ foreach ($variables as $key => $value) {
+ $argsToAdd->push("ARG {$key}");
}
ray($argsToAdd);
@@ -3163,19 +3211,22 @@ private function modify_dockerfiles_for_compose($composeFile)
private function add_build_secrets_to_compose($composeFile)
{
- // Get environment variables for secrets
- $variables = $this->pull_request_id === 0
- ? $this->application->environment_variables()->where('key', 'not like', 'NIXPACKS_%')->get()
- : $this->application->environment_variables_preview()->where('key', 'not like', 'NIXPACKS_%')->get();
+ // Generate env variables if not already done
+ // This populates $this->env_args with both user-defined and COOLIFY_* variables
+ if (! $this->env_args || $this->env_args->isEmpty()) {
+ $this->generate_env_variables();
+ }
+
+ $variables = $this->env_args;
if ($variables->isEmpty()) {
return $composeFile;
}
$secrets = [];
- foreach ($variables as $env) {
- $secrets[$env->key] = [
- 'environment' => $env->key,
+ foreach ($variables as $key => $value) {
+ $secrets[$key] = [
+ 'environment' => $key,
];
}
@@ -3190,9 +3241,9 @@ private function add_build_secrets_to_compose($composeFile)
if (! isset($service['build']['secrets'])) {
$service['build']['secrets'] = [];
}
- foreach ($variables as $env) {
- if (! in_array($env->key, $service['build']['secrets'])) {
- $service['build']['secrets'][] = $env->key;
+ foreach ($variables as $key => $value) {
+ if (! in_array($key, $service['build']['secrets'])) {
+ $service['build']['secrets'][] = $key;
}
}
}
@@ -3311,7 +3362,6 @@ private function next(string $status)
queue_next_deployment($this->application);
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
- ray($this->application->team()->id);
event(new ApplicationConfigurationChanged($this->application->team()->id));
if (! $this->only_this_server) {
diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php
index dccd1e499..ebdc014ae 100644
--- a/app/Livewire/Project/Application/DeploymentNavbar.php
+++ b/app/Livewire/Project/Application/DeploymentNavbar.php
@@ -50,6 +50,28 @@ public function force_start()
}
}
+ public function copyLogsToClipboard(): string
+ {
+ $logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
+
+ if (! $logs) {
+ return '';
+ }
+
+ $markdown = "# Deployment Logs\n\n";
+ $markdown .= "```\n";
+
+ foreach ($logs as $log) {
+ if (isset($log['output'])) {
+ $markdown .= $log['output']."\n";
+ }
+ }
+
+ $markdown .= "```\n";
+
+ return $markdown;
+ }
+
public function cancel()
{
$deployment_uuid = $this->application_deployment_queue->deployment_uuid;
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 1491e4712..af26c97bd 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -1119,3 +1119,64 @@ function escapeDollarSign($value)
return str_replace($search, $replace, $value);
}
+
+/**
+ * Generate Docker build arguments from environment variables collection
+ *
+ * @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
+ * @return \Illuminate\Support\Collection Collection of formatted --build-arg strings
+ */
+function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
+{
+ $variables = collect($variables);
+
+ return $variables->map(function ($var) {
+ $key = is_array($var) ? data_get($var, 'key') : $var->key;
+ $value = is_array($var) ? data_get($var, 'value') : $var->value;
+ $isMultiline = is_array($var) ? data_get($var, 'is_multiline', false) : ($var->is_multiline ?? false);
+
+ if ($isMultiline) {
+ // For multiline variables, strip surrounding quotes and escape for bash
+ $raw_value = trim($value, "'");
+ $escaped_value = str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $raw_value);
+
+ return "--build-arg {$key}=\"{$escaped_value}\"";
+ }
+
+ // For regular variables, use escapeshellarg for security
+ $value = escapeshellarg($value);
+
+ return "--build-arg {$key}={$value}";
+ });
+}
+
+/**
+ * Generate Docker environment flags from environment variables collection
+ *
+ * @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
+ * @return string Space-separated environment flags
+ */
+function generateDockerEnvFlags($variables): string
+{
+ $variables = collect($variables);
+
+ return $variables
+ ->map(function ($var) {
+ $key = is_array($var) ? data_get($var, 'key') : $var->key;
+ $value = is_array($var) ? data_get($var, 'value') : $var->value;
+ $isMultiline = is_array($var) ? data_get($var, 'is_multiline', false) : ($var->is_multiline ?? false);
+
+ if ($isMultiline) {
+ // For multiline variables, strip surrounding quotes and escape for bash
+ $raw_value = trim($value, "'");
+ $escaped_value = str_replace(['\\', '"', '$', '`'], ['\\\\', '\\"', '\\$', '\\`'], $raw_value);
+
+ return "-e {$key}=\"{$escaped_value}\"";
+ }
+
+ $escaped_value = escapeshellarg($value);
+
+ return "-e {$key}={$escaped_value}";
+ })
+ ->implode(' ');
+}
diff --git a/config/constants.php b/config/constants.php
index 749d6435b..ddda70d19 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -2,7 +2,7 @@
return [
'coolify' => [
- 'version' => '4.0.0-beta.433',
+ 'version' => '4.0.0-beta.434',
'helper_version' => '1.0.11',
'realtime_version' => '1.0.10',
'self_hosted' => env('SELF_HOSTED', true),
diff --git a/resources/views/livewire/project/application/deployment-navbar.blade.php b/resources/views/livewire/project/application/deployment-navbar.blade.php
index effb6b6fe..60c660bf7 100644
--- a/resources/views/livewire/project/application/deployment-navbar.blade.php
+++ b/resources/views/livewire/project/application/deployment-navbar.blade.php
@@ -5,6 +5,9 @@
@else
Show Debug Logs
@endif
+ @if (isDev())
+ Copy Logs
+ @endif
@if (data_get($application_deployment_queue, 'status') === 'queued')
Force Start
@endif
diff --git a/tests/Feature/MultilineEnvironmentVariableTest.php b/tests/Feature/MultilineEnvironmentVariableTest.php
new file mode 100644
index 000000000..e32a2ce99
--- /dev/null
+++ b/tests/Feature/MultilineEnvironmentVariableTest.php
@@ -0,0 +1,208 @@
+ 'SSH_PRIVATE_KEY', 'value' => "'{$sshKey}'", 'is_multiline' => true],
+ ['key' => 'REGULAR_VAR', 'value' => 'simple value', 'is_multiline' => false],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+
+ // SSH key should use double quotes and have proper escaping
+ $sshArg = $buildArgs->first();
+ expect($sshArg)->toStartWith('--build-arg SSH_PRIVATE_KEY="');
+ expect($sshArg)->toEndWith('"');
+ expect($sshArg)->toContain('BEGIN OPENSSH PRIVATE KEY');
+ expect($sshArg)->not->toContain("'BEGIN"); // Should not have the wrapper single quotes
+
+ // Regular var should use escapeshellarg (single quotes)
+ $regularArg = $buildArgs->last();
+ expect($regularArg)->toBe("--build-arg REGULAR_VAR='simple value'");
+});
+
+test('multiline variables with special bash characters are escaped correctly', function () {
+ $valueWithSpecialChars = "line1\nline2 with \"quotes\"\nline3 with \$variables\nline4 with `backticks`";
+
+ $variables = [
+ ['key' => 'SPECIAL_VALUE', 'value' => "'{$valueWithSpecialChars}'", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Verify double quotes are escaped
+ expect($arg)->toContain('\\"quotes\\"');
+ // Verify dollar signs are escaped
+ expect($arg)->toContain('\\$variables');
+ // Verify backticks are escaped
+ expect($arg)->toContain('\\`backticks\\`');
+});
+
+test('single-line environment variables use escapeshellarg', function () {
+ $variables = [
+ ['key' => 'SIMPLE_VAR', 'value' => 'simple value with spaces', 'is_multiline' => false],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Should use single quotes from escapeshellarg
+ expect($arg)->toBe("--build-arg SIMPLE_VAR='simple value with spaces'");
+});
+
+test('multiline certificate with newlines is preserved', function () {
+ $certificate = '-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAKL0UG+mRkSvMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTkwOTE3MDUzMzI5WhcNMjkwOTE0MDUzMzI5WjBF
+-----END CERTIFICATE-----';
+
+ $variables = [
+ ['key' => 'TLS_CERT', 'value' => "'{$certificate}'", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Newlines should be preserved in the output
+ expect($arg)->toContain("\n");
+ expect($arg)->toContain('BEGIN CERTIFICATE');
+ expect($arg)->toContain('END CERTIFICATE');
+ expect(substr_count($arg, "\n"))->toBeGreaterThan(0);
+});
+
+test('multiline JSON configuration is properly escaped', function () {
+ $jsonConfig = '{
+ "key": "value",
+ "nested": {
+ "array": [1, 2, 3]
+ }
+}';
+
+ $variables = [
+ ['key' => 'JSON_CONFIG', 'value' => "'{$jsonConfig}'", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // All double quotes in JSON should be escaped
+ expect($arg)->toContain('\\"key\\"');
+ expect($arg)->toContain('\\"value\\"');
+ expect($arg)->toContain('\\"nested\\"');
+});
+
+test('empty multiline variable is handled correctly', function () {
+ $variables = [
+ ['key' => 'EMPTY_VAR', 'value' => "''", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ expect($arg)->toBe('--build-arg EMPTY_VAR=""');
+});
+
+test('multiline variable with only newlines', function () {
+ $onlyNewlines = "\n\n\n";
+
+ $variables = [
+ ['key' => 'NEWLINES_ONLY', 'value' => "'{$onlyNewlines}'", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ expect($arg)->toContain("\n");
+ // Should have 3 newlines preserved
+ expect(substr_count($arg, "\n"))->toBe(3);
+});
+
+test('multiline variable with backslashes is escaped correctly', function () {
+ $valueWithBackslashes = "path\\to\\file\nC:\\Windows\\System32";
+
+ $variables = [
+ ['key' => 'PATH_VAR', 'value' => "'{$valueWithBackslashes}'", 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Backslashes should be doubled
+ expect($arg)->toContain('path\\\\to\\\\file');
+ expect($arg)->toContain('C:\\\\Windows\\\\System32');
+});
+
+test('generateDockerEnvFlags produces correct format', function () {
+ $variables = [
+ ['key' => 'NORMAL_VAR', 'value' => 'value', 'is_multiline' => false],
+ ['key' => 'MULTILINE_VAR', 'value' => "'line1\nline2'", 'is_multiline' => true],
+ ];
+
+ $envFlags = generateDockerEnvFlags($variables);
+
+ expect($envFlags)->toContain('-e NORMAL_VAR=');
+ expect($envFlags)->toContain('-e MULTILINE_VAR="');
+ expect($envFlags)->toContain('line1');
+ expect($envFlags)->toContain('line2');
+});
+
+test('helper functions work with collection input', function () {
+ $variables = collect([
+ (object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
+ (object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
+ ]);
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ expect($buildArgs)->toHaveCount(2);
+
+ $envFlags = generateDockerEnvFlags($variables);
+ expect($envFlags)->toBeString();
+ expect($envFlags)->toContain('-e VAR1=');
+ expect($envFlags)->toContain('-e VAR2="');
+});
+
+test('variables without is_multiline default to false', function () {
+ $variables = [
+ ['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Should use escapeshellarg (single quotes) since is_multiline defaults to false
+ expect($arg)->toBe("--build-arg NO_FLAG_VAR='some value'");
+});
+
+test('real world SSH key example', function () {
+ // Simulate what real_value returns (wrapped in single quotes)
+ $sshKey = "'-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
+hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
+AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
+uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
+-----END OPENSSH PRIVATE KEY-----'";
+
+ $variables = [
+ ['key' => 'KEY', 'value' => $sshKey, 'is_multiline' => true],
+ ];
+
+ $buildArgs = generateDockerBuildArgs($variables);
+ $arg = $buildArgs->first();
+
+ // Should produce clean output without wrapper quotes
+ expect($arg)->toStartWith('--build-arg KEY="-----BEGIN OPENSSH PRIVATE KEY-----');
+ expect($arg)->toEndWith('-----END OPENSSH PRIVATE KEY-----"');
+ // Should NOT have the escaped quote sequence that was in the bug
+ expect($arg)->not->toContain("''");
+ expect($arg)->not->toContain("'\\''");
+});
diff --git a/versions.json b/versions.json
index b5cf3360a..cb9fb7dc2 100644
--- a/versions.json
+++ b/versions.json
@@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
- "version": "4.0.0-beta.433"
+ "version": "4.0.0-beta.434"
},
"nightly": {
- "version": "4.0.0-beta.434"
+ "version": "4.0.0-beta.435"
},
"helper": {
"version": "1.0.11"