feat(buildpack): add Railpack as a build pack option

This commit is contained in:
Aditya Tripathi committed 2026-03-23 17:12:02 +00:00
1 parent 8be226788e
commit 793077d74f
18 files changed
+485 -28

No files matched your search

+1
View File
@@ -8,4 +8,5 @@ enum BuildPackTypes: string
case STATIC = 'static';
case DOCKERFILE = 'dockerfile';
case DOCKERCOMPOSE = 'dockercompose';
case RAILPACK = 'railpack';
}
@@ -150,7 +150,7 @@ class ApplicationsController extends Controller
'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'],
'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], '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.'],
@@ -318,7 +318,7 @@ class ApplicationsController extends Controller
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
@@ -483,7 +483,7 @@ class ApplicationsController extends Controller
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
@@ -644,7 +644,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', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], '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.'],
@@ -2318,7 +2318,7 @@ class ApplicationsController extends Controller
'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'domains' => ['type' => 'string', 'description' => 'The application URLs in a comma-separated list.'],
+174 -2
View File
@@ -121,6 +121,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private $env_nixpacks_args;
private $env_railpack_args;
private $docker_compose;
private $docker_compose_base64;
@@ -476,8 +478,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->deploy_dockerfile_buildpack();
} elseif ($this->application->build_pack === 'static') {
$this->deploy_static_buildpack();
} else {
} elseif ($this->application->build_pack === 'nixpacks') {
$this->deploy_nixpacks_buildpack();
} elseif ($this->application->build_pack === 'railpack') {
$this->deploy_railpack_buildpack();
} else {
throw new \RuntimeException("Unsupported build pack: {$this->application->build_pack}");
}
$this->post_deployment();
}
@@ -921,6 +927,37 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->rolling_update();
}
private function deploy_railpack_buildpack()
{
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->generate_image_names();
if (! $this->force_rebuild) {
$this->check_image_locally_or_remotely();
if ($this->should_skip_build()) {
return;
}
}
$this->clone_repository();
$this->cleanup_git();
$this->generate_compose_file();
// Save build-time .env file BEFORE the build
$this->save_buildtime_environment_variables();
$this->generate_build_env_variables();
$this->build_railpack_image();
// Save runtime environment variables AFTER the build
$this->save_runtime_environment_variables();
$this->push_to_docker_registry();
$this->rolling_update();
}
private function deploy_static_buildpack()
{
if ($this->use_build_server) {
@@ -1943,7 +1980,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->application->build_pack === 'dockerfile') {
$this->add_build_env_variables_to_dockerfile();
}
$this->build_image();
if ($this->application->build_pack === 'railpack') {
$this->build_railpack_image();
} else {
$this->build_image();
}
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this->save_runtime_environment_variables();
@@ -2376,6 +2417,137 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
}
private function generate_railpack_env_variables(): void
{
$this->env_railpack_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->railpack_environment_variables as $env) {
if (! is_null($env->real_value) && $env->real_value !== '') {
$this->env_railpack_args->push("--env {$env->key}={$env->real_value}");
}
}
} else {
foreach ($this->application->railpack_environment_variables_preview as $env) {
if (! is_null($env->real_value) && $env->real_value !== '') {
$this->env_railpack_args->push("--env {$env->key}={$env->real_value}");
}
}
}
// Note: COOLIFY_* vars are NOT passed to railpack prepare because railpack treats
// all --env vars as secrets that must be provided during docker buildx build.
// COOLIFY_* vars are informational and available at runtime via .env file.
$this->env_railpack_args = $this->env_railpack_args->implode(' ');
}
private function build_railpack_image(): void
{
$this->generate_railpack_env_variables();
// Step 1: Generate build plan with railpack prepare
$prepare_command = 'railpack prepare';
if ($this->env_railpack_args) {
$prepare_command .= " {$this->env_railpack_args}";
}
if ($this->application->build_command) {
$prepare_command .= " --build-cmd \"{$this->application->build_command}\"";
}
if ($this->application->start_command) {
$prepare_command .= " --start-cmd \"{$this->application->start_command}\"";
}
if ($this->application->install_command) {
$prepare_command .= " --env RAILPACK_INSTALL_CMD=\"{$this->application->install_command}\"";
}
$prepare_command .= " --plan-out /artifacts/railpack-plan.json {$this->workdir}";
$this->application_deployment_queue->addLogEntry('Generating Railpack build plan.');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $prepare_command), 'hidden' => true],
);
// Step 2: Build image using docker buildx with railpack frontend.
// Railpack's frontend requires full BuildKit (mergeop), so we use a docker-container driver builder.
$this->application_deployment_queue->addLogEntry('Building docker image with Railpack.');
$this->application_deployment_queue->addLogEntry('To check the current progress, click on Show Debug Logs.');
$image_name = $this->application->settings->is_static
? $this->build_image_name
: $this->production_image_name;
if ($this->application->settings->is_static && $this->application->static_image) {
$this->pull_latest_image($this->application->static_image);
}
$cache_args = '';
if ($this->force_rebuild) {
$cache_args = '--no-cache';
} else {
$cache_args = "--build-arg cache-key='{$this->application->uuid}'";
}
$build_command = 'docker buildx create --name coolify-railpack --driver docker-container 2>/dev/null || true'
.' && docker buildx build --builder coolify-railpack'
." {$this->addHosts} --network host"
." --build-arg BUILDKIT_SYNTAX='ghcr.io/railwayapp/railpack-frontend'"
." {$cache_args}"
.' -f /artifacts/railpack-plan.json'
.' --progress plain'
.' --load'
." -t {$image_name}"
." {$this->workdir}";
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | 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,
]
);
// Step 3: If static, copy built assets into nginx image
if ($this->application->settings->is_static) {
$publishDir = trim($this->application->publish_directory, '/');
$publishDir = $publishDir ? "/{$publishDir}" : '';
$dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/
LABEL coolify.deploymentId={$this->deployment_uuid}
COPY --from={$this->build_image_name} /app{$publishDir} .
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
} else {
$nginx_config = $this->application->settings->is_spa
? base64_encode(defaultNginxConfiguration('spa'))
: 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],
);
}
}
private function generate_coolify_env_variables(bool $forBuildTime = false): Collection
{
$coolify_envs = collect([]);
+1 -1
View File
@@ -598,7 +598,7 @@ class General extends Component
// Sync property to model before checking/modifying
$this->syncData(toModel: true);
if ($this->buildPack !== 'nixpacks') {
if ($this->buildPack !== 'nixpacks' && $this->buildPack !== 'railpack') {
$this->isStatic = false;
$this->application->settings->is_static = false;
$this->application->settings->save();
@@ -62,7 +62,7 @@ class GithubPrivateRepository extends Component
protected int $page = 1;
public $build_pack = 'nixpacks';
public $build_pack = 'railpack';
public bool $show_is_static = true;
@@ -82,7 +82,7 @@ class GithubPrivateRepository extends Component
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true;
$this->port = 3000;
} elseif ($this->build_pack === 'static') {
@@ -45,7 +45,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public string $branch;
public $build_pack = 'nixpacks';
public $build_pack = 'railpack';
public bool $show_is_static = true;
@@ -95,7 +95,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true;
$this->port = 3000;
} elseif ($this->build_pack === 'static') {
@@ -57,7 +57,7 @@ class PublicGitRepository extends Component
public string $git_repository;
public $build_pack = 'nixpacks';
public $build_pack = 'railpack';
public bool $show_is_static = true;
@@ -99,7 +99,7 @@ class PublicGitRepository extends Component
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
$this->show_is_static = true;
$this->port = 3000;
} elseif ($this->build_pack === 'static') {
+20 -4
View File
@@ -39,7 +39,7 @@ use Visus\Cuid2\Cuid2;
'git_full_url' => ['type' => 'string', 'nullable' => true, 'description' => 'Git full URL.'],
'docker_registry_image_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image name.'],
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image tag.'],
'build_pack' => ['type' => 'string', 'description' => 'Build pack.', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose']],
'build_pack' => ['type' => 'string', 'description' => 'Build pack.', 'enum' => ['nixpacks', 'railpack', 'static', 'dockerfile', 'dockercompose']],
'static_image' => ['type' => 'string', 'description' => 'Static image used when static site is deployed.'],
'install_command' => ['type' => 'string', 'description' => 'Install command.'],
'build_command' => ['type' => 'string', 'description' => 'Build command.'],
@@ -854,7 +854,8 @@ class Application extends BaseModel
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->where('is_preview', false)
->where('key', 'not like', 'NIXPACKS_%');
->where('key', 'not like', 'NIXPACKS_%')
->where('key', 'not like', 'RAILPACK_%');
}
public function nixpacks_environment_variables()
@@ -864,6 +865,13 @@ class Application extends BaseModel
->where('key', 'like', 'NIXPACKS_%');
}
public function railpack_environment_variables()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->where('is_preview', false)
->where('key', 'like', 'RAILPACK_%');
}
public function environment_variables_preview()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
@@ -882,7 +890,8 @@ class Application extends BaseModel
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->where('is_preview', true)
->where('key', 'not like', 'NIXPACKS_%');
->where('key', 'not like', 'NIXPACKS_%')
->where('key', 'not like', 'RAILPACK_%');
}
public function nixpacks_environment_variables_preview()
@@ -892,6 +901,13 @@ class Application extends BaseModel
->where('key', 'like', 'NIXPACKS_%');
}
public function railpack_environment_variables_preview()
{
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
->where('is_preview', true)
->where('key', 'like', 'RAILPACK_%');
}
public function scheduled_tasks(): HasMany
{
return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc');
@@ -1011,7 +1027,7 @@ class Application extends BaseModel
public function could_set_build_commands(): bool
{
if ($this->build_pack === 'nixpacks') {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
return true;
}