2023-03-31 11:32:07 +00:00
< ? php
namespace App\Jobs ;
2023-06-30 20:24:39 +00:00
use App\Enums\ApplicationDeploymentStatus ;
2024-01-26 17:46:50 +00:00
use App\Enums\ProcessStatus ;
2023-12-11 12:43:16 +00:00
use App\Events\ApplicationStatusChanged ;
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 ;
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 ;
2023-06-30 20:24:39 +00:00
use App\Traits\ExecuteRemoteCommand ;
2023-08-09 12:44:36 +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 ;
2023-11-20 12:49:10 +00:00
use RuntimeException ;
2023-08-08 09:51:36 +00:00
use Spatie\Url\Url ;
2023-06-30 20:24:39 +00:00
use Symfony\Component\Yaml\Yaml ;
2023-06-28 16:20:01 +00:00
use Throwable ;
2023-05-30 13:52:17 +00:00
use Visus\Cuid2\Cuid2 ;
2024-01-08 15:33:34 +00:00
use Yosymfony\Toml\Toml ;
2023-03-31 11:32:07 +00:00
2023-09-14 08:12:44 +00:00
class ApplicationDeploymentJob implements ShouldQueue , ShouldBeEncrypted
2023-03-31 11:32:07 +00:00
{
2023-06-30 20:24:39 +00:00
use Dispatchable , InteractsWithQueue , Queueable , SerializesModels , ExecuteRemoteCommand ;
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 ;
private int $application_deployment_queue_id ;
2023-03-31 11:32:07 +00:00
2023-09-22 13:29:19 +00:00
private bool $newVersionIsHealthy = false ;
2023-05-24 12:26:50 +00:00
private ApplicationDeploymentQueue $application_deployment_queue ;
2023-06-30 20:24:39 +00:00
private Application $application ;
private string $deployment_uuid ;
private int $pull_request_id ;
private string $commit ;
private bool $force_rebuild ;
2023-11-01 11:19:08 +00:00
private bool $restart_only ;
2023-06-30 20:24:39 +00:00
2023-10-10 09:16:38 +00:00
private ? string $dockerImage = null ;
private ? string $dockerImageTag = null ;
2023-10-06 11:46:42 +00:00
private GithubApp | GitlabApp | string $source = 'other' ;
2023-06-30 20:24:39 +00:00
private StandaloneDocker | SwarmDocker $destination ;
2024-01-16 14:19:14 +00:00
// Deploy to Server
2023-06-30 20:24:39 +00:00
private Server $server ;
2024-01-16 14:19:14 +00:00
// Build Server
private Server $build_server ;
private bool $use_build_server = false ;
// Save original server between phases
private Server $original_server ;
2023-11-21 14:31:46 +00:00
private Server $mainServer ;
2023-11-08 14:40:06 +00:00
private ? ApplicationPreview $preview = null ;
2023-11-14 12:26:14 +00:00
private ? string $git_type = null ;
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 ;
2023-10-18 08:32:08 +00:00
private ? string $currently_running_container_name = null ;
2023-10-06 08:07:25 +00:00
private string $basedir ;
2023-05-24 12:26:50 +00:00
private string $workdir ;
2023-10-10 12:02:43 +00:00
private ? string $build_pack = null ;
2023-08-09 12:44:36 +00:00
private string $configuration_dir ;
2023-05-30 13:52:17 +00:00
private string $build_image_name ;
private string $production_image_name ;
2023-06-30 20:24:39 +00:00
private bool $is_debug_enabled ;
private $build_args ;
private $env_args ;
2024-01-11 11:56:02 +00:00
private $env_nixpacks_args ;
2023-06-30 20:24:39 +00:00
private $docker_compose ;
2023-08-09 12:44:36 +00:00
private $docker_compose_base64 ;
2024-01-08 15:33:34 +00:00
private ? string $nixpacks_plan = null ;
private ? string $nixpacks_type = null ;
2023-10-10 12:02:43 +00:00
private string $dockerfile_location = '/Dockerfile' ;
2023-11-24 14:48:23 +00:00
private string $docker_compose_location = '/docker-compose.yml' ;
2023-12-17 19:56:12 +00:00
private ? string $docker_compose_custom_start_command = null ;
private ? string $docker_compose_custom_build_command = null ;
2023-10-17 09:23:49 +00:00
private ? string $addHosts = null ;
2023-11-07 12:49:15 +00:00
private ? string $buildTarget = null ;
2023-06-30 20:24:39 +00:00
private Collection $saved_outputs ;
2023-11-16 14:23:07 +00:00
private ? string $full_healthcheck_url = null ;
2024-03-13 09:50:05 +00:00
private bool $custom_healthcheck_found = false ;
2023-08-08 09:51:36 +00:00
2023-10-17 10:35:04 +00:00
private string $serverUser = 'root' ;
private string $serverUserHomeDir = '/root' ;
2023-10-17 12:23:07 +00:00
private string $dockerConfigFileExists = 'NOK' ;
2023-10-17 10:35:04 +00:00
2023-10-18 13:33:07 +00:00
private int $customPort = 22 ;
2023-11-08 10:30:54 +00:00
private ? string $customRepository = null ;
2023-10-18 13:33:07 +00:00
2023-10-26 08:33:57 +00:00
private ? string $fullRepoUrl = null ;
private ? string $branch = null ;
2023-09-04 14:03:11 +00:00
public $tries = 1 ;
2023-06-30 20:24:39 +00:00
public function __construct ( int $application_deployment_queue_id )
{
$this -> application_deployment_queue = ApplicationDeploymentQueue :: find ( $application_deployment_queue_id );
$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' );
2023-06-30 20:24:39 +00:00
$this -> application_deployment_queue_id = $application_deployment_queue_id ;
$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 ;
$this -> force_rebuild = $this -> application_deployment_queue -> force_rebuild ;
2023-11-01 11:19:08 +00:00
$this -> restart_only = $this -> application_deployment_queue -> restart_only ;
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 ();
2023-11-21 14:31:46 +00:00
$this -> server = $this -> mainServer = $this -> destination -> server ;
2023-10-17 10:35:04 +00:00
$this -> serverUser = $this -> server -> user ;
2023-11-24 14:48:23 +00:00
$this -> basedir = $this -> application -> generateBaseDir ( $this -> deployment_uuid );
2023-10-06 08:07:25 +00:00
$this -> workdir = " { $this -> basedir } " . rtrim ( $this -> application -> base_directory , '/' );
2023-08-09 12:44:36 +00:00
$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-02-15 10:55:43 +00:00
ray ( 'New container name: ' , $this -> container_name );
2023-09-18 11:01:01 +00:00
savePrivateKeyToFs ( $this -> server );
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 ) {
2023-05-30 13:52:17 +00:00
$this -> preview = ApplicationPreview :: findPreviewByApplicationAndPullId ( $this -> application -> id , $this -> pull_request_id );
2023-06-30 20:24:39 +00:00
if ( $this -> application -> fqdn ) {
2023-11-13 12:20:12 +00:00
if ( str ( $this -> application -> fqdn ) -> contains ( ',' )) {
$url = Url :: fromString ( str ( $this -> application -> fqdn ) -> explode ( ',' )[ 0 ]);
$preview_fqdn = getFqdnWithoutPort ( str ( $this -> application -> fqdn ) -> explode ( ',' )[ 0 ]);
} else {
$url = Url :: fromString ( $this -> application -> fqdn );
if ( data_get ( $this -> preview , 'fqdn' )) {
$preview_fqdn = getFqdnWithoutPort ( data_get ( $this -> preview , 'fqdn' ));
}
2023-10-01 10:02:44 +00:00
}
2023-06-30 20:24:39 +00:00
$template = $this -> application -> preview_url_template ;
$host = $url -> getHost ();
$schema = $url -> getScheme ();
$random = new Cuid2 ( 7 );
$preview_fqdn = str_replace ( '{{random}}' , $random , $template );
$preview_fqdn = str_replace ( '{{domain}}' , $host , $preview_fqdn );
$preview_fqdn = str_replace ( '{{pr_id}}' , $this -> pull_request_id , $preview_fqdn );
$preview_fqdn = " $schema :// $preview_fqdn " ;
$this -> preview -> fqdn = $preview_fqdn ;
$this -> preview -> save ();
}
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
{
2024-02-26 13:22:24 +00:00
try {
// Generate custom host<->ip mapping
$allContainers = instant_remote_process ([ " docker network inspect { $this -> destination -> network } -f ' { { json .Containers}}' " ], $this -> server );
if ( ! is_null ( $allContainers )) {
$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-02-26 13:22:24 +00:00
$this -> addHosts = $ips -> map ( function ( $ip , $name ) {
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 ) {
$this -> buildTarget = " --target { $this -> application -> dockerfile_target_build } " ;
}
2023-11-07 12:49:15 +00:00
2024-02-26 13:22:24 +00:00
// Check custom port
[ '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-03-01 10:43:42 +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-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 ;
}
2023-11-14 18:29:59 +00:00
if ( $this -> restart_only && $this -> application -> build_pack !== 'dockerimage' ) {
2023-11-01 11:19:08 +00:00
$this -> just_restart ();
2023-11-20 12:49:10 +00:00
if ( $this -> server -> isProxyShouldRun ()) {
dispatch ( new ContainerStatusJob ( $this -> server ));
}
$this -> next ( ApplicationDeploymentStatus :: FINISHED -> value );
2024-01-11 13:34:48 +00:00
$this -> application -> isConfigurationChanged ( false );
2024-03-13 13:41:31 +00:00
$this -> run_post_deployment_command ();
2023-11-20 12:49:10 +00:00
return ;
2024-01-29 09:43:18 +00:00
} else if ( $this -> pull_request_id !== 0 ) {
$this -> deploy_pull_request ();
2023-11-01 11:19:08 +00:00
} else if ( $this -> application -> dockerfile ) {
2023-08-11 20:41:47 +00:00
$this -> deploy_simple_dockerfile ();
2023-11-24 14:48:23 +00:00
} else if ( $this -> application -> build_pack === 'dockercompose' ) {
$this -> deploy_docker_compose_buildpack ();
2023-10-10 09:16:38 +00:00
} else if ( $this -> application -> build_pack === 'dockerimage' ) {
2023-10-12 10:01:09 +00:00
$this -> deploy_dockerimage_buildpack ();
2023-10-10 12:02:43 +00:00
} else if ( $this -> application -> build_pack === 'dockerfile' ) {
2023-10-12 10:01:09 +00:00
$this -> deploy_dockerfile_buildpack ();
2023-11-10 10:33:15 +00:00
} else if ( $this -> application -> build_pack === 'static' ) {
$this -> deploy_static_buildpack ();
2023-05-30 13:52:17 +00:00
} else {
2024-01-29 09:43:18 +00:00
$this -> deploy_nixpacks_buildpack ();
2023-05-30 13:52:17 +00:00
}
2023-09-14 10:45:50 +00:00
if ( $this -> server -> isProxyShouldRun ()) {
dispatch ( new ContainerStatusJob ( $this -> server ));
}
2024-01-16 14:19:14 +00:00
// Otherwise built image needs to be pushed before from the build server.
2024-02-07 13:55:06 +00:00
// ray($this->use_build_server);
// if (!$this->use_build_server) {
// if ($this->application->additional_servers->count() > 0) {
// $this->push_to_docker_registry(forceFail: true);
// } else {
// $this->push_to_docker_registry();
// }
// }
2023-06-30 20:24:39 +00:00
$this -> next ( ApplicationDeploymentStatus :: FINISHED -> value );
2024-01-26 17:46:50 +00:00
if ( $this -> pull_request_id !== 0 ) {
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 :: FINISHED );
}
2024-01-26 17:46:50 +00:00
}
2024-02-08 09:27:43 +00:00
$this -> run_post_deployment_command ();
2023-10-18 09:20:40 +00:00
$this -> application -> isConfigurationChanged ( true );
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
}
2024-01-29 09:43:18 +00:00
ray ( $e );
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 {
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
} else {
$this -> write_deployment_configurations ();
2023-07-07 12:56:20 +00:00
}
2024-02-19 08:51:39 +00:00
$this -> execute_remote_command (
[
" docker rm -f { $this -> deployment_uuid } >/dev/null 2>&1 " ,
" hidden " => true ,
" ignore_errors " => true ,
]
);
2024-03-19 10:48:25 +00:00
2024-03-13 09:44:15 +00:00
// $this->execute_remote_command(
// [
// "docker image prune -f >/dev/null 2>&1",
// "hidden" => true,
// "ignore_errors" => true,
// ]
// );
2024-03-19 10:48:25 +00:00
2023-12-12 14:48:51 +00:00
ApplicationStatusChanged :: dispatch ( data_get ( $this -> application , 'environment.project.team.id' ));
2023-05-30 13:52:17 +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-01-16 14:19:14 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' $dockerfile_base64 ' | base64 -d > { $this -> workdir } { $this -> dockerfile_location } " )
2023-08-11 20:41:47 +00:00
],
);
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2024-02-06 14:05:11 +00:00
if ( ! $this -> force_rebuild ) {
$this -> check_image_locally_or_remotely ();
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty () && ! $this -> application -> isConfigurationChanged ()) {
$this -> create_workdir ();
$this -> application_deployment_queue -> addLogEntry ( " No configuration changed & image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Build step skipped. " );
$this -> generate_compose_file ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2024-02-06 14:05:11 +00:00
$this -> rolling_update ();
return ;
}
}
2023-08-11 20:41:47 +00:00
$this -> generate_compose_file ();
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
$this -> build_image ();
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
}
2023-10-12 10:01:09 +00:00
private function deploy_dockerimage_buildpack ()
2023-10-10 09:16:38 +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 ()) {
$this -> dockerImageTag = 'latest' ;
} else {
$this -> dockerImageTag = $this -> application -> docker_registry_image_tag ;
}
2024-02-06 14:05:11 +00:00
ray ( " echo 'Starting deployment of { $this -> dockerImage } : { $this -> dockerImageTag } to { $this -> server -> name } .' " );
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> dockerImage } : { $this -> dockerImageTag } 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 ();
$this -> rolling_update ();
}
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 ;
}
if ( data_get ( $this -> application , 'docker_compose_custom_build_command' )) {
$this -> docker_compose_custom_build_command = $this -> application -> docker_compose_custom_build_command ;
}
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-02-06 14:05:11 +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 ();
$this -> generate_image_names ();
$this -> cleanup_git ();
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-03-04 09:13:40 +00:00
$this -> application -> parseRawCompose ();
2024-01-02 12:55:35 +00:00
$yaml = $composeFile = $this -> application -> docker_compose_raw ;
} else {
$composeFile = $this -> application -> parseCompose ( pull_request_id : $this -> pull_request_id );
$yaml = Yaml :: dump ( $composeFile -> toArray (), 10 );
}
2023-11-24 14:48:23 +00:00
$this -> docker_compose_base64 = base64_encode ( $yaml );
$this -> execute_remote_command ([
2023-11-29 13:59:06 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> docker_compose_base64 } ' | base64 -d > { $this -> workdir } { $this -> docker_compose_location } " ), " hidden " => true
2023-11-24 14:48:23 +00:00
]);
2023-11-27 13:28:21 +00:00
$this -> save_environment_variables ();
2023-12-10 03:14:06 +00:00
// Build new container to limit downtime.
2023-12-17 19:56:12 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Pulling & building required images. " );
if ( $this -> docker_compose_custom_build_command ) {
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , " cd { $this -> basedir } && { $this -> docker_compose_custom_build_command } " ), " hidden " => true ],
);
} else {
$this -> execute_remote_command (
2024-01-15 11:59:21 +00:00
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } build " ), " hidden " => true ],
2023-12-17 19:56:12 +00:00
);
}
2023-11-27 13:28:21 +00:00
$this -> stop_running_container ( force : true );
$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 ([
" docker network create --attachable ' { $networkId } ' >/dev/null || true " , " hidden " => true , " ignore_errors " => true
], [
" docker network connect { $networkId } coolify-proxy || true " , " hidden " => true , " ignore_errors " => true
]);
}
2024-03-04 09:13:40 +00:00
2023-12-17 19:56:12 +00:00
// Start compose file
2024-03-04 09:13:40 +00:00
if ( $this -> application -> settings -> is_raw_compose_deployment_enabled ) {
if ( $this -> docker_compose_custom_start_command ) {
$this -> execute_remote_command (
2024-03-18 11:40:58 +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
);
2024-03-19 10:48:25 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
} else {
2024-03-19 10:48:25 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
$server_workdir = $this -> application -> workdir ();
ray ( " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $server_workdir } -f { $server_workdir } { $this -> docker_compose_location } up -d " );
$this -> execute_remote_command (
[ " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $server_workdir } -f { $server_workdir } { $this -> docker_compose_location } up -d " , " hidden " => true ],
);
}
2023-12-17 19:56:12 +00:00
} else {
2024-03-04 09:13:40 +00:00
if ( $this -> docker_compose_custom_start_command ) {
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , " cd { $this -> basedir } && { $this -> docker_compose_custom_start_command } " ), " hidden " => true ],
);
2024-03-19 10:48:25 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
} else {
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } up -d " ), " hidden " => true ],
);
2024-03-19 10:48:25 +00:00
$this -> write_deployment_configurations ();
2024-03-04 09:13:40 +00:00
}
2023-12-17 19:56:12 +00:00
}
2024-03-04 09:13:40 +00:00
2023-12-17 19:56:12 +00:00
$this -> application_deployment_queue -> addLogEntry ( " New container started. " );
2023-11-24 14:48:23 +00:00
}
2023-10-12 10:01:09 +00:00
private function deploy_dockerfile_buildpack ()
2023-10-10 12:02:43 +00:00
{
2024-03-01 10:43:42 +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-10-10 12:02:43 +00:00
$this -> set_base_dir ();
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2024-03-13 09:44:15 +00:00
$this -> clone_repository ();
2024-02-06 14:05:11 +00:00
if ( ! $this -> force_rebuild ) {
$this -> check_image_locally_or_remotely ();
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty () && ! $this -> application -> isConfigurationChanged ()) {
$this -> create_workdir ();
$this -> application_deployment_queue -> addLogEntry ( " No configuration changed & image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Build step skipped. " );
$this -> generate_compose_file ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2024-02-06 14:05:11 +00:00
$this -> rolling_update ();
return ;
}
}
2023-10-10 12:02:43 +00:00
$this -> cleanup_git ();
$this -> generate_compose_file ();
$this -> generate_build_env_variables ();
$this -> add_build_env_variables_to_dockerfile ();
2023-10-15 14:54:16 +00:00
$this -> build_image ();
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
}
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-02-06 14:05:11 +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-10-07 10:54:19 +00:00
$this -> set_base_dir ();
2023-11-01 11:19:08 +00:00
$this -> generate_image_names ();
2023-08-11 20:41:47 +00:00
if ( ! $this -> force_rebuild ) {
2023-11-20 12:49:10 +00:00
$this -> check_image_locally_or_remotely ();
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty () && ! $this -> application -> isConfigurationChanged ()) {
2023-12-04 10:20:50 +00:00
$this -> create_workdir ();
2024-01-16 14:19:14 +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. " );
2023-08-11 20:41:47 +00:00
$this -> generate_compose_file ();
2024-02-07 13:55:06 +00:00
ray ( 'pushing to docker registry' );
$this -> push_to_docker_registry ();
2023-08-21 16:00:12 +00:00
$this -> rolling_update ();
2023-08-11 20:41:47 +00:00
return ;
}
2023-10-18 09:20:40 +00:00
if ( $this -> application -> isConfigurationChanged ()) {
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Configuration changed. Rebuilding image. " );
2023-10-18 09:20:40 +00:00
}
2023-08-11 20:41:47 +00:00
}
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 ();
$this -> generate_build_env_variables ();
$this -> build_image ();
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-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-02-06 14:05:11 +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 -> set_base_dir ();
$this -> generate_image_names ();
2024-02-06 14:05:11 +00:00
if ( ! $this -> force_rebuild ) {
$this -> check_image_locally_or_remotely ();
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty () && ! $this -> application -> isConfigurationChanged ()) {
$this -> create_workdir ();
$this -> application_deployment_queue -> addLogEntry ( " No configuration changed & image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Build step skipped. " );
$this -> generate_compose_file ();
2024-02-07 13:55:06 +00:00
$this -> push_to_docker_registry ();
2024-02-06 14:05:11 +00:00
$this -> rolling_update ();
return ;
}
}
2023-11-10 10:33:15 +00:00
$this -> clone_repository ();
$this -> cleanup_git ();
$this -> generate_compose_file ();
2024-02-07 13:55:06 +00:00
$this -> build_image ();
$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 ()
{
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-03-01 10:43:42 +00:00
if ( $this -> pull_request_id === 0 ) {
$composeFileName = " $this->configuration_dir /docker-compose.yml " ;
} else {
2024-02-07 13:55:06 +00:00
$composeFileName = " $this->configuration_dir /docker-compose-pr- { $this -> pull_request_id } .yml " ;
2024-03-01 10:43:42 +00:00
$this -> docker_compose_location = " /docker-compose-pr- { $this -> pull_request_id } .yml " ;
2024-02-07 13:55:06 +00:00
}
$this -> execute_remote_command (
[
" mkdir -p $this->configuration_dir "
],
[
" echo ' { $this -> docker_compose_base64 } ' | base64 -d > $composeFileName " ,
],
[
" echo ' { $readme } ' > $this->configuration_dir /README.md " ,
]
);
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
}
}
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 ()) {
ray ( 'empty docker_registry_image_name' );
return ;
}
if ( $this -> restart_only ) {
ray ( 'restart_only' );
return ;
}
if ( $this -> application -> build_pack === 'dockerimage' ) {
ray ( 'dockerimage' );
return ;
}
if ( $this -> use_build_server ) {
ray ( 'use_build_server' );
$forceFail = true ;
}
if ( $this -> server -> isSwarm () && $this -> build_pack !== 'dockerimage' ) {
ray ( 'isSwarm' );
$forceFail = true ;
}
if ( $this -> application -> additional_servers -> count () > 0 ) {
ray ( 'additional_servers' );
$forceFail = true ;
}
if ( $this -> application -> additional_servers () -> wherePivot ( 'server_id' , $this -> server -> id ) -> count () > 0 ) {
ray ( 'this is an additional_servers, no pushy pushy' );
return ;
}
ray ( 'push_to_docker_registry noww: ' . $this -> production_image_name );
try {
instant_remote_process ([ " docker images --format ' { { json .}}' { $this -> production_image_name } " ], $this -> server );
$this -> application_deployment_queue -> addLogEntry ( " ---------------------------------------- " );
$this -> application_deployment_queue -> addLogEntry ( " Pushing image to docker registry ( { $this -> production_image_name } ). " );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " docker push { $this -> production_image_name } " ), 'hidden' => true
],
);
if ( $this -> application -> docker_registry_image_tag ) {
// Tag image with latest
$this -> application_deployment_queue -> addLogEntry ( " Tagging and pushing image with latest tag. " );
$this -> execute_remote_command (
[
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
],
[
executeInDocker ( $this -> deployment_uuid , " docker push { $this -> application -> docker_registry_image_name } : { $this -> application -> docker_registry_image_tag } " ), 'ignore_errors' => true , 'hidden' => true
],
);
}
$this -> application_deployment_queue -> addLogEntry ( " Image pushed to docker registry. " );
} catch ( Exception $e ) {
$this -> application_deployment_queue -> addLogEntry ( " Failed to push image to docker registry. Please check debug logs for more information. " );
if ( $forceFail ) {
throw new RuntimeException ( $e -> getMessage (), 69420 );
}
ray ( $e );
}
}
private function generate_image_names ()
{
if ( $this -> application -> dockerfile ) {
if ( $this -> application -> docker_registry_image_name ) {
$this -> build_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } :build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } :latest " );
} else {
$this -> build_image_name = Str :: lower ( " { $this -> application -> uuid } :build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } :latest " );
}
} else if ( $this -> application -> build_pack === 'dockerimage' ) {
$this -> production_image_name = Str :: lower ( " { $this -> dockerImage } : { $this -> dockerImageTag } " );
} else if ( $this -> pull_request_id !== 0 ) {
if ( $this -> application -> docker_registry_image_name ) {
$this -> build_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } :pr- { $this -> pull_request_id } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } :pr- { $this -> pull_request_id } " );
} else {
$this -> build_image_name = Str :: lower ( " { $this -> application -> uuid } :pr- { $this -> pull_request_id } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } :pr- { $this -> pull_request_id } " );
}
} else {
$this -> dockerImageTag = str ( $this -> commit ) -> substr ( 0 , 128 );
2024-02-08 14:22:07 +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 ) {
$this -> build_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } : { $this -> dockerImageTag } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> docker_registry_image_name } : { $this -> dockerImageTag } " );
} else {
$this -> build_image_name = Str :: lower ( " { $this -> application -> uuid } : { $this -> dockerImageTag } -build " );
$this -> production_image_name = Str :: lower ( " { $this -> application -> uuid } : { $this -> dockerImageTag } " );
}
}
}
private function just_restart ()
{
$this -> application_deployment_queue -> addLogEntry ( " Starting deployment of { $this -> customRepository } : { $this -> application -> git_branch } to { $this -> server -> name } . " );
$this -> prepare_builder_image ();
$this -> check_git_if_build_needed ();
$this -> set_base_dir ();
$this -> generate_image_names ();
$this -> check_image_locally_or_remotely ();
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isNotEmpty ()) {
$this -> application_deployment_queue -> addLogEntry ( " Image found ( { $this -> production_image_name } ) with the same Git Commit SHA. Restarting container. " );
$this -> create_workdir ();
$this -> generate_compose_file ();
$this -> rolling_update ();
return ;
}
throw new RuntimeException ( 'Cannot find image anywhere. Please redeploy the application.' );
}
private function check_image_locally_or_remotely ()
{
$this -> execute_remote_command ([
" docker images -q { $this -> production_image_name } 2>/dev/null " , " hidden " => true , " save " => " local_image_found "
]);
if ( str ( $this -> saved_outputs -> get ( 'local_image_found' )) -> isEmpty () && $this -> application -> docker_registry_image_name ) {
$this -> execute_remote_command ([
" docker pull { $this -> production_image_name } 2>/dev/null " , " ignore_errors " => true , " hidden " => true
]);
$this -> execute_remote_command ([
" docker images -q { $this -> production_image_name } 2>/dev/null " , " hidden " => true , " save " => " local_image_found "
]);
}
}
private function save_environment_variables ()
{
$envs = collect ([]);
if ( $this -> pull_request_id !== 0 ) {
foreach ( $this -> application -> environment_variables_preview as $env ) {
$envs -> push ( $env -> key . '=' . $env -> real_value );
}
} else {
foreach ( $this -> application -> environment_variables as $env ) {
$envs -> push ( $env -> key . '=' . $env -> real_value );
}
}
$envs_base64 = base64_encode ( $envs -> implode ( " \n " ));
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' $envs_base64 ' | base64 -d > $this->workdir /.env " )
],
);
}
2024-01-09 11:19:39 +00:00
private function framework_based_notification ()
{
// Laravel old env variables
if ( $this -> pull_request_id === 0 ) {
$nixpacks_php_fallback_path = $this -> application -> environment_variables -> where ( 'key' , 'NIXPACKS_PHP_FALLBACK_PATH' ) -> first ();
$nixpacks_php_root_dir = $this -> application -> environment_variables -> where ( 'key' , 'NIXPACKS_PHP_ROOT_DIR' ) -> first ();
} else {
$nixpacks_php_fallback_path = $this -> application -> environment_variables_preview -> where ( 'key' , 'NIXPACKS_PHP_FALLBACK_PATH' ) -> first ();
$nixpacks_php_root_dir = $this -> application -> environment_variables_preview -> where ( 'key' , 'NIXPACKS_PHP_ROOT_DIR' ) -> first ();
}
if ( $nixpacks_php_fallback_path ? -> value === '/index.php' && $nixpacks_php_root_dir ? -> value === '/app/public' && $this -> newVersionIsHealthy === false ) {
2024-03-27 10:07:29 +00:00
$this -> application_deployment_queue -> addLogEntry ( " There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel " , 'stderr' );
2024-01-09 11:19:39 +00:00
}
}
2023-08-21 16:00:12 +00:00
private function rolling_update ()
{
2023-11-28 17:31:04 +00:00
if ( $this -> server -> isSwarm ()) {
2023-12-18 13:01:25 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Rolling update started. " );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " docker stack deploy --with-registry-auth -c { $this -> workdir } { $this -> docker_compose_location } { $this -> application -> uuid } " )
],
);
$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-03-18 11:18:11 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 || ( bool ) $this -> application -> settings -> is_consistent_container_name_enabled || $this -> pull_request_id !== 0 || str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip' ) || str ( $this -> application -> custom_docker_run_options ) -> contains ( '--ip6' )) {
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " ---------------------------------------- " );
2024-02-15 10:55:43 +00:00
if ( count ( $this -> application -> ports_mappings_array ) > 0 ) {
$this -> application_deployment_queue -> addLogEntry ( " Application has ports mapped to the host system, rolling update is not supported. " );
}
if (( bool ) $this -> application -> settings -> is_consistent_container_name_enabled ) {
$this -> application_deployment_queue -> addLogEntry ( " Consistent container name feature enabled, rolling update is not supported. " );
}
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 ;
$this -> application_deployment_queue -> addLogEntry ( " Pull request deployment, rolling update is not supported. " );
}
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' )) {
$this -> application_deployment_queue -> addLogEntry ( " Custom IP address is set, rolling update is not supported. " );
}
2023-11-28 17:31:04 +00:00
$this -> stop_running_container ( force : true );
$this -> start_by_compose_file ();
} else {
2024-01-16 14:19:14 +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 ();
$this -> application_deployment_queue -> addLogEntry ( " Rolling update completed. " );
}
}
2024-01-09 11:19:39 +00:00
$this -> framework_based_notification ();
2023-11-28 17:31:04 +00:00
}
private function health_check ()
{
if ( $this -> server -> isSwarm ()) {
// Implement healthcheck for swarm
} else {
2024-03-13 09:50:05 +00:00
if ( $this -> application -> isHealthcheckDisabled () && $this -> custom_healthcheck_found === false ) {
2023-11-28 17:31:04 +00:00
$this -> newVersionIsHealthy = true ;
return ;
}
// ray('New container name: ', $this->container_name);
if ( $this -> container_name ) {
$counter = 1 ;
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Waiting for healthcheck to pass on the new container. " );
2023-11-28 17:31:04 +00:00
if ( $this -> full_healthcheck_url ) {
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-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 } " ,
" hidden " => true ,
2024-01-11 11:56:02 +00:00
" save " => " health_check " ,
" append " => false
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-01-09 07:42:37 +00:00
if ( Str :: of ( $this -> saved_outputs -> get ( 'health_check' )) -> replace ( '"' , '' ) -> value () === 'healthy' ) {
2023-11-28 17:31:04 +00:00
$this -> newVersionIsHealthy = true ;
$this -> application -> update ([ 'status' => 'running' ]);
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " New container is healthy. " );
2023-11-28 17:31:04 +00:00
break ;
}
2024-01-09 07:42:37 +00:00
if ( Str :: of ( $this -> saved_outputs -> get ( 'health_check' )) -> replace ( '"' , '' ) -> value () === 'unhealthy' ) {
$this -> newVersionIsHealthy = false ;
break ;
}
2023-11-28 17:31:04 +00:00
$counter ++ ;
2024-03-14 09:10:03 +00:00
Sleep :: for ( $this -> application -> health_check_interval ) -> seconds ();
2023-08-21 16:00:12 +00:00
}
}
}
2023-08-11 20:41:47 +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 ();
return ;
}
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
$this -> server = $this -> build_server ;
}
2023-11-08 14:40:06 +00:00
$this -> newVersionIsHealthy = true ;
2023-11-01 14:39:47 +00:00
$this -> generate_image_names ();
2024-01-16 14:19:14 +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 ();
$this -> clone_repository ();
2023-10-07 10:54:19 +00:00
$this -> set_base_dir ();
2023-06-30 20:24:39 +00:00
$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 ();
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 ();
2024-03-01 10:43:42 +00:00
$this -> push_to_docker_registry ();
// $this->stop_running_container();
$this -> rolling_update ();
2023-05-24 12:26:50 +00:00
}
2023-12-04 10:20:50 +00:00
private function create_workdir ()
{
$this -> execute_remote_command (
[
" command " => executeInDocker ( $this -> deployment_uuid , " mkdir -p { $this -> workdir } " )
],
);
}
2023-08-08 09:51:36 +00:00
private function prepare_builder_image ()
2023-03-31 11:32:07 +00:00
{
2023-09-05 06:49:33 +00:00
$helperImage = config ( 'coolify.helper_image' );
2023-11-21 14:31:46 +00:00
// Get user home directory
$this -> serverUserHomeDir = instant_remote_process ([ " echo \$ HOME " ], $this -> server );
$this -> dockerConfigFileExists = instant_remote_process ([ " test -f { $this -> serverUserHomeDir } /.docker/config.json && echo 'OK' || echo 'NOK' " ], $this -> server );
2024-01-16 14:19:14 +00:00
if ( $this -> use_build_server ) {
if ( $this -> dockerConfigFileExists === 'NOK' ) {
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.' );
}
$runCommand = " docker run -d --name { $this -> deployment_uuid } --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-01-16 14:19:14 +00:00
if ( $this -> dockerConfigFileExists === 'OK' ) {
$runCommand = " docker run -d --network { $this -> destination -> network } --name { $this -> deployment_uuid } --rm -v { $this -> serverUserHomeDir } /.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
} else {
$runCommand = " docker run -d --network { $this -> destination -> network } --name { $this -> deployment_uuid } --rm -v /var/run/docker.sock:/var/run/docker.sock { $helperImage } " ;
}
2023-10-17 12:23:07 +00:00
}
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Preparing container with helper image: $helperImage . " );
2023-06-30 20:24:39 +00:00
$this -> execute_remote_command (
2023-08-08 09:51:36 +00:00
[
2023-09-04 14:03:11 +00:00
$runCommand ,
2023-08-08 09:51:36 +00:00
" hidden " => true ,
],
[
2023-11-10 20:02:39 +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
}
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 ()) {
$this -> application_deployment_queue -> addLogEntry ( " Additional destinations are not supported in swarm mode. " );
return ;
}
if ( $destination_ids -> contains ( $this -> destination -> id )) {
ray ( 'Same destination found in additional destinations. Skipping.' );
return ;
}
2023-11-21 14:31:46 +00:00
foreach ( $destination_ids as $destination_id ) {
$destination = StandaloneDocker :: find ( $destination_id );
$server = $destination -> server ;
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?! " );
2023-11-21 14:31:46 +00:00
continue ;
}
2024-02-05 13:40:54 +00:00
// ray('Deploying to additional destination: ', $server->name);
$deployment_uuid = new Cuid2 ();
queue_application_deployment (
deployment_uuid : $deployment_uuid ,
application : $this -> application ,
server : $server ,
destination : $destination ,
no_questions_asked : true ,
);
2024-02-22 09:57:05 +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 ,
'environment_name' => data_get ( $this -> application , 'environment.name' ),
]));
2023-11-21 14:31:46 +00:00
}
}
2023-10-10 09:16:38 +00:00
private function set_base_dir ()
{
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Setting base directory to { $this -> workdir } . " );
2023-10-07 10:54:19 +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
{
2023-10-26 08:33:57 +00:00
$this -> generate_git_import_commands ();
2023-10-27 09:44:10 +00:00
$private_key = data_get ( $this -> application , 'private_key.private_key' );
if ( $private_key ) {
$private_key = base64_encode ( $private_key );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " mkdir -p /root/.ssh " )
],
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $private_key } ' | base64 -d > /root/.ssh/id_rsa " )
],
[
executeInDocker ( $this -> deployment_uuid , " chmod 600 /root/.ssh/id_rsa " )
],
[
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 -i /root/.ssh/id_rsa \" git ls-remote { $this -> fullRepoUrl } { $this -> branch } " ),
" hidden " => true ,
" save " => " git_commit_sha "
],
);
} else {
$this -> execute_remote_command (
[
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 } { $this -> branch } " ),
" hidden " => true ,
" save " => " git_commit_sha "
],
);
}
2023-11-01 11:19:08 +00:00
if ( $this -> saved_outputs -> get ( 'git_commit_sha' )) {
$this -> commit = $this -> saved_outputs -> get ( 'git_commit_sha' ) -> before ( " \t " );
}
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 ---------------------------------------- " );
$this -> application_deployment_queue -> addLogEntry ( " Importing { $this -> customRepository } : { $this -> application -> git_branch } (commit sha { $this -> application -> git_commit_sha } ) 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
[
2023-10-26 08:33:57 +00:00
$importCommands , " hidden " => true
]
2023-06-30 20:24:39 +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-01-29 09:43:18 +00:00
[ 'commands' => $commands , 'branch' => $this -> branch , 'fullRepoUrl' => $this -> fullRepoUrl ] = $this -> application -> generateGitImportCommands (
deployment_uuid : $this -> deployment_uuid ,
pull_request_id : $this -> pull_request_id ,
git_type : $this -> git_type ,
commit : $this -> commit
);
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 set_git_import_settings ( $git_clone_command )
2023-05-05 08:51:58 +00:00
{
2023-11-24 14:48:23 +00:00
return $this -> application -> setGitImportSettings ( $this -> deployment_uuid , $git_clone_command );
2023-05-05 08:51:58 +00:00
}
2023-05-05 07:02:50 +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 " );
2023-10-06 08:07:25 +00:00
$this -> execute_remote_command (
2024-01-08 15:33: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
}
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 } " );
2024-01-08 15:33:34 +00:00
$parsed = Toml :: Parse ( $this -> nixpacks_plan );
// Do any modifications here
2024-01-11 10:32:32 +00:00
$this -> generate_env_variables ();
$merged_envs = $this -> env_args -> merge ( collect ( data_get ( $parsed , 'variables' , [])));
data_set ( $parsed , 'variables' , $merged_envs -> toArray ());
$this -> nixpacks_plan = json_encode ( $parsed , JSON_PRETTY_PRINT );
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 ();
$nixpacks_command = " nixpacks plan -f toml { $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 } " ;
2023-10-06 08:07:25 +00:00
return $nixpacks_command ;
2023-08-08 09:51:36 +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-02-19 08:19:50 +00:00
if ( ! is_null ( $env -> real_value )) {
$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-02-19 08:19:50 +00:00
if ( ! is_null ( $env -> real_value )) {
$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
2024-01-11 11:56:02 +00:00
$this -> env_nixpacks_args = $this -> env_nixpacks_args -> implode ( ' ' );
}
2023-08-08 09:51:36 +00:00
private function generate_env_variables ()
{
$this -> env_args = collect ([]);
if ( $this -> pull_request_id === 0 ) {
2024-01-08 15:33:34 +00:00
foreach ( $this -> application -> build_environment_variables as $env ) {
2024-02-19 08:19:50 +00:00
if ( ! is_null ( $env -> real_value )) {
$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 {
2024-01-08 15:33:34 +00:00
foreach ( $this -> application -> build_environment_variables_preview as $env ) {
2024-02-19 08:19:50 +00:00
if ( ! is_null ( $env -> real_value )) {
$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
}
2024-01-15 11:59:21 +00:00
$this -> env_args -> put ( 'SOURCE_COMMIT' , $this -> commit );
2023-08-08 09:51:36 +00:00
}
private function generate_compose_file ()
{
$ports = $this -> application -> settings -> is_static ? [ 80 ] : $this -> application -> ports_exposes_array ;
2024-03-11 14:19:04 +00:00
$onlyPort = null ;
if ( count ( $ports ) > 0 ) {
$onlyPort = $ports [ 0 ];
}
2023-08-08 09:51:36 +00:00
$persistent_storages = $this -> generate_local_persistent_volumes ();
$volume_names = $this -> generate_local_persistent_volumes_only_volume_names ();
$environment_variables = $this -> generate_environment_variables ( $ports );
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 ) {
return ! Str :: startsWith ( $value , 'coolify.' );
});
2024-03-11 14:19:04 +00:00
$found_caddy_labels = $labels -> filter ( function ( $value , $key ) {
return Str :: startsWith ( $value , 'caddy_' );
});
if ( $found_caddy_labels -> count () === 0 ) {
if ( $this -> pull_request_id !== 0 ) {
$domains = str ( data_get ( $this -> preview , 'fqdn' )) -> explode ( ',' );
} else {
$domains = str ( data_get ( $this -> application , 'fqdn' )) -> explode ( ',' );
}
$labels = $labels -> merge ( fqdnLabelsForCaddy (
network : $this -> application -> destination -> network ,
uuid : $this -> application -> uuid ,
domains : $domains ,
onlyPort : $onlyPort ,
is_force_https_enabled : $this -> application -> isForceHttpsEnabled (),
is_gzip_enabled : $this -> application -> isGzipEnabled (),
is_stripprefix_enabled : $this -> application -> isStripprefixEnabled ()
));
}
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 {
$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
}
2023-10-26 09:38:37 +00:00
$labels = $labels -> merge ( defaultLabels ( $this -> application -> id , $this -> application -> uuid , $this -> pull_request_id )) -> toArray ();
2024-03-13 09:44:15 +00:00
// Check for custom HEALTHCHECK
2024-03-13 09:50:05 +00:00
$this -> custom_healthcheck_found = false ;
if ( $this -> application -> build_pack === 'dockerfile' || $this -> application -> dockerfile ) {
2024-03-13 09:44:15 +00:00
$this -> execute_remote_command ([
2024-03-14 20:19:37 +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
]);
2024-03-14 20:19:37 +00:00
$dockerfile = collect ( Str :: of ( $this -> saved_outputs -> get ( 'dockerfile_from_repo' )) -> trim () -> explode ( " \n " ));
2024-03-13 09:44:15 +00:00
if ( str ( $dockerfile ) -> contains ( 'HEALTHCHECK' )) {
2024-03-13 09:50:05 +00:00
$this -> custom_healthcheck_found = true ;
2024-03-13 09:44:15 +00:00
}
}
2023-08-08 09:51:36 +00:00
$docker_compose = [
'version' => '3.8' ,
'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-08-08 09:51:36 +00:00
'environment' => $environment_variables ,
2023-09-24 15:48:25 +00:00
'expose' => $ports ,
2023-03-31 11:32:07 +00:00
'networks' => [
$this -> destination -> network ,
],
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 ,
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 ,
2023-09-13 10:08:44 +00:00
'attachable' => true
2023-03-31 11:32:07 +00:00
]
]
];
2024-03-13 09:50:05 +00:00
if ( ! $this -> custom_healthcheck_found ) {
2024-03-13 09:44:15 +00:00
$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-01-12 13:15:15 +00:00
if ( ! is_null ( $this -> application -> limits_cpuset )) {
2024-01-12 12:47:01 +00:00
data_set ( $docker_compose , 'services.' . $this -> container_name . '.cpuset' , $this -> application -> limits_cpuset );
}
2023-11-28 17:31:04 +00:00
if ( $this -> server -> isSwarm ()) {
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' => [
'order' => 'start-first'
],
'rollback_config' => [
'order' => 'start-first'
],
'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 ,
]
]
];
2023-12-18 13:01:25 +00:00
if ( data_get ( $this -> application , 'settings.is_swarm_only_worker_nodes' )) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'deploy' ][ 'placement' ] = [
'constraints' => [
'node.role == worker'
]
];
}
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 ()) {
2023-11-17 10:13:16 +00:00
$docker_compose [ 'services' ][ $this -> container_name ][ 'logging' ] = [
'driver' => 'fluentd' ,
'options' => [
'fluentd-address' => " tcp://127.0.0.1:24224 " ,
'fluentd-async' => " true " ,
'fluentd-sub-second-precision' => " true " ,
]
];
}
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' ],
'options' => data_get ( $this -> application , 'settings.gpu_options' , [])
]
];
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 ;
}
} else if ( data_get ( $this -> application , 'settings.gpu_device_ids' )) {
$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 ()) {
data_forget ( $docker_compose , 'services.' . $this -> container_name . '.healthcheck' );
}
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
}
2023-05-30 13:52:17 +00:00
if ( count ( $persistent_storages ) > 0 ) {
$docker_compose [ 'services' ][ $this -> container_name ][ 'volumes' ] = $persistent_storages ;
2023-04-04 13:25:42 +00:00
}
if ( count ( $volume_names ) > 0 ) {
$docker_compose [ 'volumes' ] = $volume_names ;
}
2023-10-15 14:54:16 +00:00
// if ($this->build_pack === 'dockerfile') {
// $docker_compose['services'][$this->container_name]['build'] = [
// 'context' => $this->workdir,
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
// ];
// }
2023-11-28 19:49:38 +00:00
2024-03-02 12:22:05 +00:00
if ( $this -> pull_request_id === 0 ) {
2024-03-18 11:18:11 +00:00
$custom_compose = convert_docker_run_to_compose ( $this -> application -> custom_docker_run_options );
2024-03-01 10:43:42 +00:00
if (( bool ) $this -> application -> settings -> is_consistent_container_name_enabled ) {
2024-03-14 09:10:03 +00:00
$docker_compose [ 'services' ][ $this -> application -> uuid ] = $docker_compose [ 'services' ][ $this -> container_name ];
2024-03-01 10:43:42 +00:00
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 -> application -> uuid ], 'networks' );
2024-03-01 10:43:42 +00:00
}
if ( $ipv4 ) {
2024-03-14 09:10:03 +00:00
$docker_compose [ 'services' ][ $this -> application -> uuid ][ '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 -> application -> uuid ][ '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 -> application -> uuid ] = array_merge_recursive ( $docker_compose [ 'services' ][ $this -> application -> uuid ], $custom_compose );
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 );
2023-09-15 13:34:25 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> docker_compose_base64 } ' | base64 -d > { $this -> workdir } /docker-compose.yml " ), " 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 ) {
$volume_name = $persistentStorage -> host_path ? ? $persistentStorage -> name ;
2023-06-05 10:07:55 +00:00
if ( $this -> pull_request_id !== 0 ) {
$volume_name = $volume_name . '-pr-' . $this -> pull_request_id ;
}
2023-04-04 13:25:42 +00:00
$local_persistent_volumes [] = $volume_name . ':' . $persistentStorage -> mount_path ;
}
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 ) {
$name = $name . '-pr-' . $this -> pull_request_id ;
}
$local_persistent_volumes_names [ $name ] = [
'name' => $name ,
2023-04-04 13:25:42 +00:00
'external' => false ,
];
}
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
2023-06-30 20:24:39 +00:00
private function generate_environment_variables ( $ports )
{
$environment_variables = collect ();
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> runtime_environment_variables as $env ) {
2024-03-15 21:16:22 +00:00
// This is necessary because we have to escape the value of the environment variable
2024-03-18 10:36:36 +00:00
// but only if the environment variable is created after 4.0.0-beta.240
2024-03-15 21:16:22 +00:00
// when I implemented the escaping feature.
// Old environment variables are not escaped, because it could break the application
// as the application could expect the unescaped value.
2024-03-18 10:36:36 +00:00
if ( $env -> version === '4.0.0-beta.239' ) {
2024-03-20 15:41:10 +00:00
$real_value = $env -> real_value ;
2024-03-18 10:36:36 +00:00
} else {
$real_value = escapeEnvVariables ( $env -> real_value );
2024-03-15 21:16:22 +00:00
}
2024-03-14 22:00:06 +00:00
$environment_variables -> push ( " $env->key = $real_value " );
2023-06-30 20:24:39 +00:00
}
2023-11-08 09:13:20 +00:00
foreach ( $this -> application -> nixpacks_environment_variables as $env ) {
2024-03-18 10:36:36 +00:00
if ( $env -> version === '4.0.0-beta.239' ) {
2024-03-20 15:41:10 +00:00
$real_value = $env -> real_value ;
2024-03-18 10:36:36 +00:00
} else {
$real_value = escapeEnvVariables ( $env -> real_value );
2024-03-15 21:16:22 +00:00
}
2024-03-14 22:00:06 +00:00
$environment_variables -> push ( " $env->key = $real_value " );
2023-11-08 09:13:20 +00:00
}
2023-06-30 20:24:39 +00:00
} else {
foreach ( $this -> application -> runtime_environment_variables_preview as $env ) {
2024-03-18 10:36:36 +00:00
if ( $env -> version === '4.0.0-beta.239' ) {
2024-03-20 15:41:10 +00:00
$real_value = $env -> real_value ;
2024-03-18 10:36:36 +00:00
} else {
$real_value = escapeEnvVariables ( $env -> real_value );
2024-03-15 21:16:22 +00:00
}
2024-03-14 22:00:06 +00:00
$environment_variables -> push ( " $env->key = $real_value " );
2023-06-30 20:24:39 +00:00
}
2023-11-08 09:13:20 +00:00
foreach ( $this -> application -> nixpacks_environment_variables_preview as $env ) {
2024-03-18 10:36:36 +00:00
if ( $env -> version === '4.0.0-beta.239' ) {
2024-03-20 15:41:10 +00:00
$real_value = $env -> real_value ;
2024-03-18 10:36:36 +00:00
} else {
$real_value = escapeEnvVariables ( $env -> real_value );
2024-03-15 21:16:22 +00:00
}
2024-03-14 22:00:06 +00:00
$environment_variables -> push ( " $env->key = $real_value " );
2023-11-08 09:13:20 +00:00
}
2023-06-30 20:24:39 +00:00
}
// Add PORT if not exists, use the first port as default
2024-01-15 11:59:21 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => Str :: of ( $env ) -> startsWith ( 'PORT' )) -> isEmpty ()) {
2023-06-30 20:24:39 +00:00
$environment_variables -> push ( " PORT= { $ports [ 0 ] } " );
2024-01-03 12:20:24 +00:00
}
2024-03-14 22:00:06 +00:00
// Add HOST if not exists
if ( $environment_variables -> filter ( fn ( $env ) => Str :: of ( $env ) -> startsWith ( 'HOST' )) -> isEmpty ()) {
$environment_variables -> push ( " HOST=0.0.0.0 " );
}
2024-01-15 11:59:21 +00:00
if ( $environment_variables -> filter ( fn ( $env ) => Str :: of ( $env ) -> startsWith ( 'SOURCE_COMMIT' )) -> isEmpty ()) {
2023-12-27 12:06:59 +00:00
if ( ! is_null ( $this -> commit )) {
$environment_variables -> push ( " SOURCE_COMMIT= { $this -> commit } " );
2024-01-15 11:59:21 +00:00
} else {
$environment_variables -> push ( " SOURCE_COMMIT=unknown " );
2023-12-27 12:06:59 +00:00
}
2023-06-30 20:24:39 +00:00
}
return $environment_variables -> all ();
}
2023-08-08 09:51:36 +00:00
private function generate_healthcheck_commands ()
2023-06-30 20:24:39 +00:00
{
2023-10-10 09:16:38 +00:00
if ( $this -> application -> dockerfile || $this -> application -> build_pack === 'dockerfile' || $this -> application -> build_pack === 'dockerimage' ) {
2023-09-13 10:08:44 +00:00
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0' ;
}
2023-08-08 09:51:36 +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 = [
2023-09-22 13:29:19 +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 "
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 = [
2023-09-22 13:29:19 +00:00
" curl -s -X { $this -> application -> health_check_method } -f { $this -> application -> health_check_scheme } :// { $this -> application -> health_check_host } : { $health_check_port } / "
2023-08-08 09:51:36 +00:00
];
2023-03-31 13:51:50 +00:00
}
2023-08-08 09:51:36 +00:00
return implode ( ' ' , $generated_healthchecks_commands );
2023-06-30 20:24:39 +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 (
[
executeInDocker ( $this -> deployment_uuid , " docker pull { $image } " ), " hidden " => true
]
);
}
2023-08-08 09:51:36 +00:00
private function build_image ()
2023-06-30 20:24:39 +00:00
{
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " ---------------------------------------- " );
2023-11-10 10:33:15 +00:00
if ( $this -> application -> build_pack === 'static' ) {
2024-01-16 14:19:14 +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-01-16 14:19:14 +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
2023-11-10 10:33:15 +00:00
if ( $this -> application -> settings -> is_static || $this -> application -> build_pack === 'static' ) {
2023-11-13 11:30:25 +00:00
if ( $this -> application -> static_image ) {
$this -> pull_latest_image ( $this -> application -> static_image );
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Continuing with the building process. " );
2023-11-13 11:30:25 +00:00
}
2023-11-10 10:33:15 +00:00
if ( $this -> application -> build_pack === 'static' ) {
$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
COPY ./ nginx . conf / etc / nginx / conf . d / default . conf " );
$nginx_config = base64_encode ( " server {
listen 80 ;
listen [ :: ] : 80 ;
server_name localhost ;
location / {
root / usr / share / nginx / html ;
index index . html ;
try_files \ $uri \ $uri . html \ $uri / index . html \ $uri / / index . html = 404 ;
}
error_page 500 502 503 504 / 50 x . html ;
location = / 50 x . html {
root / usr / share / nginx / html ;
}
} " );
} 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-01-10 11:26:27 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> nixpacks_plan } ' | base64 -d > /artifacts/thegameplan.json " ), " hidden " => true ]);
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
$this -> execute_remote_command ([
2024-01-10 11:26:27 +00:00
executeInDocker ( $this -> deployment_uuid , " nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n { $this -> build_image_name } { $this -> workdir } " ), " hidden " => true
2024-01-08 15:33:34 +00:00
]);
} else {
$this -> execute_remote_command ([
2024-01-10 11:26:27 +00:00
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 } " ), " hidden " => true
2024-01-08 15:33:34 +00:00
]);
}
2024-01-10 11:26:27 +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 {
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
2024-01-11 10:32:32 +00:00
$build_command = " 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 } " ;
$base64_build_command = base64_encode ( $build_command );
2024-01-08 15:33:34 +00:00
} else {
2024-01-11 10:32:32 +00:00
$build_command = " 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 } " ;
$base64_build_command = base64_encode ( $build_command );
2024-01-08 15:33:34 +00:00
}
2024-01-11 10:32:32 +00:00
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d > /artifacts/build.sh " ), " hidden " => true
],
[
executeInDocker ( $this -> deployment_uuid , " bash /artifacts/build.sh " ), " hidden " => true
]
);
2024-01-03 12:20:24 +00:00
}
2023-11-10 10:33:15 +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 }
COPY -- from = $this -> build_image_name / app / { $this -> application -> publish_directory } .
COPY ./ nginx . conf / etc / nginx / conf . d / default . conf " );
2023-11-10 10:33:15 +00:00
$nginx_config = base64_encode ( " server {
2023-08-08 09:51:36 +00:00
listen 80 ;
listen [ :: ] : 80 ;
server_name localhost ;
location / {
root / usr / share / nginx / html ;
index index . html ;
try_files \ $uri \ $uri . html \ $uri / index . html \ $uri / / index . html = 404 ;
}
error_page 500 502 503 504 / 50 x . html ;
location = / 50 x . html {
root / usr / share / nginx / html ;
}
} " );
2023-11-10 10:33:15 +00:00
}
2024-01-11 10:32:32 +00:00
$build_command = " docker build { $this -> addHosts } --network host -f { $this -> workdir } /Dockerfile { $this -> build_args } --progress plain -t { $this -> production_image_name } { $this -> workdir } " ;
$base64_build_command = base64_encode ( $build_command );
2023-08-08 09:51:36 +00:00
$this -> execute_remote_command (
[
2023-11-10 10:33:15 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile } ' | base64 -d > { $this -> workdir } /Dockerfile " )
2023-08-08 09:51:36 +00:00
],
[
2023-09-15 13:34:25 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $nginx_config } ' | base64 -d > { $this -> workdir } /nginx.conf " )
2023-08-08 09:51:36 +00:00
],
[
2024-01-11 10:32:32 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d > /artifacts/build.sh " ), " hidden " => true
],
[
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 ) {
2024-01-11 10:32:32 +00:00
$build_command = " 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 } " ;
$base64_build_command = base64_encode ( $build_command );
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d > /artifacts/build.sh " ), " hidden " => true
],
[
executeInDocker ( $this -> deployment_uuid , " bash /artifacts/build.sh " ), " hidden " => true
]
);
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-01-10 11:26:27 +00:00
$this -> execute_remote_command ([ executeInDocker ( $this -> deployment_uuid , " echo ' { $this -> nixpacks_plan } ' | base64 -d > /artifacts/thegameplan.json " ), " hidden " => true ]);
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
$this -> execute_remote_command ([
2024-01-10 11:26:27 +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 } " ), " hidden " => true
2024-01-08 15:33:34 +00:00
]);
} else {
$this -> execute_remote_command ([
2024-01-10 11:26:27 +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 } " ), " hidden " => true
2024-01-08 15:33:34 +00:00
]);
}
2024-01-10 11:26:27 +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 {
2024-01-08 15:33:34 +00:00
if ( $this -> force_rebuild ) {
2024-01-11 10:32:32 +00:00
$build_command = " 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 } " ;
$base64_build_command = base64_encode ( $build_command );
2024-01-08 15:33:34 +00:00
} else {
2024-01-11 10:32:32 +00:00
$build_command = " 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 } " ;
$base64_build_command = base64_encode ( $build_command );
2024-01-08 15:33:34 +00:00
}
2024-01-11 10:32:32 +00:00
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , " echo ' { $base64_build_command } ' | base64 -d > /artifacts/build.sh " ), " hidden " => true
],
[
executeInDocker ( $this -> deployment_uuid , " bash /artifacts/build.sh " ), " hidden " => true
]
);
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-01-16 14:19:14 +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
2023-09-24 19:15:43 +00:00
private function stop_running_container ( bool $force = false )
2023-08-08 09:51:36 +00:00
{
2023-11-27 10:54:55 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Removing old containers. " );
2023-11-08 14:40:06 +00:00
if ( $this -> newVersionIsHealthy || $force ) {
$containers = getCurrentApplicationContainerStatus ( $this -> server , $this -> application -> id , $this -> pull_request_id );
2023-11-27 13:28:21 +00:00
if ( $this -> pull_request_id === 0 ) {
2023-11-08 14:40:06 +00:00
$containers = $containers -> filter ( function ( $container ) {
2024-03-01 10:43:42 +00:00
return data_get ( $container , 'Names' ) !== $this -> container_name && data_get ( $container , 'Names' ) !== $this -> container_name . '-pr-' . $this -> pull_request_id ;
2023-11-08 14:40:06 +00:00
});
}
$containers -> each ( function ( $container ) {
$containerName = data_get ( $container , 'Names' );
2023-09-22 13:29:19 +00:00
$this -> execute_remote_command (
2024-03-01 10:43:42 +00:00
[ " docker rm -f $containerName >/dev/null 2>&1 " , " hidden " => true , " ignore_errors " => true ],
2023-09-22 13:29:19 +00:00
);
2023-11-08 14:40:06 +00:00
});
2024-02-15 10:55:43 +00:00
if ( $this -> application -> settings -> is_consistent_container_name_enabled ) {
$this -> execute_remote_command (
2024-03-01 10:43:42 +00:00
[ " docker rm -f $this->container_name >/dev/null 2>&1 " , " hidden " => true , " ignore_errors " => true ],
2024-02-15 10:55:43 +00:00
);
}
2023-11-08 14:40:06 +00:00
} else {
2023-11-27 10:54:55 +00:00
$this -> application_deployment_queue -> addLogEntry ( " New container is not healthy, rolling back to the old container. " );
2024-01-02 20:07:16 +00:00
$this -> application_deployment_queue -> update ([
'status' => ApplicationDeploymentStatus :: FAILED -> value ,
]);
2023-11-08 14:40:06 +00:00
$this -> execute_remote_command (
2024-03-01 10:43:42 +00:00
[ " docker rm -f $this->container_name >/dev/null 2>&1 " , " hidden " => true , " ignore_errors " => true ],
2023-11-08 14:40:06 +00:00
);
2023-08-21 16:00:12 +00:00
}
2023-06-30 20:24:39 +00:00
}
2023-08-08 09:51:36 +00:00
2023-12-12 14:48:51 +00:00
private function build_by_compose_file ()
{
2023-12-11 12:43:16 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Pulling & building required images. " );
2023-12-10 03:14:06 +00:00
if ( $this -> application -> build_pack === 'dockerimage' ) {
$this -> application_deployment_queue -> addLogEntry ( " Pulling latest images from the registry. " );
$this -> execute_remote_command (
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-directory { $this -> workdir } pull " ), " hidden " => true ],
2024-01-15 11:59:21 +00:00
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $this -> workdir } build " ), " hidden " => true ],
2023-12-10 03:14:06 +00:00
);
} else {
2023-12-17 19:56:12 +00:00
$this -> execute_remote_command (
2024-01-15 11:59:21 +00:00
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $this -> workdir } -f { $this -> workdir } { $this -> docker_compose_location } build " ), " hidden " => true ],
2023-12-17 19:56:12 +00:00
);
2023-12-10 03:14:06 +00:00
}
$this -> application_deployment_queue -> addLogEntry ( " New images built. " );
}
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' ) {
2023-11-27 10:54:55 +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 (
2023-11-16 14:23:07 +00:00
[ executeInDocker ( $this -> deployment_uuid , " docker compose --project-directory { $this -> workdir } pull " ), " hidden " => true ],
2024-01-15 11:59:21 +00:00
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --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 (
2024-01-16 14:19:14 +00:00
[ " SOURCE_COMMIT= { $this -> commit } docker compose --project-directory { $this -> configuration_dir } -f { $this -> configuration_dir } { $this -> docker_compose_location } up --build -d " , " hidden " => true ],
2023-12-06 08:36:11 +00:00
);
} else {
$this -> execute_remote_command (
2024-01-16 14:19:14 +00:00
[ executeInDocker ( $this -> deployment_uuid , " SOURCE_COMMIT= { $this -> commit } docker compose --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
}
2023-11-28 13:23:59 +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
private function generate_build_env_variables ()
2023-03-31 13:51:50 +00:00
{
2023-10-25 18:19:38 +00:00
$this -> build_args = collect ([ " --build-arg SOURCE_COMMIT= \" { $this -> commit } \" " ]);
2023-08-08 09:51:36 +00:00
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> build_environment_variables as $env ) {
2024-01-23 16:13:23 +00:00
$value = escapeshellarg ( $env -> real_value );
2024-01-11 10:32:32 +00:00
$this -> build_args -> push ( " --build-arg { $env -> key } = { $value } " );
2023-08-08 09:51:36 +00:00
}
} else {
foreach ( $this -> application -> build_environment_variables_preview as $env ) {
2024-01-23 16:13:23 +00:00
$value = escapeshellarg ( $env -> real_value );
2024-01-11 10:32:32 +00:00
$this -> build_args -> push ( " --build-arg { $env -> key } = { $value } " );
2023-08-08 09:51:36 +00:00
}
2023-05-30 13:52:17 +00:00
}
2023-07-06 12:00:19 +00:00
2023-08-08 09:51:36 +00:00
$this -> build_args = $this -> build_args -> implode ( ' ' );
}
2023-05-10 11:05:32 +00:00
2023-08-08 09:51:36 +00:00
private function add_build_env_variables_to_dockerfile ()
{
$this -> execute_remote_command ([
2023-11-14 13:07:33 +00:00
executeInDocker ( $this -> deployment_uuid , " cat { $this -> workdir } { $this -> dockerfile_location } " ), " hidden " => true , " save " => 'dockerfile'
2023-08-08 09:51:36 +00:00
]);
$dockerfile = collect ( Str :: of ( $this -> saved_outputs -> get ( 'dockerfile' )) -> trim () -> explode ( " \n " ));
2023-11-28 10:10:42 +00:00
if ( $this -> pull_request_id === 0 ) {
foreach ( $this -> application -> build_environment_variables as $env ) {
2024-01-23 16:13:23 +00:00
$dockerfile -> splice ( 1 , 0 , " ARG { $env -> key } = { $env -> real_value } " );
2023-11-28 10:10:42 +00:00
}
} else {
foreach ( $this -> application -> build_environment_variables_preview as $env ) {
2024-01-23 16:13:23 +00:00
$dockerfile -> splice ( 1 , 0 , " ARG { $env -> key } = { $env -> real_value } " );
2023-11-28 10:10:42 +00:00
}
2023-03-31 13:51:50 +00:00
}
2023-08-08 09:51:36 +00:00
$dockerfile_base64 = base64_encode ( $dockerfile -> implode ( " \n " ));
$this -> execute_remote_command ([
2023-11-14 13:07:33 +00:00
executeInDocker ( $this -> deployment_uuid , " echo ' { $dockerfile_base64 } ' | base64 -d > { $this -> workdir } { $this -> dockerfile_location } " ),
2023-08-08 09:51:36 +00:00
" hidden " => true
]);
}
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-03-13 13:41:31 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Executing pre-deployment command (see debug log for output): { $this -> application -> pre_deployment_command } " );
2024-02-08 10:02:30 +00:00
foreach ( $containers as $container ) {
$containerName = data_get ( $container , 'Names' );
2024-03-13 13:41:31 +00:00
if ( $containers -> count () == 1 || str_starts_with ( $containerName , $this -> application -> pre_deployment_command_container . '-' . $this -> application -> uuid )) {
2024-02-08 10:02:30 +00:00
$cmd = 'sh -c "' . str_replace ( '"' , '\"' , $this -> application -> pre_deployment_command ) . '"' ;
$exec = " docker exec { $containerName } { $cmd } " ;
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , $exec ), 'hidden' => true
],
);
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-03-13 13:41:31 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Executing post-deployment command (see debug log for output): { $this -> application -> post_deployment_command } " );
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-03-13 13:41:31 +00:00
if ( $containers -> count () == 1 || str_starts_with ( $containerName , $this -> application -> post_deployment_command_container . '-' . $this -> application -> uuid )) {
2024-02-08 09:27:43 +00:00
$cmd = 'sh -c "' . str_replace ( '"' , '\"' , $this -> application -> post_deployment_command ) . '"' ;
$exec = " docker exec { $containerName } { $cmd } " ;
$this -> execute_remote_command (
[
executeInDocker ( $this -> deployment_uuid , $exec ), 'hidden' => true
],
);
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
}
2023-08-08 09:51:36 +00:00
private function next ( string $status )
{
2024-01-29 11:51:20 +00:00
queue_next_deployment ( $this -> application );
2023-08-08 09:51:36 +00:00
// If the deployment is cancelled by the user, don't update the status
2024-03-13 09:24:45 +00:00
if (
$this -> application_deployment_queue -> status !== ApplicationDeploymentStatus :: CANCELLED_BY_USER -> value && $this -> application_deployment_queue -> status !== ApplicationDeploymentStatus :: FAILED -> value
) {
2023-08-08 09:51:36 +00:00
$this -> application_deployment_queue -> update ([
'status' => $status ,
2023-06-30 20:24:39 +00:00
]);
2023-08-08 09:51:36 +00:00
}
2024-03-13 09:24:45 +00:00
if ( $this -> application_deployment_queue -> status === ApplicationDeploymentStatus :: FAILED -> value ) {
2023-12-13 11:01:27 +00:00
$this -> application -> environment -> project -> team ? -> notify ( new DeploymentFailed ( $this -> application , $this -> deployment_uuid , $this -> preview ));
2024-02-05 13:40:54 +00:00
return ;
}
if ( $status === ApplicationDeploymentStatus :: FINISHED -> value ) {
2024-02-22 09:57:05 +00:00
if ( ! $this -> only_this_server ) {
$this -> deploy_to_additional_destinations ();
}
2024-03-01 18:07:21 +00:00
$this -> application -> environment -> project -> team ? -> notify ( new DeploymentSuccess ( $this -> application , $this -> deployment_uuid , $this -> preview ));
2023-03-31 13:51:50 +00:00
}
2023-03-31 12:30:08 +00:00
}
2023-08-08 09:51:36 +00:00
public function failed ( Throwable $exception ) : void
2023-05-24 12:26:50 +00:00
{
2024-02-26 13:22:24 +00:00
$this -> next ( ApplicationDeploymentStatus :: FAILED -> value );
2024-01-16 14:19:14 +00:00
$this -> application_deployment_queue -> addLogEntry ( " Oops something is not okay, are you okay? 😢 " , 'stderr' );
2024-01-29 10:23:04 +00:00
if ( str ( $exception -> getMessage ()) -> isNotEmpty ()) {
$this -> application_deployment_queue -> addLogEntry ( $exception -> getMessage (), '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 ();
2024-02-26 13:22:24 +00:00
ray ( $code );
2024-02-06 14:05:11 +00:00
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
$this -> application_deployment_queue -> addLogEntry ( " Deployment failed. Removing the new version of your application. " , 'stderr' );
$this -> execute_remote_command (
2024-03-01 10:43:42 +00:00
[ " 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
}