fix(proxy): connect coolify-proxy to destination network before compose up

Add StandaloneDocker::proxyConnectCommand() and run it inline when a
destination is created, replacing the synchronous
ConnectProxyToNetworksJob dispatch. Deployments also run it before
docker compose up, so the proxy is attached to the destination network
even when it was missing.
This commit is contained in:
Andras Bacsai
2026-10-04 00:35:21 +02:00
parent 80b8b219a2
commit 9eb666e982
5 changed files with 22 additions and 57 deletions
+1
View File
@@ -4699,6 +4699,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
// Ensure .env file exists before docker compose tries to load it (defensive programming)
$this->execute_remote_command(
["touch {$this->configuration_dir}/.env", 'hidden' => true],
[$this->destination->proxyConnectCommand(), 'hidden' => true, 'ignore_errors' => true],
);
if ($this->application->build_pack === 'dockerimage') {
-55
View File
@@ -1,55 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Laravel\Horizon\Contracts\Silenced;
/**
* Asynchronously connects the coolify-proxy to Docker networks.
*
* This job is dispatched from PushServerUpdateJob when the proxy is found running
* to ensure it's connected to all required networks without blocking the status update.
*/
class ConnectProxyToNetworksJob implements ShouldBeEncrypted, ShouldQueue, Silenced
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 60;
public function middleware(): array
{
// Prevent overlapping executions for the same server and throttle to max once per 10 seconds
return [
(new WithoutOverlapping('connect-proxy-networks-'.$this->server->uuid))
->expireAfter(60)
->dontRelease(),
];
}
public function __construct(public Server $server) {}
public function handle()
{
if (! $this->server->isFunctional()) {
return;
}
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
if (empty($connectProxyToDockerNetworks)) {
return;
}
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
}
+8 -2
View File
@@ -2,7 +2,6 @@
namespace App\Models;
use App\Jobs\ConnectProxyToNetworksJob;
use App\Support\ValidationPatterns;
use App\Traits\HasSafeStringAttribute;
use Illuminate\Database\Eloquent\Collection;
@@ -45,8 +44,8 @@ class StandaloneDocker extends BaseModel
$server = $newStandaloneDocker->server;
instant_remote_process([
$newStandaloneDocker->networkCreateCommand(),
$newStandaloneDocker->proxyConnectCommand(),
], $server, false);
ConnectProxyToNetworksJob::dispatchSync($server);
});
}
@@ -57,6 +56,13 @@ class StandaloneDocker extends BaseModel
return "docker network inspect {$safeNetwork} >/dev/null 2>&1 || docker network create --attachable {$safeNetwork} >/dev/null";
}
public function proxyConnectCommand(): string
{
$safeNetwork = escapeshellarg($this->network);
return "docker network connect {$safeNetwork} coolify-proxy >/dev/null 2>&1 || true";
}
public function setNetworkAttribute(string $value): void
{
if (! ValidationPatterns::isValidDockerNetwork($value)) {
@@ -381,6 +381,7 @@ test('a Dockerfile deployment to an additional server writes missing content fil
'application_deployment_queue' => $queue,
'server' => $otherServer,
'mainServer' => $otherServer,
'destination' => $this->destination,
'deployment_uuid' => 'deployment-uuid',
'workdir' => '/artifacts/deployment-uuid',
'configuration_dir' => $this->application->workdir(),
@@ -403,6 +404,10 @@ test('a Dockerfile deployment to an additional server writes missing content fil
->and($write)->toBeLessThan($up)
->and(ContentFilesUpRecorder::ssh()[$write])->toContain("@'{$otherServer->ip}'")
->and($this->logEntries)->toContain(['Writing 1 missing configuration file.', 'stdout']);
$connect = ContentFilesUpRecorder::indexOf('deployment', "docker network connect '{$this->destination->network}' coolify-proxy");
expect($connect)->not->toBeNull()
->and($connect)->toBeLessThan($up);
});
test('the content file check and write are safe for non-root servers', function () {
@@ -35,6 +35,14 @@ it('creates standalone destinations with a bridge network', function () {
->toBe("docker network inspect 'test-network' >/dev/null 2>&1 || docker network create --attachable 'test-network' >/dev/null");
});
it('connects the proxy to a new standalone destination network', function () {
$model = new StandaloneDocker;
$model->network = 'test-network';
expect($model->proxyConnectCommand())
->toBe("docker network connect 'test-network' coolify-proxy >/dev/null 2>&1 || true");
});
it('SwarmDocker rejects network names with shell metacharacters', function (string $network) {
$model = new SwarmDocker;
$model->network = $network;