fix(railpack): fail fast when buildx is unavailable

Require Docker buildx before Railpack builds, normalize environment
variable keys before validation, and align private deploy key API docs with
the supported dockerfile build pack.
This commit is contained in:
Andras Bacsai
2026-05-11 17:31:29 +02:00
parent 0395db30f0
commit ab1958d741
7 changed files with 43 additions and 8 deletions
@@ -650,7 +650,7 @@ class ApplicationsController extends Controller
'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'],
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['dockerfile'], 'description' => 'The build pack type.'],
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
'name' => ['type' => 'string', 'description' => 'The application name.'],
+18
View File
@@ -181,6 +181,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private bool $dockerBuildkitSupported = false;
private bool $dockerBuildxAvailable = false;
private bool $dockerSecretsSupported = false;
private bool $skip_build = false;
@@ -421,6 +423,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
$this->dockerBuildkitSupported = false;
$this->dockerBuildxAvailable = false;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+).");
return;
@@ -434,8 +437,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (trim($buildxAvailable) === 'available') {
$this->dockerBuildkitSupported = true;
$this->dockerBuildxAvailable = true;
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
} else {
$this->dockerBuildxAvailable = false;
// Fallback: test DOCKER_BUILDKIT=1 support via --progress flag
$buildkitTest = instant_remote_process(
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q '\\-\\-progress' && echo 'supported' || echo 'not-supported'"],
@@ -468,6 +474,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
} catch (Exception $e) {
$this->dockerBuildkitSupported = false;
$this->dockerBuildxAvailable = false;
$this->dockerSecretsSupported = false;
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
}
@@ -2760,8 +2767,19 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
return $prepare_command;
}
private function ensure_docker_buildx_available_for_railpack(): void
{
if ($this->dockerBuildxAvailable) {
return;
}
throw new DeploymentException('Railpack deployments require the Docker buildx CLI plugin on the build server. Install or enable docker buildx and retry the deployment.');
}
private function build_railpack_image(): void
{
$this->ensure_docker_buildx_available_for_railpack();
$railpackVariables = $this->generate_railpack_env_variables();
$railpackConfigPath = $this->generate_railpack_config_file();
+3 -1
View File
@@ -371,7 +371,9 @@ class EnvironmentVariable extends BaseModel
protected function key(): Attribute
{
return Attribute::make(
set: fn (string $value) => ValidationPatterns::validatedEnvironmentVariableKey($value),
set: fn (string $value) => ValidationPatterns::validatedEnvironmentVariableKey(
ValidationPatterns::normalizeEnvironmentVariableKey($value)
),
);
}
+1 -5
View File
@@ -1451,11 +1451,7 @@
"build_pack": {
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
"dockerfile"
],
"description": "The build pack type."
},
+1 -1
View File
@@ -935,7 +935,7 @@ paths:
description: 'The Dockerfile content.'
build_pack:
type: string
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
enum: [dockerfile]
description: 'The build pack type.'
ports_exposes:
type: string
@@ -205,6 +205,17 @@ it('builds railpack prepare command using railpack env for install and cli flags
expect($command)->not->toContain('RAILPACK_START_CMD=');
});
it('fails fast when docker buildx is unavailable for railpack builds', function () {
[$job, $reflection] = makeRailpackDeploymentJob();
$dockerBuildxAvailableProperty = $reflection->getProperty('dockerBuildxAvailable');
$dockerBuildxAvailableProperty->setAccessible(true);
$dockerBuildxAvailableProperty->setValue($job, false);
expect(fn () => invokeRailpackMethod($job, $reflection, 'ensure_docker_buildx_available_for_railpack'))
->toThrow(DeploymentException::class, 'Railpack deployments require the Docker buildx CLI plugin');
});
it('builds railpack docker command with matching env and secret flags for all railpack variables', function () {
[$job, $reflection] = makeRailpackDeploymentJob([
'uuid' => 'application-uuid',
+8
View File
@@ -1,5 +1,6 @@
<?php
use App\Models\EnvironmentVariable;
use App\Support\ValidationPatterns;
it('accepts valid names with common characters', function (string $name) {
@@ -165,3 +166,10 @@ it('generates environment variable key rules with correct defaults', function ()
it('normalizes environment variable keys by trimming surrounding whitespace', function () {
expect(ValidationPatterns::normalizeEnvironmentVariableKey(' node.name '))->toBe('node.name');
});
it('normalizes environment variable keys before model validation', function () {
$environmentVariable = new EnvironmentVariable;
$environmentVariable->key = ' APP_ENV ';
expect($environmentVariable->key)->toBe('APP_ENV');
});