Proxy cannot start or restart when a Service has an empty docker_compose_raw (getTopLevelNetworks returns null)
Description and Error Message
On a non-swarm server, if any Service has an empty string in docker_compose_raw, every proxy start and restart on that server fails with a TypeError:
isDockerPredefinedNetwork(): Argument #1 ($network) must be of type string, null given,
called in /var/www/html/bootstrap/helpers/proxy.php on line 86
The root cause is that getTopLevelNetworks() is not a total function.
bootstrap/helpers/shared.php:1582:
function getTopLevelNetworks(Service|Application $resource)
{
if ($resource->getMorphClass() === Service::class) {
if ($resource->docker_compose_raw) { // '' is falsy -> body skipped
...
return $topLevelNetworks->keys(); // only return in this branch
}
} elseif ($resource->getMorphClass() === Application::class) {
...
return $topLevelNetworks->keys();
}
} // no else, no fallback return
For a Service whose docker_compose_raw is falsy, the return inside the guard is skipped and control falls off the end of the function, so it returns null implicitly.
That null is pushed in collectDockerNetworksByServer() (proxy.php:55-60):
foreach ($server->services()->get() as $service) {
if ($service->isRunning()) {
$networks->push($service->networks());
}
$allNetworks->push($service->networks()); // unconditional
}
Collection::flatten() appends non-array items verbatim, so the null survives flatten() and unique() and reaches the filter() callbacks at proxy.php:86 and :89, which call isDockerPredefinedNetwork(string $network). That is a non-nullable parameter on a user-defined function, so PHP raises a TypeError rather than coercing.
Two things make this hard to diagnose:
ensureProxyNetworksExist()(proxy.php:146) callscollectDockerNetworksByServer()before theisSwarm()check, and it is merged by bothRestartProxyJob:149andStartProxy:80. So the proxy can be neither restarted nor started — but the API still responds as though the restart was queued, and the failure only appears in the Laravel log.connectProxyToNetworks()is not affected on non-swarm servers — it only callscollectDockerNetworksByServer()insideif ($server->isSwarm())(proxy.php:110) and otherwise returns a plain shell loop. SoConnectProxyToNetworksJobkeeps succeeding every minute, which makes the proxy look healthy and masks the fault.
Note that services.docker_compose_raw is TEXT NOT NULL, so the stored value is an empty string, not NULL. serviceParser() early-returns on a falsy compose, so an affected service keeps a perfectly valid docker_compose column while docker_compose_raw is empty — there is no visible sign anything is wrong until a proxy restart is attempted.
Expected Behavior
A Service with an empty docker_compose_raw should contribute no networks and be skipped. Proxy start/restart should continue to work for the rest of the server, rather than failing server-wide.
getTopLevelNetworks() should return a Collection on every path.
Steps to Reproduce
- Self-hosted Coolify, non-swarm server, Traefik proxy.
- Create a Service (any Docker Compose service).
- Set its
docker_compose_rawto an empty string:UPDATE services SET docker_compose_raw = '' WHERE uuid = '<service-uuid>';(In normal use this state can also be reached through the UI —EditCompose::instantSave()writesdocker_compose_rawfrom its Livewire property while validating onlyisContainerLabelEscapeEnabled.) - Restart the proxy for that server from the UI or the API.
- The UI/API reports the restart as queued, but the proxy container is never recreated.
storage/logs/laravel.logcontains theTypeErrorabove. docker inspect coolify-proxy --format '{{.State.StartedAt}}'confirms the container was not restarted.
Coolify Version
4.3.21
Are you using Coolify Cloud?
No (self-hosted)
Operating System and Version (self-hosted)
Ubuntu 24.04.5 LTS (Noble Numbat), Docker 29.8.1
Additional Information
getTopLevelNetworks is byte-identical on v4.3.21, v4.x, main and next, so upgrading does not resolve it.
Suggested fix — make the function total:
// end of getTopLevelNetworks()
return collect();
Optionally also null-guard the two filters in collectDockerNetworksByServer():
return ! is_null($network) && ! isDockerPredefinedNetwork($network);
Related: the Application branch (shared.php:1651) has no truthiness guard at all and calls Yaml::parse($resource->docker_compose_raw) directly. applications.docker_compose_raw is nullable, and the surrounding catch (Exception $e) would not catch a TypeError, so the same class of failure looks reachable there too.
isDockerPredefinedNetwork() and the two filter() calls were introduced in #7400.
Disclosure: AI tooling was used to trace the call paths and draft this report. The analysis was verified by reading the source at tag v4.3.21 and against a live self-hosted installation exhibiting the bug.
Source: coollabsio/coolify