fix: deploy monitoring as a swarm service instead of a standalone container (#5055)

Monitoring was the only component still using docker.createContainer
directly instead of docker.createService like every other Dokploy
component (postgres, traefik, forward-auth, etc). This meant it never
benefited from Swarm's own reconciliation, relying only on Docker's
restart policy with no self-healing if the daemon didn't come back
cleanly after a reboot.

Also removed the try/catch that silently swallowed setup errors,
which made the UI report success even when the container/service
failed to start.
This commit is contained in:
Mauricio Siu
2026-08-12 01:36:16 -06:00
committed by GitHub
parent f12ecc3350
commit 3848fa9c0d
+147 -110
View File
@@ -1,16 +1,13 @@
import { findServerById } from "@dokploy/server/services/server"; import { findServerById } from "@dokploy/server/services/server";
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings"; import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import type { ContainerCreateOptions } from "dockerode"; import type { CreateServiceOptions } from "dockerode";
import { IS_CLOUD } from "../constants"; import { IS_CLOUD } from "../constants";
import { getDokployImageTag } from "../services/settings"; import { getDokployImageTag } from "../services/settings";
import { pullImage, pullRemoteImage } from "../utils/docker/utils"; import { pullImage, pullRemoteImage } from "../utils/docker/utils";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
import { getRemoteDocker } from "../utils/servers/remote-docker"; import { getRemoteDocker } from "../utils/servers/remote-docker";
export const setupMonitoring = async (serverId: string) => { const getMonitoringImage = () => {
const server = await findServerById(serverId);
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest"; let imageName = "dokploy/monitoring:latest";
if ( if (
@@ -21,134 +18,174 @@ export const setupMonitoring = async (serverId: string) => {
imageName = "dokploy/monitoring:canary"; imageName = "dokploy/monitoring:canary";
} }
const settings: ContainerCreateOptions = { return imageName;
name: containerName, };
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
Image: imageName, const deployMonitoringService = async (
HostConfig: { docker: Awaited<ReturnType<typeof getRemoteDocker>>,
// Memory: 100 * 1024 * 1024, // 100MB en bytes serviceName: string,
// PidMode: "host", settings: CreateServiceOptions,
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"], ) => {
// Privileged: true, try {
RestartPolicy: { const service = docker.getService(serviceName);
Name: "always", const inspect = await service.inspect();
await service.update({
version: Number.parseInt(inspect.Version.Index),
...settings,
TaskTemplate: {
...settings.TaskTemplate,
ForceUpdate: (inspect.Spec.TaskTemplate.ForceUpdate ?? 0) + 1,
}, },
PortBindings: { });
[`${server.metricsConfig.server.port}/tcp`]: [ console.log("Monitoring Updated ✅");
} catch (error: any) {
if (error?.statusCode && error.statusCode !== 404) {
throw error;
}
await docker.createService(settings);
console.log("Monitoring Started ✅");
}
};
export const setupMonitoring = async (serverId: string) => {
const server = await findServerById(serverId);
const serviceName = "dokploy-monitoring";
const imageName = getMonitoringImage();
const settings: CreateServiceOptions = {
Name: serviceName,
TaskTemplate: {
ContainerSpec: {
Image: imageName,
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
Mounts: [
{ {
HostPort: server.metricsConfig.server.port.toString(), Type: "bind",
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
ReadOnly: true,
},
{
Type: "bind",
Source: "/sys",
Target: "/host/sys",
ReadOnly: true,
},
{
Type: "bind",
Source: "/etc/os-release",
Target: "/etc/os-release",
ReadOnly: true,
},
{
Type: "bind",
Source: "/proc",
Target: "/host/proc",
ReadOnly: true,
},
{
Type: "bind",
Source: "/etc/dokploy/monitoring/monitoring.db",
Target: "/app/monitoring.db",
}, },
], ],
}, },
Binds: [ Networks: [{ Target: "host" }],
"/var/run/docker.sock:/var/run/docker.sock:ro", Placement: {
"/sys:/host/sys:ro", Constraints: ["node.role==manager"],
"/etc/os-release:/etc/os-release:ro", },
"/proc:/host/proc:ro",
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
],
NetworkMode: "host",
}, },
ExposedPorts: { Mode: {
[`${server.metricsConfig.server.port}/tcp`]: {}, Replicated: {
Replicas: 1,
},
}, },
}; };
const docker = await getRemoteDocker(serverId); const docker = await getRemoteDocker(serverId);
try {
await execAsyncRemote(
serverId,
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
if (serverId) {
await pullRemoteImage(imageName, serverId);
}
// Check if container exists await execAsyncRemote(
const container = docker.getContainer(containerName); serverId,
try { "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
await container.inspect(); );
await container.remove({ force: true }); await pullRemoteImage(imageName, serverId);
console.log("Removed existing container"); await deployMonitoringService(docker, serviceName, settings);
} catch {
// Container doesn't exist, continue
}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Monitoring Started ");
} catch (error) {
console.log("Monitoring Not Found: Starting ", error);
}
}; };
export const setupWebMonitoring = async () => { export const setupWebMonitoring = async () => {
const webServerSettings = await getWebServerSettings(); const webServerSettings = await getWebServerSettings();
const containerName = "dokploy-monitoring"; const serviceName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest"; const imageName = getMonitoringImage();
const port = webServerSettings?.metricsConfig?.server?.port;
if ( const settings: CreateServiceOptions = {
(getDokployImageTag() !== "latest" || Name: serviceName,
process.env.NODE_ENV === "development") && TaskTemplate: {
!IS_CLOUD ContainerSpec: {
) { Image: imageName,
imageName = "dokploy/monitoring:canary"; Env: [
} `METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`,
],
const settings: ContainerCreateOptions = { Mounts: [
name: containerName,
Env: [`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
// PidMode: "host",
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
RestartPolicy: {
Name: "always",
},
PortBindings: {
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: [
{ {
HostPort: webServerSettings?.metricsConfig?.server?.port.toString(), Type: "bind",
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
ReadOnly: true,
},
{
Type: "bind",
Source: "/sys",
Target: "/host/sys",
ReadOnly: true,
},
{
Type: "bind",
Source: "/etc/os-release",
Target: "/etc/os-release",
ReadOnly: true,
},
{
Type: "bind",
Source: "/proc",
Target: "/host/proc",
ReadOnly: true,
},
{
Type: "bind",
Source: "/etc/dokploy/monitoring/monitoring.db",
Target: "/app/monitoring.db",
}, },
], ],
}, },
Binds: [ Placement: {
"/var/run/docker.sock:/var/run/docker.sock:ro", Constraints: ["node.role==manager"],
"/sys:/host/sys:ro", },
"/etc/os-release:/etc/os-release:ro",
"/proc:/host/proc:ro",
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
],
// NetworkMode: "host",
}, },
ExposedPorts: { Mode: {
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: {}, Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
TargetPort: port,
PublishedPort: port,
Protocol: "tcp",
PublishMode: "host",
},
],
}, },
}; };
const docker = await getRemoteDocker(); const docker = await getRemoteDocker();
try {
await execAsync(
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
await pullImage(imageName);
const container = docker.getContainer(containerName); await execAsync(
try { "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
await container.inspect(); );
await container.remove({ force: true }); await pullImage(imageName);
console.log("Removed existing container"); await deployMonitoringService(docker, serviceName, settings);
} catch {}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
await newContainer.start();
console.log("Monitoring Started ");
} catch (error) {
console.log("Monitoring Not Found: Starting ", error);
}
}; };