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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,16 +1,13 @@
import { findServerById } from "@dokploy/server/services/server";
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 { getDokployImageTag } from "../services/settings";
import { pullImage, pullRemoteImage } from "../utils/docker/utils";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
import { getRemoteDocker } from "../utils/servers/remote-docker";
export const setupMonitoring = async (serverId: string) => {
const server = await findServerById(serverId);
const containerName = "dokploy-monitoring";
const getMonitoringImage = () => {
let imageName = "dokploy/monitoring:latest";
if (
@ -21,134 +18,174 @@ export const setupMonitoring = async (serverId: string) => {
imageName = "dokploy/monitoring:canary";
}
const settings: ContainerCreateOptions = {
name: containerName,
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
// PidMode: "host",
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
RestartPolicy: {
Name: "always",
return imageName;
};
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,
},
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: [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"/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",
Networks: [{ Target: "host" }],
Placement: {
Constraints: ["node.role==manager"],
},
},
ExposedPorts: {
[`${server.metricsConfig.server.port}/tcp`]: {},
Mode: {
Replicated: {
Replicas: 1,
},
},
};
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
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);
}
await execAsyncRemote(
serverId,
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
await pullRemoteImage(imageName, serverId);
await deployMonitoringService(docker, serviceName, settings);
};
export const setupWebMonitoring = async () => {
const webServerSettings = await getWebServerSettings();
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest";
const serviceName = "dokploy-monitoring";
const imageName = getMonitoringImage();
const port = webServerSettings?.metricsConfig?.server?.port;
if (
(getDokployImageTag() !== "latest" ||
process.env.NODE_ENV === "development") &&
!IS_CLOUD
) {
imageName = "dokploy/monitoring:canary";
}
const settings: ContainerCreateOptions = {
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`]: [
const settings: CreateServiceOptions = {
Name: serviceName,
TaskTemplate: {
ContainerSpec: {
Image: imageName,
Env: [
`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`,
],
Mounts: [
{
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: [
"/var/run/docker.sock:/var/run/docker.sock:ro",
"/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",
Placement: {
Constraints: ["node.role==manager"],
},
},
ExposedPorts: {
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: {},
Mode: {
Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
TargetPort: port,
PublishedPort: port,
Protocol: "tcp",
PublishMode: "host",
},
],
},
};
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);
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);
}
await execAsync(
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
);
await pullImage(imageName);
await deployMonitoringService(docker, serviceName, settings);
};