refactor(railpack): extract static image build, fix port logic, bump to v0.22.0

Extract build_railpack_static_image() into its own method, prevent port
override when is_static is set, bump Railpack to 0.22.0, and improve
test setup with beforeEach and correct polymorphic env var fields.
This commit is contained in:
Aditya Tripathi 2026-03-23 19:02:10 +00:00
parent 793077d74f
commit cddbaf581f
7 changed files with 93 additions and 62 deletions

View file

@ -483,7 +483,7 @@ private function decide_what_to_do()
} elseif ($this->application->build_pack === 'railpack') { } elseif ($this->application->build_pack === 'railpack') {
$this->deploy_railpack_buildpack(); $this->deploy_railpack_buildpack();
} else { } else {
throw new \RuntimeException("Unsupported build pack: {$this->application->build_pack}"); throw new DeploymentException("Unsupported build pack: {$this->application->build_pack}");
} }
$this->post_deployment(); $this->post_deployment();
} }
@ -2517,35 +2517,40 @@ private function build_railpack_image(): void
// Step 3: If static, copy built assets into nginx image // Step 3: If static, copy built assets into nginx image
if ($this->application->settings->is_static) { if ($this->application->settings->is_static) {
$publishDir = trim($this->application->publish_directory, '/'); $this->build_railpack_static_image();
$publishDir = $publishDir ? "/{$publishDir}" : ''; }
$dockerfile = base64_encode("FROM {$this->application->static_image} }
private function build_railpack_static_image(): void
{
$publishDir = trim($this->application->publish_directory, '/');
$publishDir = $publishDir ? "/{$publishDir}" : '';
$dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/ WORKDIR /usr/share/nginx/html/
LABEL coolify.deploymentId={$this->deployment_uuid} LABEL coolify.deploymentId={$this->deployment_uuid}
COPY --from={$this->build_image_name} /app{$publishDir} . COPY --from={$this->build_image_name} /app{$publishDir} .
COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
$nginx_config = base64_encode($this->application->custom_nginx_configuration); $nginx_config = base64_encode($this->application->custom_nginx_configuration);
} else { } else {
$nginx_config = $this->application->settings->is_spa $nginx_config = $this->application->settings->is_spa
? base64_encode(defaultNginxConfiguration('spa')) ? base64_encode(defaultNginxConfiguration('spa'))
: base64_encode(defaultNginxConfiguration()); : base64_encode(defaultNginxConfiguration());
}
$static_build = $this->dockerBuildkitSupported
? "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}"
: "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile -t {$this->production_image_name} {$this->workdir}";
$base64_static_build = base64_encode($static_build);
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")],
[executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")],
[executeInDocker($this->deployment_uuid, "echo '{$base64_static_build}' | base64 -d | tee ".self::BUILD_SCRIPT_PATH.' > /dev/null'), 'hidden' => true],
[executeInDocker($this->deployment_uuid, 'cat '.self::BUILD_SCRIPT_PATH), 'hidden' => true],
[executeInDocker($this->deployment_uuid, 'bash '.self::BUILD_SCRIPT_PATH), 'hidden' => true],
);
} }
$static_build = $this->dockerBuildkitSupported
? "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}"
: "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile -t {$this->production_image_name} {$this->workdir}";
$base64_static_build = base64_encode($static_build);
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")],
[executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")],
[executeInDocker($this->deployment_uuid, "echo '{$base64_static_build}' | base64 -d | tee ".self::BUILD_SCRIPT_PATH.' > /dev/null'), 'hidden' => true],
[executeInDocker($this->deployment_uuid, 'cat '.self::BUILD_SCRIPT_PATH), 'hidden' => true],
[executeInDocker($this->deployment_uuid, 'bash '.self::BUILD_SCRIPT_PATH), 'hidden' => true],
);
} }
private function generate_coolify_env_variables(bool $forBuildTime = false): Collection private function generate_coolify_env_variables(bool $forBuildTime = false): Collection

View file

@ -84,7 +84,9 @@ public function updatedBuildPack()
{ {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') { if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true; $this->show_is_static = true;
$this->port = 3000; if (! $this->is_static) {
$this->port = 3000;
}
} elseif ($this->build_pack === 'static') { } elseif ($this->build_pack === 'static') {
$this->show_is_static = false; $this->show_is_static = false;
$this->is_static = false; $this->is_static = false;

View file

@ -97,7 +97,9 @@ public function updatedBuildPack()
{ {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') { if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true; $this->show_is_static = true;
$this->port = 3000; if (! $this->is_static) {
$this->port = 3000;
}
} elseif ($this->build_pack === 'static') { } elseif ($this->build_pack === 'static') {
$this->show_is_static = false; $this->show_is_static = false;
$this->is_static = false; $this->is_static = false;

View file

@ -101,7 +101,9 @@ public function updatedBuildPack()
{ {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') { if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true; $this->show_is_static = true;
$this->port = 3000; if (! $this->isStatic) {
$this->port = 3000;
}
} elseif ($this->build_pack === 'static') { } elseif ($this->build_pack === 'static') {
$this->show_is_static = false; $this->show_is_static = false;
$this->isStatic = false; $this->isStatic = false;

View file

@ -12,7 +12,7 @@ ARG PACK_VERSION=0.38.2
# https://github.com/railwayapp/nixpacks/releases # https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.41.0 ARG NIXPACKS_VERSION=1.41.0
# https://github.com/railwayapp/railpack/releases # https://github.com/railwayapp/railpack/releases
ARG RAILPACK_VERSION=0.21.0 ARG RAILPACK_VERSION=0.22.0
# https://github.com/jdx/mise/releases — must match railpack's pinned version (https://raw.githubusercontent.com/railwayapp/railpack/refs/heads/main/core/mise/version.txt) # https://github.com/jdx/mise/releases — must match railpack's pinned version (https://raw.githubusercontent.com/railwayapp/railpack/refs/heads/main/core/mise/version.txt)
ARG MISE_VERSION=2026.3.12 ARG MISE_VERSION=2026.3.12
# https://github.com/minio/mc/releases # https://github.com/minio/mc/releases

View file

@ -78,26 +78,29 @@
// Add environment variables that should be deleted // Add environment variables that should be deleted
EnvironmentVariable::create([ EnvironmentVariable::create([
'application_id' => $application->id, 'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'SERVICE_FQDN_APP', 'key' => 'SERVICE_FQDN_APP',
'value' => 'app.example.com', 'value' => 'app.example.com',
'is_build_time' => false, 'is_buildtime' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
EnvironmentVariable::create([ EnvironmentVariable::create([
'application_id' => $application->id, 'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'SERVICE_URL_APP', 'key' => 'SERVICE_URL_APP',
'value' => 'http://app.example.com', 'value' => 'http://app.example.com',
'is_build_time' => false, 'is_buildtime' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
EnvironmentVariable::create([ EnvironmentVariable::create([
'application_id' => $application->id, 'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'REGULAR_VAR', 'key' => 'REGULAR_VAR',
'value' => 'should_remain', 'value' => 'should_remain',
'is_build_time' => false, 'is_buildtime' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
@ -154,6 +157,34 @@
'docker_compose_raw' => 'version: "3.8"\nservices:\n app:\n image: nginx', 'docker_compose_raw' => 'version: "3.8"\nservices:\n app:\n image: nginx',
]); ]);
// Add environment variables that should be deleted
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'SERVICE_FQDN_APP',
'value' => 'app.example.com',
'is_buildtime' => false,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'SERVICE_URL_APP',
'value' => 'http://app.example.com',
'is_buildtime' => false,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'REGULAR_VAR',
'value' => 'should_remain',
'is_buildtime' => false,
'is_preview' => false,
]);
$application->build_pack = 'railpack'; $application->build_pack = 'railpack';
$application->save(); $application->save();
$application->refresh(); $application->refresh();
@ -161,6 +192,13 @@
expect($application->build_pack)->toBe('railpack'); expect($application->build_pack)->toBe('railpack');
expect($application->docker_compose_domains)->toBeNull(); expect($application->docker_compose_domains)->toBeNull();
expect($application->docker_compose_raw)->toBeNull(); expect($application->docker_compose_raw)->toBeNull();
// Verify SERVICE_FQDN_* and SERVICE_URL_* were deleted
expect($application->environment_variables()->where('key', 'SERVICE_FQDN_APP')->count())->toBe(0);
expect($application->environment_variables()->where('key', 'SERVICE_URL_APP')->count())->toBe(0);
// Verify regular variables remain
expect($application->environment_variables()->where('key', 'REGULAR_VAR')->count())->toBe(1);
}); });
test('model does not clear dockerfile fields when switching to dockerfile', function () { test('model does not clear dockerfile fields when switching to dockerfile', function () {

View file

@ -10,13 +10,15 @@
uses(RefreshDatabase::class); uses(RefreshDatabase::class);
describe('Application Railpack Support', function () { describe('Application Railpack Support', function () {
test('could_set_build_commands returns true for railpack', function () { beforeEach(function () {
$team = Team::factory()->create(); $team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]); $project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]); $this->environment = Environment::factory()->create(['project_id' => $project->id]);
});
test('could_set_build_commands returns true for railpack', function () {
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'railpack', 'build_pack' => 'railpack',
]); ]);
@ -24,12 +26,8 @@
}); });
test('could_set_build_commands returns true for nixpacks', function () { test('could_set_build_commands returns true for nixpacks', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',
]); ]);
@ -37,12 +35,8 @@
}); });
test('could_set_build_commands returns false for dockerfile', function () { test('could_set_build_commands returns false for dockerfile', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'dockerfile', 'build_pack' => 'dockerfile',
]); ]);
@ -50,12 +44,8 @@
}); });
test('railpack_environment_variables returns only RAILPACK_ prefixed vars', function () { test('railpack_environment_variables returns only RAILPACK_ prefixed vars', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'railpack', 'build_pack' => 'railpack',
]); ]);
@ -92,12 +82,8 @@
}); });
test('runtime_environment_variables excludes RAILPACK_ and NIXPACKS_ prefixed vars', function () { test('runtime_environment_variables excludes RAILPACK_ and NIXPACKS_ prefixed vars', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'railpack', 'build_pack' => 'railpack',
]); ]);
@ -134,12 +120,8 @@
}); });
test('railpack_environment_variables_preview returns only RAILPACK_ prefixed preview vars', function () { test('railpack_environment_variables_preview returns only RAILPACK_ prefixed preview vars', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]);
$application = Application::factory()->create([ $application = Application::factory()->create([
'environment_id' => $environment->id, 'environment_id' => $this->environment->id,
'build_pack' => 'railpack', 'build_pack' => 'railpack',
]); ]);