2023-03-31 11:32:07 +00:00
< ? php
namespace App\Jobs ;
2024-05-07 13:41:50 +00:00
use App\Actions\Docker\GetContainersStatus ;
2023-06-30 20:24:39 +00:00
use App\Enums\ApplicationDeploymentStatus ;
2024-01-26 17:46:50 +00:00
use App\Enums\ProcessStatus ;
2025-09-22 07:44:30 +00:00
use App\Events\ApplicationConfigurationChanged ;
2025-05-19 19:50:32 +00:00
use App\Events\ServiceStatusChanged ;
2023-03-31 11:32:07 +00:00
use App\Models\Application ;
2023-05-24 12:26:50 +00:00
use App\Models\ApplicationDeploymentQueue ;
2023-05-30 13:52:17 +00:00
use App\Models\ApplicationPreview ;
2024-06-13 11:14:24 +00:00
use App\Models\EnvironmentVariable ;
2023-06-30 20:24:39 +00:00
use App\Models\GithubApp ;
use App\Models\GitlabApp ;
use App\Models\Server ;
use App\Models\StandaloneDocker ;
use App\Models\SwarmDocker ;
2023-07-28 08:55:26 +00:00
use App\Notifications\Application\DeploymentFailed ;
2023-08-08 09:51:36 +00:00
use App\Notifications\Application\DeploymentSuccess ;
2025-09-23 06:53:14 +00:00
use App\Traits\EnvironmentVariableAnalyzer ;
2023-06-30 20:24:39 +00:00
use App\Traits\ExecuteRemoteCommand ;
2025-01-16 14:39:53 +00:00
use Carbon\Carbon ;
2024-05-29 13:15:03 +00:00
use Exception ;
2023-03-31 11:32:07 +00:00
use Illuminate\Bus\Queueable ;
2023-09-14 08:12:44 +00:00
use Illuminate\Contracts\Queue\ShouldBeEncrypted ;
2023-03-31 11:32:07 +00:00
use Illuminate\Contracts\Queue\ShouldQueue ;
use Illuminate\Foundation\Bus\Dispatchable ;
use Illuminate\Queue\InteractsWithQueue ;
use Illuminate\Queue\SerializesModels ;
2023-04-04 12:11:53 +00:00
use Illuminate\Support\Collection ;
2024-03-14 09:10:03 +00:00
use Illuminate\Support\Sleep ;
2023-06-30 20:24:39 +00:00
use Illuminate\Support\Str ;
2024-05-29 13:15:03 +00:00
use RuntimeException ;
2025-07-08 08:42:34 +00:00
use Spatie\Url\Url ;
2023-06-30 20:24:39 +00:00
use Symfony\Component\Yaml\Yaml ;
2024-05-29 13:15:03 +00:00
use Throwable ;
2023-05-30 13:52:17 +00:00
use Visus\Cuid2\Cuid2 ;
2023-03-31 11:32:07 +00:00
2024-06-10 20:43:34 +00:00
class ApplicationDeploymentJob implements ShouldBeEncrypted , ShouldQueue
2023-03-31 11:32:07 +00:00
{
2025-09-23 06:53:14 +00:00
use Dispatchable , EnvironmentVariableAnalyzer , ExecuteRemoteCommand , InteractsWithQueue , Queueable , SerializesModels ;
2023-06-30 20:24:39 +00:00
2025-01-07 08:25:25 +00:00
public $tries = 1 ;
2023-11-16 12:27:51 +00:00
public $timeout = 3600 ;
2023-06-30 20:24:39 +00:00
public static int $batch_counter = 0 ;
2024-05-29 13:15:03 +00:00
private bool $newVersionIsHealthy = false ;
2024-06-10 20:43:34 +00:00
2023-05-24 12:26:50 +00:00
private ApplicationDeploymentQueue $application_deployment_queue ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private Application $application ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private string $deployment_uuid ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private int $pull_request_id ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private string $commit ;
2024-06-10 20:43:34 +00:00
2024-04-17 13:30:08 +00:00
private bool $rollback ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private bool $force_rebuild ;
2024-06-10 20:43:34 +00:00
2023-11-01 11:19:08 +00:00
private bool $restart_only ;
2023-06-30 20:24:39 +00:00
2024-05-29 13:15:03 +00:00
private ? string $dockerImage = null ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private ? string $dockerImageTag = null ;
2023-10-10 09:16:38 +00:00
2023-10-06 11:46:42 +00:00
private GithubApp | GitlabApp | string $source = 'other' ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private StandaloneDocker | SwarmDocker $destination ;
2024-06-10 20:43:34 +00:00
2024-01-16 14:19:14 +00:00
// Deploy to Server
2023-06-30 20:24:39 +00:00
private Server $server ;
2024-06-10 20:43:34 +00:00
2024-01-16 14:19:14 +00:00
// Build Server
private Server $build_server ;
2024-06-10 20:43:34 +00:00
2024-01-16 14:19:14 +00:00
private bool $use_build_server = false ;
2024-06-10 20:43:34 +00:00
2024-01-16 14:19:14 +00:00
// Save original server between phases
private Server $original_server ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private Server $mainServer ;
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
private bool $is_this_additional_server = false ;
2024-06-10 20:43:34 +00:00
2023-11-08 14:40:06 +00:00
private ? ApplicationPreview $preview = null ;
2024-06-10 20:43:34 +00:00
2023-11-14 12:26:14 +00:00
private ? string $git_type = null ;
2024-06-10 20:43:34 +00:00
2024-02-22 09:57:05 +00:00
private bool $only_this_server = false ;
2023-05-23 07:53:24 +00:00
2023-06-30 20:24:39 +00:00
private string $container_name ;
2024-06-10 20:43:34 +00:00
2023-10-18 08:32:08 +00:00
private ? string $currently_running_container_name = null ;
2024-06-10 20:43:34 +00:00
2023-10-06 08:07:25 +00:00
private string $basedir ;
2024-06-10 20:43:34 +00:00
2023-05-24 12:26:50 +00:00
private string $workdir ;
2024-06-10 20:43:34 +00:00
2023-10-10 12:02:43 +00:00
private ? string $build_pack = null ;
2024-06-10 20:43:34 +00:00
2023-08-09 12:44:36 +00:00
private string $configuration_dir ;
2024-06-10 20:43:34 +00:00
2023-05-30 13:52:17 +00:00
private string $build_image_name ;
2024-06-10 20:43:34 +00:00
2023-05-30 13:52:17 +00:00
private string $production_image_name ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private bool $is_debug_enabled ;
2024-06-10 20:43:34 +00:00
2024-09-04 09:34:31 +00:00
private Collection | string $build_args ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private $env_args ;
2024-06-10 20:43:34 +00:00
2024-01-11 11:56:02 +00:00
private $env_nixpacks_args ;
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private $docker_compose ;
2024-06-10 20:43:34 +00:00
2023-08-09 12:44:36 +00:00
private $docker_compose_base64 ;
2024-06-10 20:43:34 +00:00
2024-01-08 15:33:34 +00:00
private ? string $nixpacks_plan = null ;
2024-06-10 20:43:34 +00:00
2024-12-18 11:02:56 +00:00
private Collection $nixpacks_plan_json ;
2024-01-08 15:33:34 +00:00
private ? string $nixpacks_type = null ;
2024-06-10 20:43:34 +00:00
2023-10-10 12:02:43 +00:00
private string $dockerfile_location = '/Dockerfile' ;
2024-06-10 20:43:34 +00:00
2024-07-02 14:12:04 +00:00
private string $docker_compose_location = '/docker-compose.yaml' ;
2024-06-10 20:43:34 +00:00
2023-12-17 19:56:12 +00:00
private ? string $docker_compose_custom_start_command = null ;
2024-06-10 20:43:34 +00:00
2023-12-17 19:56:12 +00:00
private ? string $docker_compose_custom_build_command = null ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private ? string $addHosts = null ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private ? string $buildTarget = null ;
2024-06-10 20:43:34 +00:00
2024-12-05 09:46:27 +00:00
private bool $disableBuildCache = false ;
2023-06-30 20:24:39 +00:00
private Collection $saved_outputs ;
2024-06-10 20:43:34 +00:00
2025-09-22 07:44:30 +00:00
private ? string $secrets_hash_key = null ;
2023-11-16 14:23:07 +00:00
private ? string $full_healthcheck_url = null ;
2023-08-08 09:51:36 +00:00
2024-05-29 13:15:03 +00:00
private string $serverUser = 'root' ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private string $serverUserHomeDir = '/root' ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private string $dockerConfigFileExists = 'NOK' ;
2023-10-17 10:35:04 +00:00
2024-05-29 13:15:03 +00:00
private int $customPort = 22 ;
2024-06-10 20:43:34 +00:00
2024-05-29 13:15:03 +00:00
private ? string $customRepository = null ;
2023-10-18 13:33:07 +00:00
2024-05-29 13:15:03 +00:00
private ? string $fullRepoUrl = null ;
2024-06-10 20:43:34 +00:00
2023-10-26 08:33:57 +00:00
private ? string $branch = null ;
2024-05-15 09:30:35 +00:00
private ? string $coolify_variables = null ;
2024-08-12 14:06:24 +00:00
private bool $preserveRepository = false ;
2024-07-18 11:14:07 +00:00
2025-09-16 15:16:01 +00:00
private bool $dockerBuildkitSupported = false ;
2025-09-18 16:15:20 +00:00
private bool $skip_build = false ;
2025-09-16 15:16:01 +00:00
private Collection | string $build_secrets ;
2025-01-10 19:07:01 +00:00
public function tags ()
{
// Do not remove this one, it needs to properly identify which worker is running the job
return [ 'App\Models\ApplicationDeploymentQueue:' . $this -> application_deployment_queue_id ];
}
2024-06-10 20:43:34 +00:00
2025-01-10 19:07:01 +00:00
public function __construct ( public int $application_deployment_queue_id )
2023-06-30 20:24:39 +00:00
{
2024-11-22 10:16:01 +00:00
$this -> onQueue ( 'high' );
2025-01-10 19:07:01 +00:00
$this -> application_deployment_queue = ApplicationDeploymentQueue :: find ( $this -> application_deployment_queue_id );
2025-01-06 12:26:09 +00:00
$this -> nixpacks_plan_json = collect ([]);
2023-06-30 20:24:39 +00:00
$this -> application = Application :: find ( $this -> application_deployment_queue -> application_id );
2023-10-10 12:02:43 +00:00
$this -> build_pack = data_get ( $this -> application , 'build_pack' );
2024-09-04 09:34:31 +00:00
$this -> build_args = collect ([]);
2025-09-16 15:16:01 +00:00
$this -> build_secrets = '' ;
2023-06-30 20:24:39 +00:00
$this -> deployment_uuid = $this -> application_deployment_queue -> deployment_uuid ;
$this -> pull_request_id = $this -> application_deployment_queue -> pull_request_id ;
$this -> commit = $this -> application_deployment_queue -> commit ;
2024-04-17 13:30:08 +00:00
$this -> rollback = $this -> application_deployment_queue -> rollback ;
2024-12-05 09:46:27 +00:00
$this -> disableBuildCache = $this -> application -> settings -> disable_build_cache ;
2023-06-30 20:24:39 +00:00
$this -> force_rebuild = $this -> application_deployment_queue -> force_rebuild ;
2024-12-05 09:46:27 +00:00
if ( $this -> disableBuildCache ) {
$this -> force_rebuild = true ;
}
2023-11-01 11:19:08 +00:00
$this -> restart_only = $this -> application_deployment_queue -> restart_only ;
2024-05-30 10:28:29 +00:00
$this -> restart_only = $this -> restart_only && $this -> application -> build_pack !== 'dockerimage' && $this -> application -> build_pack !== 'dockerfile' ;
2024-02-22 09:57:05 +00:00
$this -> only_this_server = $this -> application_deployment_queue -> only_this_server ;
2023-06-30 20:24:39 +00:00
2023-11-14 12:26:14 +00:00
$this -> git_type = data_get ( $this -> application_deployment_queue , 'git_type' );
2023-10-06 11:46:42 +00:00
$source = data_get ( $this -> application , 'source' );
if ( $source ) {
$this -> source = $source -> getMorphClass () :: where ( 'id' , $this -> application -> source -> id ) -> first ();
}
2024-02-05 13:40:54 +00:00
$this -> server = Server :: find ( $this -> application_deployment_queue -> server_id );
2024-02-08 11:34:01 +00:00
$this -> timeout = $this -> server -> settings -> dynamic_timeout ;
2024-02-05 13:40:54 +00:00
$this -> destination = $this -> server -> destinations () -> where ( 'id' , $this -> application_deployment_queue -> destination_id ) -> first ();
2024-05-29 13:15:03 +00:00
$this -> server = $this -> mainServer = $this -> destination -> server ;
$this -> serverUser = $this -> server -> user ;
2024-05-30 10:28:29 +00:00
$this -> is_this_additional_server = $this -> application -> additional_servers () -> wherePivot ( 'server_id' , $this -> server -> id ) -> count () > 0 ;
2024-07-18 11:14:07 +00:00
$this -> preserveRepository = $this -> application -> settings -> is_preserve_repository_enabled ;
2024-05-30 10:28:29 +00:00
2023-11-24 14:48:23 +00:00
$this -> basedir = $this -> application -> generateBaseDir ( $this -> deployment_uuid );
2024-06-13 11:15:09 +00:00
$this -> workdir = " { $this -> basedir } " . rtrim ( $this -> application -> base_directory , '/' );
$this -> configuration_dir = application_configuration_dir () . " / { $this -> application -> uuid } " ;
2023-06-30 20:24:39 +00:00
$this -> is_debug_enabled = $this -> application -> settings -> is_debug_enabled ;
2023-05-30 13:52:17 +00:00
2023-10-01 10:02:44 +00:00
$this -> container_name = generateApplicationContainerName ( $this -> application , $this -> pull_request_id );
2024-07-02 08:02:43 +00:00
if ( $this -> application -> settings -> custom_internal_name && ! $this -> application -> settings -> is_consistent_container_name_enabled ) {
2024-08-15 10:13:29 +00:00
if ( $this -> pull_request_id === 0 ) {
$this -> container_name = $this -> application -> settings -> custom_internal_name ;
} else {
2025-09-08 13:15:57 +00:00
$this -> container_name = addPreviewDeploymentSuffix ( $this -> application -> settings -> custom_internal_name , $this -> pull_request_id );
2024-08-15 10:13:29 +00:00
}
2024-07-02 08:02:43 +00:00
}
2024-02-15 10:55:43 +00:00
2023-06-30 20:24:39 +00:00
$this -> saved_outputs = collect ();
2023-05-30 13:52:17 +00:00
2023-06-30 20:24:39 +00:00
// Set preview fqdn
2023-06-05 10:07:55 +00:00
if ( $this -> pull_request_id !== 0 ) {
2025-07-17 07:31:55 +00:00
$this -> preview = ApplicationPreview :: findPreviewByApplicationAndPullId ( $this -> application -> id , $this -> pull_request_id );
if ( $this -> preview ) {
if ( $this -> application -> build_pack === 'dockercompose' ) {
2025-07-14 13:47:16 +00:00
$this -> preview -> generate_preview_fqdn_compose ();
2025-07-17 07:31:55 +00:00
} else {
2025-07-14 17:12:57 +00:00
$this -> preview -> generate_preview_fqdn ();
}
2025-07-14 13:47:16 +00:00
}
2024-01-29 09:43:18 +00:00
if ( $this -> application -> is_github_based ()) {
ApplicationPullRequestUpdateJob :: dispatch ( application : $this -> application , preview : $this -> preview , deployment_uuid : $this -> deployment_uuid , status : ProcessStatus :: IN_PROGRESS );
}
2024-02-26 13:28:02 +00:00
if ( $this -> application -> build_pack === 'dockerfile' ) {
if ( data_get ( $this -> application , 'dockerfile_location' )) {
$this -> dockerfile_location = $this -> application -> dockerfile_location ;
}
}
2023-05-30 13:52:17 +00:00
}
2023-03-31 12:30:08 +00:00
}
2023-06-30 20:24:39 +00:00
2023-03-31 11:32:07 +00:00
public function handle () : void
{
2025-09-16 11:40:51 +00:00
// Check if deployment was cancelled before we even started
$this -> application_deployment_queue -> refresh ();
if ( $this -> application_deployment_queue -> status === ApplicationDeploymentStatus :: CANCELLED_BY_USER -> value ) {
$this -> application_deployment_queue -> addLogEntry ( 'Deployment was cancelled before starting.' );
return ;
}
2024-05-24 09:50:31 +00:00
$this -> application_deployment_queue -> update ([
'status' => ApplicationDeploymentStatus :: IN_PROGRESS -> value ,
2025-01-10 14:39:22 +00:00
'horizon_job_worker' => gethostname (),
2024-05-24 09:50:31 +00:00
]);
2024-11-03 08:02:14 +00:00
if ( $this -> server -> isFunctional () === false ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Server is not functional.' );
$this -> fail ( 'Server is not functional.' );
2024-04-09 06:46:00 +00:00
return ;
}
2024-02-26 13:22:24 +00:00
try {
2025-02-04 14:23:28 +00:00
// Make sure the private key is stored in the filesystem
$this -> server -> privateKey -> storeInFileSystem ();
2024-02-26 13:22:24 +00:00
// Generate custom host<->ip mapping
$allContainers = instant_remote_process ([ " docker network inspect { $this -> destination -> network } -f ' { { json .Containers}}' " ], $this -> server );
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $allContainers )) {
2024-02-26 13:22:24 +00:00
$allContainers = format_docker_command_output_to_json ( $allContainers );
$ips = collect ([]);
if ( count ( $allContainers ) > 0 ) {
$allContainers = $allContainers [ 0 ];
$allContainers = collect ( $allContainers ) -> sort () -> values ();
foreach ( $allContainers as $container ) {
$containerName = data_get ( $container , 'Name' );
if ( $containerName === 'coolify-proxy' ) {
continue ;
}
if ( preg_match ( '/-(\d{12})/' , $containerName )) {
continue ;
}
$containerIp = data_get ( $container , 'IPv4Address' );
if ( $containerName && $containerIp ) {
$containerIp = str ( $containerIp ) -> before ( '/' );
$ips -> put ( $containerName , $containerIp -> value ());
}
2023-11-28 17:31:04 +00:00
}
2023-10-17 09:23:49 +00:00
}
2024-05-29 13:15:03 +00:00
$this -> addHosts = $ips -> map ( function ( $ip , $name ) {
2024-02-26 13:22:24 +00:00
return " --add-host $name : $ip " ;
}) -> implode ( ' ' );
2023-10-17 09:23:49 +00:00
}
2024-02-26 13:22:24 +00:00
if ( $this -> application -> dockerfile_target_build ) {
2024-05-29 13:15:03 +00:00
$this -> buildTarget = " --target { $this -> application -> dockerfile_target_build } " ;
2024-02-26 13:22:24 +00:00
}
2023-11-07 12:49:15 +00:00
2024-02-26 13:22:24 +00:00
// Check custom port
2024-05-29 13:15:03 +00:00
[ 'repository' => $this -> customRepository , 'port' => $this -> customPort ] = $this -> application -> customRepository ();
2023-11-24 14:48:23 +00:00
2024-02-26 13:22:24 +00:00
if ( data_get ( $this -> application , 'settings.is_build_server_enabled' )) {
$teamId = data_get ( $this -> application , 'environment.project.team.id' );
$buildServers = Server :: buildServers ( $teamId ) -> get ();
if ( $buildServers -> count () === 0 ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'No suitable build server found. Using the deployment server.' );
2024-02-26 13:22:24 +00:00
$this -> build_server = $this -> server ;
$this -> original_server = $this -> server ;
} else {
$this -> build_server = $buildServers -> random ();
2024-08-15 12:05:17 +00:00
$this -> application_deployment_queue -> build_server_id = $this -> build_server -> id ;
2024-03-01 10:43:42 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Found a suitable build server ( { $this -> build_server -> name } ). " );
2024-02-26 13:22:24 +00:00
$this -> original_server = $this -> server ;
$this -> use_build_server = true ;
}
2024-01-16 14:19:14 +00:00
} else {
2024-02-26 13:22:24 +00:00
// Set build server & original_server to the same as deployment server
$this -> build_server = $this -> server ;
2024-01-16 14:19:14 +00:00
$this -> original_server = $this -> server ;
}
2025-09-17 13:18:26 +00:00
$this -> detectBuildKitCapabilities ();
2024-04-12 11:24:42 +00:00
$this -> decide_what_to_do ();
2023-08-09 12:44:36 +00:00
} catch ( Exception $e ) {
2024-01-29 11:51:20 +00:00
if ( $this -> pull_request_id !== 0 && $this -> application -> is_github_based ()) {
ApplicationPullRequestUpdateJob :: dispatch ( application : $this -> application , preview : $this -> preview , deployment_uuid : $this -> deployment_uuid , status : ProcessStatus :: ERROR );
2024-01-26 17:46:50 +00:00
}
2023-07-07 12:56:20 +00:00
$this -> fail ( $e );
2023-08-24 19:09:58 +00:00
throw $e ;
2023-05-30 13:52:17 +00:00
} finally {
2025-11-09 13:41:35 +00:00
// Wrap cleanup operations in try-catch to prevent exceptions from interfering
// with Laravel's job failure handling and status updates
try {
$this -> application_deployment_queue -> update ([
'finished_at' => Carbon :: now () -> toImmutable (),
]);
} catch ( Exception $e ) {
// Log but don't fail - finished_at is not critical
\Log :: warning ( 'Failed to update finished_at for deployment ' . $this -> deployment_uuid . ': ' . $e -> getMessage ());
}
2025-01-16 11:05:59 +00:00
2025-11-09 13:41:35 +00:00
try {
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
} else {
$this -> write_deployment_configurations ();
}
} catch ( Exception $e ) {
// Log but don't fail - configuration writing errors shouldn't prevent status updates
$this -> application_deployment_queue -> addLogEntry ( 'Warning: Failed to write deployment configurations: ' . $e -> getMessage (), 'stderr' );
2023-07-07 12:56:20 +00:00
}
2025-09-16 15:16:01 +00:00
2025-11-09 13:41:35 +00:00
try {
$this -> application_deployment_queue -> addLogEntry ( " Gracefully shutting down build container: { $this -> deployment_uuid } " );
$this -> graceful_shutdown_container ( $this -> deployment_uuid );
} catch ( Exception $e ) {
// Log but don't fail - container cleanup errors are expected when container is already gone
\Log :: warning ( 'Failed to shutdown container ' . $this -> deployment_uuid . ': ' . $e -> getMessage ());
}
2024-03-19 10:48:25 +00:00
2025-11-09 13:41:35 +00:00
try {
ServiceStatusChanged :: dispatch ( data_get ( $this -> application , 'environment.project.team.id' ));
} catch ( Exception $e ) {
// Log but don't fail - event dispatch errors shouldn't prevent status updates
\Log :: warning ( 'Failed to dispatch ServiceStatusChanged for deployment ' . $this -> deployment_uuid . ': ' . $e -> getMessage ());
}
2023-05-30 13:52:17 +00:00
}
}
2024-06-10 20:43:34 +00:00
2025-09-17 08:08:29 +00:00
private function detectBuildKitCapabilities () : void
2025-09-16 15:16:01 +00:00
{
2025-09-17 08:34:38 +00:00
// If build secrets are not enabled, skip detection and use traditional args
if ( ! $this -> application -> settings -> use_build_secrets ) {
$this -> dockerBuildkitSupported = false ;
return ;
}
2025-09-16 15:16:01 +00:00
$serverToCheck = $this -> use_build_server ? $this -> build_server : $this -> server ;
2025-09-17 08:08:29 +00:00
$serverName = $this -> use_build_server ? " build server ( { $serverToCheck -> name } ) " : " deployment server ( { $serverToCheck -> name } ) " ;
2025-09-16 15:16:01 +00:00
try {
$dockerVersion = instant_remote_process (
[ " docker version --format ' { { .Server.Version}}' " ],
$serverToCheck
);
$versionParts = explode ( '.' , $dockerVersion );
$majorVersion = ( int ) $versionParts [ 0 ];
$minorVersion = ( int ) ( $versionParts [ 1 ] ? ? 0 );
2025-09-17 08:08:29 +00:00
if ( $majorVersion < 18 || ( $majorVersion == 18 && $minorVersion < 9 )) {
$this -> dockerBuildkitSupported = false ;
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Docker { $dockerVersion } on { $serverName } does not support BuildKit (requires 18.09+). Build secrets feature disabled. " );
2025-09-17 08:08:29 +00:00
return ;
}
$buildkitEnabled = instant_remote_process (
[ " docker buildx version >/dev/null 2>&1 && echo 'available' || echo 'not-available' " ],
$serverToCheck
);
if ( trim ( $buildkitEnabled ) !== 'available' ) {
2025-09-16 15:16:01 +00:00
$buildkitTest = instant_remote_process (
[ " DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported' " ],
$serverToCheck
);
if ( trim ( $buildkitTest ) === 'supported' ) {
$this -> dockerBuildkitSupported = true ;
2025-09-17 08:08:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Docker { $dockerVersion } with BuildKit secrets support detected on { $serverName } . " );
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets are enabled and will be used for enhanced security.' );
2025-09-16 15:16:01 +00:00
} else {
2025-09-17 08:08:29 +00:00
$this -> dockerBuildkitSupported = false ;
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Docker { $dockerVersion } on { $serverName } does not have BuildKit secrets support. " );
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets feature is enabled but not supported. Using traditional build arguments.' );
2025-09-16 15:16:01 +00:00
}
} else {
2025-09-17 08:08:29 +00:00
// Buildx is available, which means BuildKit is available
// Now specifically test for secrets support
$secretsTest = instant_remote_process (
[ " docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported' " ],
$serverToCheck
);
if ( trim ( $secretsTest ) === 'supported' ) {
$this -> dockerBuildkitSupported = true ;
$this -> application_deployment_queue -> addLogEntry ( " Docker { $dockerVersion } with BuildKit and Buildx detected on { $serverName } . " );
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets are enabled and will be used for enhanced security.' );
2025-09-17 08:08:29 +00:00
} else {
$this -> dockerBuildkitSupported = false ;
$this -> application_deployment_queue -> addLogEntry ( " Docker { $dockerVersion } with Buildx on { $serverName } , but secrets not supported. " );
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets feature is enabled but not supported. Using traditional build arguments.' );
2025-09-17 08:08:29 +00:00
}
2025-09-16 15:16:01 +00:00
}
} catch ( \Exception $e ) {
$this -> dockerBuildkitSupported = false ;
2025-09-17 08:08:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Could not detect BuildKit capabilities on { $serverName } : { $e -> getMessage () } " );
2025-09-17 08:34:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets feature is enabled but detection failed. Using traditional build arguments.' );
2025-09-16 15:16:01 +00:00
}
}
2024-04-12 11:24:42 +00:00
private function decide_what_to_do ()
{
2024-05-30 10:28:29 +00:00
if ( $this -> restart_only ) {
2024-04-12 11:24:42 +00:00
$this -> just_restart ();
2024-06-11 11:17:15 +00:00
2024-04-12 12:03:29 +00:00
return ;
2024-06-10 20:43:34 +00:00
} elseif ( $this -> pull_request_id !== 0 ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_pull_request ();
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> dockerfile ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_simple_dockerfile ();
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> build_pack === 'dockercompose' ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_docker_compose_buildpack ();
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> build_pack === 'dockerimage' ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_dockerimage_buildpack ();
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> build_pack === 'dockerfile' ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_dockerfile_buildpack ();
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> build_pack === 'static' ) {
2024-04-12 11:24:42 +00:00
$this -> deploy_static_buildpack ();
} else {
$this -> deploy_nixpacks_buildpack ();
}
2024-04-12 12:03:29 +00:00
$this -> post_deployment ();
}
2024-06-10 20:43:34 +00:00
2024-04-12 12:03:29 +00:00
private function post_deployment ()
{
2025-05-30 08:09:25 +00:00
GetContainersStatus :: dispatch ( $this -> server );
2025-10-26 08:06:44 +00:00
$this -> completeDeployment ();
2024-04-12 11:24:42 +00:00
if ( $this -> pull_request_id !== 0 ) {
if ( $this -> application -> is_github_based ()) {
ApplicationPullRequestUpdateJob :: dispatch ( application : $this -> application , preview : $this -> preview , deployment_uuid : $this -> deployment_uuid , status : ProcessStatus :: FINISHED );
}
}
$this -> run_post_deployment_command ();
$this -> application -> isConfigurationChanged ( true );
}
2024-06-10 20:43:34 +00:00
2023-08-11 20:41:47 +00:00
private function deploy_simple_dockerfile ()
{
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2023-08-11 20:41:47 +00:00
$dockerfile_base64 = base64_encode ( $this -> application -> dockerfile );
2024-02-06 14:05:11 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> application -> name } to { $this -> server -> name } . " );
2023-08-11 20:41:47 +00:00
$this -> prepare_builder_image ();
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' $dockerfile_base64 ' | base64 -d | tee { $this -> workdir } { $this -> dockerfile_location } > /dev/null " ),
2023-08-11 20:41:47 +00:00
],
);
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2023-08-11 20:41:47 +00:00
$this -> generate_compose_file ();
2025-10-09 14:38:17 +00:00
// Save build-time .env file BEFORE the build
$this -> save_buildtime_environment_variables ();
2023-08-11 20:41:47 +00:00
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
$this -> build_image ();
2025-10-14 18:43:11 +00:00
// Save runtime environment variables AFTER the build
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this -> save_runtime_environment_variables ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
2023-08-11 20:41:47 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-12 10:01:09 +00:00
private function deploy_dockerimage_buildpack ()
2023-10-10 09:16:38 +00:00
{
2024-05-29 13:15:03 +00:00
$this -> dockerImage = $this -> application -> docker_registry_image_name ;
2024-02-22 13:45:41 +00:00
if ( str ( $this -> application -> docker_registry_image_tag ) -> isEmpty ()) {
2024-05-29 13:15:03 +00:00
$this -> dockerImageTag = 'latest' ;
2024-02-22 13:45:41 +00:00
} else {
2024-05-29 13:15:03 +00:00
$this -> dockerImageTag = $this -> application -> docker_registry_image_tag ;
2024-02-22 13:45:41 +00:00
}
2025-08-27 20:40:14 +00:00
// Check if this is an image hash deployment
$isImageHash = str ( $this -> dockerImageTag ) -> startsWith ( 'sha256-' );
$displayName = $isImageHash ? " { $this -> dockerImage } @sha256: " . str ( $this -> dockerImageTag ) -> after ( 'sha256-' ) : " { $this -> dockerImage } : { $this -> dockerImageTag } " ;
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $displayName } to { $this -> server -> name } . " );
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2023-10-10 09:16:38 +00:00
$this -> prepare_builder_image ();
$this -> generate_compose_file ();
2025-10-21 18:39:39 +00:00
// Save runtime environment variables (including empty .env file if no variables defined)
$this -> save_runtime_environment_variables ();
2023-10-10 09:16:38 +00:00
$this -> rolling_update ();
}
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
private function deploy_docker_compose_buildpack ()
{
if ( data_get ( $this -> application , 'docker_compose_location' )) {
$this -> docker_compose_location = $this -> application -> docker_compose_location ;
}
2023-12-17 19:56:12 +00:00
if ( data_get ( $this -> application , 'docker_compose_custom_start_command' )) {
$this -> docker_compose_custom_start_command = $this -> application -> docker_compose_custom_start_command ;
2024-06-13 11:15:09 +00:00
if ( ! str ( $this -> docker_compose_custom_start_command ) -> contains ( '--project-directory' )) {
$this -> docker_compose_custom_start_command = str ( $this -> docker_compose_custom_start_command ) -> replaceFirst ( 'compose' , 'compose --project-directory ' . $this -> workdir ) -> value ();
2024-06-04 19:26:49 +00:00
}
2023-12-17 19:56:12 +00:00
}
if ( data_get ( $this -> application , 'docker_compose_custom_build_command' )) {
$this -> docker_compose_custom_build_command = $this -> application -> docker_compose_custom_build_command ;
2024-06-13 11:15:09 +00:00
if ( ! str ( $this -> docker_compose_custom_build_command ) -> contains ( '--project-directory' )) {
$this -> docker_compose_custom_build_command = str ( $this -> docker_compose_custom_build_command ) -> replaceFirst ( 'compose' , 'compose --project-directory ' . $this -> workdir ) -> value ();
2024-06-04 19:26:49 +00:00
}
2023-12-17 19:56:12 +00:00
}
2023-11-27 13:28:21 +00:00
if ( $this -> pull_request_id === 0 ) {
2024-02-06 14:05:11 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> application -> name } to { $this -> server -> name } . " );
2023-11-27 13:28:21 +00:00
} else {
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting pull request (# { $this -> pull_request_id } ) deployment of { $this -> customRepository } : { $this -> application -> git_branch } to { $this -> server -> name } . " );
2023-11-27 13:28:21 +00:00
}
2024-01-30 13:12:40 +00:00
$this -> prepare_builder_image ();
2023-11-24 14:48:23 +00:00
$this -> check_git_if_build_needed ();
$this -> clone_repository ();
2024-08-21 12:31:17 +00:00
if ( $this -> preserveRepository ) {
foreach ( $this -> application -> fileStorages as $fileStorage ) {
$path = $fileStorage -> fs_path ;
$saveName = 'file_stat_' . $fileStorage -> id ;
$realPathInGit = str ( $path ) -> replace ( $this -> application -> workdir (), $this -> workdir ) -> value ();
// check if the file is a directory or a file inside the repository
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , " stat -c '%F' { $realPathInGit } " ), 'hidden' => true , 'ignore_errors' => true , 'save' => $saveName ]
);
if ( $this -> saved_outputs -> has ( $saveName )) {
$fileStat = $this -> saved_outputs -> get ( $saveName );
if ( $fileStat -> value () === 'directory' && ! $fileStorage -> is_directory ) {
$fileStorage -> is_directory = true ;
$fileStorage -> content = null ;
$fileStorage -> save ();
$fileStorage -> deleteStorageOnServer ();
$fileStorage -> saveStorageOnServer ();
} elseif ( $fileStat -> value () === 'regular file' && $fileStorage -> is_directory ) {
$fileStorage -> is_directory = false ;
$fileStorage -> is_based_on_git = true ;
$fileStorage -> save ();
$fileStorage -> deleteStorageOnServer ();
$fileStorage -> saveStorageOnServer ();
}
}
}
}
2023-11-24 14:48:23 +00:00
$this -> generate_image_names ();
$this -> cleanup_git ();
2025-09-16 15:16:01 +00:00
$this -> generate_build_env_variables ();
2023-12-04 10:20:50 +00:00
$this -> application -> loadComposeFile ( isInit : false );
2024-01-02 12:55:35 +00:00
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
2024-08-23 18:57:22 +00:00
$this -> application -> oldRawParser ();
2024-01-02 12:55:35 +00:00
$yaml = $composeFile = $this -> application -> docker_compose_raw ;
2025-09-16 15:16:01 +00:00
// For raw compose, we cannot automatically add secrets configuration
// User must define it manually in their docker-compose file
2025-09-17 08:34:38 +00:00
if ( $this -> application -> settings -> use_build_secrets && $this -> dockerBuildkitSupported && ! empty ( $this -> build_secrets )) {
2025-09-16 15:16:01 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.' );
}
2024-01-02 12:55:35 +00:00
} else {
2024-08-29 10:39:37 +00:00
$composeFile = $this -> application -> parse ( pull_request_id : $this -> pull_request_id , preview_id : data_get ( $this -> preview , 'id' ));
2025-10-04 17:19:15 +00:00
// Always add .env file to services
$services = collect ( data_get ( $composeFile , 'services' , []));
$services = $services -> map ( function ( $service , $name ) {
$service [ 'env_file' ] = [ '.env' ];
2024-06-10 20:43:34 +00:00
2025-10-04 17:19:15 +00:00
return $service ;
});
$composeFile [ 'services' ] = $services -> toArray ();
2025-07-07 14:07:08 +00:00
if ( empty ( $composeFile )) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Failed to parse docker-compose file.' );
$this -> fail ( 'Failed to parse docker-compose file.' );
2024-05-30 10:28:29 +00:00
return ;
}
2025-09-16 15:16:01 +00:00
2025-09-17 08:34:38 +00:00
// Add build secrets to compose file if enabled and BuildKit is supported
if ( $this -> application -> settings -> use_build_secrets && $this -> dockerBuildkitSupported && ! empty ( $this -> build_secrets )) {
2025-09-16 15:16:01 +00:00
$composeFile = $this -> add_build_secrets_to_compose ( $composeFile );
}
2024-08-22 13:05:04 +00:00
$yaml = Yaml :: dump ( convertToArray ( $composeFile ), 10 );
2024-01-02 12:55:35 +00:00
}
2023-11-24 14:48:23 +00:00
$this -> docker_compose_base64 = base64_encode ( $yaml );
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> docker_compose_base64 } ' | base64 -d | tee { $this -> workdir } { $this -> docker_compose_location } > /dev/null " ),
'hidden' => true ,
2023-11-24 14:48:23 +00:00
]);
2025-09-19 11:46:00 +00:00
// Modify Dockerfiles for ARGs and build secrets
$this -> modify_dockerfiles_for_compose ( $composeFile );
2023-12-10 03:14:06 +00:00
// Build new container to limit downtime.
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Pulling & building required images.' );
2023-12-17 19:56:12 +00:00
2025-10-04 17:19:15 +00:00
// Save build-time .env file BEFORE the build
$this -> save_buildtime_environment_variables ();
2023-12-17 19:56:12 +00:00
if ( $this -> docker_compose_custom_build_command ) {
2025-09-16 15:16:01 +00:00
// Prepend DOCKER_BUILDKIT=1 if BuildKit is supported
$build_command = $this -> docker_compose_custom_build_command ;
if ( $this -> dockerBuildkitSupported ) {
$build_command = " DOCKER_BUILDKIT=1 { $build_command } " ;
}
2023-12-17 19:56:12 +00:00
$this -> execute_remote_command (
2025-09-16 15:16:01 +00:00
[ executeInDocker ( $this -> deployment_uuid , " cd { $this -> basedir } && { $build_command } " ), 'hidden' => true ],
2023-12-17 19:56:12 +00:00
);
} else {
2024-05-30 08:14:43 +00:00
$command = " { $this -> coolify_variables } docker compose " ;
2025-09-16 15:16:01 +00:00
// Prepend DOCKER_BUILDKIT=1 if BuildKit is supported
if ( $this -> dockerBuildkitSupported ) {
$command = " DOCKER_BUILDKIT=1 { $command } " ;
}
2025-10-06 08:31:58 +00:00
// Use build-time .env file from /artifacts (outside Docker context to prevent it from being in the image)
$command .= ' --env-file /artifacts/build-time.env' ;
2025-05-09 12:07:02 +00:00
if ( $this -> force_rebuild ) {
$command .= " --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } build --pull --no-cache " ;
2025-05-20 12:28:24 +00:00
} else {
2025-05-09 12:07:02 +00:00
$command .= " --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } build --pull " ;
}
2025-09-19 11:46:00 +00:00
if ( ! $this -> application -> settings -> use_build_secrets && $this -> build_args instanceof \Illuminate\Support\Collection && $this -> build_args -> isNotEmpty ()) {
$build_args_string = $this -> build_args -> implode ( ' ' );
2025-09-24 15:27:42 +00:00
// Escape single quotes for bash -c context used by executeInDocker
$build_args_string = str_replace ( " ' " , " ' \\ '' " , $build_args_string );
2025-09-19 11:46:00 +00:00
$command .= " { $build_args_string } " ;
$this -> application_deployment_queue -> addLogEntry ( 'Adding build arguments to Docker Compose build command.' );
}
2023-12-17 19:56:12 +00:00
$this -> execute_remote_command (
2024-06-10 20:43:34 +00:00
[ executeInDocker ( $this -> deployment_uuid , $command ), 'hidden' => true ],
2023-12-17 19:56:12 +00:00
);
}
2025-10-04 17:19:15 +00:00
// Save runtime environment variables AFTER the build
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this -> save_runtime_environment_variables ();
2023-11-27 13:28:21 +00:00
$this -> stop_running_container ( force : true );
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Starting new application.' );
2023-11-27 13:28:21 +00:00
$networkId = $this -> application -> uuid ;
if ( $this -> pull_request_id !== 0 ) {
$networkId = " { $this -> application -> uuid } - { $this -> pull_request_id } " ;
}
2023-11-29 09:06:52 +00:00
if ( $this -> server -> isSwarm ()) {
// TODO
} else {
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
" docker network inspect ' { $networkId } ' >/dev/null 2>&1 || docker network create --attachable ' { $networkId } ' >/dev/null || true " ,
'hidden' => true ,
'ignore_errors' => true ,
2023-11-29 09:06:52 +00:00
], [
2024-09-16 14:38:34 +00:00
" docker network connect { $networkId } coolify-proxy >/dev/null 2>&1 || true " ,
2024-08-28 16:12:00 +00:00
'hidden' => true ,
'ignore_errors' => true ,
2023-11-29 09:06:52 +00:00
]);
}
2024-03-04 09:13:40 +00:00
2023-12-17 19:56:12 +00:00
// Start compose file
2024-08-12 14:06:24 +00:00
$server_workdir = $this -> application -> workdir ();
2024-03-04 09:13:40 +00:00
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
if ( $this -> docker_compose_custom_start_command ) {
2024-07-22 13:09:50 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
$this -> execute_remote_command (
2024-06-10 20:43:34 +00:00
[ executeInDocker ( $this -> deployment_uuid , " cd { $this -> workdir } && { $this -> docker_compose_custom_start_command } " ), 'hidden' => true ],
2024-03-04 09:13:40 +00:00
);
} else {
2024-03-19 10:48:25 +00:00
$this -> write_deployment_configurations ();
2024-07-25 09:07:32 +00:00
$this -> docker_compose_location = '/docker-compose.yaml' ;
2024-05-30 08:14:43 +00:00
$command = " { $this -> coolify_variables } docker compose " ;
2025-10-04 17:19:15 +00:00
// Always use .env file
$command .= " --env-file { $server_workdir } /.env " ;
2024-05-30 08:14:43 +00:00
$command .= " --project-directory { $server_workdir } -f { $server_workdir } { $this -> docker_compose_location } up -d " ;
2024-03-04 09:13:40 +00:00
$this -> execute_remote_command (
2024-06-10 20:43:34 +00:00
[ 'command' => $command , 'hidden' => true ],
2024-03-04 09:13:40 +00:00
);
}
2023-12-17 19:56:12 +00:00
} else {
2024-03-04 09:13:40 +00:00
if ( $this -> docker_compose_custom_start_command ) {
2024-07-22 13:09:50 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
$this -> execute_remote_command (
2024-06-10 20:43:34 +00:00
[ executeInDocker ( $this -> deployment_uuid , " cd { $this -> basedir } && { $this -> docker_compose_custom_start_command } " ), 'hidden' => true ],
2024-03-04 09:13:40 +00:00
);
} else {
2024-05-30 08:14:43 +00:00
$command = " { $this -> coolify_variables } docker compose " ;
2024-08-12 14:06:24 +00:00
if ( $this -> preserveRepository ) {
2025-10-04 17:19:15 +00:00
// Always use .env file
$command .= " --env-file { $server_workdir } /.env " ;
2024-08-12 14:06:24 +00:00
$command .= " --project-name { $this -> application -> uuid } --project-directory { $server_workdir } -f { $server_workdir } { $this -> docker_compose_location } up -d " ;
$this -> write_deployment_configurations ();
$this -> execute_remote_command (
[ 'command' => $command , 'hidden' => true ],
);
} else {
2025-10-04 17:19:15 +00:00
// Always use .env file
$command .= " --env-file { $this -> workdir } /.env " ;
2024-08-12 14:06:24 +00:00
$command .= " --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } up -d " ;
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , $command ), 'hidden' => true ],
);
2024-08-22 13:05:04 +00:00
$this -> write_deployment_configurations ();
2024-05-30 08:14:43 +00:00
}
2024-03-04 09:13:40 +00:00
}
2023-12-17 19:56:12 +00:00
}
2024-03-04 09:13:40 +00:00
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'New container started.' );
2023-11-24 14:48:23 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-12 10:01:09 +00:00
private function deploy_dockerfile_buildpack ()
2023-10-10 12:02:43 +00:00
{
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> customRepository } : { $this -> application -> git_branch } to { $this -> server -> name } . " );
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2024-02-26 13:28:02 +00:00
if ( data_get ( $this -> application , 'dockerfile_location' )) {
$this -> dockerfile_location = $this -> application -> dockerfile_location ;
}
2023-10-10 12:02:43 +00:00
$this -> prepare_builder_image ();
2023-11-03 16:55:53 +00:00
$this -> check_git_if_build_needed ();
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2024-03-13 09:44:15 +00:00
$this -> clone_repository ();
2024-06-13 11:15:09 +00:00
if ( ! $this -> force_rebuild ) {
2024-04-05 13:33:11 +00:00
$this -> check_image_locally_or_remotely ();
2024-05-30 10:28:29 +00:00
if ( $this -> should_skip_build ()) {
2024-04-05 13:33:11 +00:00
return ;
}
}
2023-10-10 12:02:43 +00:00
$this -> cleanup_git ();
$this -> generate_compose_file ();
2025-10-04 17:19:15 +00:00
// Save build-time .env file BEFORE the build
$this -> save_buildtime_environment_variables ();
2023-10-10 12:02:43 +00:00
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
2023-10-15 14:54:16 +00:00
$this -> build_image ();
2025-10-04 17:19:15 +00:00
// Save runtime environment variables AFTER the build
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this -> save_runtime_environment_variables ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2023-11-24 14:48:23 +00:00
$this -> rolling_update ();
2023-10-10 12:02:43 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-12 10:01:09 +00:00
private function deploy_nixpacks_buildpack ()
2023-08-11 20:41:47 +00:00
{
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> customRepository } : { $this -> application -> git_branch } to { $this -> server -> name } . " );
2023-08-11 20:41:47 +00:00
$this -> prepare_builder_image ();
2023-10-26 08:33:57 +00:00
$this -> check_git_if_build_needed ();
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2024-06-13 11:15:09 +00:00
if ( ! $this -> force_rebuild ) {
2023-11-20 12:49:10 +00:00
$this -> check_image_locally_or_remotely ();
2024-05-30 10:28:29 +00:00
if ( $this -> should_skip_build ()) {
2023-08-11 20:41:47 +00:00
return ;
}
}
2023-10-26 08:33:57 +00:00
$this -> clone_repository ();
2023-08-11 20:41:47 +00:00
$this -> cleanup_git ();
2023-10-12 10:01:09 +00:00
$this -> generate_nixpacks_confs ();
2023-08-11 20:41:47 +00:00
$this -> generate_compose_file ();
2025-10-04 17:19:15 +00:00
// Save build-time .env file BEFORE the build for Nixpacks
$this -> save_buildtime_environment_variables ();
2023-08-11 20:41:47 +00:00
$this -> generate_build_env_variables ();
$this -> build_image ();
2025-09-18 16:15:20 +00:00
// For Nixpacks, save runtime environment variables AFTER the build
2025-10-04 17:19:15 +00:00
// This overwrites the build-time .env with ALL variables (build-time + runtime)
2025-09-18 16:15:20 +00:00
$this -> save_runtime_environment_variables ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
}
2024-06-10 20:43:34 +00:00
2023-11-10 10:33:15 +00:00
private function deploy_static_buildpack ()
{
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> customRepository } : { $this -> application -> git_branch } to { $this -> server -> name } . " );
2023-11-10 10:33:15 +00:00
$this -> prepare_builder_image ();
$this -> check_git_if_build_needed ();
$this -> generate_image_names ();
2024-06-13 11:15:09 +00:00
if ( ! $this -> force_rebuild ) {
2024-02-06 14:05:11 +00:00
$this -> check_image_locally_or_remotely ();
2024-05-30 10:28:29 +00:00
if ( $this -> should_skip_build ()) {
2024-02-06 14:05:11 +00:00
return ;
}
}
2023-11-10 10:33:15 +00:00
$this -> clone_repository ();
$this -> cleanup_git ();
$this -> generate_compose_file ();
2025-10-04 17:19:15 +00:00
// Save build-time .env file BEFORE the build
$this -> save_buildtime_environment_variables ();
2025-09-16 15:16:01 +00:00
$this -> build_static_image ();
2025-10-04 17:19:15 +00:00
// Save runtime environment variables AFTER the build
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this -> save_runtime_environment_variables ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2023-11-10 10:33:15 +00:00
$this -> rolling_update ();
}
2023-08-21 16:00:12 +00:00
2024-02-07 13:55:06 +00:00
private function write_deployment_configurations ()
{
2024-07-18 11:14:07 +00:00
if ( $this -> preserveRepository ) {
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
}
if ( str ( $this -> configuration_dir ) -> isNotEmpty ()) {
$this -> execute_remote_command (
[
" mkdir -p $this->configuration_dir " ,
],
[
" docker cp { $this -> deployment_uuid } : { $this -> workdir } /. { $this -> configuration_dir } " ,
],
);
}
2024-08-21 12:31:17 +00:00
foreach ( $this -> application -> fileStorages as $fileStorage ) {
2024-08-12 14:06:24 +00:00
if ( ! $fileStorage -> is_based_on_git && ! $fileStorage -> is_directory ) {
$fileStorage -> saveStorageOnServer ();
}
2024-08-21 12:31:17 +00:00
}
2024-07-18 11:14:07 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
}
2024-02-07 13:55:06 +00:00
if ( isset ( $this -> docker_compose_base64 )) {
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
}
$readme = generate_readme_file ( $this -> application -> name , $this -> application_deployment_queue -> updated_at );
2024-07-25 09:07:32 +00:00
$mainDir = $this -> configuration_dir ;
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
$mainDir = $this -> application -> workdir ();
}
2024-03-01 10:43:42 +00:00
if ( $this -> pull_request_id === 0 ) {
2024-07-25 09:07:32 +00:00
$composeFileName = " $mainDir /docker-compose.yaml " ;
2024-03-01 10:43:42 +00:00
} else {
2025-09-08 13:15:57 +00:00
$composeFileName = " $mainDir / " . addPreviewDeploymentSuffix ( 'docker-compose' , $this -> pull_request_id ) . '.yaml' ;
$this -> docker_compose_location = '/' . addPreviewDeploymentSuffix ( 'docker-compose' , $this -> pull_request_id ) . '.yaml' ;
2024-02-07 13:55:06 +00:00
}
$this -> execute_remote_command (
[
2024-07-25 09:07:32 +00:00
" mkdir -p $mainDir " ,
2024-02-07 13:55:06 +00:00
],
[
2024-04-17 08:49:34 +00:00
" echo ' { $this -> docker_compose_base64 } ' | base64 -d | tee $composeFileName > /dev/null " ,
2024-02-07 13:55:06 +00:00
],
[
2024-07-25 09:07:32 +00:00
" echo ' { $readme } ' > $mainDir /README.md " ,
2024-02-07 13:55:06 +00:00
]
);
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
}
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
private function push_to_docker_registry ()
{
2024-02-08 11:34:01 +00:00
$forceFail = true ;
2024-02-07 13:55:06 +00:00
if ( str ( $this -> application -> docker_registry_image_name ) -> isEmpty ()) {
return ;
}
if ( $this -> restart_only ) {
return ;
}
if ( $this -> application -> build_pack === 'dockerimage' ) {
return ;
}
if ( $this -> use_build_server ) {
$forceFail = true ;
}
if ( $this -> server -> isSwarm () && $this -> build_pack !== 'dockerimage' ) {
$forceFail = true ;
}
if ( $this -> application -> additional_servers -> count () > 0 ) {
$forceFail = true ;
}
2024-05-30 10:28:29 +00:00
if ( $this -> is_this_additional_server ) {
2024-02-07 13:55:06 +00:00
return ;
}
try {
instant_remote_process ([ " docker images --format ' { { json .}}' { $this -> production_image_name } " ], $this -> server );
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-02-07 13:55:06 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Pushing image to docker registry ( { $this -> production_image_name } ). " );
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " docker push { $this -> production_image_name } " ),
'hidden' => true ,
2024-02-07 13:55:06 +00:00
],
);
if ( $this -> application -> docker_registry_image_tag ) {
2024-05-30 10:28:29 +00:00
// Tag image with docker_registry_image_tag
$this -> application_deployment_queue -> addLogEntry ( " Tagging and pushing image with { $this -> application -> docker_registry_image_tag } tag. " );
2024-02-07 13:55:06 +00:00
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " docker tag { $this -> production_image_name } { $this -> application -> docker_registry_image_name } : { $this -> application -> docker_registry_image_tag } " ),
'ignore_errors' => true ,
'hidden' => true ,
2024-02-07 13:55:06 +00:00
],
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " docker push { $this -> application -> docker_registry_image_name } : { $this -> application -> docker_registry_image_tag } " ),
'ignore_errors' => true ,
'hidden' => true ,
2024-02-07 13:55:06 +00:00
],
);
}
} catch ( Exception $e ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Failed to push image to docker registry. Please check debug logs for more information.' );
2024-02-07 13:55:06 +00:00
if ( $forceFail ) {
throw new RuntimeException ( $e -> getMessage (), 69420 );
}
}
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
private function generate_image_names ()
{
if ( $this -> application -> dockerfile ) {
if ( $this -> application -> docker_registry_image_name ) {
2024-04-08 07:36:21 +00:00
$this -> build_image_name = " { $this -> application -> docker_registry_image_name } :build " ;
$this -> production_image_name = " { $this -> application -> docker_registry_image_name } :latest " ;
2024-02-07 13:55:06 +00:00
} else {
2024-04-08 07:36:21 +00:00
$this -> build_image_name = " { $this -> application -> uuid } :build " ;
$this -> production_image_name = " { $this -> application -> uuid } :latest " ;
2024-02-07 13:55:06 +00:00
}
2024-06-10 20:43:34 +00:00
} elseif ( $this -> application -> build_pack === 'dockerimage' ) {
2025-08-27 20:40:14 +00:00
// Check if this is an image hash deployment
if ( str ( $this -> dockerImageTag ) -> startsWith ( 'sha256-' )) {
$hash = str ( $this -> dockerImageTag ) -> after ( 'sha256-' );
$this -> production_image_name = " { $this -> dockerImage } @sha256: { $hash } " ;
} else {
$this -> production_image_name = " { $this -> dockerImage } : { $this -> dockerImageTag } " ;
}
2024-06-10 20:43:34 +00:00
} elseif ( $this -> pull_request_id !== 0 ) {
2024-02-07 13:55:06 +00:00
if ( $this -> application -> docker_registry_image_name ) {
2024-04-08 07:36:21 +00:00
$this -> build_image_name = " { $this -> application -> docker_registry_image_name } :pr- { $this -> pull_request_id } -build " ;
$this -> production_image_name = " { $this -> application -> docker_registry_image_name } :pr- { $this -> pull_request_id } " ;
2024-02-07 13:55:06 +00:00
} else {
2024-04-08 07:36:21 +00:00
$this -> build_image_name = " { $this -> application -> uuid } :pr- { $this -> pull_request_id } -build " ;
$this -> production_image_name = " { $this -> application -> uuid } :pr- { $this -> pull_request_id } " ;
2024-02-07 13:55:06 +00:00
}
} else {
2024-05-29 13:15:03 +00:00
$this -> dockerImageTag = str ( $this -> commit ) -> substr ( 0 , 128 );
2024-05-30 10:28:29 +00:00
// if ($this->application->docker_registry_image_tag) {
// $this->dockerImageTag = $this->application->docker_registry_image_tag;
// }
2024-02-07 13:55:06 +00:00
if ( $this -> application -> docker_registry_image_name ) {
2024-05-29 13:15:03 +00:00
$this -> build_image_name = " { $this -> application -> docker_registry_image_name } : { $this -> dockerImageTag } -build " ;
$this -> production_image_name = " { $this -> application -> docker_registry_image_name } : { $this -> dockerImageTag } " ;
2024-02-07 13:55:06 +00:00
} else {
2024-05-29 13:15:03 +00:00
$this -> build_image_name = " { $this -> application -> uuid } : { $this -> dockerImageTag } -build " ;
$this -> production_image_name = " { $this -> application -> uuid } : { $this -> dockerImageTag } " ;
2024-02-07 13:55:06 +00:00
}
}
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
private function just_restart ()
{
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Restarting { $this -> customRepository } : { $this -> application -> git_branch } on { $this -> server -> name } . " );
2024-02-07 13:55:06 +00:00
$this -> prepare_builder_image ();
$this -> check_git_if_build_needed ();
$this -> generate_image_names ();
$this -> check_image_locally_or_remotely ();
2024-06-11 11:12:53 +00:00
$this -> should_skip_build ();
2025-10-26 08:06:44 +00:00
$this -> completeDeployment ();
2024-05-30 10:28:29 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
private function should_skip_build ()
{
2024-02-07 13:55:06 +00:00
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty ()) {
2024-05-30 10:28:29 +00:00
if ( $this -> is_this_additional_server ) {
2025-09-18 16:15:20 +00:00
$this -> skip_build = true ;
2024-05-30 10:28:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Build step skipped. " );
$this -> generate_compose_file ();
2025-10-07 12:26:23 +00:00
// Save runtime environment variables even when skipping build
$this -> save_runtime_environment_variables ();
2024-05-30 10:28:29 +00:00
$this -> push_to_docker_registry ();
$this -> rolling_update ();
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
return true ;
}
2024-06-13 11:15:09 +00:00
if ( ! $this -> application -> isConfigurationChanged ()) {
2024-05-30 10:28:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " No configuration changed & image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Build step skipped. " );
2025-09-18 16:15:20 +00:00
$this -> skip_build = true ;
2024-05-30 10:28:29 +00:00
$this -> generate_compose_file ();
2025-10-07 12:26:23 +00:00
// Save runtime environment variables even when skipping build
$this -> save_runtime_environment_variables ();
2024-05-30 10:28:29 +00:00
$this -> push_to_docker_registry ();
$this -> rolling_update ();
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
return true ;
} else {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Configuration changed. Rebuilding image.' );
2024-05-30 10:28:29 +00:00
}
2024-04-12 12:03:29 +00:00
} else {
2024-05-30 10:28:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Image not found ( { $this -> production_image_name } ). Building new image. " );
}
if ( $this -> restart_only ) {
2024-04-12 12:03:29 +00:00
$this -> restart_only = false ;
$this -> decide_what_to_do ();
2024-02-07 13:55:06 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-30 10:28:29 +00:00
return false ;
2024-02-07 13:55:06 +00:00
}
2024-06-10 20:43:34 +00:00
2024-02-07 13:55:06 +00:00
private function check_image_locally_or_remotely ()
{
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
" docker images -q { $this -> production_image_name } 2>/dev/null " ,
'hidden' => true ,
'save' => 'local_image_found' ,
2024-02-07 13:55:06 +00:00
]);
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isEmpty () && $this -> application -> docker_registry_image_name ) {
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
" docker pull { $this -> production_image_name } 2>/dev/null " ,
'ignore_errors' => true ,
'hidden' => true ,
2024-02-07 13:55:06 +00:00
]);
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
" docker images -q { $this -> production_image_name } 2>/dev/null " ,
'hidden' => true ,
'save' => 'local_image_found' ,
2024-02-07 13:55:06 +00:00
]);
}
}
2024-06-10 20:43:34 +00:00
2025-09-18 16:15:20 +00:00
private function generate_runtime_environment_variables ()
2024-02-07 13:55:06 +00:00
{
$envs = collect ([]);
2024-05-17 09:10:57 +00:00
$sort = $this -> application -> settings -> is_env_sorting_enabled ;
if ( $sort ) {
$sorted_environment_variables = $this -> application -> environment_variables -> sortBy ( 'key' );
$sorted_environment_variables_preview = $this -> application -> environment_variables_preview -> sortBy ( 'key' );
} else {
$sorted_environment_variables = $this -> application -> environment_variables -> sortBy ( 'id' );
$sorted_environment_variables_preview = $this -> application -> environment_variables_preview -> sortBy ( 'id' );
}
2025-07-07 14:07:08 +00:00
if ( $this -> build_pack === 'dockercompose' ) {
$sorted_environment_variables = $sorted_environment_variables -> filter ( function ( $env ) {
2025-09-08 13:15:57 +00:00
return ! str ( $env -> key ) -> startsWith ( 'SERVICE_FQDN_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_URL_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_NAME_' );
2025-07-07 14:07:08 +00:00
});
$sorted_environment_variables_preview = $sorted_environment_variables_preview -> filter ( function ( $env ) {
2025-09-08 13:15:57 +00:00
return ! str ( $env -> key ) -> startsWith ( 'SERVICE_FQDN_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_URL_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_NAME_' );
2025-07-07 14:07:08 +00:00
});
}
2024-04-26 10:59:51 +00:00
$ports = $this -> application -> main_port ();
2025-04-11 15:31:58 +00:00
$coolify_envs = $this -> generate_coolify_env_variables ();
$coolify_envs -> each ( function ( $item , $key ) use ( $envs ) {
$envs -> push ( $key . '=' . $item );
});
if ( $this -> pull_request_id === 0 ) {
2025-09-15 13:39:07 +00:00
// Generate SERVICE_ variables first for dockercompose
2025-07-07 14:07:08 +00:00
if ( $this -> build_pack === 'dockercompose' ) {
$domains = collect ( json_decode ( $this -> application -> docker_compose_domains )) ? ? collect ([]);
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
foreach ( $domains as $forServiceName => $domain ) {
$parsedDomain = data_get ( $domain , 'domain' );
if ( filled ( $parsedDomain )) {
$parsedDomain = str ( $parsedDomain ) -> explode ( ',' ) -> first ();
2025-07-08 08:42:34 +00:00
$coolifyUrl = Url :: fromString ( $parsedDomain );
$coolifyScheme = $coolifyUrl -> getScheme ();
$coolifyFqdn = $coolifyUrl -> getHost ();
$coolifyUrl = $coolifyUrl -> withScheme ( $coolifyScheme ) -> withHost ( $coolifyFqdn ) -> withPort ( null );
$envs -> push ( 'SERVICE_URL_' . str ( $forServiceName ) -> upper () . '=' . $coolifyUrl -> __toString ());
$envs -> push ( 'SERVICE_FQDN_' . str ( $forServiceName ) -> upper () . '=' . $coolifyFqdn );
2025-07-07 14:07:08 +00:00
}
}
2025-09-08 13:15:57 +00:00
// Generate SERVICE_NAME for dockercompose services from processed compose
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
$dockerCompose = Yaml :: parse ( $this -> application -> docker_compose_raw );
} else {
$dockerCompose = Yaml :: parse ( $this -> application -> docker_compose );
}
$services = data_get ( $dockerCompose , 'services' , []);
foreach ( $services as $serviceName => $_ ) {
$envs -> push ( 'SERVICE_NAME_' . str ( $serviceName ) -> upper () . '=' . $serviceName );
}
2025-07-07 14:07:08 +00:00
}
2025-09-15 13:39:07 +00:00
2025-09-18 16:15:20 +00:00
// Filter runtime variables (only include variables that are available at runtime)
2025-09-15 13:39:07 +00:00
$runtime_environment_variables = $sorted_environment_variables -> filter ( function ( $env ) {
2025-09-18 16:15:20 +00:00
return $env -> is_runtime ;
2025-09-11 15:38:16 +00:00
});
2025-09-15 13:39:07 +00:00
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
$runtime_environment_variables = $runtime_environment_variables -> sortBy ( function ( $env ) {
if ( str ( $env -> value ) -> startsWith ( '$SERVICE_' ) || str ( $env -> value ) -> contains ( '${SERVICE_' )) {
return 2 ;
}
return 1 ;
});
foreach ( $runtime_environment_variables as $env ) {
2025-07-07 10:30:44 +00:00
$envs -> push ( $env -> key . '=' . $env -> real_value );
2024-05-17 09:10:57 +00:00
}
// Add PORT if not exists, use the first port as default
2024-08-06 09:16:49 +00:00
if ( $this -> build_pack !== 'dockercompose' ) {
2025-09-15 13:39:07 +00:00
if ( $this -> application -> environment_variables -> where ( 'key' , 'PORT' ) -> isEmpty ()) {
2024-08-06 09:16:49 +00:00
$envs -> push ( " PORT= { $ports [ 0 ] } " );
}
2024-05-17 09:10:57 +00:00
}
// Add HOST if not exists
2025-09-15 13:39:07 +00:00
if ( $this -> application -> environment_variables -> where ( 'key' , 'HOST' ) -> isEmpty ()) {
2024-06-10 20:43:34 +00:00
$envs -> push ( 'HOST=0.0.0.0' );
2024-05-17 09:10:57 +00:00
}
2025-09-15 13:39:07 +00:00
} else {
// Generate SERVICE_ variables first for dockercompose preview
2025-07-07 14:07:08 +00:00
if ( $this -> build_pack === 'dockercompose' ) {
$domains = collect ( json_decode ( data_get ( $this -> preview , 'docker_compose_domains' ))) ? ? collect ([]);
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
foreach ( $domains as $forServiceName => $domain ) {
$parsedDomain = data_get ( $domain , 'domain' );
if ( filled ( $parsedDomain )) {
$parsedDomain = str ( $parsedDomain ) -> explode ( ',' ) -> first ();
2025-07-08 08:42:34 +00:00
$coolifyUrl = Url :: fromString ( $parsedDomain );
$coolifyScheme = $coolifyUrl -> getScheme ();
$coolifyFqdn = $coolifyUrl -> getHost ();
$coolifyUrl = $coolifyUrl -> withScheme ( $coolifyScheme ) -> withHost ( $coolifyFqdn ) -> withPort ( null );
$envs -> push ( 'SERVICE_URL_' . str ( $forServiceName ) -> upper () . '=' . $coolifyUrl -> __toString ());
$envs -> push ( 'SERVICE_FQDN_' . str ( $forServiceName ) -> upper () . '=' . $coolifyFqdn );
2025-07-07 14:07:08 +00:00
}
}
2025-09-08 13:15:57 +00:00
// Generate SERVICE_NAME for dockercompose services
$rawDockerCompose = Yaml :: parse ( $this -> application -> docker_compose_raw );
$rawServices = data_get ( $rawDockerCompose , 'services' , []);
foreach ( $rawServices as $rawServiceName => $_ ) {
2025-09-11 11:59:02 +00:00
$envs -> push ( 'SERVICE_NAME_' . str ( $rawServiceName ) -> upper () . '=' . addPreviewDeploymentSuffix ( $rawServiceName , $this -> pull_request_id ));
2025-09-08 13:15:57 +00:00
}
2025-07-07 14:07:08 +00:00
}
2025-09-15 13:39:07 +00:00
2025-09-18 16:15:20 +00:00
// Filter runtime variables for preview (only include variables that are available at runtime)
2025-09-15 13:39:07 +00:00
$runtime_environment_variables_preview = $sorted_environment_variables_preview -> filter ( function ( $env ) {
2025-09-18 16:15:20 +00:00
return $env -> is_runtime ;
2025-09-15 13:39:07 +00:00
});
// Sort runtime environment variables: those referencing SERVICE_ variables come after others
$runtime_environment_variables_preview = $runtime_environment_variables_preview -> sortBy ( function ( $env ) {
if ( str ( $env -> value ) -> startsWith ( '$SERVICE_' ) || str ( $env -> value ) -> contains ( '${SERVICE_' )) {
return 2 ;
}
return 1 ;
});
foreach ( $runtime_environment_variables_preview as $env ) {
$envs -> push ( $env -> key . '=' . $env -> real_value );
}
// Add PORT if not exists, use the first port as default
if ( $this -> build_pack !== 'dockercompose' ) {
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'PORT' ) -> isEmpty ()) {
$envs -> push ( " PORT= { $ports [ 0 ] } " );
}
}
// Add HOST if not exists
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'HOST' ) -> isEmpty ()) {
$envs -> push ( 'HOST=0.0.0.0' );
2025-07-07 14:07:08 +00:00
}
2024-02-07 13:55:06 +00:00
}
2025-10-04 17:19:15 +00:00
// Return the generated environment variables instead of storing them globally
return $envs ;
}
private function save_runtime_environment_variables ()
{
// This method saves the .env file with ALL runtime variables
// For builds, it should be called AFTER the build to include runtime-only variables
// Generate runtime environment variables locally
$environment_variables = $this -> generate_runtime_environment_variables ();
// Handle empty environment variables
if ( $environment_variables -> isEmpty ()) {
2025-10-21 18:39:39 +00:00
// For Docker Compose and Docker Image, we need to create an empty .env file
2025-10-04 17:19:15 +00:00
// because we always reference it in the compose file
2025-10-21 18:39:39 +00:00
if ( $this -> build_pack === 'dockercompose' || $this -> build_pack === 'dockerimage' ) {
2025-10-04 17:19:15 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Creating empty .env file (no environment variables defined).' );
// Create empty .env file
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " touch $this->workdir /.env " ),
]
);
// Also create in configuration directory
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
$this -> execute_remote_command (
[
" touch $this->configuration_dir /.env " ,
]
);
$this -> server = $this -> build_server ;
} else {
$this -> execute_remote_command (
[
" touch $this->configuration_dir /.env " ,
]
);
}
} else {
// For non-Docker Compose deployments, clean up any existing .env files
2025-09-11 14:22:03 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
$this -> execute_remote_command (
[
2025-10-04 17:19:15 +00:00
'command' => " rm -f $this->configuration_dir /.env " ,
2025-09-11 14:22:03 +00:00
'hidden' => true ,
'ignore_errors' => true ,
]
);
$this -> server = $this -> build_server ;
$this -> execute_remote_command (
[
2025-10-04 17:19:15 +00:00
'command' => " rm -f $this->configuration_dir /.env " ,
2025-09-11 14:22:03 +00:00
'hidden' => true ,
'ignore_errors' => true ,
]
);
} else {
$this -> execute_remote_command (
[
2025-10-04 17:19:15 +00:00
'command' => " rm -f $this->configuration_dir /.env " ,
2025-09-11 14:22:03 +00:00
'hidden' => true ,
'ignore_errors' => true ,
]
);
}
2024-04-18 11:53:10 +00:00
}
2025-10-04 17:19:15 +00:00
return ;
}
// Write the environment variables to file
$envs_base64 = base64_encode ( $environment_variables -> implode ( " \n " ));
// Write .env file to workdir (for container runtime)
$this -> application_deployment_queue -> addLogEntry ( 'Creating .env file with runtime variables for build phase.' , hidden : true );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' $envs_base64 ' | base64 -d | tee $this->workdir /.env > /dev/null " ),
],
[
executeInDocker ( $this -> deployment_uuid , " cat $this->workdir /.env " ),
'hidden' => true ,
]
);
// Write .env file to configuration directory
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
$this -> execute_remote_command (
[
" echo ' $envs_base64 ' | base64 -d | tee $this->configuration_dir /.env > /dev/null " ,
]
);
$this -> server = $this -> build_server ;
2024-04-18 10:37:06 +00:00
} else {
2025-10-04 17:19:15 +00:00
$this -> execute_remote_command (
[
" echo ' $envs_base64 ' | base64 -d | tee $this->configuration_dir /.env > /dev/null " ,
]
);
}
}
2025-09-18 16:15:20 +00:00
2025-10-04 17:19:15 +00:00
private function generate_buildtime_environment_variables ()
{
2025-10-15 11:35:58 +00:00
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] ========================================' );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] Generating build-time environment variables' );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] ========================================' );
}
2025-10-04 17:19:15 +00:00
$envs = collect ([]);
$coolify_envs = $this -> generate_coolify_env_variables ();
// Add COOLIFY variables
$coolify_envs -> each ( function ( $item , $key ) use ( $envs ) {
2025-10-15 11:35:58 +00:00
$envs -> push ( $key . '=' . escapeBashEnvValue ( $item ));
2025-10-04 17:19:15 +00:00
});
// Add SERVICE_NAME variables for Docker Compose builds
if ( $this -> build_pack === 'dockercompose' ) {
if ( $this -> pull_request_id === 0 ) {
// Generate SERVICE_NAME for dockercompose services from processed compose
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
$dockerCompose = Yaml :: parse ( $this -> application -> docker_compose_raw );
2025-09-18 16:15:20 +00:00
} else {
2025-10-04 17:19:15 +00:00
$dockerCompose = Yaml :: parse ( $this -> application -> docker_compose );
}
$services = data_get ( $dockerCompose , 'services' , []);
foreach ( $services as $serviceName => $_ ) {
2025-10-15 11:35:58 +00:00
$envs -> push ( 'SERVICE_NAME_' . str ( $serviceName ) -> upper () . '=' . escapeBashEnvValue ( $serviceName ));
2025-09-18 16:15:20 +00:00
}
2025-10-04 17:19:15 +00:00
// Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments
$domains = collect ( json_decode ( $this -> application -> docker_compose_domains )) ? ? collect ([]);
foreach ( $domains as $forServiceName => $domain ) {
$parsedDomain = data_get ( $domain , 'domain' );
if ( filled ( $parsedDomain )) {
$parsedDomain = str ( $parsedDomain ) -> explode ( ',' ) -> first ();
$coolifyUrl = Url :: fromString ( $parsedDomain );
$coolifyScheme = $coolifyUrl -> getScheme ();
$coolifyFqdn = $coolifyUrl -> getHost ();
$coolifyUrl = $coolifyUrl -> withScheme ( $coolifyScheme ) -> withHost ( $coolifyFqdn ) -> withPort ( null );
2025-10-15 11:35:58 +00:00
$envs -> push ( 'SERVICE_URL_' . str ( $forServiceName ) -> upper () . '=' . escapeBashEnvValue ( $coolifyUrl -> __toString ()));
$envs -> push ( 'SERVICE_FQDN_' . str ( $forServiceName ) -> upper () . '=' . escapeBashEnvValue ( $coolifyFqdn ));
2025-10-04 17:19:15 +00:00
}
}
} else {
// Generate SERVICE_NAME for preview deployments
$rawDockerCompose = Yaml :: parse ( $this -> application -> docker_compose_raw );
$rawServices = data_get ( $rawDockerCompose , 'services' , []);
foreach ( $rawServices as $rawServiceName => $_ ) {
2025-10-15 11:35:58 +00:00
$envs -> push ( 'SERVICE_NAME_' . str ( $rawServiceName ) -> upper () . '=' . escapeBashEnvValue ( addPreviewDeploymentSuffix ( $rawServiceName , $this -> pull_request_id )));
2025-10-04 17:19:15 +00:00
}
// Generate SERVICE_FQDN & SERVICE_URL for preview deployments with PR-specific domains
$domains = collect ( json_decode ( data_get ( $this -> preview , 'docker_compose_domains' ))) ? ? collect ([]);
foreach ( $domains as $forServiceName => $domain ) {
$parsedDomain = data_get ( $domain , 'domain' );
if ( filled ( $parsedDomain )) {
$parsedDomain = str ( $parsedDomain ) -> explode ( ',' ) -> first ();
$coolifyUrl = Url :: fromString ( $parsedDomain );
$coolifyScheme = $coolifyUrl -> getScheme ();
$coolifyFqdn = $coolifyUrl -> getHost ();
$coolifyUrl = $coolifyUrl -> withScheme ( $coolifyScheme ) -> withHost ( $coolifyFqdn ) -> withPort ( null );
2025-10-15 11:35:58 +00:00
$envs -> push ( 'SERVICE_URL_' . str ( $forServiceName ) -> upper () . '=' . escapeBashEnvValue ( $coolifyUrl -> __toString ()));
$envs -> push ( 'SERVICE_FQDN_' . str ( $forServiceName ) -> upper () . '=' . escapeBashEnvValue ( $coolifyFqdn ));
2025-10-04 17:19:15 +00:00
}
}
}
}
// Add build-time user variables only
if ( $this -> pull_request_id === 0 ) {
$sorted_environment_variables = $this -> application -> environment_variables ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true ) // ONLY build-time variables
-> orderBy ( $this -> application -> settings -> is_env_sorting_enabled ? 'key' : 'id' )
-> get ();
// For Docker Compose, filter out SERVICE_FQDN and SERVICE_URL as we generate these
if ( $this -> build_pack === 'dockercompose' ) {
$sorted_environment_variables = $sorted_environment_variables -> filter ( function ( $env ) {
return ! str ( $env -> key ) -> startsWith ( 'SERVICE_FQDN_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_URL_' );
});
}
foreach ( $sorted_environment_variables as $env ) {
2025-10-15 11:35:58 +00:00
// For literal/multiline vars, real_value includes quotes that we need to remove
if ( $env -> is_literal || $env -> is_multiline ) {
// Strip outer quotes from real_value and apply proper bash escaping
$value = trim ( $env -> real_value , " ' " );
$escapedValue = escapeBashEnvValue ( $value );
$envs -> push ( $env -> key . '=' . $escapedValue );
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] Build-time env: { $env -> key } " );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] Type: literal/multiline' );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] raw real_value: { $env -> real_value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] stripped value: { $value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] final escaped: { $escapedValue } " );
}
} else {
// For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted ( $env -> real_value );
$envs -> push ( $env -> key . '=' . $escapedValue );
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] Build-time env: { $env -> key } " );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] Type: normal (allows expansion)' );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] real_value: { $env -> real_value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] final escaped: { $escapedValue } " );
}
}
2025-10-04 17:19:15 +00:00
}
} else {
$sorted_environment_variables = $this -> application -> environment_variables_preview ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true ) // ONLY build-time variables
-> orderBy ( $this -> application -> settings -> is_env_sorting_enabled ? 'key' : 'id' )
-> get ();
// For Docker Compose, filter out SERVICE_FQDN and SERVICE_URL as we generate these with PR-specific values
if ( $this -> build_pack === 'dockercompose' ) {
$sorted_environment_variables = $sorted_environment_variables -> filter ( function ( $env ) {
return ! str ( $env -> key ) -> startsWith ( 'SERVICE_FQDN_' ) && ! str ( $env -> key ) -> startsWith ( 'SERVICE_URL_' );
});
}
foreach ( $sorted_environment_variables as $env ) {
2025-10-15 11:35:58 +00:00
// For literal/multiline vars, real_value includes quotes that we need to remove
if ( $env -> is_literal || $env -> is_multiline ) {
// Strip outer quotes from real_value and apply proper bash escaping
$value = trim ( $env -> real_value , " ' " );
$escapedValue = escapeBashEnvValue ( $value );
$envs -> push ( $env -> key . '=' . $escapedValue );
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] Build-time env: { $env -> key } " );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] Type: literal/multiline' );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] raw real_value: { $env -> real_value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] stripped value: { $value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] final escaped: { $escapedValue } " );
}
} else {
// For normal vars, use double quotes to allow $VAR expansion
$escapedValue = escapeBashDoubleQuoted ( $env -> real_value );
$envs -> push ( $env -> key . '=' . $escapedValue );
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] Build-time env: { $env -> key } " );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] Type: normal (allows expansion)' );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] real_value: { $env -> real_value } " );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] final escaped: { $escapedValue } " );
}
}
2024-04-18 11:53:10 +00:00
}
2025-09-18 16:15:20 +00:00
}
2025-10-04 17:19:15 +00:00
// Return the generated environment variables
2025-10-15 11:35:58 +00:00
if ( isDev ()) {
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] ========================================' );
$this -> application_deployment_queue -> addLogEntry ( " [DEBUG] Total build-time env variables: { $envs -> count () } " );
$this -> application_deployment_queue -> addLogEntry ( '[DEBUG] ========================================' );
}
2025-10-04 17:19:15 +00:00
return $envs ;
2025-09-18 16:15:20 +00:00
}
2025-10-04 17:19:15 +00:00
private function save_buildtime_environment_variables ()
2025-09-18 16:15:20 +00:00
{
2025-10-04 17:19:15 +00:00
// Generate build-time environment variables locally
$environment_variables = $this -> generate_buildtime_environment_variables ();
2025-10-06 08:31:58 +00:00
// Save .env file for build phase in /artifacts to prevent it from being copied into Docker images
2025-10-04 17:19:15 +00:00
if ( $environment_variables -> isNotEmpty ()) {
$envs_base64 = base64_encode ( $environment_variables -> implode ( " \n " ));
2025-09-18 16:15:20 +00:00
2025-10-06 08:31:58 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Creating build-time .env file in /artifacts (outside Docker context).' , hidden : true );
2025-09-18 16:15:20 +00:00
2024-04-18 10:37:06 +00:00
$this -> execute_remote_command (
[
2025-10-06 08:31:58 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' $envs_base64 ' | base64 -d | tee /artifacts/build-time.env > /dev/null " ),
2025-10-04 17:19:15 +00:00
],
[
2025-10-06 08:31:58 +00:00
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build-time.env' ),
2025-10-04 17:19:15 +00:00
'hidden' => true ,
2024-04-18 10:37:06 +00:00
],
2024-04-18 11:53:10 +00:00
);
2025-10-09 14:38:17 +00:00
} elseif ( $this -> build_pack === 'dockercompose' || $this -> build_pack === 'dockerfile' ) {
// For Docker Compose and Dockerfile, create an empty .env file even if there are no build-time variables
// This ensures the file exists when referenced in build commands
2025-10-06 08:31:58 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Creating empty build-time .env file in /artifacts (no build-time variables defined).' , hidden : true );
2025-09-18 16:15:20 +00:00
2025-10-04 17:19:15 +00:00
$this -> execute_remote_command (
[
2025-10-06 08:31:58 +00:00
executeInDocker ( $this -> deployment_uuid , 'touch /artifacts/build-time.env' ),
2025-10-04 17:19:15 +00:00
]
);
2024-05-29 13:17:39 +00:00
}
2024-02-07 13:55:06 +00:00
}
2024-09-10 10:44:16 +00:00
private function elixir_finetunes ()
{
if ( $this -> pull_request_id === 0 ) {
$envType = 'environment_variables' ;
} else {
$envType = 'environment_variables_preview' ;
}
$mix_env = $this -> application -> { $envType } -> where ( 'key' , 'MIX_ENV' ) -> first ();
2025-09-11 14:51:56 +00:00
if ( ! $mix_env ) {
2024-09-10 10:44:16 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'MIX_ENV environment variable not found.' , type : 'error' );
$this -> application_deployment_queue -> addLogEntry ( 'Please add MIX_ENV environment variable and set it to be build time variable if you facing any issues with the deployment.' , type : 'error' );
}
$secret_key_base = $this -> application -> { $envType } -> where ( 'key' , 'SECRET_KEY_BASE' ) -> first ();
2025-09-11 14:51:56 +00:00
if ( ! $secret_key_base ) {
2024-09-10 10:44:16 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'SECRET_KEY_BASE environment variable not found.' , type : 'error' );
$this -> application_deployment_queue -> addLogEntry ( 'Please add SECRET_KEY_BASE environment variable and set it to be build time variable if you facing any issues with the deployment.' , type : 'error' );
}
$database_url = $this -> application -> { $envType } -> where ( 'key' , 'DATABASE_URL' ) -> first ();
2025-09-11 14:51:56 +00:00
if ( ! $database_url ) {
2024-09-10 10:44:16 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'DATABASE_URL environment variable not found.' , type : 'error' );
$this -> application_deployment_queue -> addLogEntry ( 'Please add DATABASE_URL environment variable and set it to be build time variable if you facing any issues with the deployment.' , type : 'error' );
}
}
2024-06-13 11:14:24 +00:00
private function laravel_finetunes ()
2024-01-09 11:19:39 +00:00
{
if ( $this -> pull_request_id === 0 ) {
2024-09-10 10:44:16 +00:00
$envType = 'environment_variables' ;
2024-01-09 11:19:39 +00:00
} else {
2024-09-10 10:44:16 +00:00
$envType = 'environment_variables_preview' ;
2024-01-09 11:19:39 +00:00
}
2024-09-10 10:44:16 +00:00
$nixpacks_php_fallback_path = $this -> application -> { $envType } -> where ( 'key' , 'NIXPACKS_PHP_FALLBACK_PATH' ) -> first ();
$nixpacks_php_root_dir = $this -> application -> { $envType } -> where ( 'key' , 'NIXPACKS_PHP_ROOT_DIR' ) -> first ();
2024-06-13 11:15:09 +00:00
if ( ! $nixpacks_php_fallback_path ) {
2024-07-24 19:11:12 +00:00
$nixpacks_php_fallback_path = new EnvironmentVariable ;
2024-06-13 11:14:24 +00:00
$nixpacks_php_fallback_path -> key = 'NIXPACKS_PHP_FALLBACK_PATH' ;
$nixpacks_php_fallback_path -> value = '/index.php' ;
2025-01-22 13:16:48 +00:00
$nixpacks_php_fallback_path -> resourceable_id = $this -> application -> id ;
$nixpacks_php_fallback_path -> resourceable_type = 'App\Models\Application' ;
2024-06-13 11:14:24 +00:00
$nixpacks_php_fallback_path -> save ();
}
2024-06-13 11:15:09 +00:00
if ( ! $nixpacks_php_root_dir ) {
2024-07-24 19:11:12 +00:00
$nixpacks_php_root_dir = new EnvironmentVariable ;
2024-06-13 11:14:24 +00:00
$nixpacks_php_root_dir -> key = 'NIXPACKS_PHP_ROOT_DIR' ;
$nixpacks_php_root_dir -> value = '/app/public' ;
2025-01-22 13:16:48 +00:00
$nixpacks_php_root_dir -> resourceable_id = $this -> application -> id ;
$nixpacks_php_root_dir -> resourceable_type = 'App\Models\Application' ;
2024-06-13 11:14:24 +00:00
$nixpacks_php_root_dir -> save ();
}
2024-06-13 11:15:09 +00:00
2024-06-13 11:14:24 +00:00
return [ $nixpacks_php_fallback_path , $nixpacks_php_root_dir ];
2024-01-09 11:19:39 +00:00
}
2024-06-10 20:43:34 +00:00
2023-08-21 16:00:12 +00:00
private function rolling_update ()
{
2025-09-16 11:40:51 +00:00
$this -> checkForCancellation ();
2023-11-28 17:31:04 +00:00
if ( $this -> server -> isSwarm ()) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Rolling update started.' );
2023-12-18 13:01:25 +00:00
$this -> execute_remote_command (
[
2025-01-23 14:57:34 +00:00
executeInDocker ( $this -> deployment_uuid , " docker stack deploy --detach=true --with-registry-auth -c { $this -> workdir } { $this -> docker_compose_location } { $this -> application -> uuid } " ),
2023-12-18 13:01:25 +00:00
],
);
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Rolling update completed.' );
2023-09-24 19:15:43 +00:00
} else {
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> write_deployment_configurations ();
$this -> server = $this -> original_server ;
}
2024-08-06 11:05:34 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 || ( bool ) $this -> application -> settings -> is_consistent_container_name_enabled || str ( $this -> application -> settings -> custom_internal_name ) -> isNotEmpty () || $this -> pull_request_id !== 0 || str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip' ) || str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip6' )) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-02-15 10:55:43 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Application has ports mapped to the host system, rolling update is not supported.' );
2024-02-15 10:55:43 +00:00
}
if (( bool ) $this -> application -> settings -> is_consistent_container_name_enabled ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Consistent container name feature enabled, rolling update is not supported.' );
2024-02-15 10:55:43 +00:00
}
2024-07-18 10:10:59 +00:00
if ( str ( $this -> application -> settings -> custom_internal_name ) -> isNotEmpty ()) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Custom internal name is set, rolling update is not supported.' );
2024-05-06 09:45:22 +00:00
}
2024-03-02 12:22:05 +00:00
if ( $this -> pull_request_id !== 0 ) {
2024-03-01 10:43:42 +00:00
$this -> application -> settings -> is_consistent_container_name_enabled = true ;
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Pull request deployment, rolling update is not supported.' );
2024-03-01 10:43:42 +00:00
}
2024-03-18 11:18:11 +00:00
if ( str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip' ) || str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip6' )) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Custom IP address is set, rolling update is not supported.' );
2024-03-18 11:18:11 +00:00
}
2023-11-28 17:31:04 +00:00
$this -> stop_running_container ( force : true );
$this -> start_by_compose_file ();
} else {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
$this -> application_deployment_queue -> addLogEntry ( 'Rolling update started.' );
2023-11-28 17:31:04 +00:00
$this -> start_by_compose_file ();
$this -> health_check ();
$this -> stop_running_container ();
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Rolling update completed.' );
2023-11-28 17:31:04 +00:00
}
}
}
2024-06-10 20:43:34 +00:00
2023-11-28 17:31:04 +00:00
private function health_check ()
{
if ( $this -> server -> isSwarm ()) {
// Implement healthcheck for swarm
} else {
2024-04-29 11:33:28 +00:00
if ( $this -> application -> isHealthcheckDisabled () && $this -> application -> custom_healthcheck_found === false ) {
2024-05-29 13:15:03 +00:00
$this -> newVersionIsHealthy = true ;
2024-06-10 20:43:34 +00:00
2023-11-28 17:31:04 +00:00
return ;
}
2024-04-29 11:33:28 +00:00
if ( $this -> application -> custom_healthcheck_found ) {
2025-10-22 11:03:17 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Custom healthcheck found in Dockerfile.' );
2024-04-29 11:33:28 +00:00
}
2023-11-28 17:31:04 +00:00
if ( $this -> container_name ) {
$counter = 1 ;
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Waiting for healthcheck to pass on the new container.' );
2025-03-24 10:43:10 +00:00
if ( $this -> full_healthcheck_url && ! $this -> application -> custom_healthcheck_found ) {
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Healthcheck URL (inside the container): { $this -> full_healthcheck_url } " );
2023-11-28 17:31:04 +00:00
}
2024-04-29 10:55:38 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Waiting for the start period ( { $this -> application -> health_check_start_period } seconds) before starting healthcheck. " );
$sleeptime = 0 ;
while ( $sleeptime < $this -> application -> health_check_start_period ) {
Sleep :: for ( 1 ) -> seconds ();
$sleeptime ++ ;
}
2024-01-29 11:51:20 +00:00
while ( $counter <= $this -> application -> health_check_retries ) {
2023-11-28 17:31:04 +00:00
$this -> execute_remote_command (
[
" docker inspect --format=' { { json .State.Health.Status}}' { $this -> container_name } " ,
2024-06-10 20:43:34 +00:00
'hidden' => true ,
'save' => 'health_check' ,
'append' => false ,
2023-11-28 17:31:04 +00:00
],
2024-05-18 16:48:33 +00:00
[
" docker inspect --format=' { { json .State.Health.Log}}' { $this -> container_name } " ,
2024-06-10 20:43:34 +00:00
'hidden' => true ,
'save' => 'health_check_logs' ,
'append' => false ,
2024-05-18 16:48:33 +00:00
],
2023-11-28 17:31:04 +00:00
);
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Attempt { $counter } of { $this -> application -> health_check_retries } | Healthcheck status: { $this -> saved_outputs -> get ( 'health_check' ) } " );
2024-05-18 16:48:33 +00:00
$health_check_logs = data_get ( collect ( json_decode ( $this -> saved_outputs -> get ( 'health_check_logs' ))) -> last (), 'Output' , '(no logs)' );
if ( empty ( $health_check_logs )) {
$health_check_logs = '(no logs)' ;
}
$health_check_return_code = data_get ( collect ( json_decode ( $this -> saved_outputs -> get ( 'health_check_logs' ))) -> last (), 'ExitCode' , '(no return code)' );
if ( $health_check_logs !== '(no logs)' || $health_check_return_code !== '(no return code)' ) {
$this -> application_deployment_queue -> addLogEntry ( " Healthcheck logs: { $health_check_logs } | Return code: { $health_check_return_code } " );
}
2024-06-25 08:37:10 +00:00
if ( str ( $this -> saved_outputs -> get ( 'health_check' )) -> replace ( '"' , '' ) -> value () === 'healthy' ) {
2024-05-29 13:15:03 +00:00
$this -> newVersionIsHealthy = true ;
2023-11-28 17:31:04 +00:00
$this -> application -> update ([ 'status' => 'running' ]);
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'New container is healthy.' );
2023-11-28 17:31:04 +00:00
break ;
}
2024-06-25 08:37:10 +00:00
if ( str ( $this -> saved_outputs -> get ( 'health_check' )) -> replace ( '"' , '' ) -> value () === 'unhealthy' ) {
2024-05-29 13:15:03 +00:00
$this -> newVersionIsHealthy = false ;
2024-05-24 09:50:31 +00:00
$this -> query_logs ();
2024-01-09 07:42:37 +00:00
break ;
}
2023-11-28 17:31:04 +00:00
$counter ++ ;
2024-04-29 10:55:38 +00:00
$sleeptime = 0 ;
while ( $sleeptime < $this -> application -> health_check_interval ) {
Sleep :: for ( 1 ) -> seconds ();
$sleeptime ++ ;
}
2023-08-21 16:00:12 +00:00
}
2024-06-25 08:37:10 +00:00
if ( str ( $this -> saved_outputs -> get ( 'health_check' )) -> replace ( '"' , '' ) -> value () === 'starting' ) {
2024-05-24 09:50:31 +00:00
$this -> query_logs ();
}
2023-08-21 16:00:12 +00:00
}
}
2023-08-11 20:41:47 +00:00
}
2024-06-10 20:43:34 +00:00
2024-05-24 09:50:31 +00:00
private function query_logs ()
{
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
$this -> application_deployment_queue -> addLogEntry ( 'Container logs:' );
2024-05-24 09:50:31 +00:00
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
'command' => " docker logs -n 100 { $this -> container_name } " ,
'type' => 'stderr' ,
'ignore_errors' => true ,
2024-05-24 09:50:31 +00:00
],
);
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-05-24 09:50:31 +00:00
}
2024-06-10 20:43:34 +00:00
2023-06-30 20:24:39 +00:00
private function deploy_pull_request ()
2023-05-23 13:48:05 +00:00
{
2024-03-19 10:48:25 +00:00
if ( $this -> application -> build_pack === 'dockercompose' ) {
$this -> deploy_docker_compose_buildpack ();
2024-06-10 20:43:34 +00:00
2024-03-19 10:48:25 +00:00
return ;
}
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2024-05-29 13:15:03 +00:00
$this -> newVersionIsHealthy = true ;
2023-11-01 14:39:47 +00:00
$this -> generate_image_names ();
2024-05-29 13:15:03 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Starting pull request (# { $this -> pull_request_id } ) deployment of { $this -> customRepository } : { $this -> application -> git_branch } . " );
2023-06-30 20:24:39 +00:00
$this -> prepare_builder_image ();
2024-05-17 13:30:27 +00:00
$this -> check_git_if_build_needed ();
2023-06-30 20:24:39 +00:00
$this -> clone_repository ();
$this -> cleanup_git ();
2023-08-11 20:41:47 +00:00
if ( $this -> application -> build_pack === 'nixpacks' ) {
$this -> generate_nixpacks_confs ();
}
2023-06-30 20:24:39 +00:00
$this -> generate_compose_file ();
2025-10-04 17:19:15 +00:00
// Save build-time .env file BEFORE the build
$this -> save_buildtime_environment_variables ();
2023-11-28 10:10:42 +00:00
$this -> generate_build_env_variables ();
2024-01-29 09:43:18 +00:00
if ( $this -> application -> build_pack === 'dockerfile' ) {
2024-01-12 07:38:08 +00:00
$this -> add_build_env_variables_to_dockerfile ();
}
2023-06-30 20:24:39 +00:00
$this -> build_image ();
2025-10-04 17:19:15 +00:00
// This overwrites the build-time .env with ALL variables (build-time + runtime)
$this -> save_runtime_environment_variables ();
2024-03-01 10:43:42 +00:00
$this -> push_to_docker_registry ();
$this -> rolling_update ();
2023-05-24 12:26:50 +00:00
}
2024-06-10 20:43:34 +00:00
2023-12-04 10:20:50 +00:00
private function create_workdir ()
{
2024-05-29 13:17:39 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> original_server ;
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
'command' => " mkdir -p { $this -> configuration_dir } " ,
2024-05-29 13:17:39 +00:00
],
);
$this -> server = $this -> build_server ;
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
'command' => executeInDocker ( $this -> deployment_uuid , " mkdir -p { $this -> workdir } " ),
2024-05-29 13:17:39 +00:00
],
[
2024-06-10 20:43:34 +00:00
'command' => " mkdir -p { $this -> configuration_dir } " ,
2024-05-29 13:17:39 +00:00
],
);
} else {
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
'command' => executeInDocker ( $this -> deployment_uuid , " mkdir -p { $this -> workdir } " ),
2024-05-29 13:17:39 +00:00
],
[
2024-06-10 20:43:34 +00:00
'command' => " mkdir -p { $this -> configuration_dir } " ,
2024-05-29 13:17:39 +00:00
],
);
}
2023-12-04 10:20:50 +00:00
}
2024-06-10 20:43:34 +00:00
2025-10-07 12:26:23 +00:00
private function prepare_builder_image ( bool $firstTry = true )
2023-03-31 11:32:07 +00:00
{
2025-09-16 11:40:51 +00:00
$this -> checkForCancellation ();
2024-11-12 14:18:48 +00:00
$helperImage = config ( 'constants.coolify.helper_image' );
2025-11-03 07:38:43 +00:00
$helperImage = " { $helperImage } : " . getHelperVersion ();
2023-11-21 14:31:46 +00:00
// Get user home directory
2024-06-10 20:43:34 +00:00
$this -> serverUserHomeDir = instant_remote_process ([ 'echo $HOME' ], $this -> server );
2024-05-29 13:15:03 +00:00
$this -> dockerConfigFileExists = instant_remote_process ([ " test -f { $this -> serverUserHomeDir } /.docker/config.json && echo 'OK' || echo 'NOK' " ], $this -> server );
2025-09-17 08:08:29 +00:00
$env_flags = $this -> generate_docker_env_flags_for_secrets ();
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
2024-05-29 13:15:03 +00:00
if ( $this -> dockerConfigFileExists === 'NOK' ) {
2024-01-16 14:19:14 +00:00
throw new RuntimeException ( 'Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.' );
}
2025-09-17 08:08:29 +00:00
$runCommand = " docker run -d --name { $this -> deployment_uuid } { $env_flags } --rm -v { $this -> serverUserHomeDir } /.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
2023-10-17 12:23:07 +00:00
} else {
2024-05-29 13:15:03 +00:00
if ( $this -> dockerConfigFileExists === 'OK' ) {
2025-09-17 08:08:29 +00:00
$runCommand = " docker run -d --network { $this -> destination -> network } --name { $this -> deployment_uuid } { $env_flags } --rm -v { $this -> serverUserHomeDir } /.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
2024-01-16 14:19:14 +00:00
} else {
2025-09-17 08:08:29 +00:00
$runCommand = " docker run -d --network { $this -> destination -> network } --name { $this -> deployment_uuid } { $env_flags } --rm -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
2024-01-16 14:19:14 +00:00
}
2023-10-17 12:23:07 +00:00
}
2025-10-07 12:26:23 +00:00
if ( $firstTry ) {
$this -> application_deployment_queue -> addLogEntry ( " Preparing container with helper image: $helperImage " );
} else {
$this -> application_deployment_queue -> addLogEntry ( 'Preparing container with helper image with updated envs.' );
}
2025-04-02 15:03:13 +00:00
$this -> graceful_shutdown_container ( $this -> deployment_uuid );
2024-05-08 08:36:38 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[
2023-09-04 14:03:11 +00:00
$runCommand ,
2024-06-10 20:43:34 +00:00
'hidden' => true ,
2023-08-08 09:51:36 +00:00
],
[
2024-06-10 20:43:34 +00:00
'command' => executeInDocker ( $this -> deployment_uuid , " mkdir -p { $this -> basedir } " ),
2023-08-08 09:51:36 +00:00
],
2023-06-30 20:24:39 +00:00
);
2024-02-08 10:02:30 +00:00
$this -> run_pre_deployment_command ();
2023-03-31 11:32:07 +00:00
}
2024-06-10 20:43:34 +00:00
2025-10-03 07:20:05 +00:00
private function restart_builder_container_with_actual_commit ()
{
// Stop and remove the current helper container
$this -> graceful_shutdown_container ( $this -> deployment_uuid );
// Clear cached env_args to force regeneration with actual SOURCE_COMMIT value
$this -> env_args = null ;
// Restart the helper container with updated environment variables (including actual SOURCE_COMMIT)
2025-10-07 12:26:23 +00:00
$this -> prepare_builder_image ( firstTry : false );
2025-10-03 07:20:05 +00:00
}
2023-11-21 14:31:46 +00:00
private function deploy_to_additional_destinations ()
{
2024-02-06 14:05:11 +00:00
if ( $this -> application -> additional_networks -> count () === 0 ) {
2024-02-05 13:40:54 +00:00
return ;
}
2024-03-26 12:56:30 +00:00
if ( $this -> pull_request_id !== 0 ) {
return ;
}
2024-02-06 14:05:11 +00:00
$destination_ids = $this -> application -> additional_networks -> pluck ( 'id' );
2024-02-05 13:40:54 +00:00
if ( $this -> server -> isSwarm ()) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Additional destinations are not supported in swarm mode.' );
2024-02-05 13:40:54 +00:00
return ;
}
if ( $destination_ids -> contains ( $this -> destination -> id )) {
return ;
}
2023-11-21 14:31:46 +00:00
foreach ( $destination_ids as $destination_id ) {
2025-04-14 11:58:27 +00:00
$destination = StandaloneDocker :: find ( $destination_id );
if ( ! $destination ) {
continue ;
}
2023-11-21 14:31:46 +00:00
$server = $destination -> server ;
2024-05-29 13:15:03 +00:00
if ( $server -> team_id !== $this -> mainServer -> team_id ) {
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Skipping deployment to { $server -> name } . Not in the same team?! " );
2024-06-10 20:43:34 +00:00
2023-11-21 14:31:46 +00:00
continue ;
}
2024-07-24 19:11:12 +00:00
$deployment_uuid = new Cuid2 ;
2024-02-05 13:40:54 +00:00
queue_application_deployment (
deployment_uuid : $deployment_uuid ,
application : $this -> application ,
server : $server ,
destination : $destination ,
no_questions_asked : true ,
);
2024-06-13 11:15:09 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Deployment to { $server -> name } . Logs: " . route ( 'project.application.deployment.show' , [
2024-02-05 13:40:54 +00:00
'project_uuid' => data_get ( $this -> application , 'environment.project.uuid' ),
'application_uuid' => data_get ( $this -> application , 'uuid' ),
'deployment_uuid' => $deployment_uuid ,
2024-12-17 12:42:16 +00:00
'environment_uuid' => data_get ( $this -> application , 'environment.uuid' ),
2024-02-05 13:40:54 +00:00
]));
2023-11-21 14:31:46 +00:00
}
}
2024-06-10 20:43:34 +00:00
2024-05-15 09:30:35 +00:00
private function set_coolify_variables ()
{
$this -> coolify_variables = " SOURCE_COMMIT= { $this -> commit } " ;
if ( $this -> pull_request_id === 0 ) {
$fqdn = $this -> application -> fqdn ;
} else {
$fqdn = $this -> preview -> fqdn ;
}
if ( isset ( $fqdn )) {
2025-07-08 08:49:09 +00:00
$url = Url :: fromString ( $fqdn );
$fqdn = $url -> getHost ();
$url = $url -> withHost ( $fqdn ) -> withPort ( null ) -> __toString ();
2025-07-07 10:55:35 +00:00
if (( int ) $this -> application -> compose_parsing_version >= 3 ) {
2025-07-08 08:49:09 +00:00
$this -> coolify_variables .= " COOLIFY_URL= { $url } " ;
$this -> coolify_variables .= " COOLIFY_FQDN= { $fqdn } " ;
2025-07-07 10:55:35 +00:00
} else {
2025-07-08 08:49:09 +00:00
$this -> coolify_variables .= " COOLIFY_URL= { $fqdn } " ;
2025-07-07 10:55:35 +00:00
$this -> coolify_variables .= " COOLIFY_FQDN= { $url } " ;
}
2024-05-15 09:30:35 +00:00
}
if ( isset ( $this -> application -> git_branch )) {
$this -> coolify_variables .= " COOLIFY_BRANCH= { $this -> application -> git_branch } " ;
}
2025-10-03 07:20:05 +00:00
$this -> coolify_variables .= " COOLIFY_RESOURCE_UUID= { $this -> application -> uuid } " ;
$this -> coolify_variables .= " COOLIFY_CONTAINER_NAME= { $this -> container_name } " ;
2024-05-15 09:30:35 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-26 08:33:57 +00:00
private function check_git_if_build_needed ()
2023-05-05 07:02:50 +00:00
{
2025-04-28 18:43:00 +00:00
if ( is_object ( $this -> source ) && $this -> source -> getMorphClass () === \App\Models\GithubApp :: class && $this -> source -> is_public === false ) {
2025-04-22 08:20:57 +00:00
$repository = githubApi ( $this -> source , " repos/ { $this -> customRepository } " );
$data = data_get ( $repository , 'data' );
2025-06-21 17:28:38 +00:00
$repository_project_id = data_get ( $data , 'id' );
if ( isset ( $repository_project_id )) {
2025-04-22 08:20:57 +00:00
if ( blank ( $this -> application -> repository_project_id ) || $this -> application -> repository_project_id !== $repository_project_id ) {
$this -> application -> repository_project_id = $repository_project_id ;
$this -> application -> save ();
}
}
}
2023-10-26 08:33:57 +00:00
$this -> generate_git_import_commands ();
2024-05-17 13:30:27 +00:00
$local_branch = $this -> branch ;
if ( $this -> pull_request_id !== 0 ) {
$local_branch = " pull/ { $this -> pull_request_id } /head " ;
}
2025-08-13 08:14:47 +00:00
// Build an exact refspec for ls-remote so we don't match similarly named branches (e.g., changeset-release/main)
if ( $this -> pull_request_id === 0 ) {
$lsRemoteRef = " refs/heads/ { $local_branch } " ;
} else {
if ( $this -> git_type === 'github' || $this -> git_type === 'gitea' ) {
$lsRemoteRef = " refs/pull/ { $this -> pull_request_id } /head " ;
} elseif ( $this -> git_type === 'gitlab' ) {
$lsRemoteRef = " refs/merge-requests/ { $this -> pull_request_id } /head " ;
} else {
// Fallback to the original value if provider-specific ref is unknown
$lsRemoteRef = $local_branch ;
}
}
2024-09-26 10:19:49 +00:00
$private_key = data_get ( $this -> application , 'private_key.private_key' );
2023-10-27 09:44:10 +00:00
if ( $private_key ) {
2024-09-26 10:19:49 +00:00
$private_key = base64_encode ( $private_key );
2023-10-27 09:44:10 +00:00
$this -> execute_remote_command (
[
2024-09-26 10:19:49 +00:00
executeInDocker ( $this -> deployment_uuid , 'mkdir -p /root/.ssh' ),
],
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $private_key } ' | base64 -d | tee /root/.ssh/id_rsa > /dev/null " ),
],
[
executeInDocker ( $this -> deployment_uuid , 'chmod 600 /root/.ssh/id_rsa' ),
],
[
2025-08-13 08:14:47 +00:00
executeInDocker ( $this -> deployment_uuid , " GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $this -> customPort } -o Port= { $this -> customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \" git ls-remote { $this -> fullRepoUrl } { $lsRemoteRef } " ),
2024-06-10 20:43:34 +00:00
'hidden' => true ,
'save' => 'git_commit_sha' ,
2024-09-26 10:19:49 +00:00
]
2023-10-27 09:44:10 +00:00
);
} else {
$this -> execute_remote_command (
[
2025-08-13 08:14:47 +00:00
executeInDocker ( $this -> deployment_uuid , " GIT_SSH_COMMAND= \" ssh -o ConnectTimeout=30 -p { $this -> customPort } -o Port= { $this -> customPort } -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \" git ls-remote { $this -> fullRepoUrl } { $lsRemoteRef } " ),
2024-06-10 20:43:34 +00:00
'hidden' => true ,
'save' => 'git_commit_sha' ,
2023-10-27 09:44:10 +00:00
],
);
}
2024-06-13 11:15:09 +00:00
if ( $this -> saved_outputs -> get ( 'git_commit_sha' ) && ! $this -> rollback ) {
2025-10-14 09:55:17 +00:00
// Extract commit SHA from git ls-remote output, handling multi-line output (e.g., redirect warnings)
// Expected format: "commit_sha\trefs/heads/branch" possibly preceded by warning lines
// Note: Git warnings can be on the same line as the result (no newline)
$lsRemoteOutput = $this -> saved_outputs -> get ( 'git_commit_sha' );
// Find the part containing a tab (the actual ls-remote result)
// Handle cases where warning is on the same line as the result
if ( $lsRemoteOutput -> contains ( " \t " )) {
// Get everything from the last occurrence of a valid commit SHA pattern before the tab
// A valid commit SHA is 40 hex characters
$output = $lsRemoteOutput -> value ();
// Extract the line with the tab (actual ls-remote result)
2025-10-14 13:21:38 +00:00
preg_match ( '/\b([0-9a-fA-F]{40})(?=\s*\t)/' , $output , $matches );
2025-10-14 09:55:17 +00:00
if ( isset ( $matches [ 1 ])) {
$this -> commit = $matches [ 1 ];
$this -> application_deployment_queue -> commit = $this -> commit ;
$this -> application_deployment_queue -> save ();
}
}
2023-11-01 11:19:08 +00:00
}
2024-05-15 09:30:35 +00:00
$this -> set_coolify_variables ();
2025-10-03 07:20:05 +00:00
// Restart helper container with actual SOURCE_COMMIT value
if ( $this -> application -> settings -> use_build_secrets && $this -> commit !== 'HEAD' ) {
$this -> application_deployment_queue -> addLogEntry ( 'Restarting helper container with actual SOURCE_COMMIT value.' );
$this -> restart_builder_container_with_actual_commit ();
}
2023-10-26 08:33:57 +00:00
}
2024-06-10 20:43:34 +00:00
2023-10-26 08:33:57 +00:00
private function clone_repository ()
{
$importCommands = $this -> generate_git_import_commands ();
2023-11-27 10:54:55 +00:00
$this -> application_deployment_queue -> addLogEntry ( " \n ---------------------------------------- " );
2025-10-14 18:44:19 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Importing { $this -> customRepository } : { $this -> application -> git_branch } (commit sha { $this -> commit } ) to { $this -> basedir } . " );
2023-11-27 13:28:21 +00:00
if ( $this -> pull_request_id !== 0 ) {
$this -> application_deployment_queue -> addLogEntry ( " Checking out tag pull/ { $this -> pull_request_id } /head. " );
}
2023-10-26 08:33:57 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[
2024-08-28 16:12:00 +00:00
$importCommands ,
'hidden' => true ,
2023-10-26 08:33:57 +00:00
]
2023-06-30 20:24:39 +00:00
);
2024-04-12 08:54:09 +00:00
$this -> create_workdir ();
2024-05-15 09:30:35 +00:00
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " cd { $this -> workdir } && git log -1 { $this -> commit } --pretty=%B " ),
2024-06-10 20:43:34 +00:00
'hidden' => true ,
'save' => 'commit_message' ,
2024-05-15 09:30:35 +00:00
]
);
if ( $this -> saved_outputs -> get ( 'commit_message' )) {
2025-01-16 13:42:33 +00:00
$commit_message = str ( $this -> saved_outputs -> get ( 'commit_message' ));
2024-05-16 11:33:35 +00:00
$this -> application_deployment_queue -> commit_message = $commit_message -> value ();
ApplicationDeploymentQueue :: whereCommit ( $this -> commit ) -> whereApplicationId ( $this -> application -> id ) -> update (
[ 'commit_message' => $commit_message -> value ()]
);
2024-05-15 09:30:35 +00:00
}
2023-05-05 07:02:50 +00:00
}
2023-06-30 20:24:39 +00:00
2023-10-26 08:33:57 +00:00
private function generate_git_import_commands ()
2023-08-08 09:51:36 +00:00
{
2024-05-29 13:15:03 +00:00
[ 'commands' => $commands , 'branch' => $this -> branch , 'fullRepoUrl' => $this -> fullRepoUrl ] = $this -> application -> generateGitImportCommands (
2024-01-29 09:43:18 +00:00
deployment_uuid : $this -> deployment_uuid ,
pull_request_id : $this -> pull_request_id ,
git_type : $this -> git_type ,
commit : $this -> commit
);
2024-06-10 20:43:34 +00:00
2023-11-24 14:48:23 +00:00
return $commands ;
2023-06-30 20:24:39 +00:00
}
2023-06-05 10:07:55 +00:00
2023-08-08 09:51:36 +00:00
private function cleanup_git ()
2023-03-31 11:32:07 +00:00
{
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command (
2023-10-06 08:07:25 +00:00
[ executeInDocker ( $this -> deployment_uuid , " rm -fr { $this -> basedir } /.git " )],
2023-08-08 09:51:36 +00:00
);
}
2023-06-05 10:07:55 +00:00
2023-08-11 20:41:47 +00:00
private function generate_nixpacks_confs ()
2023-08-08 09:51:36 +00:00
{
2023-10-06 08:07:25 +00:00
$nixpacks_command = $this -> nixpacks_build_cmd ();
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Generating nixpacks configuration with: $nixpacks_command " );
2025-10-01 17:57:54 +00:00
2023-10-06 08:07:25 +00:00
$this -> execute_remote_command (
2024-06-10 20:43:34 +00:00
[ executeInDocker ( $this -> deployment_uuid , $nixpacks_command ), 'save' => 'nixpacks_plan' , 'hidden' => true ],
[ executeInDocker ( $this -> deployment_uuid , " nixpacks detect { $this -> workdir } " ), 'save' => 'nixpacks_type' , 'hidden' => true ],
2023-08-08 09:51:36 +00:00
);
2024-01-08 15:33:34 +00:00
if ( $this -> saved_outputs -> get ( 'nixpacks_type' )) {
$this -> nixpacks_type = $this -> saved_outputs -> get ( 'nixpacks_type' );
2024-01-16 14:26:44 +00:00
if ( str ( $this -> nixpacks_type ) -> isEmpty ()) {
throw new RuntimeException ( 'Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers' );
}
2024-01-08 15:33:34 +00:00
}
2024-06-13 11:14:24 +00:00
2024-01-08 15:33:34 +00:00
if ( $this -> saved_outputs -> get ( 'nixpacks_plan' )) {
$this -> nixpacks_plan = $this -> saved_outputs -> get ( 'nixpacks_plan' );
if ( $this -> nixpacks_plan ) {
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Found application type: { $this -> nixpacks_type } . " );
$this -> application_deployment_queue -> addLogEntry ( " If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/ { $this -> nixpacks_type } " );
2025-10-01 17:57:54 +00:00
$parsed = json_decode ( $this -> nixpacks_plan , true );
2024-06-13 11:14:24 +00:00
2024-01-08 15:33:34 +00:00
// Do any modifications here
2025-09-18 16:15:20 +00:00
// We need to generate envs here because nixpacks need to know to generate a proper Dockerfile
2024-01-11 10:32:32 +00:00
$this -> generate_env_variables ();
2024-12-18 11:02:56 +00:00
$merged_envs = collect ( data_get ( $parsed , 'variables' , [])) -> merge ( $this -> env_args );
2024-05-23 12:28:11 +00:00
$aptPkgs = data_get ( $parsed , 'phases.setup.aptPkgs' , []);
if ( count ( $aptPkgs ) === 0 ) {
2024-06-13 12:48:23 +00:00
$aptPkgs = [ 'curl' , 'wget' ];
2024-05-23 12:28:11 +00:00
data_set ( $parsed , 'phases.setup.aptPkgs' , [ 'curl' , 'wget' ]);
} else {
2024-06-13 11:15:09 +00:00
if ( ! in_array ( 'curl' , $aptPkgs )) {
2024-05-23 12:28:11 +00:00
$aptPkgs [] = 'curl' ;
}
2024-06-13 11:15:09 +00:00
if ( ! in_array ( 'wget' , $aptPkgs )) {
2024-05-23 12:28:11 +00:00
$aptPkgs [] = 'wget' ;
}
data_set ( $parsed , 'phases.setup.aptPkgs' , $aptPkgs );
2024-05-23 11:08:46 +00:00
}
2024-01-11 10:32:32 +00:00
data_set ( $parsed , 'variables' , $merged_envs -> toArray ());
2024-06-13 11:14:24 +00:00
$is_laravel = data_get ( $parsed , 'variables.IS_LARAVEL' , false );
if ( $is_laravel ) {
$variables = $this -> laravel_finetunes ();
data_set ( $parsed , 'variables.NIXPACKS_PHP_FALLBACK_PATH' , $variables [ 0 ] -> value );
data_set ( $parsed , 'variables.NIXPACKS_PHP_ROOT_DIR' , $variables [ 1 ] -> value );
}
2024-09-10 10:44:16 +00:00
if ( $this -> nixpacks_type === 'elixir' ) {
$this -> elixir_finetunes ();
}
2025-10-07 12:19:34 +00:00
if ( $this -> nixpacks_type === 'node' ) {
// Check if NIXPACKS_NODE_VERSION is set
$variables = data_get ( $parsed , 'variables' , []);
if ( ! isset ( $variables [ 'NIXPACKS_NODE_VERSION' ])) {
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
$this -> application_deployment_queue -> addLogEntry ( '⚠️ NIXPACKS_NODE_VERSION not set. Nixpacks will use Node.js 18 by default, which is EOL.' );
$this -> application_deployment_queue -> addLogEntry ( 'You can override this by setting NIXPACKS_NODE_VERSION=22 in your environment variables.' );
}
}
2024-01-11 10:32:32 +00:00
$this -> nixpacks_plan = json_encode ( $parsed , JSON_PRETTY_PRINT );
2024-12-18 11:02:56 +00:00
$this -> nixpacks_plan_json = collect ( $parsed );
2024-05-23 12:28:11 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Final Nixpacks plan: { $this -> nixpacks_plan } " , hidden : true );
2024-07-19 13:40:33 +00:00
if ( $this -> nixpacks_type === 'rust' ) {
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
$this -> application -> health_check_enabled = false ;
$this -> application -> save ();
}
2024-01-08 15:33:34 +00:00
}
}
2023-08-08 09:51:36 +00:00
}
2023-06-05 10:07:55 +00:00
2023-08-08 09:51:36 +00:00
private function nixpacks_build_cmd ()
{
2024-01-11 11:56:02 +00:00
$this -> generate_nixpacks_env_variables ();
2025-09-29 10:05:14 +00:00
$nixpacks_command = " nixpacks plan -f json { $this -> env_nixpacks_args } " ;
2023-08-08 09:51:36 +00:00
if ( $this -> application -> build_command ) {
$nixpacks_command .= " --build-cmd \" { $this -> application -> build_command } \" " ;
}
if ( $this -> application -> start_command ) {
$nixpacks_command .= " --start-cmd \" { $this -> application -> start_command } \" " ;
}
if ( $this -> application -> install_command ) {
$nixpacks_command .= " --install-cmd \" { $this -> application -> install_command } \" " ;
}
2024-01-08 15:33:34 +00:00
$nixpacks_command .= " { $this -> workdir } " ;
2024-06-10 20:43:34 +00:00
2023-10-06 08:07:25 +00:00
return $nixpacks_command ;
2023-08-08 09:51:36 +00:00
}
2024-06-10 20:43:34 +00:00
2024-01-11 11:56:02 +00:00
private function generate_nixpacks_env_variables ()
{
$this -> env_nixpacks_args = collect ([]);
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> nixpacks_environment_variables as $env ) {
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $env -> real_value )) {
2024-02-19 08:19:50 +00:00
$this -> env_nixpacks_args -> push ( " --env { $env -> key } = { $env -> real_value } " );
}
2024-01-11 11:56:02 +00:00
}
} else {
foreach ( $this -> application -> nixpacks_environment_variables_preview as $env ) {
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $env -> real_value )) {
2024-02-19 08:19:50 +00:00
$this -> env_nixpacks_args -> push ( " --env { $env -> key } = { $env -> real_value } " );
}
2024-01-11 11:56:02 +00:00
}
}
2023-08-08 09:51:36 +00:00
2025-09-10 14:15:08 +00:00
// Add COOLIFY_* environment variables to Nixpacks build context
$coolify_envs = $this -> generate_coolify_env_variables ();
$coolify_envs -> each ( function ( $value , $key ) {
$this -> env_nixpacks_args -> push ( " --env { $key } = { $value } " );
});
2024-01-11 11:56:02 +00:00
$this -> env_nixpacks_args = $this -> env_nixpacks_args -> implode ( ' ' );
}
2024-06-10 20:43:34 +00:00
2025-04-11 15:31:58 +00:00
private function generate_coolify_env_variables () : Collection
{
$coolify_envs = collect ([]);
$local_branch = $this -> branch ;
if ( $this -> pull_request_id !== 0 ) {
// Add SOURCE_COMMIT if not exists
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'SOURCE_COMMIT' ) -> isEmpty ()) {
if ( ! is_null ( $this -> commit )) {
$coolify_envs -> put ( 'SOURCE_COMMIT' , $this -> commit );
} else {
$coolify_envs -> put ( 'SOURCE_COMMIT' , 'unknown' );
}
}
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'COOLIFY_FQDN' ) -> isEmpty ()) {
2025-05-21 07:45:49 +00:00
if (( int ) $this -> application -> compose_parsing_version >= 3 ) {
$coolify_envs -> put ( 'COOLIFY_URL' , $this -> preview -> fqdn );
} else {
$coolify_envs -> put ( 'COOLIFY_FQDN' , $this -> preview -> fqdn );
}
2025-04-11 15:31:58 +00:00
}
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'COOLIFY_URL' ) -> isEmpty ()) {
$url = str ( $this -> preview -> fqdn ) -> replace ( 'http://' , '' ) -> replace ( 'https://' , '' );
2025-05-21 07:45:49 +00:00
if (( int ) $this -> application -> compose_parsing_version >= 3 ) {
$coolify_envs -> put ( 'COOLIFY_FQDN' , $url );
} else {
$coolify_envs -> put ( 'COOLIFY_URL' , $url );
}
2025-04-11 15:31:58 +00:00
}
if ( $this -> application -> build_pack !== 'dockercompose' || $this -> application -> compose_parsing_version === '1' || $this -> application -> compose_parsing_version === '2' ) {
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'COOLIFY_BRANCH' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_BRANCH' , $local_branch );
}
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'COOLIFY_RESOURCE_UUID' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_RESOURCE_UUID' , $this -> application -> uuid );
}
if ( $this -> application -> environment_variables_preview -> where ( 'key' , 'COOLIFY_CONTAINER_NAME' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_CONTAINER_NAME' , $this -> container_name );
}
}
add_coolify_default_environment_variables ( $this -> application , $coolify_envs , $this -> application -> environment_variables_preview );
} else {
// Add SOURCE_COMMIT if not exists
if ( $this -> application -> environment_variables -> where ( 'key' , 'SOURCE_COMMIT' ) -> isEmpty ()) {
if ( ! is_null ( $this -> commit )) {
$coolify_envs -> put ( 'SOURCE_COMMIT' , $this -> commit );
} else {
$coolify_envs -> put ( 'SOURCE_COMMIT' , 'unknown' );
}
}
if ( $this -> application -> environment_variables -> where ( 'key' , 'COOLIFY_FQDN' ) -> isEmpty ()) {
if (( int ) $this -> application -> compose_parsing_version >= 3 ) {
$coolify_envs -> put ( 'COOLIFY_URL' , $this -> application -> fqdn );
} else {
$coolify_envs -> put ( 'COOLIFY_FQDN' , $this -> application -> fqdn );
}
}
if ( $this -> application -> environment_variables -> where ( 'key' , 'COOLIFY_URL' ) -> isEmpty ()) {
$url = str ( $this -> application -> fqdn ) -> replace ( 'http://' , '' ) -> replace ( 'https://' , '' );
if (( int ) $this -> application -> compose_parsing_version >= 3 ) {
$coolify_envs -> put ( 'COOLIFY_FQDN' , $url );
} else {
$coolify_envs -> put ( 'COOLIFY_URL' , $url );
}
}
if ( $this -> application -> build_pack !== 'dockercompose' || $this -> application -> compose_parsing_version === '1' || $this -> application -> compose_parsing_version === '2' ) {
if ( $this -> application -> environment_variables -> where ( 'key' , 'COOLIFY_BRANCH' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_BRANCH' , $local_branch );
}
if ( $this -> application -> environment_variables -> where ( 'key' , 'COOLIFY_RESOURCE_UUID' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_RESOURCE_UUID' , $this -> application -> uuid );
}
if ( $this -> application -> environment_variables -> where ( 'key' , 'COOLIFY_CONTAINER_NAME' ) -> isEmpty ()) {
$coolify_envs -> put ( 'COOLIFY_CONTAINER_NAME' , $this -> container_name );
}
}
add_coolify_default_environment_variables ( $this -> application , $coolify_envs , $this -> application -> environment_variables );
}
return $coolify_envs ;
}
2023-08-08 09:51:36 +00:00
private function generate_env_variables ()
{
$this -> env_args = collect ([]);
2024-05-17 09:10:57 +00:00
$this -> env_args -> put ( 'SOURCE_COMMIT' , $this -> commit );
2025-10-04 13:06:49 +00:00
2025-04-11 15:31:58 +00:00
$coolify_envs = $this -> generate_coolify_env_variables ();
2025-10-04 13:06:49 +00:00
$coolify_envs -> each ( function ( $value , $key ) {
$this -> env_args -> put ( $key , $value );
});
2025-09-18 16:15:20 +00:00
// For build process, include only environment variables where is_buildtime = true
2023-08-08 09:51:36 +00:00
if ( $this -> pull_request_id === 0 ) {
2025-09-18 16:15:20 +00:00
$envs = $this -> application -> environment_variables ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true )
-> get ();
2025-09-11 14:22:03 +00:00
foreach ( $envs as $env ) {
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $env -> real_value )) {
2024-02-19 08:19:50 +00:00
$this -> env_args -> put ( $env -> key , $env -> real_value );
}
2024-01-08 15:33:34 +00:00
}
2023-08-08 09:51:36 +00:00
} else {
2025-09-18 16:15:20 +00:00
$envs = $this -> application -> environment_variables_preview ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true )
-> get ();
2025-09-11 14:22:03 +00:00
foreach ( $envs as $env ) {
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $env -> real_value )) {
2024-02-19 08:19:50 +00:00
$this -> env_args -> put ( $env -> key , $env -> real_value );
}
2024-01-08 15:33:34 +00:00
}
2023-08-08 09:51:36 +00:00
}
}
private function generate_compose_file ()
{
2025-09-16 11:40:51 +00:00
$this -> checkForCancellation ();
2024-04-12 12:03:29 +00:00
$this -> create_workdir ();
2024-04-26 10:59:51 +00:00
$ports = $this -> application -> main_port ();
2023-08-08 09:51:36 +00:00
$persistent_storages = $this -> generate_local_persistent_volumes ();
2024-05-27 12:14:44 +00:00
$persistent_file_volumes = $this -> application -> fileStorages () -> get ();
2023-08-08 09:51:36 +00:00
$volume_names = $this -> generate_local_persistent_volumes_only_volume_names ();
2023-10-18 08:32:08 +00:00
if ( data_get ( $this -> application , 'custom_labels' )) {
2023-12-13 08:23:27 +00:00
$this -> application -> parseContainerLabels ();
2023-12-11 22:34:18 +00:00
$labels = collect ( preg_split ( " / \r \n | \n | \r / " , base64_decode ( $this -> application -> custom_labels )));
2023-10-27 09:23:29 +00:00
$labels = $labels -> filter ( function ( $value , $key ) {
2024-06-13 11:15:09 +00:00
return ! Str :: startsWith ( $value , 'coolify.' );
2023-10-27 09:23:29 +00:00
});
2023-12-12 11:10:46 +00:00
$this -> application -> custom_labels = base64_encode ( $labels -> implode ( " \n " ));
2023-10-27 09:23:29 +00:00
$this -> application -> save ();
2023-10-26 09:38:37 +00:00
} else {
2025-01-08 16:05:11 +00:00
if ( $this -> application -> settings -> is_container_label_readonly_enabled ) {
2024-07-17 12:52:40 +00:00
$labels = collect ( generateLabelsApplication ( $this -> application , $this -> preview ));
}
2023-10-18 08:32:08 +00:00
}
2023-11-01 14:39:47 +00:00
if ( $this -> pull_request_id !== 0 ) {
2023-11-13 12:20:12 +00:00
$labels = collect ( generateLabelsApplication ( $this -> application , $this -> preview ));
2023-11-01 14:39:47 +00:00
}
2024-05-15 15:52:14 +00:00
if ( $this -> application -> settings -> is_container_label_escape_enabled ) {
$labels = $labels -> map ( function ( $value , $key ) {
return escapeDollarSign ( $value );
});
}
2024-12-02 17:38:21 +00:00
$labels = $labels -> merge ( defaultLabels ( $this -> application -> id , $this -> application -> uuid , $this -> application -> project () -> name , $this -> application -> name , $this -> application -> environment -> name , $this -> pull_request_id )) -> toArray ();
2024-04-29 11:33:28 +00:00
2024-03-13 09:44:15 +00:00
// Check for custom HEALTHCHECK
2024-03-13 09:50:05 +00:00
if ( $this -> application -> build_pack === 'dockerfile' || $this -> application -> dockerfile ) {
2024-03-13 09:44:15 +00:00
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } { $this -> dockerfile_location } " ),
'hidden' => true ,
'save' => 'dockerfile_from_repo' ,
'ignore_errors' => true ,
2024-03-13 09:44:15 +00:00
]);
2025-03-24 10:43:10 +00:00
$this -> application -> parseHealthcheckFromDockerfile ( $this -> saved_outputs -> get ( 'dockerfile_from_repo' ));
2024-03-13 09:44:15 +00:00
}
2025-04-09 06:42:50 +00:00
$custom_network_aliases = [];
2025-11-01 12:24:05 +00:00
if ( ! empty ( $this -> application -> custom_network_aliases_array )) {
2025-11-01 12:13:14 +00:00
$custom_network_aliases = $this -> application -> custom_network_aliases_array ;
2025-04-09 06:33:42 +00:00
}
2023-08-08 09:51:36 +00:00
$docker_compose = [
'services' => [
$this -> container_name => [
'image' => $this -> production_image_name ,
'container_name' => $this -> container_name ,
2023-08-21 16:00:12 +00:00
'restart' => RESTART_MODE ,
2023-09-24 15:48:25 +00:00
'expose' => $ports ,
2023-03-31 11:32:07 +00:00
'networks' => [
2024-05-06 09:45:22 +00:00
$this -> destination -> network => [
2025-01-05 06:47:57 +00:00
'aliases' => array_merge (
[ $this -> container_name ],
2025-04-09 06:42:50 +00:00
$custom_network_aliases
2025-01-05 06:47:57 +00:00
),
2024-06-10 20:43:34 +00:00
],
2023-03-31 11:32:07 +00:00
],
2023-05-17 09:59:48 +00:00
'mem_limit' => $this -> application -> limits_memory ,
'memswap_limit' => $this -> application -> limits_memory_swap ,
'mem_swappiness' => $this -> application -> limits_memory_swappiness ,
'mem_reservation' => $this -> application -> limits_memory_reservation ,
2023-12-27 12:01:57 +00:00
'cpus' => ( float ) $this -> application -> limits_cpus ,
2023-05-17 09:59:48 +00:00
'cpu_shares' => $this -> application -> limits_cpu_shares ,
2024-06-10 20:43:34 +00:00
],
2023-03-31 11:32:07 +00:00
],
'networks' => [
$this -> destination -> network => [
2023-09-05 06:49:33 +00:00
'external' => true ,
2023-03-31 11:32:07 +00:00
'name' => $this -> destination -> network ,
2024-06-10 20:43:34 +00:00
'attachable' => true ,
],
],
2023-03-31 11:32:07 +00:00
];
2025-10-04 17:19:15 +00:00
// Always use .env file
$docker_compose [ 'services' ][ $this -> container_name ][ 'env_file' ] = [ '.env' ];
2025-10-22 11:03:17 +00:00
// Only add Coolify healthcheck if no custom HEALTHCHECK found in Dockerfile
// If custom_healthcheck_found is true, the Dockerfile's HEALTHCHECK will be used
// If healthcheck is disabled, no healthcheck will be added
if ( ! $this -> application -> custom_healthcheck_found && ! $this -> application -> isHealthcheckDisabled ()) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'healthcheck' ] = [
'test' => [
'CMD-SHELL' ,
$this -> generate_healthcheck_commands (),
],
'interval' => $this -> application -> health_check_interval . 's' ,
'timeout' => $this -> application -> health_check_timeout . 's' ,
'retries' => $this -> application -> health_check_retries ,
'start_period' => $this -> application -> health_check_start_period . 's' ,
];
}
2024-04-29 11:33:28 +00:00
2024-06-13 11:15:09 +00:00
if ( ! is_null ( $this -> application -> limits_cpuset )) {
data_set ( $docker_compose , 'services.' . $this -> container_name . '.cpuset' , $this -> application -> limits_cpuset );
2024-01-12 12:47:01 +00:00
}
2023-11-28 17:31:04 +00:00
if ( $this -> server -> isSwarm ()) {
2024-06-13 11:15:09 +00:00
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.container_name' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.expose' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.restart' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.mem_limit' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.memswap_limit' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.mem_swappiness' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.mem_reservation' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.cpus' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.cpuset' );
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.cpu_shares' );
2023-11-28 19:49:38 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ] = [
'mode' => 'replicated' ,
2023-12-18 13:01:25 +00:00
'replicas' => data_get ( $this -> application , 'swarm_replicas' , 1 ),
2023-11-28 19:49:38 +00:00
'update_config' => [
2024-06-10 20:43:34 +00:00
'order' => 'start-first' ,
2023-11-28 19:49:38 +00:00
],
'rollback_config' => [
2024-06-10 20:43:34 +00:00
'order' => 'start-first' ,
2023-11-28 19:49:38 +00:00
],
'labels' => $labels ,
'resources' => [
'limits' => [
'cpus' => $this -> application -> limits_cpus ,
'memory' => $this -> application -> limits_memory ,
],
'reservations' => [
'cpus' => $this -> application -> limits_cpus ,
'memory' => $this -> application -> limits_memory ,
2024-06-10 20:43:34 +00:00
],
],
2023-11-28 19:49:38 +00:00
];
2024-07-13 04:15:17 +00:00
if ( data_get ( $this -> application , 'swarm_placement_constraints' )) {
2024-07-13 04:26:51 +00:00
$swarm_placement_constraints = Yaml :: parse ( base64_decode ( data_get ( $this -> application , 'swarm_placement_constraints' )));
2024-07-13 04:15:17 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ] = array_merge (
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ],
2024-07-13 04:26:51 +00:00
$swarm_placement_constraints
2024-07-13 04:15:17 +00:00
);
}
2023-12-18 13:01:25 +00:00
if ( data_get ( $this -> application , 'settings.is_swarm_only_worker_nodes' )) {
2024-07-13 04:15:17 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'placement' ][ 'constraints' ][] = 'node.role == worker' ;
2023-12-18 13:01:25 +00:00
}
if ( $this -> pull_request_id !== 0 ) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'replicas' ] = 1 ;
}
2023-11-28 17:31:04 +00:00
} else {
2023-11-28 19:49:38 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'labels' ] = $labels ;
2023-11-28 17:31:04 +00:00
}
2024-03-04 10:01:14 +00:00
if ( $this -> server -> isLogDrainEnabled () && $this -> application -> isLogDrainEnabled ()) {
2024-08-28 09:11:14 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'logging' ] = generate_fluentd_configuration ();
2023-11-17 10:13:16 +00:00
}
2023-11-20 10:35:31 +00:00
if ( $this -> application -> settings -> is_gpu_enabled ) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'resources' ][ 'reservations' ][ 'devices' ] = [
[
'driver' => data_get ( $this -> application , 'settings.gpu_driver' , 'nvidia' ),
'capabilities' => [ 'gpu' ],
2024-06-10 20:43:34 +00:00
'options' => data_get ( $this -> application , 'settings.gpu_options' , []),
],
2023-11-20 10:35:31 +00:00
];
if ( data_get ( $this -> application , 'settings.gpu_count' )) {
$count = data_get ( $this -> application , 'settings.gpu_count' );
if ( $count === 'all' ) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'resources' ][ 'reservations' ][ 'devices' ][ 0 ][ 'count' ] = $count ;
} else {
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'resources' ][ 'reservations' ][ 'devices' ][ 0 ][ 'count' ] = ( int ) $count ;
}
2024-06-10 20:43:34 +00:00
} elseif ( data_get ( $this -> application , 'settings.gpu_device_ids' )) {
2023-11-20 10:35:31 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'resources' ][ 'reservations' ][ 'devices' ][ 0 ][ 'ids' ] = data_get ( $this -> application , 'settings.gpu_device_ids' );
}
}
2023-10-01 16:14:13 +00:00
if ( $this -> application -> isHealthcheckDisabled ()) {
2024-06-13 11:15:09 +00:00
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.healthcheck' );
2023-10-01 16:14:13 +00:00
}
2023-06-05 10:07:55 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 && $this -> pull_request_id === 0 ) {
2023-05-30 13:52:17 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'ports' ] = $this -> application -> ports_mappings_array ;
2023-03-31 11:32:07 +00:00
}
2024-08-13 10:32:39 +00:00
2023-05-30 13:52:17 +00:00
if ( count ( $persistent_storages ) > 0 ) {
2024-08-15 11:32:44 +00:00
if ( ! data_get ( $docker_compose , 'services.' . $this -> container_name . '.volumes' )) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = [];
}
2024-08-13 10:32:39 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = array_merge ( $docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ], $persistent_storages );
2023-04-04 13:25:42 +00:00
}
2024-05-27 12:14:44 +00:00
if ( count ( $persistent_file_volumes ) > 0 ) {
2024-08-15 11:32:44 +00:00
if ( ! data_get ( $docker_compose , 'services.' . $this -> container_name . '.volumes' )) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = [];
}
2024-08-13 10:32:39 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = array_merge ( $docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ], $persistent_file_volumes -> map ( function ( $item ) {
2024-05-27 12:14:44 +00:00
return " $item->fs_path : $item->mount_path " ;
2024-08-13 10:32:39 +00:00
}) -> toArray ());
2024-05-27 12:14:44 +00:00
}
2023-04-04 13:25:42 +00:00
if ( count ( $volume_names ) > 0 ) {
$docker_compose [ 'volumes' ] = $volume_names ;
}
2023-11-28 19:49:38 +00:00
2024-03-02 12:22:05 +00:00
if ( $this -> pull_request_id === 0 ) {
2024-11-06 09:13:40 +00:00
$custom_compose = convertDockerRunToCompose ( $this -> application -> custom_docker_run_options );
2024-06-10 20:43:34 +00:00
if (( bool ) $this -> application -> settings -> is_consistent_container_name_enabled ) {
2024-07-02 08:02:43 +00:00
if ( ! $this -> application -> settings -> custom_internal_name ) {
$docker_compose [ 'services' ][ $this -> application -> uuid ] = $docker_compose [ 'services' ][ $this -> container_name ];
if ( count ( $custom_compose ) > 0 ) {
$ipv4 = data_get ( $custom_compose , 'ip.0' );
$ipv6 = data_get ( $custom_compose , 'ip6.0' );
data_forget ( $custom_compose , 'ip' );
data_forget ( $custom_compose , 'ip6' );
if ( $ipv4 || $ipv6 ) {
data_forget ( $docker_compose [ 'services' ][ $this -> application -> uuid ], 'networks' );
}
if ( $ipv4 ) {
$docker_compose [ 'services' ][ $this -> application -> uuid ][ 'networks' ][ $this -> destination -> network ][ 'ipv4_address' ] = $ipv4 ;
}
if ( $ipv6 ) {
$docker_compose [ 'services' ][ $this -> application -> uuid ][ 'networks' ][ $this -> destination -> network ][ 'ipv6_address' ] = $ipv6 ;
}
$docker_compose [ 'services' ][ $this -> application -> uuid ] = array_merge_recursive ( $docker_compose [ 'services' ][ $this -> application -> uuid ], $custom_compose );
2024-03-01 10:43:42 +00:00
}
2024-02-25 22:13:27 +00:00
}
2024-03-01 10:43:42 +00:00
} else {
if ( count ( $custom_compose ) > 0 ) {
$ipv4 = data_get ( $custom_compose , 'ip.0' );
$ipv6 = data_get ( $custom_compose , 'ip6.0' );
data_forget ( $custom_compose , 'ip' );
data_forget ( $custom_compose , 'ip6' );
if ( $ipv4 || $ipv6 ) {
2024-03-14 09:10:03 +00:00
data_forget ( $docker_compose [ 'services' ][ $this -> container_name ], 'networks' );
2024-03-01 10:43:42 +00:00
}
if ( $ipv4 ) {
2024-03-14 09:10:03 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'networks' ][ $this -> destination -> network ][ 'ipv4_address' ] = $ipv4 ;
2024-03-01 10:43:42 +00:00
}
if ( $ipv6 ) {
2024-03-14 09:10:03 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'networks' ][ $this -> destination -> network ][ 'ipv6_address' ] = $ipv6 ;
2024-03-01 10:43:42 +00:00
}
2024-03-14 09:10:03 +00:00
$docker_compose [ 'services' ][ $this -> container_name ] = array_merge_recursive ( $docker_compose [ 'services' ][ $this -> container_name ], $custom_compose );
2024-02-25 22:13:27 +00:00
}
2024-02-15 10:55:43 +00:00
}
2024-01-29 15:21:23 +00:00
}
2024-01-29 15:07:00 +00:00
2023-06-30 20:24:39 +00:00
$this -> docker_compose = Yaml :: dump ( $docker_compose , 10 );
2023-08-09 12:44:36 +00:00
$this -> docker_compose_base64 = base64_encode ( $this -> docker_compose );
2024-07-03 14:27:28 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> docker_compose_base64 } ' | base64 -d | tee { $this -> workdir } /docker-compose.yaml > /dev/null " ), 'hidden' => true ]);
2023-03-31 11:32:07 +00:00
}
2023-08-08 09:51:36 +00:00
2023-04-04 13:25:42 +00:00
private function generate_local_persistent_volumes ()
{
2023-06-05 19:17:44 +00:00
$local_persistent_volumes = [];
2023-04-04 13:25:42 +00:00
foreach ( $this -> application -> persistentStorages as $persistentStorage ) {
2024-04-11 11:20:46 +00:00
if ( $persistentStorage -> host_path !== '' && $persistentStorage -> host_path !== null ) {
$volume_name = $persistentStorage -> host_path ;
} else {
$volume_name = $persistentStorage -> name ;
}
2023-06-05 10:07:55 +00:00
if ( $this -> pull_request_id !== 0 ) {
2025-09-08 13:15:57 +00:00
$volume_name = addPreviewDeploymentSuffix ( $volume_name , $this -> pull_request_id );
2023-06-05 10:07:55 +00:00
}
2024-06-13 11:15:09 +00:00
$local_persistent_volumes [] = $volume_name . ':' . $persistentStorage -> mount_path ;
2023-04-04 13:25:42 +00:00
}
2024-06-10 20:43:34 +00:00
2023-06-05 19:17:44 +00:00
return $local_persistent_volumes ;
2023-04-04 13:25:42 +00:00
}
2023-08-08 09:51:36 +00:00
2023-04-04 13:25:42 +00:00
private function generate_local_persistent_volumes_only_volume_names ()
{
2023-06-05 19:17:44 +00:00
$local_persistent_volumes_names = [];
2023-04-04 13:25:42 +00:00
foreach ( $this -> application -> persistentStorages as $persistentStorage ) {
if ( $persistentStorage -> host_path ) {
continue ;
}
2023-06-05 10:07:55 +00:00
$name = $persistentStorage -> name ;
if ( $this -> pull_request_id !== 0 ) {
2025-09-08 13:15:57 +00:00
$name = addPreviewDeploymentSuffix ( $name , $this -> pull_request_id );
2023-06-05 10:07:55 +00:00
}
$local_persistent_volumes_names [ $name ] = [
'name' => $name ,
2023-04-04 13:25:42 +00:00
'external' => false ,
];
}
2024-06-10 20:43:34 +00:00
2023-06-05 19:17:44 +00:00
return $local_persistent_volumes_names ;
2023-04-04 13:25:42 +00:00
}
2023-08-08 09:51:36 +00:00
private function generate_healthcheck_commands ()
2023-06-30 20:24:39 +00:00
{
2024-06-13 11:15:09 +00:00
if ( ! $this -> application -> health_check_port ) {
2023-09-22 13:29:19 +00:00
$health_check_port = $this -> application -> ports_exposes_array [ 0 ];
} else {
$health_check_port = $this -> application -> health_check_port ;
2023-04-14 11:18:55 +00:00
}
2023-12-06 20:09:41 +00:00
if ( $this -> application -> settings -> is_static || $this -> application -> build_pack === 'static' ) {
$health_check_port = 80 ;
}
2023-08-08 09:51:36 +00:00
if ( $this -> application -> health_check_path ) {
2023-11-16 14:23:07 +00:00
$this -> full_healthcheck_url = " { $this -> application -> health_check_method } : { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } { $this -> application -> health_check_path } " ;
2023-08-08 09:51:36 +00:00
$generated_healthchecks_commands = [
2024-06-10 20:43:34 +00:00
" curl -s -X { $this -> application -> health_check_method } -f { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } { $this -> application -> health_check_path } > /dev/null || wget -q -O- { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } { $this -> application -> health_check_path } > /dev/null || exit 1 " ,
2023-08-08 09:51:36 +00:00
];
} else {
2023-11-16 14:23:07 +00:00
$this -> full_healthcheck_url = " { $this -> application -> health_check_method } : { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } / " ;
2023-08-08 09:51:36 +00:00
$generated_healthchecks_commands = [
2024-06-10 20:43:34 +00:00
" curl -s -X { $this -> application -> health_check_method } -f { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } / > /dev/null || wget -q -O- { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } / > /dev/null || exit 1 " ,
2023-08-08 09:51:36 +00:00
];
2023-03-31 13:51:50 +00:00
}
2024-06-10 20:43:34 +00:00
2023-08-08 09:51:36 +00:00
return implode ( ' ' , $generated_healthchecks_commands );
2023-06-30 20:24:39 +00:00
}
2024-06-10 20:43:34 +00:00
2023-11-13 11:30:25 +00:00
private function pull_latest_image ( $image )
{
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Pulling latest image ( $image ) from the registry. " );
2023-11-13 11:30:25 +00:00
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " docker pull { $image } " ),
'hidden' => true ,
2023-11-13 11:30:25 +00:00
]
);
}
2024-06-10 20:43:34 +00:00
2025-09-16 15:16:01 +00:00
private function build_static_image ()
{
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
$this -> application_deployment_queue -> addLogEntry ( 'Static deployment. Copying static assets to the image.' );
if ( $this -> application -> static_image ) {
$this -> pull_latest_image ( $this -> application -> static_image );
}
$dockerfile = base64_encode ( " FROM { $this -> application -> static_image }
WORKDIR / usr / share / nginx / html /
LABEL coolify . deploymentId = { $this -> deployment_uuid }
COPY . .
RUN rm - f / usr / share / nginx / html / nginx . conf
RUN rm - f / usr / share / nginx / html / Dockerfile
RUN rm - f / usr / share / nginx / html / docker - compose . yaml
RUN rm - f / usr / share / nginx / html /. env
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 {
if ( $this -> application -> settings -> is_spa ) {
$nginx_config = base64_encode ( defaultNginxConfiguration ( 'spa' ));
} else {
$nginx_config = base64_encode ( defaultNginxConfiguration ());
}
}
$build_command = " docker build { $this -> addHosts } --network host -f { $this -> workdir } /Dockerfile --progress plain -t { $this -> production_image_name } { $this -> workdir } " ;
$base64_build_command = base64_encode ( $build_command );
$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_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
]
);
$this -> application_deployment_queue -> addLogEntry ( 'Building docker image completed.' );
}
2025-10-06 08:31:58 +00:00
/**
* Wrap a docker build command with environment export from / artifacts / build - time . env
* This enables shell interpolation of variables ( e . g . , APP_URL = $COOLIFY_URL )
*
* @ param string $build_command The docker build command to wrap
* @ return string The wrapped command with export statement
*/
private function wrap_build_command_with_env_export ( string $build_command ) : string
{
return " cd { $this -> workdir } && set -a && source /artifacts/build-time.env && set +a && { $build_command } " ;
}
2023-08-08 09:51:36 +00:00
private function build_image ()
2023-06-30 20:24:39 +00:00
{
2025-09-16 15:16:01 +00:00
// Add Coolify related variables to the build args/secrets
if ( $this -> dockerBuildkitSupported ) {
// Coolify variables are already included in the secrets from generate_build_env_variables
// build_secrets is already a string at this point
} else {
2025-10-04 17:19:15 +00:00
// Traditional build args approach - generate COOLIFY_ variables locally
// Generate COOLIFY_ variables locally for build args
$coolify_envs = $this -> generate_coolify_env_variables ();
$coolify_envs -> each ( function ( $value , $key ) {
2025-09-16 15:16:01 +00:00
$this -> build_args -> push ( " --build-arg ' { $key } ' " );
});
2025-09-16 16:20:51 +00:00
$this -> build_args = $this -> build_args instanceof \Illuminate\Support\Collection
? $this -> build_args -> implode ( ' ' )
: ( string ) $this -> build_args ;
2025-09-16 15:16:01 +00:00
}
2024-08-29 13:49:22 +00:00
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-12-05 09:46:27 +00:00
if ( $this -> disableBuildCache ) {
$this -> application_deployment_queue -> addLogEntry ( 'Docker build cache is disabled. It will not be used during the build process.' );
}
2023-11-10 10:33:15 +00:00
if ( $this -> application -> build_pack === 'static' ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Static deployment. Copying static assets to the image.' );
2023-11-10 10:33:15 +00:00
} else {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Building docker image started.' );
$this -> application_deployment_queue -> addLogEntry ( 'To check the current progress, click on Show Debug Logs.' );
2023-11-10 10:33:15 +00:00
}
2023-08-08 09:51:36 +00:00
2025-09-16 15:16:01 +00:00
if ( $this -> application -> settings -> is_static ) {
2023-11-13 11:30:25 +00:00
if ( $this -> application -> static_image ) {
$this -> pull_latest_image ( $this -> application -> static_image );
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Continuing with the building process.' );
2023-11-13 11:30:25 +00:00
}
2025-09-16 15:16:01 +00:00
if ( $this -> application -> build_pack === 'nixpacks' ) {
$this -> nixpacks_plan = base64_encode ( $this -> nixpacks_plan );
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> nixpacks_plan } ' | base64 -d | tee /artifacts/thegameplan.json > /dev/null " ), 'hidden' => true ]);
if ( $this -> force_rebuild ) {
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n { $this -> build_image_name } { $this -> workdir } -o { $this -> workdir } " ),
'hidden' => true ,
], [
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } /.nixpacks/Dockerfile " ),
'hidden' => true ,
]);
2025-09-17 08:34:38 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-09-16 15:16:01 +00:00
// Modify the nixpacks Dockerfile to use build secrets
2025-09-17 16:46:10 +00:00
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> build_image_name } { $this -> workdir } " );
2025-09-17 08:34:38 +00:00
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile --progress plain -t { $this -> build_image_name } { $this -> build_args } { $this -> workdir } " );
ray ( $build_command );
2025-03-31 13:10:50 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile --progress plain -t { $this -> build_image_name } { $this -> build_args } { $this -> workdir } " );
2025-03-31 13:10:50 +00:00
}
2024-11-11 13:37:19 +00:00
} else {
2025-09-16 15:16:01 +00:00
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " nixpacks build -c /artifacts/thegameplan.json --cache-key ' { $this -> application -> uuid } ' --no-error-without-start -n { $this -> build_image_name } { $this -> workdir } -o { $this -> workdir } " ),
'hidden' => true ,
], [
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } /.nixpacks/Dockerfile " ),
'hidden' => true ,
]);
2025-10-06 08:31:58 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-09-16 15:16:01 +00:00
// Modify the nixpacks Dockerfile to use build secrets
2025-09-17 16:46:10 +00:00
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> build_image_name } { $this -> workdir } " );
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> build_image_name } { $this -> build_args } { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile --progress plain -t { $this -> build_image_name } { $this -> build_args } { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
}
2023-11-10 10:33:15 +00:00
}
2024-06-13 12:48:23 +00:00
2025-09-16 15:16:01 +00:00
$base64_build_command = base64_encode ( $build_command );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
]
);
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , 'rm /artifacts/thegameplan.json' ), 'hidden' => true ]);
2023-11-10 10:33:15 +00:00
} else {
2025-09-16 15:16:01 +00:00
// Dockerfile buildpack
2025-09-17 16:46:10 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
// Modify the Dockerfile to use build secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } { $this -> dockerfile_location } " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t $this->build_image_name { $this -> workdir } " );
} else {
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t $this->build_image_name { $this -> workdir } " );
}
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } { $this -> dockerfile_location } " );
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
if ( $this -> force_rebuild ) {
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t $this->build_image_name { $this -> build_args } { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t $this->build_image_name { $this -> build_args } { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
}
2024-01-03 12:20:24 +00:00
} else {
2025-09-16 15:16:01 +00:00
// Traditional build with args
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --no-cache { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t $this->build_image_name { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build { $this -> buildTarget } --network { $this -> destination -> network } -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t $this->build_image_name { $this -> workdir } " );
2024-01-08 15:33:34 +00:00
}
2024-01-03 12:20:24 +00:00
}
2025-09-16 15:16:01 +00:00
$base64_build_command = base64_encode ( $build_command );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
]
);
}
2025-10-14 18:45:40 +00:00
$publishDir = trim ( $this -> application -> publish_directory , '/' );
2025-10-14 15:15:41 +00:00
$publishDir = $publishDir ? " / { $publishDir } " : '' ;
2025-09-16 15:16:01 +00:00
$dockerfile = base64_encode ( " FROM { $this -> application -> static_image }
2023-08-08 09:51:36 +00:00
WORKDIR / usr / share / nginx / html /
LABEL coolify . deploymentId = { $this -> deployment_uuid }
2025-10-14 15:15:41 +00:00
COPY -- from = $this -> build_image_name / app { $publishDir } .
2023-08-08 09:51:36 +00:00
COPY ./ nginx . conf / etc / nginx / conf . d / default . conf " );
2025-09-16 15:16:01 +00:00
if ( str ( $this -> application -> custom_nginx_configuration ) -> isNotEmpty ()) {
$nginx_config = base64_encode ( $this -> application -> custom_nginx_configuration );
} else {
if ( $this -> application -> settings -> is_spa ) {
$nginx_config = base64_encode ( defaultNginxConfiguration ( 'spa' ));
2024-11-11 13:37:19 +00:00
} else {
2025-09-16 15:16:01 +00:00
$nginx_config = base64_encode ( defaultNginxConfiguration ());
2023-08-08 09:51:36 +00:00
}
2023-11-10 10:33:15 +00:00
}
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build { $this -> addHosts } --network host -f { $this -> workdir } /Dockerfile { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
2024-01-11 10:32:32 +00:00
$base64_build_command = base64_encode ( $build_command );
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command (
[
2024-06-10 20:43:34 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile } ' | base64 -d | tee { $this -> workdir } /Dockerfile > /dev/null " ),
2023-08-08 09:51:36 +00:00
],
[
2024-06-10 20:43:34 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $nginx_config } ' | base64 -d | tee { $this -> workdir } /nginx.conf > /dev/null " ),
2023-08-08 09:51:36 +00:00
],
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
2024-01-11 10:32:32 +00:00
],
2024-09-16 09:50:03 +00:00
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
2024-01-11 10:32:32 +00:00
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
2023-08-08 09:51:36 +00:00
]
);
2023-06-30 20:24:39 +00:00
} else {
2023-11-13 11:30:25 +00:00
// Pure Dockerfile based deployment
2023-11-17 11:22:45 +00:00
if ( $this -> application -> dockerfile ) {
2025-09-17 16:46:10 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
// Modify the Dockerfile to use build secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } { $this -> dockerfile_location } " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
if ( $this -> force_rebuild ) {
$build_command = " DOCKER_BUILDKIT=1 docker build --no-cache --pull { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " ;
} else {
$build_command = " DOCKER_BUILDKIT=1 docker build --pull { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " ;
}
2024-05-29 16:22:19 +00:00
} else {
2025-09-16 15:16:01 +00:00
// Traditional build with args
if ( $this -> force_rebuild ) {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --no-cache --pull { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --pull { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
}
2024-05-29 16:22:19 +00:00
}
2024-01-11 10:32:32 +00:00
$base64_build_command = base64_encode ( $build_command );
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
2024-01-11 10:32:32 +00:00
],
2024-09-16 09:50:03 +00:00
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
2024-01-11 10:32:32 +00:00
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
2024-01-11 10:32:32 +00:00
]
);
2023-11-17 11:22:45 +00:00
} else {
2024-01-03 20:15:58 +00:00
if ( $this -> application -> build_pack === 'nixpacks' ) {
2024-01-08 15:33:34 +00:00
$this -> nixpacks_plan = base64_encode ( $this -> nixpacks_plan );
2024-06-10 20:43:34 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> nixpacks_plan } ' | base64 -d | tee /artifacts/thegameplan.json > /dev/null " ), 'hidden' => true ]);
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n { $this -> production_image_name } { $this -> workdir } -o { $this -> workdir } " ),
'hidden' => true ,
2025-09-11 14:22:03 +00:00
], [
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } /.nixpacks/Dockerfile " ),
'hidden' => true ,
2024-01-08 15:33:34 +00:00
]);
2025-10-06 08:31:58 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-09-16 15:16:01 +00:00
// Modify the nixpacks Dockerfile to use build secrets
2025-09-17 16:46:10 +00:00
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --no-cache { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
}
2024-01-08 15:33:34 +00:00
} else {
$this -> execute_remote_command ([
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " nixpacks build -c /artifacts/thegameplan.json --cache-key ' { $this -> application -> uuid } ' --no-error-without-start -n { $this -> production_image_name } { $this -> workdir } -o { $this -> workdir } " ),
'hidden' => true ,
2025-09-11 14:22:03 +00:00
], [
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } /.nixpacks/Dockerfile " ),
'hidden' => true ,
2024-01-08 15:33:34 +00:00
]);
2025-10-06 08:31:58 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-09-16 15:16:01 +00:00
// Modify the nixpacks Dockerfile to use build secrets
2025-09-17 16:46:10 +00:00
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
2025-09-16 15:16:01 +00:00
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } /.nixpacks/Dockerfile " );
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build { $this -> addHosts } --network host -f { $this -> workdir } /.nixpacks/Dockerfile --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
}
2024-01-08 15:33:34 +00:00
}
2024-06-13 10:02:52 +00:00
$base64_build_command = base64_encode ( $build_command );
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
2024-06-13 10:02:52 +00:00
],
2024-09-16 09:50:03 +00:00
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
2024-06-13 10:02:52 +00:00
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
2024-06-13 10:02:52 +00:00
]
);
2024-06-10 20:43:34 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , 'rm /artifacts/thegameplan.json' ), 'hidden' => true ]);
2024-01-03 12:20:24 +00:00
} else {
2025-09-16 15:16:01 +00:00
// Dockerfile buildpack
2025-10-06 08:31:58 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-10-03 07:20:05 +00:00
// Modify the Dockerfile to use build secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } { $this -> dockerfile_location } " );
2025-09-16 15:16:01 +00:00
// Use BuildKit with secrets
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
if ( $this -> force_rebuild ) {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
} else {
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
}
} elseif ( $this -> dockerBuildkitSupported ) {
// BuildKit without secrets
$this -> modify_dockerfile_for_secrets ( " { $this -> workdir } { $this -> dockerfile_location } " );
$secrets_flags = $this -> build_secrets ? " { $this -> build_secrets } " : '' ;
if ( $this -> force_rebuild ) {
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build --no-cache { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " DOCKER_BUILDKIT=1 docker build { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $secrets_flags } --progress plain -t { $this -> production_image_name } { $this -> build_args } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
}
2024-01-08 15:33:34 +00:00
} else {
2025-09-16 15:16:01 +00:00
// Traditional build with args
if ( $this -> force_rebuild ) {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build --no-cache { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
} else {
2025-10-06 08:31:58 +00:00
$build_command = $this -> wrap_build_command_with_env_export ( " docker build { $this -> buildTarget } { $this -> addHosts } --network host -f { $this -> workdir } { $this -> dockerfile_location } { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " );
2025-09-16 15:16:01 +00:00
}
2024-01-08 15:33:34 +00:00
}
2025-09-16 15:16:01 +00:00
$base64_build_command = base64_encode ( $build_command );
2024-01-11 10:32:32 +00:00
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d | tee /artifacts/build.sh > /dev/null " ),
'hidden' => true ,
2024-01-11 10:32:32 +00:00
],
2024-09-16 09:50:03 +00:00
[
executeInDocker ( $this -> deployment_uuid , 'cat /artifacts/build.sh' ),
'hidden' => true ,
],
2024-01-11 10:32:32 +00:00
[
2024-08-28 16:12:00 +00:00
executeInDocker ( $this -> deployment_uuid , 'bash /artifacts/build.sh' ),
'hidden' => true ,
2024-01-11 10:32:32 +00:00
]
);
2024-01-03 12:20:24 +00:00
}
2023-11-17 11:22:45 +00:00
}
2023-04-14 19:09:38 +00:00
}
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Building docker image completed.' );
2023-08-08 09:51:36 +00:00
}
2023-06-30 20:24:39 +00:00
2025-07-07 14:07:08 +00:00
private function graceful_shutdown_container ( string $containerName )
2024-08-06 11:05:34 +00:00
{
2024-07-29 07:57:13 +00:00
try {
2025-07-07 14:07:08 +00:00
$timeout = isDev () ? 1 : 30 ;
2025-04-30 07:59:03 +00:00
$this -> execute_remote_command (
[ " docker stop --time= $timeout $containerName " , 'hidden' => true , 'ignore_errors' => true ],
[ " docker rm -f $containerName " , 'hidden' => true , 'ignore_errors' => true ]
);
} catch ( Exception $error ) {
2024-09-23 17:51:31 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Error stopping container $containerName : " . $error -> getMessage (), 'stderr' );
2024-07-29 07:57:13 +00:00
}
}
2023-09-24 19:15:43 +00:00
private function stop_running_container ( bool $force = false )
2023-08-08 09:51:36 +00:00
{
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Removing old containers.' );
2024-05-29 13:15:03 +00:00
if ( $this -> newVersionIsHealthy || $force ) {
2024-08-06 11:05:34 +00:00
if ( $this -> application -> settings -> is_consistent_container_name_enabled || str ( $this -> application -> settings -> custom_internal_name ) -> isNotEmpty ()) {
2024-07-29 07:57:13 +00:00
$this -> graceful_shutdown_container ( $this -> container_name );
2024-08-06 11:05:34 +00:00
} else {
$containers = getCurrentApplicationContainerStatus ( $this -> server , $this -> application -> id , $this -> pull_request_id );
if ( $this -> pull_request_id === 0 ) {
$containers = $containers -> filter ( function ( $container ) {
2025-09-08 13:15:57 +00:00
return data_get ( $container , 'Names' ) !== $this -> container_name && data_get ( $container , 'Names' ) !== addPreviewDeploymentSuffix ( $this -> container_name , $this -> pull_request_id );
2024-08-06 11:05:34 +00:00
});
}
$containers -> each ( function ( $container ) {
$this -> graceful_shutdown_container ( data_get ( $container , 'Names' ));
});
2024-02-15 10:55:43 +00:00
}
2023-11-08 14:40:06 +00:00
} else {
2024-05-06 09:45:22 +00:00
if ( $this -> application -> dockerfile || $this -> application -> build_pack === 'dockerfile' || $this -> application -> build_pack === 'dockerimage' ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-05-06 09:45:22 +00:00
$this -> application_deployment_queue -> addLogEntry ( " WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI. " );
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
2024-05-06 09:45:22 +00:00
}
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'New container is not healthy, rolling back to the old container.' );
2025-10-26 08:06:44 +00:00
$this -> failDeployment ();
2024-07-29 07:57:13 +00:00
$this -> graceful_shutdown_container ( $this -> container_name );
2023-08-21 16:00:12 +00:00
}
2023-06-30 20:24:39 +00:00
}
2023-08-08 09:51:36 +00:00
private function start_by_compose_file ()
2023-06-30 20:24:39 +00:00
{
2023-11-16 14:23:07 +00:00
if ( $this -> application -> build_pack === 'dockerimage' ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Pulling latest images from the registry.' );
2023-11-16 12:22:12 +00:00
$this -> execute_remote_command (
2024-07-11 09:14:20 +00:00
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } pull " ), 'hidden' => true ],
[ executeInDocker ( $this -> deployment_uuid , " { $this -> coolify_variables } docker compose --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } up --build -d " ), 'hidden' => true ],
2023-11-16 12:22:12 +00:00
);
2023-11-16 14:23:07 +00:00
} else {
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
2023-12-06 08:36:11 +00:00
$this -> execute_remote_command (
2025-01-08 08:37:25 +00:00
[ " { $this -> coolify_variables } docker compose --project-name { $this -> application -> uuid } --project-directory { $this -> configuration_dir } -f { $this -> configuration_dir } { $this -> docker_compose_location } up --pull always --build -d " , 'hidden' => true ],
2023-12-06 08:36:11 +00:00
);
} else {
$this -> execute_remote_command (
2024-07-11 09:14:20 +00:00
[ executeInDocker ( $this -> deployment_uuid , " { $this -> coolify_variables } docker compose --project-name { $this -> application -> uuid } --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } up --build -d " ), 'hidden' => true ],
2023-12-06 08:36:11 +00:00
);
}
2023-11-13 11:30:25 +00:00
}
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'New container started.' );
2023-06-30 20:24:39 +00:00
}
2023-08-08 09:51:36 +00:00
2025-09-23 06:53:14 +00:00
private function analyzeBuildTimeVariables ( $variables )
{
2025-09-24 16:19:36 +00:00
$userDefinedVariables = collect ([]);
$dbVariables = $this -> pull_request_id === 0
? $this -> application -> environment_variables ()
-> where ( 'is_buildtime' , true )
-> pluck ( 'key' )
: $this -> application -> environment_variables_preview ()
-> where ( 'is_buildtime' , true )
-> pluck ( 'key' );
foreach ( $variables as $key => $value ) {
if ( $dbVariables -> contains ( $key )) {
$userDefinedVariables -> put ( $key , $value );
}
}
if ( $userDefinedVariables -> isEmpty ()) {
return ;
}
$variablesArray = $userDefinedVariables -> toArray ();
2025-09-23 06:53:14 +00:00
$warnings = self :: analyzeBuildVariables ( $variablesArray );
if ( empty ( $warnings )) {
return ;
}
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
foreach ( $warnings as $warning ) {
$messages = self :: formatBuildWarning ( $warning );
foreach ( $messages as $message ) {
$this -> application_deployment_queue -> addLogEntry ( $message , type : 'warning' );
}
$this -> application_deployment_queue -> addLogEntry ( '' );
}
// Add general advice
$this -> application_deployment_queue -> addLogEntry ( '💡 Tips to resolve build issues:' , type : 'info' );
$this -> application_deployment_queue -> addLogEntry ( ' 1. Set these variables as "Runtime only" in the environment variables settings' , type : 'info' );
$this -> application_deployment_queue -> addLogEntry ( ' 2. Use different values for build-time (e.g., NODE_ENV=development for build)' , type : 'info' );
$this -> application_deployment_queue -> addLogEntry ( ' 3. Consider using multi-stage Docker builds to separate build and runtime environments' , type : 'info' );
}
2023-08-08 09:51:36 +00:00
private function generate_build_env_variables ()
2023-03-31 13:51:50 +00:00
{
2025-01-22 14:13:40 +00:00
if ( $this -> application -> build_pack === 'nixpacks' ) {
$variables = collect ( $this -> nixpacks_plan_json -> get ( 'variables' ));
2023-08-08 09:51:36 +00:00
} else {
2025-01-22 14:13:40 +00:00
$this -> generate_env_variables ();
$variables = collect ([]) -> merge ( $this -> env_args );
2023-05-30 13:52:17 +00:00
}
2025-09-23 06:53:14 +00:00
// Analyze build variables for potential issues
if ( $variables -> isNotEmpty ()) {
$this -> analyzeBuildTimeVariables ( $variables );
}
2025-09-17 08:34:38 +00:00
if ( $this -> dockerBuildkitSupported && $this -> application -> settings -> use_build_secrets ) {
2025-09-16 15:16:01 +00:00
$this -> generate_build_secrets ( $variables );
$this -> build_args = '' ;
} else {
2025-09-22 07:44:30 +00:00
$secrets_hash = '' ;
if ( $variables -> isNotEmpty ()) {
$secrets_hash = $this -> generate_secrets_hash ( $variables );
}
2025-10-02 11:54:36 +00:00
$env_vars = $this -> pull_request_id === 0
? $this -> application -> environment_variables () -> where ( 'is_buildtime' , true ) -> get ()
: $this -> application -> environment_variables_preview () -> where ( 'is_buildtime' , true ) -> get ();
// Map variables to include is_multiline flag
$vars_with_metadata = $variables -> map ( function ( $value , $key ) use ( $env_vars ) {
$env = $env_vars -> firstWhere ( 'key' , $key );
return [
'key' => $key ,
'value' => $value ,
'is_multiline' => $env ? $env -> is_multiline : false ,
];
2025-09-16 15:16:01 +00:00
});
2025-09-22 07:44:30 +00:00
2025-10-02 11:54:36 +00:00
$this -> build_args = generateDockerBuildArgs ( $vars_with_metadata );
2025-09-22 07:44:30 +00:00
if ( $secrets_hash ) {
$this -> build_args -> push ( " --build-arg COOLIFY_BUILD_SECRETS_HASH= { $secrets_hash } " );
}
2025-09-16 15:16:01 +00:00
}
}
2025-09-17 08:08:29 +00:00
private function generate_docker_env_flags_for_secrets ()
2025-09-16 15:16:01 +00:00
{
2025-09-17 08:34:38 +00:00
// Only generate env flags if build secrets are enabled
if ( ! $this -> application -> settings -> use_build_secrets ) {
return '' ;
}
2025-10-03 07:20:05 +00:00
// Generate env variables if not already done
// This populates $this->env_args with both user-defined and COOLIFY_* variables
if ( ! $this -> env_args || $this -> env_args -> isEmpty ()) {
$this -> generate_env_variables ();
}
$variables = $this -> env_args ;
2025-09-16 15:16:01 +00:00
if ( $variables -> isEmpty ()) {
2025-09-17 08:08:29 +00:00
return '' ;
2025-09-16 15:16:01 +00:00
}
2025-09-22 07:44:30 +00:00
$secrets_hash = $this -> generate_secrets_hash ( $variables );
2025-09-16 15:16:01 +00:00
2025-10-03 07:20:05 +00:00
// Get database env vars to check for multiline flag
$env_vars = $this -> pull_request_id === 0
? $this -> application -> environment_variables () -> where ( 'is_buildtime' , true ) -> get ()
: $this -> application -> environment_variables_preview () -> where ( 'is_buildtime' , true ) -> get ();
2025-01-21 12:04:43 +00:00
2025-10-02 11:54:36 +00:00
// Map to simple array format for the helper function
2025-10-03 07:20:05 +00:00
$vars_array = $variables -> map ( function ( $value , $key ) use ( $env_vars ) {
$env = $env_vars -> firstWhere ( 'key' , $key );
2025-01-21 12:04:43 +00:00
2025-10-02 11:54:36 +00:00
return [
2025-10-03 07:20:05 +00:00
'key' => $key ,
'value' => $value ,
'is_multiline' => $env ? $env -> is_multiline : false ,
2025-10-02 11:54:36 +00:00
];
2024-12-18 11:02:56 +00:00
});
2025-09-22 07:44:30 +00:00
2025-10-02 11:54:36 +00:00
$env_flags = generateDockerEnvFlags ( $vars_array );
2025-09-22 07:44:30 +00:00
$env_flags .= " -e COOLIFY_BUILD_SECRETS_HASH= { $secrets_hash } " ;
return $env_flags ;
2025-09-16 15:16:01 +00:00
}
2025-09-17 08:08:29 +00:00
private function generate_build_secrets ( Collection $variables )
2025-09-16 15:16:01 +00:00
{
2025-09-17 08:08:29 +00:00
if ( $variables -> isEmpty ()) {
$this -> build_secrets = '' ;
return ;
2025-09-16 15:16:01 +00:00
}
2025-09-17 08:08:29 +00:00
$this -> build_secrets = $variables
-> map ( function ( $value , $key ) {
return " --secret id= { $key } ,env= { $key } " ;
})
-> implode ( ' ' );
2025-09-22 07:44:30 +00:00
$this -> build_secrets .= ' --secret id=COOLIFY_BUILD_SECRETS_HASH,env=COOLIFY_BUILD_SECRETS_HASH' ;
}
private function generate_secrets_hash ( $variables )
{
if ( ! $this -> secrets_hash_key ) {
$this -> secrets_hash_key = bin2hex ( random_bytes ( 32 ));
}
if ( $variables instanceof Collection ) {
$secrets_string = $variables
-> mapWithKeys ( function ( $value , $key ) {
return [ $key => $value ];
})
-> sortKeys ()
-> map ( function ( $value , $key ) {
return " { $key } = { $value } " ;
})
-> implode ( '|' );
} else {
$secrets_string = $variables
-> map ( function ( $env ) {
return " { $env -> key } = { $env -> real_value } " ;
})
-> sort ()
-> implode ( '|' );
}
return hash_hmac ( 'sha256' , $secrets_string , $this -> secrets_hash_key );
2023-08-08 09:51:36 +00:00
}
2023-05-10 11:05:32 +00:00
2025-11-06 07:54:35 +00:00
protected function findFromInstructionLines ( $dockerfile ) : array
{
$fromLines = [];
foreach ( $dockerfile as $index => $line ) {
$trimmedLine = trim ( $line );
// Check if line starts with FROM (case-insensitive)
if ( preg_match ( '/^FROM\s+/i' , $trimmedLine )) {
$fromLines [] = $index ;
}
}
return $fromLines ;
}
2023-08-08 09:51:36 +00:00
private function add_build_env_variables_to_dockerfile ()
{
2025-09-16 15:16:01 +00:00
if ( $this -> dockerBuildkitSupported ) {
2025-09-17 13:18:26 +00:00
// We dont need to add build secrets to dockerfile for buildkit, as we already added them with --secret flag in function generate_docker_env_flags_for_secrets
2025-09-16 15:16:01 +00:00
} else {
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } { $this -> dockerfile_location } " ),
'hidden' => true ,
'save' => 'dockerfile' ,
2025-10-04 17:19:15 +00:00
'ignore_errors' => true ,
2025-09-16 15:16:01 +00:00
]);
$dockerfile = collect ( str ( $this -> saved_outputs -> get ( 'dockerfile' )) -> trim () -> explode ( " \n " ));
2025-11-06 07:54:35 +00:00
// Find all FROM instruction positions
$fromLines = $this -> findFromInstructionLines ( $dockerfile );
// If no FROM instructions found, skip ARG insertion
if ( empty ( $fromLines )) {
return ;
}
// Collect all ARG statements to insert
$argsToInsert = collect ();
2025-09-16 15:16:01 +00:00
if ( $this -> pull_request_id === 0 ) {
2025-09-18 16:15:20 +00:00
// Only add environment variables that are available during build
$envs = $this -> application -> environment_variables ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true )
-> get ();
2025-09-16 15:16:01 +00:00
foreach ( $envs as $env ) {
if ( data_get ( $env , 'is_multiline' ) === true ) {
2025-11-06 07:54:35 +00:00
$argsToInsert -> push ( " ARG { $env -> key } " );
2025-09-16 15:16:01 +00:00
} else {
2025-11-06 07:54:35 +00:00
$argsToInsert -> push ( " ARG { $env -> key } = { $env -> real_value } " );
2025-09-16 15:16:01 +00:00
}
}
2025-10-01 18:29:38 +00:00
// Add Coolify variables as ARGs
if ( $this -> coolify_variables ) {
$coolify_vars = collect ( explode ( ' ' , trim ( $this -> coolify_variables )))
-> filter ()
-> map ( function ( $var ) {
return " ARG { $var } " ;
});
2025-11-06 07:54:35 +00:00
$argsToInsert = $argsToInsert -> merge ( $coolify_vars );
2025-10-01 18:29:38 +00:00
}
2025-09-16 15:16:01 +00:00
} else {
2025-09-18 16:15:20 +00:00
// Only add preview environment variables that are available during build
$envs = $this -> application -> environment_variables_preview ()
-> where ( 'key' , 'not like' , 'NIXPACKS_%' )
-> where ( 'is_buildtime' , true )
-> get ();
2025-09-16 15:16:01 +00:00
foreach ( $envs as $env ) {
if ( data_get ( $env , 'is_multiline' ) === true ) {
2025-11-06 07:54:35 +00:00
$argsToInsert -> push ( " ARG { $env -> key } " );
2025-09-16 15:16:01 +00:00
} else {
2025-11-06 07:54:35 +00:00
$argsToInsert -> push ( " ARG { $env -> key } = { $env -> real_value } " );
2025-09-16 15:16:01 +00:00
}
}
2025-10-01 18:29:38 +00:00
// Add Coolify variables as ARGs
if ( $this -> coolify_variables ) {
$coolify_vars = collect ( explode ( ' ' , trim ( $this -> coolify_variables )))
-> filter ()
-> map ( function ( $var ) {
return " ARG { $var } " ;
});
2025-11-06 07:54:35 +00:00
$argsToInsert = $argsToInsert -> merge ( $coolify_vars );
2025-10-01 18:29:38 +00:00
}
2025-09-16 15:16:01 +00:00
}
2025-09-22 07:44:30 +00:00
2025-11-06 07:54:35 +00:00
// Insert ARGs after each FROM instruction (in reverse order to maintain correct line numbers)
if ( $argsToInsert -> isNotEmpty ()) {
foreach ( array_reverse ( $fromLines ) as $fromLineIndex ) {
// Insert all ARGs after this FROM instruction
foreach ( $argsToInsert -> reverse () as $arg ) {
$dockerfile -> splice ( $fromLineIndex + 1 , 0 , [ $arg ]);
}
}
2025-11-06 08:21:41 +00:00
$envs_mapped = $envs -> mapWithKeys ( function ( $env ) {
return [ $env -> key => $env -> real_value ];
});
$secrets_hash = $this -> generate_secrets_hash ( $envs_mapped );
2025-11-06 08:24:54 +00:00
$argsToInsert -> push ( " ARG COOLIFY_BUILD_SECRETS_HASH= { $secrets_hash } " );
2025-09-22 07:44:30 +00:00
}
2025-09-16 15:16:01 +00:00
$dockerfile_base64 = base64_encode ( $dockerfile -> implode ( " \n " ));
2025-10-09 14:38:17 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Final Dockerfile:' , type : 'info' , hidden : true );
2025-10-04 17:19:15 +00:00
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile_base64 } ' | base64 -d | tee { $this -> workdir } { $this -> dockerfile_location } > /dev/null " ),
'hidden' => true ,
],
[
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } { $this -> dockerfile_location } " ),
'hidden' => true ,
'ignore_errors' => true ,
]);
2025-09-16 15:16:01 +00:00
}
}
2025-09-17 16:46:10 +00:00
private function modify_dockerfile_for_secrets ( $dockerfile_path )
2025-09-16 15:16:01 +00:00
{
2025-09-17 08:34:38 +00:00
// Only process if build secrets are enabled and we have secrets to mount
if ( ! $this -> application -> settings -> use_build_secrets || empty ( $this -> build_secrets )) {
2025-09-16 15:16:01 +00:00
return ;
}
2025-09-17 16:46:10 +00:00
// Read the Dockerfile
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command ([
2025-09-16 15:16:01 +00:00
executeInDocker ( $this -> deployment_uuid , " cat { $dockerfile_path } " ),
2024-08-28 16:12:00 +00:00
'hidden' => true ,
2025-09-17 16:46:10 +00:00
'save' => 'dockerfile_content' ,
2023-08-08 09:51:36 +00:00
]);
2025-09-16 15:16:01 +00:00
2025-09-17 16:46:10 +00:00
$dockerfile = str ( $this -> saved_outputs -> get ( 'dockerfile_content' )) -> trim () -> explode ( " \n " );
2025-09-16 15:16:01 +00:00
// Add BuildKit syntax directive if not present
2025-09-17 16:46:10 +00:00
if ( ! str_starts_with ( $dockerfile -> first (), '# syntax=' )) {
2025-09-16 15:16:01 +00:00
$dockerfile -> prepend ( '# syntax=docker/dockerfile:1' );
}
2025-10-03 07:20:05 +00:00
// Generate env variables if not already done
// This populates $this->env_args with both user-defined and COOLIFY_* variables
if ( ! $this -> env_args || $this -> env_args -> isEmpty ()) {
$this -> generate_env_variables ();
}
$variables = $this -> env_args ;
2025-09-17 16:46:10 +00:00
if ( $variables -> isEmpty ()) {
return ;
}
// Generate mount strings for all secrets
2025-10-03 07:20:05 +00:00
$mountStrings = $variables -> map ( fn ( $value , $key ) => " --mount=type=secret,id= { $key } ,env= { $key } " ) -> implode ( ' ' );
2025-09-17 16:46:10 +00:00
2025-09-22 07:44:30 +00:00
// Add mount for the secrets hash to ensure cache invalidation
$mountStrings .= ' --mount=type=secret,id=COOLIFY_BUILD_SECRETS_HASH,env=COOLIFY_BUILD_SECRETS_HASH' ;
2025-09-16 15:16:01 +00:00
$modified = false ;
2025-09-17 16:46:10 +00:00
$dockerfile = $dockerfile -> map ( function ( $line ) use ( $mountStrings , & $modified ) {
$trimmed = ltrim ( $line );
2025-09-16 15:16:01 +00:00
2025-09-17 16:46:10 +00:00
// Skip lines that already have secret mounts or are not RUN commands
if ( str_contains ( $line , '--mount=type=secret' ) || ! str_starts_with ( $trimmed , 'RUN' )) {
2025-09-17 08:08:29 +00:00
return $line ;
}
2025-09-17 16:46:10 +00:00
// Add mount strings to RUN command
$originalCommand = trim ( substr ( $trimmed , 3 ));
$modified = true ;
2025-09-16 15:16:01 +00:00
2025-09-17 16:46:10 +00:00
return " RUN { $mountStrings } { $originalCommand } " ;
2025-09-16 15:16:01 +00:00
});
if ( $modified ) {
// Write the modified Dockerfile back
$dockerfile_base64 = base64_encode ( $dockerfile -> implode ( " \n " ));
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile_base64 } ' | base64 -d | tee { $dockerfile_path } > /dev/null " ),
'hidden' => true ,
]);
}
}
2025-09-19 11:46:00 +00:00
private function modify_dockerfiles_for_compose ( $composeFile )
{
if ( $this -> application -> build_pack !== 'dockercompose' ) {
return ;
}
2025-10-03 07:20:05 +00:00
// Generate env variables if not already done
// This populates $this->env_args with both user-defined and COOLIFY_* variables
if ( ! $this -> env_args || $this -> env_args -> isEmpty ()) {
$this -> generate_env_variables ();
}
$variables = $this -> env_args ;
2025-09-19 11:46:00 +00:00
if ( $variables -> isEmpty ()) {
$this -> application_deployment_queue -> addLogEntry ( 'No build-time variables to add to Dockerfiles.' );
return ;
}
$services = data_get ( $composeFile , 'services' , []);
foreach ( $services as $serviceName => $service ) {
if ( ! isset ( $service [ 'build' ])) {
continue ;
}
$context = '.' ;
$dockerfile = 'Dockerfile' ;
if ( is_string ( $service [ 'build' ])) {
$context = $service [ 'build' ];
} elseif ( is_array ( $service [ 'build' ])) {
$context = data_get ( $service [ 'build' ], 'context' , '.' );
$dockerfile = data_get ( $service [ 'build' ], 'dockerfile' , 'Dockerfile' );
}
$dockerfilePath = rtrim ( $context , '/' ) . '/' . ltrim ( $dockerfile , '/' );
if ( str_starts_with ( $dockerfilePath , './' )) {
$dockerfilePath = substr ( $dockerfilePath , 2 );
}
if ( str_starts_with ( $dockerfilePath , '/' )) {
$dockerfilePath = substr ( $dockerfilePath , 1 );
}
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " test -f { $this -> workdir } / { $dockerfilePath } && echo 'exists' || echo 'not found' " ),
'hidden' => true ,
'save' => 'dockerfile_check_' . $serviceName ,
]);
if ( str ( $this -> saved_outputs -> get ( 'dockerfile_check_' . $serviceName )) -> trim () -> toString () !== 'exists' ) {
$this -> application_deployment_queue -> addLogEntry ( " Dockerfile not found for service { $serviceName } at { $dockerfilePath } , skipping ARG injection. " );
continue ;
}
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } / { $dockerfilePath } " ),
'hidden' => true ,
'save' => 'dockerfile_content_' . $serviceName ,
]);
$dockerfileContent = $this -> saved_outputs -> get ( 'dockerfile_content_' . $serviceName );
if ( ! $dockerfileContent ) {
continue ;
}
$dockerfile_lines = collect ( str ( $dockerfileContent ) -> trim () -> explode ( " \n " ));
$fromIndices = [];
$dockerfile_lines -> each ( function ( $line , $index ) use ( & $fromIndices ) {
if ( str ( $line ) -> trim () -> startsWith ( 'FROM' )) {
$fromIndices [] = $index ;
2024-06-09 20:37:23 +00:00
}
2025-09-19 11:46:00 +00:00
});
if ( empty ( $fromIndices )) {
$this -> application_deployment_queue -> addLogEntry ( " No FROM instruction found in Dockerfile for service { $serviceName } , skipping. " );
continue ;
2023-11-28 10:10:42 +00:00
}
2025-09-19 11:46:00 +00:00
$isMultiStage = count ( $fromIndices ) > 1 ;
$argsToAdd = collect ([]);
2025-10-03 07:20:05 +00:00
foreach ( $variables as $key => $value ) {
$argsToAdd -> push ( " ARG { $key } " );
2025-09-19 11:46:00 +00:00
}
if ( $argsToAdd -> isEmpty ()) {
$this -> application_deployment_queue -> addLogEntry ( " Service { $serviceName } : No build-time variables to add. " );
continue ;
}
$totalAdded = 0 ;
$offset = 0 ;
foreach ( $fromIndices as $stageIndex => $fromIndex ) {
$adjustedIndex = $fromIndex + $offset ;
$stageStart = $adjustedIndex + 1 ;
$stageEnd = isset ( $fromIndices [ $stageIndex + 1 ])
? $fromIndices [ $stageIndex + 1 ] + $offset
: $dockerfile_lines -> count ();
$existingStageArgs = collect ([]);
for ( $i = $stageStart ; $i < $stageEnd ; $i ++ ) {
$line = $dockerfile_lines -> get ( $i );
if ( ! $line || ! str ( $line ) -> trim () -> startsWith ( 'ARG' )) {
break ;
}
$parts = explode ( ' ' , trim ( $line ), 2 );
if ( count ( $parts ) >= 2 ) {
$argPart = $parts [ 1 ];
$keyValue = explode ( '=' , $argPart , 2 );
$existingStageArgs -> push ( $keyValue [ 0 ]);
}
}
$stageArgsToAdd = $argsToAdd -> filter ( function ( $arg ) use ( $existingStageArgs ) {
$key = str ( $arg ) -> after ( 'ARG ' ) -> trim () -> toString ();
return ! $existingStageArgs -> contains ( $key );
});
if ( $stageArgsToAdd -> isNotEmpty ()) {
$dockerfile_lines -> splice ( $adjustedIndex + 1 , 0 , $stageArgsToAdd -> toArray ());
$totalAdded += $stageArgsToAdd -> count ();
$offset += $stageArgsToAdd -> count ();
}
}
if ( $totalAdded > 0 ) {
$dockerfile_base64 = base64_encode ( $dockerfile_lines -> implode ( " \n " ));
$this -> execute_remote_command ([
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile_base64 } ' | base64 -d | tee { $this -> workdir } / { $dockerfilePath } > /dev/null " ),
'hidden' => true ,
]);
$stageInfo = $isMultiStage ? ' (multi-stage build, added to ' . count ( $fromIndices ) . ' stages)' : '' ;
$this -> application_deployment_queue -> addLogEntry ( " Added { $totalAdded } ARG declarations to Dockerfile for service { $serviceName } { $stageInfo } . " );
} else {
$this -> application_deployment_queue -> addLogEntry ( " Service { $serviceName } : All required ARG declarations already exist. " );
}
if ( $this -> application -> settings -> use_build_secrets && $this -> dockerBuildkitSupported && ! empty ( $this -> build_secrets )) {
$fullDockerfilePath = " { $this -> workdir } / { $dockerfilePath } " ;
$this -> modify_dockerfile_for_secrets ( $fullDockerfilePath );
$this -> application_deployment_queue -> addLogEntry ( " Modified Dockerfile for service { $serviceName } to use build secrets. " );
}
}
}
2025-09-16 15:16:01 +00:00
private function add_build_secrets_to_compose ( $composeFile )
{
2025-10-03 07:20:05 +00:00
// Generate env variables if not already done
// This populates $this->env_args with both user-defined and COOLIFY_* variables
if ( ! $this -> env_args || $this -> env_args -> isEmpty ()) {
$this -> generate_env_variables ();
}
$variables = $this -> env_args ;
2025-09-16 15:16:01 +00:00
if ( $variables -> isEmpty ()) {
return $composeFile ;
}
$secrets = [];
2025-10-03 07:20:05 +00:00
foreach ( $variables as $key => $value ) {
$secrets [ $key ] = [
'environment' => $key ,
2025-09-16 15:16:01 +00:00
];
}
$services = data_get ( $composeFile , 'services' , []);
foreach ( $services as $serviceName => & $service ) {
if ( isset ( $service [ 'build' ])) {
if ( is_string ( $service [ 'build' ])) {
$service [ 'build' ] = [
'context' => $service [ 'build' ],
];
}
if ( ! isset ( $service [ 'build' ][ 'secrets' ])) {
$service [ 'build' ][ 'secrets' ] = [];
}
2025-10-03 07:20:05 +00:00
foreach ( $variables as $key => $value ) {
if ( ! in_array ( $key , $service [ 'build' ][ 'secrets' ])) {
$service [ 'build' ][ 'secrets' ][] = $key ;
2025-09-16 15:16:01 +00:00
}
2024-06-09 20:37:23 +00:00
}
2023-11-28 10:10:42 +00:00
}
2023-03-31 13:51:50 +00:00
}
2025-09-16 15:16:01 +00:00
$composeFile [ 'services' ] = $services ;
2025-09-16 16:26:12 +00:00
$existingSecrets = data_get ( $composeFile , 'secrets' , []);
2025-09-17 08:08:29 +00:00
if ( $existingSecrets instanceof \Illuminate\Support\Collection ) {
$existingSecrets = $existingSecrets -> toArray ();
}
2025-09-16 16:26:12 +00:00
$composeFile [ 'secrets' ] = array_replace ( $existingSecrets , $secrets );
2025-09-16 15:16:01 +00:00
2025-09-17 08:08:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Added build secrets configuration to docker-compose file (using environment variables).' );
2025-09-16 15:16:01 +00:00
return $composeFile ;
2023-08-08 09:51:36 +00:00
}
2024-02-08 10:02:30 +00:00
private function run_pre_deployment_command ()
{
if ( empty ( $this -> application -> pre_deployment_command )) {
return ;
}
$containers = getCurrentApplicationContainerStatus ( $this -> server , $this -> application -> id , $this -> pull_request_id );
if ( $containers -> count () == 0 ) {
return ;
}
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Executing pre-deployment command (see debug log for output/errors).' );
2024-02-08 10:02:30 +00:00
foreach ( $containers as $container ) {
$containerName = data_get ( $container , 'Names' );
2024-06-13 11:15:09 +00:00
if ( $containers -> count () == 1 || str_starts_with ( $containerName , $this -> application -> pre_deployment_command_container . '-' . $this -> application -> uuid )) {
$cmd = " sh -c ' " . str_replace ( " ' " , " ' \ '' " , $this -> application -> pre_deployment_command ) . " ' " ;
2024-02-08 10:02:30 +00:00
$exec = " docker exec { $containerName } { $cmd } " ;
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
'command' => $exec ,
'hidden' => true ,
2024-02-08 10:02:30 +00:00
],
);
2024-06-10 20:43:34 +00:00
2024-02-08 10:02:30 +00:00
return ;
}
}
2024-03-13 13:41:31 +00:00
throw new RuntimeException ( 'Pre-deployment command: Could not find a valid container. Is the container name correct?' );
2024-02-08 10:02:30 +00:00
}
2024-02-08 09:27:43 +00:00
private function run_post_deployment_command ()
{
if ( empty ( $this -> application -> post_deployment_command )) {
return ;
}
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( '----------------------------------------' );
$this -> application_deployment_queue -> addLogEntry ( 'Executing post-deployment command (see debug log for output).' );
2024-02-08 09:27:43 +00:00
$containers = getCurrentApplicationContainerStatus ( $this -> server , $this -> application -> id , $this -> pull_request_id );
foreach ( $containers as $container ) {
$containerName = data_get ( $container , 'Names' );
2024-06-13 11:15:09 +00:00
if ( $containers -> count () == 1 || str_starts_with ( $containerName , $this -> application -> post_deployment_command_container . '-' . $this -> application -> uuid )) {
$cmd = " sh -c ' " . str_replace ( " ' " , " ' \ '' " , $this -> application -> post_deployment_command ) . " ' " ;
2024-02-08 09:27:43 +00:00
$exec = " docker exec { $containerName } { $cmd } " ;
2024-06-06 13:13:21 +00:00
try {
$this -> execute_remote_command (
[
2024-08-28 16:12:00 +00:00
'command' => $exec ,
'hidden' => true ,
'save' => 'post-deployment-command-output' ,
2024-06-06 13:13:21 +00:00
],
);
} catch ( Exception $e ) {
$post_deployment_command_output = $this -> saved_outputs -> get ( 'post-deployment-command-output' );
if ( $post_deployment_command_output ) {
2024-06-10 20:43:34 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Post-deployment command failed.' );
2024-06-06 13:13:21 +00:00
$this -> application_deployment_queue -> addLogEntry ( $post_deployment_command_output , 'stderr' );
}
}
2024-02-08 09:27:43 +00:00
return ;
}
}
2024-03-13 13:41:31 +00:00
throw new RuntimeException ( 'Post-deployment command: Could not find a valid container. Is the container name correct?' );
2024-02-08 09:27:43 +00:00
}
2025-09-16 11:40:51 +00:00
/**
* Check if the deployment was cancelled and abort if it was
*/
private function checkForCancellation () : void
{
$this -> application_deployment_queue -> refresh ();
if ( $this -> application_deployment_queue -> status === ApplicationDeploymentStatus :: CANCELLED_BY_USER -> value ) {
$this -> application_deployment_queue -> addLogEntry ( 'Deployment cancelled by user, stopping execution.' );
throw new \RuntimeException ( 'Deployment cancelled by user' , 69420 );
}
}
2025-10-26 08:06:44 +00:00
/**
* Transition deployment to a new status with proper validation and side effects .
* This is the single source of truth for status transitions .
*/
private function transitionToStatus ( ApplicationDeploymentStatus $status ) : void
{
if ( $this -> isInTerminalState ()) {
return ;
}
$this -> updateDeploymentStatus ( $status );
$this -> handleStatusTransition ( $status );
queue_next_deployment ( $this -> application );
}
/**
* Check if deployment is in a terminal state ( FAILED or CANCELLED ) .
* Terminal states cannot be changed .
*/
private function isInTerminalState () : bool
2023-08-08 09:51:36 +00:00
{
2025-09-16 11:40:51 +00:00
$this -> application_deployment_queue -> refresh ();
2025-04-11 17:32:41 +00:00
2025-05-29 15:02:08 +00:00
if ( $this -> application_deployment_queue -> status === ApplicationDeploymentStatus :: FAILED -> value ) {
2025-10-26 08:06:44 +00:00
return true ;
2025-05-29 15:02:08 +00:00
}
2025-10-26 08:06:44 +00:00
2025-05-29 15:02:08 +00:00
if ( $this -> application_deployment_queue -> status === ApplicationDeploymentStatus :: CANCELLED_BY_USER -> value ) {
2025-09-16 11:40:51 +00:00
$this -> application_deployment_queue -> addLogEntry ( 'Deployment cancelled by user, stopping execution.' );
throw new \RuntimeException ( 'Deployment cancelled by user' , 69420 );
2023-08-08 09:51:36 +00:00
}
2025-04-11 17:32:41 +00:00
2025-10-26 08:06:44 +00:00
return false ;
}
/**
* Update the deployment status in the database .
*/
private function updateDeploymentStatus ( ApplicationDeploymentStatus $status ) : void
{
2025-04-11 17:32:41 +00:00
$this -> application_deployment_queue -> update ([
2025-10-26 08:06:44 +00:00
'status' => $status -> value ,
2025-04-11 17:32:41 +00:00
]);
2025-10-26 08:06:44 +00:00
}
2025-04-11 17:32:41 +00:00
2025-10-26 08:06:44 +00:00
/**
* Execute status - specific side effects ( events , notifications , additional deployments ) .
*/
private function handleStatusTransition ( ApplicationDeploymentStatus $status ) : void
{
match ( $status ) {
ApplicationDeploymentStatus :: FINISHED => $this -> handleSuccessfulDeployment (),
ApplicationDeploymentStatus :: FAILED => $this -> handleFailedDeployment (),
default => null ,
};
}
2025-09-10 11:26:35 +00:00
2025-10-26 08:06:44 +00:00
/**
* Handle side effects when deployment succeeds .
*/
private function handleSuccessfulDeployment () : void
{
event ( new ApplicationConfigurationChanged ( $this -> application -> team () -> id ));
2025-09-22 07:44:30 +00:00
2025-10-26 08:06:44 +00:00
if ( ! $this -> only_this_server ) {
$this -> deploy_to_additional_destinations ();
2023-03-31 13:51:50 +00:00
}
2025-10-26 08:06:44 +00:00
$this -> sendDeploymentNotification ( DeploymentSuccess :: class );
}
/**
* Handle side effects when deployment fails .
*/
private function handleFailedDeployment () : void
{
$this -> sendDeploymentNotification ( DeploymentFailed :: class );
}
/**
* Send deployment status notification to the team .
*/
private function sendDeploymentNotification ( string $notificationClass ) : void
{
$this -> application -> environment -> project -> team ? -> notify (
new $notificationClass ( $this -> application , $this -> deployment_uuid , $this -> preview )
);
}
/**
* Complete deployment successfully .
* Sends success notification and triggers additional deployments if needed .
*/
private function completeDeployment () : void
{
$this -> transitionToStatus ( ApplicationDeploymentStatus :: FINISHED );
}
/**
* Fail the deployment .
* Sends failure notification and queues next deployment .
*/
private function failDeployment () : void
{
$this -> transitionToStatus ( ApplicationDeploymentStatus :: FAILED );
}
2023-08-08 09:51:36 +00:00
public function failed ( Throwable $exception ) : void
2023-05-24 12:26:50 +00:00
{
2025-10-26 08:06:44 +00:00
$this -> failDeployment ();
2025-11-09 13:41:35 +00:00
$errorMessage = $exception -> getMessage () ? : 'Unknown error occurred' ;
$this -> application_deployment_queue -> addLogEntry ( " Deployment failed: { $errorMessage } " , 'stderr' );
2024-01-16 14:19:14 +00:00
2023-11-28 13:08:42 +00:00
if ( $this -> application -> build_pack !== 'dockercompose' ) {
2024-02-06 14:05:11 +00:00
$code = $exception -> getCode ();
if ( $code !== 69420 ) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
2025-09-18 09:30:49 +00:00
if ( $this -> application -> settings -> is_consistent_container_name_enabled || str ( $this -> application -> settings -> custom_internal_name ) -> isNotEmpty () || $this -> pull_request_id !== 0 ) {
// do not remove already running container for PR deployments
2024-06-13 12:48:23 +00:00
} else {
$this -> application_deployment_queue -> addLogEntry ( 'Deployment failed. Removing the new version of your application.' , 'stderr' );
$this -> execute_remote_command (
[ " docker rm -f $this->container_name >/dev/null 2>&1 " , 'hidden' => true , 'ignore_errors' => true ]
);
}
2024-02-06 14:05:11 +00:00
}
2023-11-28 13:08:42 +00:00
}
2023-05-24 12:26:50 +00:00
}
2023-08-08 09:51:36 +00:00
}