chore: prepare for PR

This commit is contained in:
Andras Bacsai 2026-02-23 14:28:28 +01:00
parent 2310ad5f7f
commit ec14b55f0a
2 changed files with 114 additions and 240 deletions

View file

@ -171,6 +171,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private bool $dockerBuildkitSupported = false;
private bool $dockerSecretsSupported = false;
private bool $skip_build = false;
private Collection|string $build_secrets;
@ -381,13 +383,6 @@ public function handle(): void
private function detectBuildKitCapabilities(): void
{
// If build secrets are not enabled, skip detection and use traditional args
if (! $this->application->settings->use_build_secrets) {
$this->dockerBuildkitSupported = false;
return;
}
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
@ -403,53 +398,55 @@ private function detectBuildKitCapabilities(): void
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
$this->dockerBuildkitSupported = false;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+). Build secrets feature disabled.");
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+).");
return;
}
$buildkitEnabled = instant_remote_process(
// Check buildx availability (always installed by Coolify on Docker 24.0+)
$buildxAvailable = instant_remote_process(
["docker buildx version >/dev/null 2>&1 && echo 'available' || echo 'not-available'"],
$serverToCheck
);
if (trim($buildkitEnabled) !== 'available') {
if (trim($buildxAvailable) === 'available') {
$this->dockerBuildkitSupported = true;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
} else {
// Fallback: test DOCKER_BUILDKIT=1 support via --progress flag
$buildkitTest = instant_remote_process(
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q '\\-\\-progress' && echo 'supported' || echo 'not-supported'"],
$serverToCheck
);
if (trim($buildkitTest) === 'supported') {
$this->dockerBuildkitSupported = true;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit secrets support detected on {$serverName}.");
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit support detected on {$serverName}.");
} else {
$this->dockerBuildkitSupported = false;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not have BuildKit secrets support.");
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit. Build output progress will be limited.");
}
} else {
// Buildx is available, which means BuildKit is available
// Now specifically test for secrets support
}
// If build secrets are enabled and BuildKit is available, verify --secret flag support
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported) {
$secretsTest = instant_remote_process(
["docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
$serverToCheck
);
if (trim($secretsTest) === 'supported') {
$this->dockerBuildkitSupported = true;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
$this->dockerSecretsSupported = true;
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
} else {
$this->dockerBuildkitSupported = false;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with Buildx on {$serverName}, but secrets not supported.");
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
$this->dockerSecretsSupported = false;
$this->application_deployment_queue->addLogEntry("Docker on {$serverName} does not support build secrets. Using traditional build arguments.");
}
}
} catch (\Exception $e) {
$this->dockerBuildkitSupported = false;
$this->dockerSecretsSupported = false;
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but detection failed. Using traditional build arguments.');
}
}
@ -632,7 +629,7 @@ private function deploy_docker_compose_buildpack()
// For raw compose, we cannot automatically add secrets configuration
// User must define it manually in their docker-compose file
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
}
} else {
@ -653,7 +650,7 @@ private function deploy_docker_compose_buildpack()
}
// Add build secrets to compose file if enabled and BuildKit is supported
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
$composeFile = $this->add_build_secrets_to_compose($composeFile);
}
@ -2817,7 +2814,11 @@ private function build_static_image()
$nginx_config = base64_encode(defaultNginxConfiguration());
}
}
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
if ($this->dockerBuildkitSupported) {
$build_command = "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
} else {
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile -t {$this->production_image_name} {$this->workdir}";
}
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
@ -2857,21 +2858,19 @@ private function wrap_build_command_with_env_export(string $build_command): stri
private function build_image()
{
// Add Coolify related variables to the build args/secrets
if ($this->dockerBuildkitSupported) {
// Coolify variables are already included in the secrets from generate_build_env_variables
// build_secrets is already a string at this point
} else {
if (! $this->dockerBuildkitSupported) {
// Traditional build args approach - generate COOLIFY_ variables locally
// Generate COOLIFY_ variables locally for build args
$coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
$coolify_envs->each(function ($value, $key) {
$this->build_args->push("--build-arg '{$key}'");
});
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
? $this->build_args->implode(' ')
: (string) $this->build_args;
}
// Always convert build_args Collection to string for command interpolation
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
? $this->build_args->implode(' ')
: (string) $this->build_args;
$this->application_deployment_queue->addLogEntry('----------------------------------------');
if ($this->disableBuildCache) {
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
@ -2899,7 +2898,7 @@ private function build_image()
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
'hidden' => true,
]);
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the nixpacks Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
@ -2907,9 +2906,8 @@ private function build_image()
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
ray($build_command);
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
}
} else {
$this->execute_remote_command([
@ -2919,18 +2917,16 @@ private function build_image()
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
'hidden' => true,
]);
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the nixpacks Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}");
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
}
}
@ -2952,7 +2948,7 @@ private function build_image()
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
} else {
// Dockerfile buildpack
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
@ -2963,19 +2959,17 @@ private function build_image()
}
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
}
} else {
// Traditional build with args
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
}
}
$base64_build_command = base64_encode($build_command);
@ -3010,7 +3004,11 @@ private function build_image()
$nginx_config = base64_encode(defaultNginxConfiguration());
}
}
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
if ($this->dockerBuildkitSupported) {
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
}
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
@ -3035,7 +3033,7 @@ private function build_image()
} else {
// Pure Dockerfile based deployment
if ($this->application->dockerfile) {
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
@ -3044,12 +3042,19 @@ private function build_image()
} else {
$build_command = "DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}";
}
} else {
// Traditional build with args
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
}
} else {
// Traditional build with args (no --progress for legacy builder compatibility)
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
}
}
$base64_build_command = base64_encode($build_command);
@ -3079,18 +3084,16 @@ private function build_image()
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
'hidden' => true,
]);
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the nixpacks Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
}
} else {
$this->execute_remote_command([
@ -3100,18 +3103,16 @@ private function build_image()
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
'hidden' => true,
]);
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the nixpacks Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
}
}
$base64_build_command = base64_encode($build_command);
@ -3132,7 +3133,7 @@ private function build_image()
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
} else {
// Dockerfile buildpack
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
// Modify the Dockerfile to use build secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
// Use BuildKit with secrets
@ -3144,19 +3145,17 @@ private function build_image()
}
} elseif ($this->dockerBuildkitSupported) {
// BuildKit without secrets
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
}
} else {
// Traditional build with args
if ($this->force_rebuild) {
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
} else {
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
}
}
$base64_build_command = base64_encode($build_command);
@ -3332,7 +3331,7 @@ private function generate_build_env_variables()
$this->analyzeBuildTimeVariables($variables);
}
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
if ($this->dockerSecretsSupported) {
$this->generate_build_secrets($variables);
$this->build_args = '';
} else {
@ -3819,7 +3818,7 @@ private function modify_dockerfiles_for_compose($composeFile)
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: All required ARG declarations already exist.");
}
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");

View file

@ -1,144 +1,59 @@
<?php
test('multiline environment variables are properly escaped for docker build args', function () {
$sshKey = '-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
-----END OPENSSH PRIVATE KEY-----';
test('generateDockerBuildArgs returns only keys without values', function () {
$variables = [
['key' => 'SSH_PRIVATE_KEY', 'value' => "'{$sshKey}'", 'is_multiline' => true],
['key' => 'SSH_PRIVATE_KEY', 'value' => "'some-ssh-key'", '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'");
// Docker gets values from the environment, so only keys should be in build args
expect($buildArgs->first())->toBe('--build-arg SSH_PRIVATE_KEY');
expect($buildArgs->last())->toBe('--build-arg REGULAR_VAR');
});
test('multiline variables with special bash characters are escaped correctly', function () {
$valueWithSpecialChars = "line1\nline2 with \"quotes\"\nline3 with \$variables\nline4 with `backticks`";
test('generateDockerBuildArgs works with collection of objects', 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);
expect($buildArgs->values()->toArray())->toBe([
'--build-arg VAR1',
'--build-arg VAR2',
]);
});
test('generateDockerBuildArgs collection can be imploded into valid command string', function () {
$variables = [
['key' => 'SPECIAL_VALUE', 'value' => "'{$valueWithSpecialChars}'", 'is_multiline' => true],
['key' => 'COOLIFY_URL', 'value' => 'http://example.com', 'is_multiline' => false],
['key' => 'COOLIFY_BRANCH', 'value' => 'main', 'is_multiline' => false],
];
$buildArgs = generateDockerBuildArgs($variables);
// The collection must be imploded to a string for command interpolation
// This was the bug: Collection was interpolated as JSON instead of a space-separated string
$argsString = $buildArgs->implode(' ');
expect($argsString)->toBe('--build-arg COOLIFY_URL --build-arg COOLIFY_BRANCH');
// Verify it does NOT produce JSON when cast to string
expect($argsString)->not->toContain('{');
expect($argsString)->not->toContain('}');
});
test('generateDockerBuildArgs handles variables without is_multiline', function () {
$variables = [
['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
];
$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');
expect($arg)->toBe('--build-arg NO_FLAG_VAR');
});
test('generateDockerEnvFlags produces correct format', function () {
@ -155,54 +70,14 @@
expect($envFlags)->toContain('line2');
});
test('helper functions work with collection input', function () {
test('generateDockerEnvFlags works 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("'\\''");
});