fix(deployments): validate build-time variable names only when building

Since 17ca63ff4 every deployment except Docker image apps failed on a
build-time variable with a name such as MY-VAR, also restarts and
deployments that reuse an existing image. v4.3.23 failed only when it
built an image.

- Check build-time names right before the build steps; the start of
  the deployment only logs a warning
- Names that break the .env file still fail every deployment
- Leave invalid names out of the build secrets helper container, so
  restarts with build secrets work
This commit is contained in:
Andras Bacsai
2026-10-05 23:10:52 +02:00
parent 7f12e3b513
commit fbd6c5be21
2 changed files with 77 additions and 17 deletions
@@ -1026,7 +1026,7 @@ it('rejects unsafe legacy keys before generating BuildKit secret flags', functio
expect($job->recordedCommands)->toBeEmpty();
});
it('rejects an unsafe stored key before running a deployment command', function () {
it('rejects an unsafe stored build-time key before running a build command', function () {
[$application, $server] = makeDeploymentControlVarFixture();
$environmentVariable = createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
@@ -1036,7 +1036,7 @@ it('rejects an unsafe stored key before running a deployment command', function
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server);
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys'))
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys', true))
->toThrow(DeploymentException::class, 'Invalid environment variable name from the deployment environment');
expect($job->recordedCommands)->toBeEmpty();
@@ -1061,7 +1061,7 @@ it('keeps deploying existing runtime-only variables whose names new variables ca
expect($job->recordedCommands)->toBeEmpty();
});
it('rejects existing variable names that would break the .env file or build commands', function (string $key, bool $isBuildtime) {
it('rejects existing variable names that would break the .env file or build commands', function (string $key, bool $isBuildtime, bool $buildsImage) {
[$application, $server] = makeDeploymentControlVarFixture();
$environmentVariable = createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
@@ -1072,14 +1072,60 @@ it('rejects existing variable names that would break the .env file or build comm
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server);
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys'))
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys', $buildsImage))
->toThrow(DeploymentException::class, 'Invalid environment variable name from the deployment environment');
})->with([
'runtime-only name with =' => ['A=B', false],
'runtime-only name with a newline' => ["A\nB", false],
'build-time name with a hyphen' => ['my-var', true],
'runtime-only name with = before the build is decided' => ['A=B', false, false],
'runtime-only name with a newline before the build is decided' => ["A\nB", false, false],
'build-time name with a hyphen when the deployment builds' => ['my-var', true, true],
]);
it('does not fail restarts or deployments that reuse an image on invalid build-time names', function (string $buildPack) {
[$application, $server] = makeDeploymentControlVarFixture(['build_pack' => $buildPack]);
$environmentVariable = createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
'value' => 'secret',
'is_buildtime' => true,
]);
DB::table('environment_variables')->where('id', $environmentVariable->id)->update(['key' => 'MY-VAR']);
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server, ['build_pack' => $buildPack]);
// handle() runs this check before it is known whether the deployment builds an image.
invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys');
expect(collect($job->recordedLogEntries)->implode("\n"))
->toContain('MY-VAR')
->toContain('Suggested name: MY_VAR')
->not->toContain('Invalid environment variable name');
expect($job->recordedCommands)->toBeEmpty();
})->with(['dockerfile', 'nixpacks', 'static', 'railpack', 'dockercompose']);
it('keeps invalid build-time names out of the helper container so restarts with build secrets work', function () {
[$application, $server] = makeDeploymentControlVarFixture(['build_pack' => 'nixpacks']);
$application->settings()->update(['use_build_secrets' => true]);
createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
'value' => 'safe-build-value',
'is_buildtime' => true,
]);
$invalidVariable = createApplicationEnvironmentVariable($application, [
'key' => 'OTHER_KEY',
'value' => 'invalid-build-value',
'is_buildtime' => true,
]);
DB::table('environment_variables')->where('id', $invalidVariable->id)->update(['key' => 'MY-VAR']);
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server, ['build_pack' => 'nixpacks']);
$envFlags = invokeDeploymentJobMethod($job, $reflection, 'generate_docker_env_flags_for_secrets');
expect($envFlags)
->toContain("-e 'SAFE_KEY=safe-build-value'")
->toContain('COOLIFY_BUILD_SECRETS_HASH=')
->not->toContain('MY-VAR')
->not->toContain('invalid-build-value');
});
it('warns instead of failing for invalid build-time names when the deployment does not build an image', function (bool $isRuntime) {
[$application, $server] = makeDeploymentControlVarFixture(['build_pack' => 'dockerimage']);
$environmentVariable = createApplicationEnvironmentVariable($application, [
@@ -1147,7 +1193,7 @@ it('fails with a clear message for invalid build-time names when the deployment
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server, ['build_pack' => $buildPack]);
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys'))
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys', true))
->toThrow(DeploymentException::class, 'Invalid environment variable name from the deployment environment: my-var');
expect(collect($job->recordedLogEntries)->implode("\n"))
->toContain('Build-time variable names must start with a letter or underscore');