fix(domains): route Compose domains using service ports

Resolve routing ports per Compose service, preserve legacy embedded ports, and ignore non-TCP declarations.
This commit is contained in:
Andras Bacsai
2026-09-07 15:17:23 +02:00
parent e5c75db84a
commit 183e28682c
16 changed files with 609 additions and 122 deletions
+21 -3
View File
@@ -607,6 +607,13 @@ function firstDockerComposeServicePort(mixed $service): ?int
->merge(data_get($service, 'ports', []));
foreach ($portDefinitions as $definition) {
$protocol = is_array($definition)
? data_get($definition, 'protocol', 'tcp')
: (str_contains((string) $definition, '/') ? str((string) $definition)->afterLast('/')->value() : 'tcp');
if ($protocol !== 'tcp') {
continue;
}
$port = is_array($definition)
? data_get($definition, 'target')
: str((string) $definition)->before('/')->afterLast(':')->value();
@@ -620,18 +627,29 @@ function firstDockerComposeServicePort(mixed $service): ?int
}
function dockerComposeServicePort(?string $compose, ?string $serviceName): ?int
{
return dockerComposeServicePorts($compose, $serviceName)[0] ?? null;
}
function dockerComposeServicePorts(?string $compose, ?string $serviceName): array
{
if (blank($compose) || blank($serviceName)) {
return null;
return [];
}
try {
$services = data_get(Yaml::parse($compose), 'services', []);
} catch (Throwable) {
return null;
return [];
}
return firstDockerComposeServicePort(is_array($services) ? ($services[$serviceName] ?? null) : null);
$service = is_array($services) ? ($services[$serviceName] ?? []) : [];
return collect(data_get($service, 'expose', []))
->merge(data_get($service, 'ports', []))
->map(fn ($definition) => firstDockerComposeServicePort(['expose' => [$definition]]))
->filter(fn ($port) => $port !== null)
->unique()->values()->all();
}
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $escape_redirect_replacement_for_compose = true, array $domainPortOverrides = [])
+6 -42
View File
@@ -390,9 +390,6 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
return collect([]);
}
$services = data_get($yaml, 'services', collect([]));
$applicationServiceCount = collect($services)
->reject(fn (mixed $service): bool => isDatabaseImage(data_get($service, 'image')))
->count();
$topLevel = collect([
'volumes' => collect(data_get($yaml, 'volumes', [])),
'networks' => collect(data_get($yaml, 'networks', [])),
@@ -1357,9 +1354,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$domainPortOverrides = $isPullRequest
? ($previewForPorts?->domain_port_overrides ?? [])
: ($originalResource->domain_port_overrides ?? []);
$exposedPorts = $originalResource->settings->is_static ? [80] : $originalResource->ports_exposes_array;
$onlyPort = firstDockerComposeServicePort($service)
?? ($applicationServiceCount === 1 ? ($exposedPorts[0] ?? null) : null);
$onlyPort = firstDockerComposeServicePort($service);
if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) {
$serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first());
}
@@ -1566,7 +1561,6 @@ function serviceParser(Service $resource): Collection
$envComments = extractYamlEnvironmentComments($compose);
$server = data_get($resource, 'server');
$allServices = get_service_templates();
try {
$yaml = Yaml::parse($compose);
@@ -1699,22 +1693,7 @@ function serviceParser(Service $resource): Collection
$containerName = "$serviceName-{$resource->uuid}";
if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry';
} else {
$tempServiceName = $serviceName;
}
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
$predefinedPort = $resource->getRequiredPort();
if ($migratedApp || $migratedDb) {
// Use the already determined migrated service
@@ -2084,22 +2063,7 @@ function serviceParser(Service $resource): Collection
$containerName = "$serviceName-{$resource->uuid}";
if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry';
} else {
$tempServiceName = $serviceName;
}
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
$predefinedPort = $resource->getRequiredPort();
if ($migratedApp || $migratedDb) {
// Use the already determined migrated service
@@ -2642,7 +2606,7 @@ function serviceParser(Service $resource): Collection
? data_get($originalResource, 'redirect')
: 'both';
$onlyPort = $originalResource instanceof ServiceApplication
? ($originalResource->getRequiredPort() ?? $predefinedPort)
? $originalResource->getRequiredPort()
: $predefinedPort;
if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) {
$serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first());
@@ -2677,7 +2641,7 @@ function serviceParser(Service $resource): Collection
service_name: $serviceName,
image: $image,
onlyPort: $onlyPort,
predefinedPort: $predefinedPort,
predefinedPort: $onlyPort,
domainPortOverrides: $originalResource->domain_port_overrides ?? [],
noindex_domains: $noindexDomains,
redirect_direction: $redirectDirection
@@ -2710,7 +2674,7 @@ function serviceParser(Service $resource): Collection
service_name: $serviceName,
image: $image,
onlyPort: $onlyPort,
predefinedPort: $predefinedPort,
predefinedPort: $onlyPort,
domainPortOverrides: $originalResource->domain_port_overrides ?? [],
noindex_domains: $noindexDomains,
redirect_direction: $redirectDirection
+7 -30
View File
@@ -2486,7 +2486,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} catch (Exception $e) {
throw new RuntimeException($e->getMessage());
}
$allServices = get_service_templates();
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$topLevelConfigs = collect(data_get($yaml, 'configs', []));
@@ -2512,25 +2511,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices, $envComments) {
// Workarounds for beta users.
if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry';
} else {
$tempServiceName = $serviceName;
}
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
// End of workarounds for beta users.
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $envComments) {
$predefinedPort = $resource->getRequiredPort();
$serviceVolumes = collect(data_get($service, 'volumes', []));
$servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
@@ -3107,7 +3089,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
? ($savedService->domain_port_overrides ?? [])
: [];
$onlyPort = $savedService instanceof ServiceApplication
? ($savedService->getRequiredPort() ?? $predefinedPort)
? $savedService->getRequiredPort()
: $predefinedPort;
if ($shouldGenerateLabelsExactly) {
switch ($resource->server->proxyType()) {
@@ -3139,7 +3121,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
service_name: $serviceName,
image: data_get($service, 'image'),
onlyPort: $onlyPort,
predefinedPort: $predefinedPort,
predefinedPort: $onlyPort,
noindex_domains: $noindexDomains,
redirect_direction: $redirectDirection,
domainPortOverrides: $domainPortOverrides,
@@ -3172,7 +3154,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
service_name: $serviceName,
image: data_get($service, 'image'),
onlyPort: $onlyPort,
predefinedPort: $predefinedPort,
predefinedPort: $onlyPort,
noindex_domains: $noindexDomains,
redirect_direction: $redirectDirection,
domainPortOverrides: $domainPortOverrides,
@@ -3318,10 +3300,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0) {
$definedNetwork = collect(["{$resource->uuid}-$pull_request_id"]);
}
$usesSharedApplicationPort = collect($services)
->reject(fn (mixed $service): bool => isDatabaseImage(data_get($service, 'image')))
->count() === 1;
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id, $preview_id, $usesSharedApplicationPort) {
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id, $preview_id) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
$servicePorts = collect(data_get($service, 'ports', []));
$serviceNetworks = collect(data_get($service, 'networks', []));
@@ -3918,9 +3897,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$domainPortOverrides = $pull_request_id === 0
? ($resource->domain_port_overrides ?? [])
: ($preview?->domain_port_overrides ?? []);
$exposedPorts = $resource->settings->is_static ? [80] : $resource->ports_exposes_array;
$onlyPort = firstDockerComposeServicePort($service)
?? ($usesSharedApplicationPort ? ($exposedPorts[0] ?? null) : null);
$onlyPort = firstDockerComposeServicePort($service);
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value: