From 623bf89543bfe6b5f38acde567938d2f7cbe49b8 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Wed, 24 Jun 2026 20:11:46 +0000 Subject: [PATCH] fix(railpack): interpolate build-time env variables by sourcing build-time .env Railpack builds forwarded build-time variables inline as `env 'KEY=VALUE'`, which single-quotes each value and prevents shell interpolation. References like BETTER_AUTH_URL=$COOLIFY_URL reached the build as the literal string "$COOLIFY_URL" instead of the resolved URL, breaking builds that validate their env (e.g. SvelteKit/better-auth). Wrap the railpack `docker buildx build` invocation with the same wrap_build_command_with_env_export() helper used by the Dockerfile and Nixpacks build paths, sourcing the build-time .env file (which writes COOLIFY_* first and double-quotes normal vars to allow $VAR expansion). Only buildpack control variables (NIXPACKS_/RAILPACK_), excluded from that file and never needing interpolation, remain inline. Secret flags are unchanged and read the interpolated values from the exported environment. Fixes #10736 --- app/Jobs/ApplicationDeploymentJob.php | 19 ++++++++-- ...pplicationDeploymentRailpackConfigTest.php | 35 ++++++++++++++++++- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 20eae036b..6b72fb339 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2667,12 +2667,22 @@ private function railpack_build_command(string $imageName, Collection $variables $cacheArgs .= ' --build-arg secrets-hash='.$this->generate_secrets_hash($variables); } - $environmentPrefix = $this->railpack_build_environment_prefix($variables); + // Build-time variables reach the build through the sourced build-time .env file + // (written by save_buildtime_environment_variables), which interpolates shell-style + // references such as BETTER_AUTH_URL=$COOLIFY_URL. Passing them inline via `env` + // would forward the literal `$COOLIFY_URL` because each value is single-quoted and + // `env` does not interpolate its own assignments. Only buildpack control variables + // (NIXPACKS_/RAILPACK_) — which are excluded from the build-time .env file and never + // need interpolation — are still passed inline. + $controlVariables = $variables->filter( + fn ($value, $key) => str($key)->startsWith(EnvironmentVariable::BUILDPACK_CONTROL_VARIABLE_PREFIXES) + ); + + $environmentPrefix = $this->railpack_build_environment_prefix($controlVariables); $secretFlags = $this->railpack_build_secret_flags($variables); $frontendImage = 'ghcr.io/railwayapp/railpack-frontend:v'.config('constants.coolify.railpack_version'); - return 'docker buildx create --name coolify-railpack --driver docker-container 2>/dev/null || true' - ." && {$environmentPrefix}docker buildx build --builder coolify-railpack" + $buildxBuildCommand = "{$environmentPrefix}docker buildx build --builder coolify-railpack" ." {$this->addHosts} --network host" ." --build-arg BUILDKIT_SYNTAX=\"{$frontendImage}\"" ." {$cacheArgs}" @@ -2682,6 +2692,9 @@ private function railpack_build_command(string $imageName, Collection $variables .' --load' ." -t {$imageName}" ." {$this->workdir}"; + + return 'docker buildx create --name coolify-railpack --driver docker-container 2>/dev/null || true' + .' && '.$this->wrap_build_command_with_env_export($buildxBuildCommand); } private function decode_railpack_config(string $config, string $source): array diff --git a/tests/Unit/ApplicationDeploymentRailpackConfigTest.php b/tests/Unit/ApplicationDeploymentRailpackConfigTest.php index e1449a59e..77815a523 100644 --- a/tests/Unit/ApplicationDeploymentRailpackConfigTest.php +++ b/tests/Unit/ApplicationDeploymentRailpackConfigTest.php @@ -236,10 +236,15 @@ function invokeRailpackMethod(object $job, ReflectionClass $reflection, string $ ], ); + // Build-time variables are interpolated by sourcing the build-time .env file before + // the build, so user/Coolify variables must NOT be forwarded inline as literals. + expect($command)->toContain('set -a && source /artifacts/build-time.env && set +a'); expect($command)->toContain("env 'RAILPACK_NODE_VERSION=22'"); expect($command)->toContain("'RAILPACK_INSTALL_CMD=npm ci && npm run postinstall'"); expect($command)->toContain("'RAILPACK_DEPLOY_APT_PACKAGES=curl wget'"); - expect($command)->toContain("'SECRET_JSON={\"token\":\"abc\"}'"); + // SECRET_JSON is not a buildpack control variable, so it is provided via the sourced + // build-time .env file (which supports $VAR interpolation) rather than inline `env`. + expect($command)->not->toContain("'SECRET_JSON={\"token\":\"abc\"}'"); expect($command)->toContain("--secret 'id=RAILPACK_NODE_VERSION,env=RAILPACK_NODE_VERSION'"); expect($command)->toContain("--secret 'id=RAILPACK_INSTALL_CMD,env=RAILPACK_INSTALL_CMD'"); expect($command)->toContain("--secret 'id=RAILPACK_DEPLOY_APT_PACKAGES,env=RAILPACK_DEPLOY_APT_PACKAGES'"); @@ -247,3 +252,31 @@ function invokeRailpackMethod(object $job, ReflectionClass $reflection, string $ expect($command)->toContain(' --build-arg secrets-hash='); expect($command)->toContain('--build-arg BUILDKIT_SYNTAX="ghcr.io/railwayapp/railpack-frontend:v'.config('constants.coolify.railpack_version').'"'); }); + +it('interpolates build-time variable references for railpack by sourcing the build-time env file', function () { + [$job, $reflection] = makeRailpackDeploymentJob([ + 'uuid' => 'application-uuid', + ]); + + // Mirrors the issue: BETTER_AUTH_URL=$COOLIFY_URL must be interpolated at build time. + $command = invokeRailpackMethod( + $job, + $reflection, + 'railpack_build_command', + [ + 'coollabsio/coolify:test', + collect([ + 'BETTER_AUTH_URL' => '$COOLIFY_URL', + 'COOLIFY_URL' => 'https://sapere-10.bobman.dev', + ]), + ], + ); + + // The literal `$COOLIFY_URL` must NOT be forwarded inline; it is resolved by the shell + // after sourcing the build-time .env file, then read through the build secret. + expect($command)->toContain('set -a && source /artifacts/build-time.env && set +a'); + expect($command)->not->toContain("'BETTER_AUTH_URL=\$COOLIFY_URL'"); + expect($command)->not->toContain("env 'BETTER_AUTH_URL"); + expect($command)->toContain("--secret 'id=BETTER_AUTH_URL,env=BETTER_AUTH_URL'"); + expect($command)->toContain("--secret 'id=COOLIFY_URL,env=COOLIFY_URL'"); +});