diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index 246214986c..14b89887d7 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -10,6 +10,7 @@ use App\Http\Controllers\Controller;
use App\Jobs\DeleteResourceJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
+use App\Models\ApplicationSetting;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\LocalFileVolume;
@@ -61,6 +62,7 @@ class ApplicationsController extends Controller
'gpu_options',
'is_consistent_container_name_enabled',
'custom_internal_name',
+ 'custom_container_name_prefix',
];
private const BOOLEAN_APPLICATION_SETTING_FIELDS = [
@@ -153,9 +155,26 @@ class ApplicationsController extends Controller
: $request->input($field);
}
+ if (array_key_exists('custom_container_name_prefix', $settings)) {
+ $settings['custom_container_name_prefix'] = str($settings['custom_container_name_prefix'])->slug()->value() ?: null;
+ }
+
return $settings;
}
+ private function containerNamePrefixValidationResponse(array $settings, Server $server, ?Application $application = null): ?JsonResponse
+ {
+ $prefix = $settings['custom_container_name_prefix'] ?? null;
+ if (! filled($prefix) || ! ApplicationSetting::isContainerNamePrefixInUse($prefix, $server, $application?->id)) {
+ return null;
+ }
+
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => ['custom_container_name_prefix' => ['This container name prefix is already in use by another application.']],
+ ], 422);
+ }
+
private function applyApplicationSettings(Application $application, array $settings): void
{
if ($settings === []) {
@@ -393,6 +412,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
@@ -587,6 +607,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
@@ -781,6 +802,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
@@ -946,6 +968,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
@@ -1107,6 +1130,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'is_http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
@@ -1335,6 +1359,9 @@ class ApplicationsController extends Controller
], 422);
}
}
+ if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $destination->server)) {
+ return $prefixValidation;
+ }
if ($type === 'public') {
$validationRules = [
'git_repository' => ['string', 'required', new ValidGitRepositoryUrl],
@@ -2967,6 +2994,7 @@ class ApplicationsController extends Controller
'gpu_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Additional GPU options.'],
'is_consistent_container_name_enabled' => ['type' => 'boolean', 'description' => 'Use a consistent container name across deployments.'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom internal container name.'],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true, 'description' => 'Prefix for generated container names (prefix-20260908T141530). Slugified and unique across the instance.'],
'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
'max_restart_count' => ['type' => 'integer', 'minimum' => 0, 'description' => 'Maximum container restart count before stopping.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
@@ -3140,6 +3168,9 @@ class ApplicationsController extends Controller
}
$applicationSettings = $this->applicationSettingsFromRequest($request);
+ if ($prefixValidation = $this->containerNamePrefixValidationResponse($applicationSettings, $application->destination->server, $application)) {
+ return $prefixValidation;
+ }
$requestedBuildPack = $request->input('build_pack', $application->build_pack);
if (($applicationSettings['is_raw_compose_deployment_enabled'] ?? false) && $requestedBuildPack !== 'dockercompose') {
return response()->json([
diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php
index a9e1c0be28..a57d529bcb 100644
--- a/app/Livewire/Project/Application/Advanced.php
+++ b/app/Livewire/Project/Application/Advanced.php
@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
+use App\Models\ApplicationSetting;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@@ -69,6 +70,9 @@ class Advanced extends Component
#[Validate(['string', 'nullable'])]
public ?string $customInternalName = null;
+ #[Validate(['string', 'nullable', 'max:'.ApplicationSetting::MAX_CONTAINER_NAME_PREFIX_LENGTH])]
+ public ?string $customContainerNamePrefix = null;
+
#[Validate(['boolean'])]
public bool $isGzipEnabled = true;
@@ -111,6 +115,7 @@ class Advanced extends Component
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
$this->application->settings->is_consistent_container_name_enabled = $this->isConsistentContainerNameEnabled;
$this->application->settings->custom_internal_name = $this->customInternalName;
+ $this->application->settings->custom_container_name_prefix = $this->customContainerNamePrefix;
$this->application->settings->is_gzip_enabled = $this->isGzipEnabled;
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
@@ -137,6 +142,7 @@ class Advanced extends Component
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
$this->isConsistentContainerNameEnabled = $this->application->settings->is_consistent_container_name_enabled;
$this->customInternalName = $this->application->settings->custom_internal_name;
+ $this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
$this->disableBuildCache = $this->application->settings->disable_build_cache;
@@ -258,6 +264,28 @@ class Advanced extends Component
}
}
+ public function saveCustomNamePrefix()
+ {
+ try {
+ $this->authorize('update', $this->application);
+
+ $this->customContainerNamePrefix = str($this->customContainerNamePrefix)->slug()->value() ?: null;
+
+ if ($this->customContainerNamePrefix && ApplicationSetting::isContainerNamePrefixInUse($this->customContainerNamePrefix, $this->application->destination->server, $this->application->id)) {
+ $this->customContainerNamePrefix = $this->application->settings->custom_container_name_prefix;
+ $this->dispatch('error', 'This container name prefix is already in use by another application on this Coolify instance.');
+
+ return;
+ }
+
+ $this->syncData(true);
+ $this->dispatch('success', 'Container name prefix saved.');
+ $this->dispatch('configurationChanged');
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function saveStopGracePeriod()
{
try {
diff --git a/app/Models/ApplicationSetting.php b/app/Models/ApplicationSetting.php
index 91c38b8790..18b26f454f 100644
--- a/app/Models/ApplicationSetting.php
+++ b/app/Models/ApplicationSetting.php
@@ -32,6 +32,7 @@ use OpenApi\Attributes as OA;
'is_stripprefix_enabled' => ['type' => 'boolean'],
'connect_to_docker_network' => ['type' => 'boolean'],
'custom_internal_name' => ['type' => 'string', 'nullable' => true],
+ 'custom_container_name_prefix' => ['type' => 'string', 'nullable' => true],
'is_container_label_escape_enabled' => ['type' => 'boolean'],
'is_env_sorting_enabled' => ['type' => 'boolean'],
'is_container_label_readonly_enabled' => ['type' => 'boolean'],
@@ -49,6 +50,12 @@ use OpenApi\Attributes as OA;
)]
class ApplicationSetting extends Model
{
+ /**
+ * Keeps generated names (prefix, timestamp and for compose apps the service name) well below the
+ * 63 character DNS label limit, with room for a longer suffix in the future.
+ */
+ public const MAX_CONTAINER_NAME_PREFIX_LENGTH = 30;
+
protected $casts = [
'is_static' => 'boolean',
'is_spa' => 'boolean',
@@ -106,6 +113,7 @@ class ApplicationSetting extends Model
'is_stripprefix_enabled',
'connect_to_docker_network',
'custom_internal_name',
+ 'custom_container_name_prefix',
'is_container_label_escape_enabled',
'is_env_sorting_enabled',
'is_container_label_readonly_enabled',
@@ -121,6 +129,18 @@ class ApplicationSetting extends Model
'stop_grace_period',
];
+ /**
+ * Like custom container names, a prefix must be unique per server so that uuid, custom container
+ * name and prefix each identify one container when resolving connections.
+ */
+ public static function isContainerNamePrefixInUse(string $prefix, Server $server, ?int $ignoreApplicationId = null): bool
+ {
+ return $server->applications()->contains(function (Application $application) use ($prefix, $ignoreApplicationId) {
+ return $application->id !== $ignoreApplicationId
+ && in_array($prefix, [$application->uuid, $application->settings->custom_container_name_prefix, $application->settings->custom_internal_name], true);
+ });
+ }
+
public function stopGracePeriodSeconds(): int
{
if (
diff --git a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php
index e3ba77163d..184aa01eb3 100644
--- a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php
+++ b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php
@@ -170,6 +170,7 @@ class ApplicationConfigurationSnapshot
$this->item('custom_network_aliases', 'Network aliases', $this->application->custom_network_aliases, 'redeploy'),
$this->item('connect_to_docker_network', 'Connect to Docker network', data_get($this->application, 'settings.connect_to_docker_network'), 'redeploy'),
$this->item('custom_internal_name', 'Custom container name', data_get($this->application, 'settings.custom_internal_name'), 'redeploy'),
+ $this->item('custom_container_name_prefix', 'Container name prefix', data_get($this->application, 'settings.custom_container_name_prefix'), 'redeploy'),
$this->item('is_consistent_container_name_enabled', 'Consistent container name', data_get($this->application, 'settings.is_consistent_container_name_enabled'), 'redeploy'),
$this->item('is_container_label_escape_enabled', 'Escape container labels', data_get($this->application, 'settings.is_container_label_escape_enabled'), 'redeploy'),
$this->item('is_container_label_readonly_enabled', 'Read-only container labels', data_get($this->application, 'settings.is_container_label_readonly_enabled'), 'redeploy'),
diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php
index b8001497ba..b32870a5f7 100644
--- a/bootstrap/helpers/api.php
+++ b/bootstrap/helpers/api.php
@@ -4,6 +4,7 @@ use App\Actions\Shared\MigrateResourceToDestination;
use App\Enums\BuildPackTypes;
use App\Enums\RedirectTypes;
use App\Enums\StaticImageTypes;
+use App\Models\ApplicationSetting;
use App\Models\Environment;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@@ -141,6 +142,7 @@ function sharedDataApplications()
'gpu_options' => 'string|nullable',
'is_consistent_container_name_enabled' => 'boolean',
'custom_internal_name' => 'string|nullable',
+ 'custom_container_name_prefix' => 'string|nullable|max:'.ApplicationSetting::MAX_CONTAINER_NAME_PREFIX_LENGTH,
'preview_url_template' => 'string',
'max_restart_count' => 'integer|min:0',
'stop_grace_period' => 'nullable|integer|min:'.MIN_STOP_GRACE_PERIOD_SECONDS.'|max:'.MAX_STOP_GRACE_PERIOD_SECONDS,
@@ -408,6 +410,7 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('gpu_options');
$request->offsetUnset('is_consistent_container_name_enabled');
$request->offsetUnset('custom_internal_name');
+ $request->offsetUnset('custom_container_name_prefix');
$request->offsetUnset('docker_compose_raw');
$request->offsetUnset('tags');
}
diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php
index 38cf3fefc4..3c87882d6f 100644
--- a/bootstrap/helpers/docker.php
+++ b/bootstrap/helpers/docker.php
@@ -358,7 +358,7 @@ function generateApplicationContainerName(Application $application, $pull_reques
return $name;
}
- return $application->uuid.'-'.$now;
+ return ($application->settings->custom_container_name_prefix ?: $application->uuid).'-'.$now;
}
}
diff --git a/database/migrations/2026_09_08_214513_add_custom_container_name_prefix_to_application_settings_table.php b/database/migrations/2026_09_08_214513_add_custom_container_name_prefix_to_application_settings_table.php
new file mode 100644
index 0000000000..d49c1d57aa
--- /dev/null
+++ b/database/migrations/2026_09_08_214513_add_custom_container_name_prefix_to_application_settings_table.php
@@ -0,0 +1,18 @@
+string('custom_container_name_prefix')->nullable();
+ });
+ }
+};
diff --git a/resources/views/livewire/project/application/advanced.blade.php b/resources/views/livewire/project/application/advanced.blade.php
index c6113808c5..27b40546bb 100644
--- a/resources/views/livewire/project/application/advanced.blade.php
+++ b/resources/views/livewire/project/application/advanced.blade.php
@@ -45,10 +45,21 @@
['value' => true, 'label' => 'Consistent name (no rolling updates)'],
]" :disabled="! $canUpdate" />
@if ($isConsistentContainerNameEnabled === true)
-