From d59c75c2b23d95f2cf798d76efa4f31b6e99f611 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:16:05 +0100 Subject: [PATCH 1/2] Fix: Docker build args injection regex to support service names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The regex pattern in injectDockerComposeBuildArgs() was too restrictive and failed to match `docker compose build servicename` commands. Changed the lookahead from `(?=\s+(?:--|-)|\s+(?:&&|\|\||;|\|)|$)` to the simpler `(?=\s|$)` to allow any content after the build command, including service names with hyphens/underscores and flags. Also improved the ApplicationDeploymentJob to use the new helper function and added comprehensive test coverage for service-specific builds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Jobs/ApplicationDeploymentJob.php | 13 +++- bootstrap/helpers/docker.php | 59 +++++++++++++++++++ ...cationDeploymentCustomBuildCommandTest.php | 55 +++++++++++++++++ 3 files changed, 124 insertions(+), 3 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 3c428cf5f..6df9a8623 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -670,13 +670,20 @@ private function deploy_docker_compose_buildpack() $build_command = "DOCKER_BUILDKIT=1 {$build_command}"; } - // Append build arguments if not using build secrets (matching default behavior) + // Inject build arguments after build subcommand if not using build secrets if (! $this->application->settings->use_build_secrets && $this->build_args instanceof \Illuminate\Support\Collection && $this->build_args->isNotEmpty()) { $build_args_string = $this->build_args->implode(' '); // Escape single quotes for bash -c context used by executeInDocker $build_args_string = str_replace("'", "'\\''", $build_args_string); - $build_command .= " {$build_args_string}"; - $this->application_deployment_queue->addLogEntry('Adding build arguments to custom Docker Compose build command.'); + + // Inject build args right after 'build' subcommand (not at the end) + $original_command = $build_command; + $build_command = injectDockerComposeBuildArgs($build_command, $build_args_string); + + // Only log if build args were actually injected (command was modified) + if ($build_command !== $original_command) { + $this->application_deployment_queue->addLogEntry('Adding build arguments to custom Docker Compose build command.'); + } } $this->execute_remote_command( diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 4a0faaec1..f6d69ef60 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -1376,3 +1376,62 @@ function injectDockerComposeFlags(string $command, string $composeFilePath, stri // Replace only first occurrence to avoid modifying comments/strings/chained commands return preg_replace('/docker\s+compose/', $dockerComposeReplacement, $command, 1); } + +/** + * Inject build arguments right after build-related subcommands in docker/docker compose commands. + * This ensures build args are only applied to build operations, not to push, pull, up, etc. + * + * Supports: + * - docker compose build + * - docker buildx build + * - docker builder build + * - docker build (legacy) + * + * Examples: + * - Input: "docker compose -f file.yml build" + * Output: "docker compose -f file.yml build --build-arg X --build-arg Y" + * + * - Input: "docker buildx build --platform linux/amd64" + * Output: "docker buildx build --build-arg X --build-arg Y --platform linux/amd64" + * + * - Input: "docker builder build --tag myimage:latest" + * Output: "docker builder build --build-arg X --build-arg Y --tag myimage:latest" + * + * - Input: "docker compose build && docker compose push" + * Output: "docker compose build --build-arg X --build-arg Y && docker compose push" + * + * - Input: "docker compose push" + * Output: "docker compose push" (unchanged - no build command found) + * + * @param string $command The docker command + * @param string $buildArgsString The build arguments to inject (e.g., "--build-arg X --build-arg Y") + * @return string The modified command with build args injected after build subcommand + */ +function injectDockerComposeBuildArgs(string $command, string $buildArgsString): string +{ + // Early return if no build args to inject + if (empty(trim($buildArgsString))) { + return $command; + } + + // Match build-related commands: + // - ' builder build' (docker builder build) + // - ' buildx build' (docker buildx build) + // - ' build' (docker compose build, docker build) + // Followed by either: + // - whitespace (allowing service names, flags, or any valid arguments) + // - end of string ($) + // This regex ensures we match build subcommands, not "build" in other contexts + // IMPORTANT: Order matters - check longer patterns first (builder build, buildx build) before ' build' + $pattern = '/( builder build| buildx build| build)(?=\s|$)/'; + + // Replace the first occurrence of build command with build command + build-args + $modifiedCommand = preg_replace( + $pattern, + '$1 '.$buildArgsString, + $command, + 1 // Only replace first occurrence + ); + + return $modifiedCommand ?? $command; +} diff --git a/tests/Unit/ApplicationDeploymentCustomBuildCommandTest.php b/tests/Unit/ApplicationDeploymentCustomBuildCommandTest.php index fc29f19c3..3dd18ee6c 100644 --- a/tests/Unit/ApplicationDeploymentCustomBuildCommandTest.php +++ b/tests/Unit/ApplicationDeploymentCustomBuildCommandTest.php @@ -615,3 +615,58 @@ expect($path)->not->toContain('//', "Double slash found for baseDir: {$case['baseDir']}"); } }); + +// Tests for injectDockerComposeBuildArgs() helper function +it('injects build args when building specific service', function () { + $command = 'docker compose build web'; + $buildArgs = '--build-arg ENV=prod'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker compose build --build-arg ENV=prod web'); +}); + +it('injects build args with service name containing hyphens', function () { + $command = 'docker compose build my-service-name'; + $buildArgs = '--build-arg TEST=value'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker compose build --build-arg TEST=value my-service-name'); +}); + +it('injects build args with service name containing underscores', function () { + $command = 'docker compose build my_service_name'; + $buildArgs = '--build-arg TEST=value'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker compose build --build-arg TEST=value my_service_name'); +}); + +it('injects build args before service name and existing flags', function () { + $command = 'docker compose build backend --no-cache'; + $buildArgs = '--build-arg FOO=bar'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker compose build --build-arg FOO=bar backend --no-cache'); +}); + +it('handles buildx with target and flags', function () { + $command = 'docker buildx build --platform linux/amd64 -t myimage:latest .'; + $buildArgs = '--build-arg VERSION=1.0'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker buildx build --build-arg VERSION=1.0 --platform linux/amd64 -t myimage:latest .'); +}); + +it('handles docker compose build with no arguments', function () { + $command = 'docker compose build'; + $buildArgs = '--build-arg FOO=bar'; + + $result = injectDockerComposeBuildArgs($command, $buildArgs); + + expect($result)->toBe('docker compose build --build-arg FOO=bar'); +}); From bf503861fcb68aec1074a2b391456404fc1068cd Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:45:14 +0100 Subject: [PATCH 2/2] Add build args to Final Build Command Preview in UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Final Build Command (Preview)" field now shows build arguments that will be injected during deployment, matching the actual command that runs. This provides transparency and helps users debug build issues. Changes: - Modified getDockerComposeBuildCommandPreviewProperty() to inject build args - Uses same helper functions as deployment (generateDockerBuildArgs, injectDockerComposeBuildArgs) - Respects use_build_secrets setting (build args only shown when disabled) - Filters environment variables where is_buildtime = true Example output: docker compose -f ./docker-compose.yaml --env-file /artifacts/build-time.env build --build-arg FOO --build-arg BAR backend 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Livewire/Project/Application/General.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 71ca9720e..ef474fb02 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -1018,11 +1018,27 @@ public function getDockerComposeBuildCommandPreviewProperty(): string // Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml) // Actual deployment uses absolute path: /artifacts/{deployment_uuid}{base_directory}{docker_compose_location} // Build-time env path references ApplicationDeploymentJob::BUILD_TIME_ENV_PATH as source of truth - return injectDockerComposeFlags( + $command = injectDockerComposeFlags( $this->dockerComposeCustomBuildCommand, ".{$normalizedBase}{$this->dockerComposeLocation}", \App\Jobs\ApplicationDeploymentJob::BUILD_TIME_ENV_PATH ); + + // Inject build args if not using build secrets + if (! $this->application->settings->use_build_secrets) { + $buildTimeEnvs = $this->application->environment_variables() + ->where('is_buildtime', true) + ->get(); + + if ($buildTimeEnvs->isNotEmpty()) { + $buildArgs = generateDockerBuildArgs($buildTimeEnvs); + $buildArgsString = $buildArgs->implode(' '); + + $command = injectDockerComposeBuildArgs($command, $buildArgsString); + } + } + + return $command; } public function getDockerComposeStartCommandPreviewProperty(): string