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

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

View file

@ -8,4 +8,5 @@ enum BuildPackTypes: string
case STATIC = 'static';
case DOCKERFILE = 'dockerfile';
case DOCKERCOMPOSE = 'dockercompose';
case RAILPACK = 'railpack';
}

View file

@ -150,7 +150,7 @@ public function applications(Request $request)
'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 @@ public function create_public_application(Request $request)
'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 @@ public function create_private_gh_app_application(Request $request)
'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 @@ public function create_private_deploy_key_application(Request $request)
'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 @@ public function delete_by_uuid(Request $request)
'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.'],

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 @@ private function decide_what_to_do()
$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 @@ private function deploy_nixpacks_buildpack()
$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 @@ private function deploy_pull_request()
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 @@ private function generate_nixpacks_env_variables()
$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([]);

View file

@ -598,7 +598,7 @@ public function updatedBuildPack()
// 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();

View file

@ -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 @@ public function updatedSelectedRepositoryId(): void
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') {

View file

@ -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 @@ public function mount()
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') {

View file

@ -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 @@ public function mount()
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') {

View file

@ -39,7 +39,7 @@
'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 @@ public function runtime_environment_variables()
{
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 @@ public function nixpacks_environment_variables()
->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 @@ public function runtime_environment_variables_preview()
{
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 @@ public function nixpacks_environment_variables_preview()
->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 @@ public function deploymentType()
public function could_set_build_commands(): bool
{
if ($this->build_pack === 'nixpacks') {
if ($this->build_pack === 'nixpacks' || $this->build_pack === 'railpack') {
return true;
}

View file

@ -11,6 +11,10 @@ ARG DOCKER_BUILDX_VERSION=0.25.0
ARG PACK_VERSION=0.38.2
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.41.0
# https://github.com/railwayapp/railpack/releases
ARG RAILPACK_VERSION=0.21.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)
ARG MISE_VERSION=2026.3.12
# https://github.com/minio/mc/releases
ARG MINIO_VERSION=RELEASE.2025-08-13T08-35-41Z
@ -25,17 +29,32 @@ ARG DOCKER_COMPOSE_VERSION
ARG DOCKER_BUILDX_VERSION
ARG PACK_VERSION
ARG NIXPACKS_VERSION
ARG RAILPACK_VERSION
ARG MISE_VERSION
USER root
WORKDIR /artifacts
RUN apk add --no-cache bash curl git git-lfs openssh-client tar tini
RUN mkdir -p ~/.docker/cli-plugins
# Install mise (musl build) at the path railpack expects (/tmp/railpack/mise/mise-VERSION).
# Railpack hardcodes a glibc mise download that fails on Alpine, so we pre-place a musl binary.
RUN mkdir -p /tmp/railpack/mise && \
if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
curl -sSL "https://github.com/jdx/mise/releases/download/v${MISE_VERSION}/mise-v${MISE_VERSION}-linux-x64-musl.tar.gz" | tar xz && \
mv mise/bin/mise "/tmp/railpack/mise/mise-${MISE_VERSION}" && rm -rf mise; \
elif [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
curl -sSL "https://github.com/jdx/mise/releases/download/v${MISE_VERSION}/mise-v${MISE_VERSION}-linux-arm64-musl.tar.gz" | tar xz && \
mv mise/bin/mise "/tmp/railpack/mise/mise-${MISE_VERSION}" && rm -rf mise; \
fi
RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \
curl -sSL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose && \
(curl -sSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker) && \
(curl -sSL https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz | tar -C /usr/local/bin/ --no-same-owner -xzv pack) && \
curl -sSL https://nixpacks.com/install.sh | bash && \
curl -sSL https://railpack.com/install.sh | RAILPACK_VERSION=${RAILPACK_VERSION} sh && \
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
;fi
@ -45,6 +64,7 @@ RUN if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
(curl -sSL https://download.docker.com/linux/static/stable/aarch64/docker-${DOCKER_VERSION}.tgz | tar -C /usr/bin/ --no-same-owner -xzv --strip-components=1 docker/docker) && \
(curl -sSL https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux-arm64.tgz | tar -C /usr/local/bin/ --no-same-owner -xzv pack) && \
curl -sSL https://nixpacks.com/install.sh | bash && \
curl -sSL https://railpack.com/install.sh | RAILPACK_VERSION=${RAILPACK_VERSION} sh && \
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
;fi

View file

@ -111,6 +111,7 @@
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
@ -564,6 +565,7 @@
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
@ -1009,6 +1011,7 @@
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
@ -1434,6 +1437,7 @@
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
@ -2442,6 +2446,7 @@
"type": "string",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"
@ -11509,6 +11514,7 @@
"description": "Build pack.",
"enum": [
"nixpacks",
"railpack",
"static",
"dockerfile",
"dockercompose"

View file

@ -81,7 +81,7 @@ paths:
description: 'The git branch.'
build_pack:
type: string
enum: [nixpacks, static, dockerfile, dockercompose]
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
description: 'The build pack type.'
ports_exposes:
type: string
@ -371,7 +371,7 @@ paths:
description: 'The destination UUID.'
build_pack:
type: string
enum: [nixpacks, static, dockerfile, dockercompose]
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
description: 'The build pack type.'
name:
type: string
@ -655,7 +655,7 @@ paths:
description: 'The destination UUID.'
build_pack:
type: string
enum: [nixpacks, static, dockerfile, dockercompose]
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
description: 'The build pack type.'
name:
type: string
@ -923,7 +923,7 @@ paths:
description: 'The Dockerfile content.'
build_pack:
type: string
enum: [nixpacks, static, dockerfile, dockercompose]
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
description: 'The build pack type.'
ports_exposes:
type: string
@ -1556,7 +1556,7 @@ paths:
description: 'The destination UUID.'
build_pack:
type: string
enum: [nixpacks, static, dockerfile, dockercompose]
enum: [nixpacks, railpack, static, dockerfile, dockercompose]
description: 'The build pack type.'
name:
type: string
@ -7256,6 +7256,7 @@ components:
description: 'Build pack.'
enum:
- nixpacks
- railpack
- static
- dockerfile
- dockercompose

View file

@ -31,6 +31,7 @@
<div class="flex gap-2">
<x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="buildPack" label="Build Pack"
required>
<option value="railpack">Railpack</option>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>
@ -218,16 +219,16 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
id="customDockerRunOptions" label="Custom Docker Options" x-bind:disabled="!canUpdate" />
@else
@if ($application->could_set_build_commands())
@if ($buildPack === 'nixpacks')
@if ($buildPack === 'nixpacks' || $buildPack === 'railpack')
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
id="installCommand" label="Install Command" x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
id="buildCommand" label="Build Command" x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
<x-forms.input helper="If you modify this, you probably need to have a {{ $buildPack === 'railpack' ? 'railpack.json' : 'nixpacks.toml' }}"
id="startCommand" label="Start Command" x-bind:disabled="!canUpdate" />
</div>
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
<div class="pt-1 text-xs">{{ $buildPack === 'railpack' ? 'Railpack' : 'Nixpacks' }} will detect the required configuration
automatically.
<a class="underline" href="https://coolify.io/docs/applications/">Framework
Specific Docs</a>

View file

@ -51,6 +51,7 @@ class="loading loading-xs dark:text-warning loading-spinner"></span>
<div class="flex gap-2">
<x-forms.input id="branch" required label="Branch" />
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
<option value="railpack">Railpack</option>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>

View file

@ -77,6 +77,7 @@
@endforeach
</x-forms.select>
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
<option value="railpack">Railpack</option>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>

View file

@ -41,6 +41,7 @@
helper="You can select other branches after configuration is done." />
@endif
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
<option value="railpack">Railpack</option>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>

View file

@ -117,6 +117,52 @@
expect($application->environment_variables()->where('key', 'REGULAR_VAR')->count())->toBe(1);
});
test('model clears dockerfile fields when build_pack changes from dockerfile to railpack', 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([
'environment_id' => $environment->id,
'build_pack' => 'dockerfile',
'dockerfile' => 'FROM node:18',
'dockerfile_location' => '/Dockerfile',
'dockerfile_target_build' => 'production',
'custom_healthcheck_found' => true,
]);
$application->build_pack = 'railpack';
$application->save();
$application->refresh();
expect($application->build_pack)->toBe('railpack');
expect($application->dockerfile)->toBeNull();
expect($application->dockerfile_location)->toBeNull();
expect($application->dockerfile_target_build)->toBeNull();
expect($application->custom_healthcheck_found)->toBeFalse();
});
test('model clears dockercompose fields when build_pack changes from dockercompose to railpack', 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([
'environment_id' => $environment->id,
'build_pack' => 'dockercompose',
'docker_compose_domains' => '{"app": "example.com"}',
'docker_compose_raw' => 'version: "3.8"\nservices:\n app:\n image: nginx',
]);
$application->build_pack = 'railpack';
$application->save();
$application->refresh();
expect($application->build_pack)->toBe('railpack');
expect($application->docker_compose_domains)->toBeNull();
expect($application->docker_compose_raw)->toBeNull();
});
test('model does not clear dockerfile fields when switching to dockerfile', function () {
$team = Team::factory()->create();
$project = Project::factory()->create(['team_id' => $team->id]);

View file

@ -0,0 +1,168 @@
<?php
use App\Models\Application;
use App\Models\Environment;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
describe('Application Railpack Support', function () {
test('could_set_build_commands returns true for railpack', 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([
'environment_id' => $environment->id,
'build_pack' => 'railpack',
]);
expect($application->could_set_build_commands())->toBeTrue();
});
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([
'environment_id' => $environment->id,
'build_pack' => 'nixpacks',
]);
expect($application->could_set_build_commands())->toBeTrue();
});
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([
'environment_id' => $environment->id,
'build_pack' => 'dockerfile',
]);
expect($application->could_set_build_commands())->toBeFalse();
});
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([
'environment_id' => $environment->id,
'build_pack' => 'railpack',
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'RAILPACK_NODE_VERSION',
'value' => '20',
'is_buildtime' => true,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'REGULAR_VAR',
'value' => 'value',
'is_buildtime' => false,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'NIXPACKS_NODE_VERSION',
'value' => '18',
'is_buildtime' => true,
'is_preview' => false,
]);
$railpackVars = $application->railpack_environment_variables;
expect($railpackVars)->toHaveCount(1);
expect($railpackVars->first()->key)->toBe('RAILPACK_NODE_VERSION');
});
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([
'environment_id' => $environment->id,
'build_pack' => 'railpack',
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'RAILPACK_NODE_VERSION',
'value' => '20',
'is_buildtime' => true,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'NIXPACKS_NODE_VERSION',
'value' => '18',
'is_buildtime' => true,
'is_preview' => false,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'APP_ENV',
'value' => 'production',
'is_buildtime' => false,
'is_preview' => false,
]);
$runtimeVars = $application->runtime_environment_variables;
expect($runtimeVars)->toHaveCount(1);
expect($runtimeVars->first()->key)->toBe('APP_ENV');
});
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([
'environment_id' => $environment->id,
'build_pack' => 'railpack',
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'RAILPACK_BUILD_CMD',
'value' => 'npm run build',
'is_buildtime' => true,
'is_preview' => true,
]);
EnvironmentVariable::create([
'resourceable_type' => Application::class,
'resourceable_id' => $application->id,
'key' => 'REGULAR_VAR',
'value' => 'value',
'is_buildtime' => false,
'is_preview' => true,
]);
$previewVars = $application->railpack_environment_variables_preview;
expect($previewVars)->toHaveCount(1);
expect($previewVars->first()->key)->toBe('RAILPACK_BUILD_CMD');
});
});

View file

@ -111,6 +111,29 @@
expect($application->dockerfile)->toBeNull();
});
test('clears dockerfile fields when switching from dockerfile to railpack', function () {
$application = Application::factory()->create([
'environment_id' => $this->environment->id,
'build_pack' => 'dockerfile',
'dockerfile' => 'FROM node:18',
'dockerfile_location' => '/Dockerfile',
'dockerfile_target_build' => 'production',
'custom_healthcheck_found' => true,
]);
Livewire::test(General::class, ['application' => $application])
->assertSuccessful()
->set('buildPack', 'railpack')
->call('updatedBuildPack');
$application->refresh();
expect($application->build_pack)->toBe('railpack');
expect($application->dockerfile)->toBeNull();
expect($application->dockerfile_location)->toBeNull();
expect($application->dockerfile_target_build)->toBeNull();
expect($application->custom_healthcheck_found)->toBeFalse();
});
test('clears dockerfile fields when switching from dockerfile to dockercompose', function () {
$application = Application::factory()->create([
'environment_id' => $this->environment->id,