diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 66f6a1ef8..6b57d8f5f 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -144,7 +144,7 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], @@ -309,7 +309,7 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], @@ -474,7 +474,7 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], @@ -776,7 +776,7 @@ class ApplicationsController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name', 'ports_exposes'], + required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name'], properties: [ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], @@ -1114,7 +1114,7 @@ class ApplicationsController extends Controller 'git_repository' => ['string', 'required', new ValidGitRepositoryUrl], 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], - 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', + 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable', 'docker_compose_domains' => 'array|nullable', 'docker_compose_domains.*' => 'array:name,domain', 'docker_compose_domains.*.name' => 'string|required', @@ -1307,7 +1307,7 @@ class ApplicationsController extends Controller 'git_repository' => 'string|required', 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], - 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', + 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable', 'github_app_uuid' => 'string|required', 'watch_paths' => 'string|nullable', 'docker_compose_domains' => 'array|nullable', @@ -1534,7 +1534,7 @@ class ApplicationsController extends Controller 'git_repository' => ['string', 'required', new ValidGitRepositoryUrl], 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], - 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', + 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable', 'private_key_uuid' => 'string|required', 'watch_paths' => 'string|nullable', 'docker_compose_domains' => 'array|nullable', @@ -1835,7 +1835,7 @@ class ApplicationsController extends Controller $validationRules = [ 'docker_registry_image_name' => 'string|required', 'docker_registry_image_tag' => 'string', - 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', + 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable', ]; $validationRules = array_merge(sharedDataApplications(), $validationRules); $validator = customApiValidator($request->all(), $validationRules); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 785e8c8e3..41eb81453 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1282,7 +1282,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // Add PORT if not exists, use the first port as default if ($this->build_pack !== 'dockercompose') { - if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) { + if ($this->application->environment_variables->where('key', 'PORT')->isEmpty() && ! empty($ports)) { $envs->push("PORT={$ports[0]}"); } } @@ -2585,7 +2585,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue 'image' => $this->production_image_name, 'container_name' => $this->container_name, 'restart' => RESTART_MODE, - 'expose' => $ports, + ...(!empty($ports) ? ['expose' => $ports] : []), 'networks' => [ $this->destination->network => [ 'aliases' => array_merge( @@ -2617,16 +2617,19 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // If custom_healthcheck_found is true, the Dockerfile's HEALTHCHECK will be used // If healthcheck is disabled, no healthcheck will be added if (! $this->application->custom_healthcheck_found && ! $this->application->isHealthcheckDisabled()) { - $docker_compose['services'][$this->container_name]['healthcheck'] = [ - 'test' => [ - 'CMD-SHELL', - $this->generate_healthcheck_commands(), - ], - 'interval' => $this->application->health_check_interval.'s', - 'timeout' => $this->application->health_check_timeout.'s', - 'retries' => $this->application->health_check_retries, - 'start_period' => $this->application->health_check_start_period.'s', - ]; + $healthcheck_command = $this->generate_healthcheck_commands(); + if ($healthcheck_command !== null) { + $docker_compose['services'][$this->container_name]['healthcheck'] = [ + 'test' => [ + 'CMD-SHELL', + $healthcheck_command, + ], + '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', + ]; + } } if (! is_null($this->application->limits_cpuset)) { @@ -2836,7 +2839,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // HTTP type healthcheck (default) if (! $this->application->health_check_port) { - $health_check_port = (int) $this->application->ports_exposes_array[0]; + if (! empty($this->application->ports_exposes_array)) { + $health_check_port = (int) $this->application->ports_exposes_array[0]; + } else { + return null; + } } else { $health_check_port = (int) $this->application->health_check_port; } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 5c186af70..885c9dbac 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -153,7 +153,7 @@ class General extends Component 'staticImage' => 'required', 'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)), 'publishDirectory' => ValidationPatterns::directoryPathRules(), - 'portsExposes' => 'required', + 'portsExposes' => 'nullable', 'portsMappings' => 'nullable', 'customNetworkAliases' => 'nullable', 'dockerfile' => 'nullable', @@ -208,7 +208,6 @@ class General extends Component 'buildPack.required' => 'The Build Pack field is required.', 'staticImage.required' => 'The Static Image field is required.', 'baseDirectory.required' => 'The Base Directory field is required.', - 'portsExposes.required' => 'The Exposed Ports field is required.', 'isStatic.required' => 'The Static setting is required.', 'isStatic.boolean' => 'The Static setting must be true or false.', 'isSpa.required' => 'The SPA setting is required.', diff --git a/app/Models/Application.php b/app/Models/Application.php index 4cc2dcf74..75b81920d 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -2147,7 +2147,7 @@ class Application extends BaseModel 'config.build_pack' => 'required|string', 'config.base_directory' => 'required|string', 'config.publish_directory' => 'required|string', - 'config.ports_exposes' => 'required|string', + 'config.ports_exposes' => 'nullable|string', 'config.settings.is_static' => 'required|boolean', ]); if ($deepValidator->fails()) { diff --git a/database/migrations/2026_03_26_000000_make_ports_exposes_nullable_in_applications_table.php b/database/migrations/2026_03_26_000000_make_ports_exposes_nullable_in_applications_table.php new file mode 100644 index 000000000..ac7b5cb55 --- /dev/null +++ b/database/migrations/2026_03_26_000000_make_ports_exposes_nullable_in_applications_table.php @@ -0,0 +1,22 @@ +string('ports_exposes')->nullable()->change(); + }); + } + + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->string('ports_exposes')->nullable(false)->default('')->change(); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index d743e346e..9539f47dc 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -492,6 +492,13 @@ @endif @endif + @if (empty($portsExposes) || $portsExposes === '0') + + This application does not expose any ports and will not be reachable through the proxy or your domains. + This behavior is normal for background workers, bots, or scheduled tasks. + If your application needs to handle HTTP traffic, please specify the port(s) it listens on. + + @endif
@if ($isStatic || $buildPack === 'static') @else - @endif