fix(railpack): isolate buildx from Docker client env (#10840)

This commit is contained in:
Andras Bacsai 2026-07-02 18:53:00 +02:00 committed by GitHub
parent cf6f5a2678
commit bb2f70ac3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 2 deletions

View file

@ -52,6 +52,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private const RAILPACK_GENERATED_CONFIG_PATH = '.coolify/railpack.generated.json';
private const DOCKER_CLIENT_ENV_KEYS = [
'BUILDKIT_HOST',
'BUILDX_BUILDER',
'BUILDX_CONFIG',
'DOCKER_API_VERSION',
'DOCKER_BUILDKIT',
'DOCKER_CERT_PATH',
'DOCKER_CLI_EXPERIMENTAL',
'DOCKER_CONFIG',
'DOCKER_CONTEXT',
'DOCKER_HOST',
'DOCKER_TLS',
'DOCKER_TLS_VERIFY',
];
public $tries = 1;
public $timeout = 3600;
@ -1701,6 +1716,10 @@ private function generate_buildtime_environment_variables()
}
foreach ($sorted_environment_variables as $env) {
if ($this->build_pack === 'railpack' && $this->is_reserved_docker_client_env_key($env->key)) {
continue;
}
$resolvedValue = $env->getResolvedValueWithServer($this->mainServer);
// For literal/multiline vars, real_value includes quotes that we need to remove
if ($env->is_literal || $env->is_multiline) {
@ -1752,6 +1771,10 @@ private function generate_buildtime_environment_variables()
}
foreach ($sorted_environment_variables as $env) {
if ($this->build_pack === 'railpack' && $this->is_reserved_docker_client_env_key($env->key)) {
continue;
}
$resolvedValue = $env->getResolvedValueWithServer($this->mainServer);
// For literal/multiline vars, real_value includes quotes that we need to remove
if ($env->is_literal || $env->is_multiline) {
@ -2589,6 +2612,20 @@ private function generate_nixpacks_env_variables()
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
}
private function is_reserved_docker_client_env_key(?string $key): bool
{
if (blank($key)) {
return false;
}
return in_array(strtoupper($key), self::DOCKER_CLIENT_ENV_KEYS, true);
}
private function without_reserved_docker_client_variables(Collection $variables): Collection
{
return $variables->reject(fn ($value, $key) => $this->is_reserved_docker_client_env_key((string) $key));
}
private function generate_railpack_env_variables(): Collection
{
$variables = $this->railpack_build_variables();
@ -2709,6 +2746,8 @@ private function railpack_build_secret_flags(Collection $variables): string
private function railpack_build_command(string $imageName, Collection $variables): string
{
$variables = $this->without_reserved_docker_client_variables($variables);
$cacheArgs = '';
if ($this->force_rebuild) {
$cacheArgs = '--no-cache';
@ -2735,7 +2774,7 @@ private function railpack_build_command(string $imageName, Collection $variables
$secretFlags = $this->railpack_build_secret_flags($variables);
$frontendImage = 'ghcr.io/railwayapp/railpack-frontend:v'.config('constants.coolify.railpack_version');
$buildxBuildCommand = "{$environmentPrefix}docker buildx build --builder coolify-railpack"
$buildxBuildCommand = "{$environmentPrefix}DOCKER_CONFIG=/root/.docker docker buildx build --builder coolify-railpack"
." {$this->addHosts} --network host"
." --build-arg BUILDKIT_SYNTAX=\"{$frontendImage}\""
." {$cacheArgs}"
@ -2746,7 +2785,7 @@ private function railpack_build_command(string $imageName, Collection $variables
." -t {$imageName}"
." {$this->workdir}";
return 'docker buildx create --name coolify-railpack --driver docker-container 2>/dev/null || true'
return 'DOCKER_CONFIG=/root/.docker docker buildx create --name coolify-railpack --driver docker-container 2>/dev/null || true'
.' && '.$this->wrap_build_command_with_env_export($buildxBuildCommand);
}
@ -2901,9 +2940,25 @@ private function ensure_docker_buildx_available_for_railpack(): void
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 ensure_helper_docker_buildx_available_for_railpack(): void
{
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, 'DOCKER_CONFIG=/root/.docker docker buildx version >/dev/null 2>&1 && echo available || echo not-available'),
'hidden' => true,
'save' => 'railpack_helper_buildx_available',
]);
if (trim((string) $this->saved_outputs->get('railpack_helper_buildx_available')) === 'available') {
return;
}
throw new DeploymentException('Railpack deployments require the Docker buildx CLI plugin inside the Coolify helper container. The helper could not find buildx at /root/.docker/cli-plugins/docker-buildx. Pull the latest helper image and retry the deployment.');
}
private function build_railpack_image(): void
{
$this->ensure_docker_buildx_available_for_railpack();
$this->ensure_helper_docker_buildx_available_for_railpack();
$railpackVariables = $this->generate_railpack_env_variables();
$railpackConfigPath = $this->generate_railpack_config_file();
@ -4103,6 +4158,10 @@ private function generate_docker_env_flags_for_secrets()
$variables = $this->env_args;
if ($this->build_pack === 'railpack') {
$variables = $this->without_reserved_docker_client_variables($variables);
}
if ($variables->isEmpty()) {
return '';
}

View file

@ -218,6 +218,75 @@ function invokeRailpackMethod(object $job, ReflectionClass $reflection, string $
->toThrow(DeploymentException::class, 'Railpack deployments require the Docker buildx CLI plugin');
});
it('checks buildx inside the helper container before railpack builds', function () {
[$job, $reflection] = makeRailpackDeploymentJob([], [
'railpack_helper_buildx_available' => 'available',
]);
invokeRailpackMethod($job, $reflection, 'ensure_helper_docker_buildx_available_for_railpack');
expect($job->recordedCommands[0][0][0])
->toContain('DOCKER_CONFIG=/root/.docker docker buildx version');
});
it('fails clearly when buildx is missing inside the helper container', function () {
[$job, $reflection] = makeRailpackDeploymentJob([], [
'railpack_helper_buildx_available' => 'not-available',
]);
expect(fn () => invokeRailpackMethod($job, $reflection, 'ensure_helper_docker_buildx_available_for_railpack'))
->toThrow(DeploymentException::class, 'helper container');
});
it('pins docker config while running railpack buildx commands', function () {
[$job, $reflection] = makeRailpackDeploymentJob([
'uuid' => 'application-uuid',
]);
$command = invokeRailpackMethod(
$job,
$reflection,
'railpack_build_command',
[
'coollabsio/coolify:test',
collect([]),
],
);
expect($command)
->toContain('DOCKER_CONFIG=/root/.docker docker buildx create --name coolify-railpack')
->toContain('DOCKER_CONFIG=/root/.docker docker buildx build --builder coolify-railpack');
});
it('filters reserved docker client variables from railpack build secrets', function () {
[$job, $reflection] = makeRailpackDeploymentJob([
'uuid' => 'application-uuid',
]);
$command = invokeRailpackMethod(
$job,
$reflection,
'railpack_build_command',
[
'coollabsio/coolify:test',
collect([
'DOCKER_CONFIG' => '/tmp/no-buildx',
'DOCKER_HOST' => 'tcp://invalid:2375',
'RAILPACK_NODE_VERSION' => '22',
'APP_ENV' => 'production',
]),
],
);
expect($command)
->toContain("--secret 'id=RAILPACK_NODE_VERSION,env=RAILPACK_NODE_VERSION'")
->toContain("--secret 'id=APP_ENV,env=APP_ENV'")
->not->toContain('id=DOCKER_CONFIG')
->not->toContain('id=DOCKER_HOST')
->not->toContain("env 'DOCKER_CONFIG=")
->not->toContain("env 'DOCKER_HOST=");
});
it('builds railpack docker command with matching env and secret flags for all railpack variables', function () {
[$job, $reflection] = makeRailpackDeploymentJob([
'uuid' => 'application-uuid',