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
+139 -102
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)}`],
const deployMonitoringService = async (
docker: Awaited<ReturnType<typeof getRemoteDocker>>,
serviceName: string,
settings: CreateServiceOptions,
) => {
try {
const service = docker.getService(serviceName);
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,
},
});
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, Image: imageName,
HostConfig: { Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
// Memory: 100 * 1024 * 1024, // 100MB en bytes Mounts: [
// PidMode: "host",
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
RestartPolicy: {
Name: "always",
},
PortBindings: {
[`${server.metricsConfig.server.port}/tcp`]: [
{ {
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", Mode: {
], Replicated: {
NetworkMode: "host", Replicas: 1,
}, },
ExposedPorts: {
[`${server.metricsConfig.server.port}/tcp`]: {},
}, },
}; };
const docker = await getRemoteDocker(serverId); const docker = await getRemoteDocker(serverId);
try {
await execAsyncRemote( await execAsyncRemote(
serverId, serverId,
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db", "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
); );
if (serverId) {
await pullRemoteImage(imageName, serverId); await pullRemoteImage(imageName, serverId);
} await deployMonitoringService(docker, serviceName, settings);
// Check if container exists
const container = docker.getContainer(containerName);
try {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
} 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: {
) {
imageName = "dokploy/monitoring:canary";
}
const settings: ContainerCreateOptions = {
name: containerName,
Env: [`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`],
Image: imageName, Image: imageName,
HostConfig: { Env: [
// Memory: 100 * 1024 * 1024, // 100MB en bytes `METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`,
// PidMode: "host", ],
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"], Mounts: [
// 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: { },
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: {}, Mode: {
Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
TargetPort: port,
PublishedPort: port,
Protocol: "tcp",
PublishMode: "host",
},
],
}, },
}; };
const docker = await getRemoteDocker(); const docker = await getRemoteDocker();
try {
await execAsync( await execAsync(
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db", "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
); );
await pullImage(imageName); await pullImage(imageName);
await deployMonitoringService(docker, serviceName, settings);
const container = docker.getContainer(containerName);
try {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
} 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);
}
}; };