fix(docker): replace deprecated stop flags with version-aware ones

Store each server's Docker version and build stop commands via
dockerStopCommand() so newer Docker uses --timeout instead of
deprecated --time/-t. Also show Docker version in server details.
This commit is contained in:
Andras Bacsai committed 2026-08-13 11:17:44 +02:00
1 parent 6481ffffcf
commit e79230cf37
32 files changed
+697 -64

No files matched your search

+1 -1
View File
@@ -40,7 +40,7 @@ class StopApplication
foreach ($containersToStop as $containerName) {
instant_remote_process(command: [
"docker stop --time=$timeout $containerName",
dockerStopCommand($timeout, $containerName, $server),
"docker rm -f $containerName",
], server: $server, throwError: false);
}
@@ -28,7 +28,7 @@ class StopApplicationOneServer
if ($containerName) {
instant_remote_process(
[
"docker stop --time=$timeout $containerName",
dockerStopCommand($timeout, $containerName, $server),
"docker rm -f $containerName",
],
$server
+1 -1
View File
@@ -104,7 +104,7 @@ class StartClickhouse
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
+1 -1
View File
@@ -191,7 +191,7 @@ class StartDragonfly
if ($this->database->enable_ssl) {
$this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt";
}
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
+1 -1
View File
@@ -209,7 +209,7 @@ class StartKeydb
if (! is_null($this->database->keydb_conf) && ! empty($this->database->keydb_conf)) {
$this->commands[] = "chown 999:999 $this->configuration_dir/keydb.conf";
}
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
+1 -1
View File
@@ -208,7 +208,7 @@ class StartMariadb
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
+1 -1
View File
@@ -257,7 +257,7 @@ class StartMongodb
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
if ($this->database->enable_ssl) {
+1 -1
View File
@@ -209,7 +209,7 @@ class StartMysql
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
+1 -1
View File
@@ -219,7 +219,7 @@ class StartPostgresql
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
if ($this->database->enable_ssl) {
+1 -1
View File
@@ -204,7 +204,7 @@ class StartRedis
if (! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf)) {
$this->commands[] = "chown 999:999 $this->configuration_dir/redis.conf";
}
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
$this->commands[] = dockerStopCommand(10, $container_name, $this->database->destination->server).' 2>/dev/null || true';
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
+1 -1
View File
@@ -56,7 +56,7 @@ class StopDatabase
{
$server = $database->destination->server;
instant_remote_process(command: [
"docker stop -t $timeout $containerName",
dockerStopCommand($timeout, $containerName, $server),
"docker rm -f $containerName",
], server: $server, throwError: false);
}
+1 -1
View File
@@ -24,7 +24,7 @@ class StopProxy
}
instant_remote_process(command: [
"docker stop -t=$timeout $containerName 2>/dev/null || true",
dockerStopCommand($timeout, $containerName, $server).' 2>/dev/null || true',
"docker rm -f $containerName 2>/dev/null || true",
'# Wait for container to be fully removed',
'for i in {1..10}; do',
+1 -1
View File
@@ -67,7 +67,7 @@ class StopService
$timeout = count($containersToStop) > 5 ? 10 : 30;
$commands = [];
$containerList = implode(' ', $containersToStop);
$commands[] = "docker stop -t $timeout $containerList";
$commands[] = dockerStopCommand($timeout, $containerList, $server);
$commands[] = "docker rm -f $containerList";
instant_remote_process(
command: $commands,
+3 -2
View File
@@ -431,6 +431,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
["docker version --format '{{.Server.Version}}'"],
$serverToCheck
);
$serverToCheck->rememberDockerVersion($dockerVersion);
$versionParts = explode('.', $dockerVersion);
$majorVersion = (int) $versionParts[0];
@@ -3972,11 +3973,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($skipRemove) {
$this->execute_remote_command(
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true]
[dockerStopCommand($timeout, $containerName, $this->server), 'hidden' => true, 'ignore_errors' => true]
);
} else {
$this->execute_remote_command(
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
[dockerStopCommand($timeout, $containerName, $this->server), 'hidden' => true, 'ignore_errors' => true],
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
);
}
+1 -1
View File
@@ -216,7 +216,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
$containerList = implode(' ', array_map('escapeshellarg', $containerNames));
$commands = [
"docker stop -t $timeout $containerList",
dockerStopCommand($timeout, $containerList, $server),
"docker rm -f $containerList",
];
instant_remote_process(
+1 -1
View File
@@ -98,7 +98,7 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
// === STOP PHASE ===
$commands = $commands->merge([
"echo 'Stopping proxy...'",
"docker stop -t=$stopTimeout $containerName 2>/dev/null || true",
dockerStopCommand($stopTimeout, $containerName, $this->server).' 2>/dev/null || true',
"docker rm -f $containerName 2>/dev/null || true",
'# Wait for container to be fully removed',
'for i in {1..15}; do',
+14
View File
@@ -194,6 +194,20 @@ class ServerConnectionCheckJob implements ShouldBeEncrypted, ShouldQueue
$output = trim($output);
if (! empty($output)) {
$dockerInfo = json_decode($output, true);
$dockerVersion = dockerEngineVersionFromJson($output);
if ($dockerVersion !== null) {
$this->server->rememberDockerVersion($dockerVersion);
}
$composeOutput = instant_remote_process_with_timeout(
['docker compose version --short'],
$this->server,
false
);
$composeVersion = parseDockerEngineVersion($composeOutput);
if ($composeVersion !== null) {
$this->server->rememberComposeVersion($composeVersion);
}
return isset($dockerInfo['Server']['Version']);
}
@@ -354,7 +354,7 @@ class Previews extends Component
foreach ($containersToStop as $containerName) {
instant_remote_process(command: [
"docker stop --time=$timeout $containerName",
dockerStopCommand($timeout, $containerName, $server),
"docker rm -f $containerName",
], server: $server, throwError: false);
}
+10 -2
View File
@@ -371,6 +371,8 @@ class Show extends Component
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
ServerReachabilityChanged::dispatch($this->server);
$this->server->gatherServerMetadata();
$this->server->refresh();
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
@@ -671,12 +673,18 @@ class Show extends Component
{
try {
$this->authorize('update', $this->server);
if (! $this->server->isFunctional()) {
$this->dispatch('error', 'Validate the server connection before fetching details.');
return;
}
$result = $this->server->gatherServerMetadata();
if ($result) {
$this->server->refresh();
$this->server->refresh()->load('settings');
$this->dispatch('success', 'Server details refreshed.');
} else {
$this->dispatch('error', 'Could not fetch server details. Is the server reachable?');
$this->dispatch('error', 'Could not collect server details. Check the application logs for the remote command output.');
}
} catch (\Throwable $e) {
handleError($e, $this);
+48 -2
View File
@@ -961,7 +961,7 @@ $schema://$host {
public function stopUnmanaged($id)
{
return instant_remote_process(['docker stop -t 0 '.escapeshellarg($id)], $this);
return instant_remote_process([dockerStopCommand(0, escapeshellarg($id), $this)], $this);
}
public function restartUnmanaged($id)
@@ -1351,7 +1351,7 @@ $schema://$host {
try {
$output = instant_remote_process([
'echo "---PRETTY_NAME---" && grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d \'"\' && echo "---ARCH---" && uname -m && echo "---KERNEL---" && uname -r && echo "---CPUS---" && nproc && echo "---MEMORY---" && free -b | awk \'/Mem:/{print $2}\' && echo "---UPTIME_SINCE---" && uptime -s',
'echo "---PRETTY_NAME---" && grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d \'"\' && echo "---ARCH---" && uname -m && echo "---KERNEL---" && uname -r && echo "---CPUS---" && nproc && echo "---MEMORY---" && free -b | awk \'/Mem:/{print $2}\' && echo "---UPTIME_SINCE---" && uptime -s && echo "---DOCKER---" && (docker version --format \'{{.Server.Version}}\' 2>/dev/null || true) && echo "---COMPOSE---" && (docker compose version --short 2>/dev/null || true)',
], $this, false);
if (! $output) {
@@ -1381,6 +1381,23 @@ $schema://$host {
$this->update(['server_metadata' => $metadata]);
try {
$detectedDockerVersion = parseDockerEngineVersion($sections['DOCKER'] ?? null);
if ($detectedDockerVersion !== null) {
$this->rememberDockerVersion($detectedDockerVersion);
}
$detectedComposeVersion = parseDockerEngineVersion($sections['COMPOSE'] ?? null);
if ($detectedComposeVersion !== null) {
$this->rememberComposeVersion($detectedComposeVersion);
}
} catch (\Throwable $e) {
Log::debug('Failed to store server runtime versions', [
'server_id' => $this->id,
'error' => $e->getMessage(),
]);
}
return $metadata;
} catch (\Throwable $e) {
Log::debug('Failed to gather server metadata', [
@@ -1604,11 +1621,40 @@ $schema://$host {
return true;
}
public function dockerVersion(): ?string
{
return $this->settings?->docker_version;
}
public function rememberDockerVersion(?string $version): void
{
$this->settings->update([
'docker_version' => parseDockerEngineVersion($version),
'docker_version_checked_at' => now(),
]);
}
public function composeVersion(): ?string
{
return $this->settings?->compose_version;
}
public function rememberComposeVersion(?string $version): void
{
$this->settings->update([
'compose_version' => parseDockerEngineVersion($version),
'compose_version_checked_at' => now(),
]);
}
public function validateDockerEngineVersion()
{
$dockerVersionRaw = instant_remote_process(['docker version --format json'], $this, false);
$dockerVersionJson = json_decode($dockerVersionRaw, true);
$dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0');
$this->rememberDockerVersion(is_string($dockerVersion) ? $dockerVersion : null);
$composeVersionRaw = instant_remote_process(['docker compose version --short'], $this, false);
$this->rememberComposeVersion(is_string($composeVersionRaw) ? $composeVersionRaw : null);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;
+10
View File
@@ -51,6 +51,10 @@ use OpenApi\Attributes as OA;
'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'],
'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'],
'connection_timeout' => ['type' => 'integer', 'description' => 'SSH connection timeout in seconds.'],
'docker_version' => ['type' => 'string', 'nullable' => true, 'description' => 'Detected Docker Engine version on the server.'],
'docker_version_checked_at' => ['type' => 'string', 'nullable' => true, 'description' => 'When Docker Engine version was last detected.'],
'compose_version' => ['type' => 'string', 'nullable' => true, 'description' => 'Detected Docker Compose plugin version on the server.'],
'compose_version_checked_at' => ['type' => 'string', 'nullable' => true, 'description' => 'When Docker Compose version was last detected.'],
]
)]
class ServerSetting extends Model
@@ -100,6 +104,10 @@ class ServerSetting extends Model
'deployment_queue_limit',
'disable_application_image_retention',
'connection_timeout',
'docker_version',
'docker_version_checked_at',
'compose_version',
'compose_version_checked_at',
];
protected $casts = [
@@ -113,6 +121,8 @@ class ServerSetting extends Model
'is_terminal_enabled' => 'boolean',
'disable_application_image_retention' => 'boolean',
'connection_timeout' => 'integer',
'docker_version_checked_at' => 'datetime',
'compose_version_checked_at' => 'datetime',
];
/**