fix(railpack): isolate buildx from Docker client env (#10840)
This commit is contained in:
parent
cf6f5a2678
commit
bb2f70ac3b
2 changed files with 130 additions and 2 deletions
|
|
@ -52,6 +52,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
private const RAILPACK_GENERATED_CONFIG_PATH = '.coolify/railpack.generated.json';
|
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 $tries = 1;
|
||||||
|
|
||||||
public $timeout = 3600;
|
public $timeout = 3600;
|
||||||
|
|
@ -1701,6 +1716,10 @@ private function generate_buildtime_environment_variables()
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($sorted_environment_variables as $env) {
|
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);
|
$resolvedValue = $env->getResolvedValueWithServer($this->mainServer);
|
||||||
// For literal/multiline vars, real_value includes quotes that we need to remove
|
// For literal/multiline vars, real_value includes quotes that we need to remove
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
if ($env->is_literal || $env->is_multiline) {
|
||||||
|
|
@ -1752,6 +1771,10 @@ private function generate_buildtime_environment_variables()
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($sorted_environment_variables as $env) {
|
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);
|
$resolvedValue = $env->getResolvedValueWithServer($this->mainServer);
|
||||||
// For literal/multiline vars, real_value includes quotes that we need to remove
|
// For literal/multiline vars, real_value includes quotes that we need to remove
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
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(' ');
|
$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
|
private function generate_railpack_env_variables(): Collection
|
||||||
{
|
{
|
||||||
$variables = $this->railpack_build_variables();
|
$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
|
private function railpack_build_command(string $imageName, Collection $variables): string
|
||||||
{
|
{
|
||||||
|
$variables = $this->without_reserved_docker_client_variables($variables);
|
||||||
|
|
||||||
$cacheArgs = '';
|
$cacheArgs = '';
|
||||||
if ($this->force_rebuild) {
|
if ($this->force_rebuild) {
|
||||||
$cacheArgs = '--no-cache';
|
$cacheArgs = '--no-cache';
|
||||||
|
|
@ -2735,7 +2774,7 @@ private function railpack_build_command(string $imageName, Collection $variables
|
||||||
$secretFlags = $this->railpack_build_secret_flags($variables);
|
$secretFlags = $this->railpack_build_secret_flags($variables);
|
||||||
$frontendImage = 'ghcr.io/railwayapp/railpack-frontend:v'.config('constants.coolify.railpack_version');
|
$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"
|
." {$this->addHosts} --network host"
|
||||||
." --build-arg BUILDKIT_SYNTAX=\"{$frontendImage}\""
|
." --build-arg BUILDKIT_SYNTAX=\"{$frontendImage}\""
|
||||||
." {$cacheArgs}"
|
." {$cacheArgs}"
|
||||||
|
|
@ -2746,7 +2785,7 @@ private function railpack_build_command(string $imageName, Collection $variables
|
||||||
." -t {$imageName}"
|
." -t {$imageName}"
|
||||||
." {$this->workdir}";
|
." {$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);
|
.' && '.$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.');
|
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
|
private function build_railpack_image(): void
|
||||||
{
|
{
|
||||||
$this->ensure_docker_buildx_available_for_railpack();
|
$this->ensure_docker_buildx_available_for_railpack();
|
||||||
|
$this->ensure_helper_docker_buildx_available_for_railpack();
|
||||||
|
|
||||||
$railpackVariables = $this->generate_railpack_env_variables();
|
$railpackVariables = $this->generate_railpack_env_variables();
|
||||||
$railpackConfigPath = $this->generate_railpack_config_file();
|
$railpackConfigPath = $this->generate_railpack_config_file();
|
||||||
|
|
@ -4103,6 +4158,10 @@ private function generate_docker_env_flags_for_secrets()
|
||||||
|
|
||||||
$variables = $this->env_args;
|
$variables = $this->env_args;
|
||||||
|
|
||||||
|
if ($this->build_pack === 'railpack') {
|
||||||
|
$variables = $this->without_reserved_docker_client_variables($variables);
|
||||||
|
}
|
||||||
|
|
||||||
if ($variables->isEmpty()) {
|
if ($variables->isEmpty()) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,75 @@ function invokeRailpackMethod(object $job, ReflectionClass $reflection, string $
|
||||||
->toThrow(DeploymentException::class, 'Railpack deployments require the Docker buildx CLI plugin');
|
->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 () {
|
it('builds railpack docker command with matching env and secret flags for all railpack variables', function () {
|
||||||
[$job, $reflection] = makeRailpackDeploymentJob([
|
[$job, $reflection] = makeRailpackDeploymentJob([
|
||||||
'uuid' => 'application-uuid',
|
'uuid' => 'application-uuid',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue