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')
+