Skip to content

Commit 3848fa9

Browse files
authored
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.
1 parent f12ecc3 commit 3848fa9

1 file changed

Lines changed: 147 additions & 110 deletions

File tree

Lines changed: 147 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { findServerById } from "@dokploy/server/services/server";
22
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
3-
import type { ContainerCreateOptions } from "dockerode";
3+
import type { CreateServiceOptions } from "dockerode";
44
import { IS_CLOUD } from "../constants";
55
import { getDokployImageTag } from "../services/settings";
66
import { pullImage, pullRemoteImage } from "../utils/docker/utils";
77
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
88
import { getRemoteDocker } from "../utils/servers/remote-docker";
99

10-
export const setupMonitoring = async (serverId: string) => {
11-
const server = await findServerById(serverId);
12-
13-
const containerName = "dokploy-monitoring";
10+
const getMonitoringImage = () => {
1411
let imageName = "dokploy/monitoring:latest";
1512

1613
if (
@@ -21,134 +18,174 @@ export const setupMonitoring = async (serverId: string) => {
2118
imageName = "dokploy/monitoring:canary";
2219
}
2320

24-
const settings: ContainerCreateOptions = {
25-
name: containerName,
26-
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
27-
Image: imageName,
28-
HostConfig: {
29-
// Memory: 100 * 1024 * 1024, // 100MB en bytes
30-
// PidMode: "host",
31-
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
32-
// Privileged: true,
33-
RestartPolicy: {
34-
Name: "always",
21+
return imageName;
22+
};
23+
24+
const deployMonitoringService = async (
25+
docker: Awaited<ReturnType<typeof getRemoteDocker>>,
26+
serviceName: string,
27+
settings: CreateServiceOptions,
28+
) => {
29+
try {
30+
const service = docker.getService(serviceName);
31+
const inspect = await service.inspect();
32+
await service.update({
33+
version: Number.parseInt(inspect.Version.Index),
34+
...settings,
35+
TaskTemplate: {
36+
...settings.TaskTemplate,
37+
ForceUpdate: (inspect.Spec.TaskTemplate.ForceUpdate ?? 0) + 1,
3538
},
36-
PortBindings: {
37-
[`${server.metricsConfig.server.port}/tcp`]: [
39+
});
40+
console.log("Monitoring Updated ✅");
41+
} catch (error: any) {
42+
if (error?.statusCode && error.statusCode !== 404) {
43+
throw error;
44+
}
45+
await docker.createService(settings);
46+
console.log("Monitoring Started ✅");
47+
}
48+
};
49+
50+
export const setupMonitoring = async (serverId: string) => {
51+
const server = await findServerById(serverId);
52+
53+
const serviceName = "dokploy-monitoring";
54+
const imageName = getMonitoringImage();
55+
56+
const settings: CreateServiceOptions = {
57+
Name: serviceName,
58+
TaskTemplate: {
59+
ContainerSpec: {
60+
Image: imageName,
61+
Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
62+
Mounts: [
63+
{
64+
Type: "bind",
65+
Source: "/var/run/docker.sock",
66+
Target: "/var/run/docker.sock",
67+
ReadOnly: true,
68+
},
3869
{
39-
HostPort: server.metricsConfig.server.port.toString(),
70+
Type: "bind",
71+
Source: "/sys",
72+
Target: "/host/sys",
73+
ReadOnly: true,
74+
},
75+
{
76+
Type: "bind",
77+
Source: "/etc/os-release",
78+
Target: "/etc/os-release",
79+
ReadOnly: true,
80+
},
81+
{
82+
Type: "bind",
83+
Source: "/proc",
84+
Target: "/host/proc",
85+
ReadOnly: true,
86+
},
87+
{
88+
Type: "bind",
89+
Source: "/etc/dokploy/monitoring/monitoring.db",
90+
Target: "/app/monitoring.db",
4091
},
4192
],
4293
},
43-
Binds: [
44-
"/var/run/docker.sock:/var/run/docker.sock:ro",
45-
"/sys:/host/sys:ro",
46-
"/etc/os-release:/etc/os-release:ro",
47-
"/proc:/host/proc:ro",
48-
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
49-
],
50-
NetworkMode: "host",
94+
Networks: [{ Target: "host" }],
95+
Placement: {
96+
Constraints: ["node.role==manager"],
97+
},
5198
},
52-
ExposedPorts: {
53-
[`${server.metricsConfig.server.port}/tcp`]: {},
99+
Mode: {
100+
Replicated: {
101+
Replicas: 1,
102+
},
54103
},
55104
};
56-
const docker = await getRemoteDocker(serverId);
57-
try {
58-
await execAsyncRemote(
59-
serverId,
60-
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
61-
);
62-
if (serverId) {
63-
await pullRemoteImage(imageName, serverId);
64-
}
65105

66-
// Check if container exists
67-
const container = docker.getContainer(containerName);
68-
try {
69-
await container.inspect();
70-
await container.remove({ force: true });
71-
console.log("Removed existing container");
72-
} catch {
73-
// Container doesn't exist, continue
74-
}
75-
76-
await docker.createContainer(settings);
77-
const newContainer = docker.getContainer(containerName);
78-
await newContainer.start();
106+
const docker = await getRemoteDocker(serverId);
79107

80-
console.log("Monitoring Started ");
81-
} catch (error) {
82-
console.log("Monitoring Not Found: Starting ", error);
83-
}
108+
await execAsyncRemote(
109+
serverId,
110+
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
111+
);
112+
await pullRemoteImage(imageName, serverId);
113+
await deployMonitoringService(docker, serviceName, settings);
84114
};
85115

86116
export const setupWebMonitoring = async () => {
87117
const webServerSettings = await getWebServerSettings();
88118

89-
const containerName = "dokploy-monitoring";
90-
let imageName = "dokploy/monitoring:latest";
119+
const serviceName = "dokploy-monitoring";
120+
const imageName = getMonitoringImage();
121+
const port = webServerSettings?.metricsConfig?.server?.port;
91122

92-
if (
93-
(getDokployImageTag() !== "latest" ||
94-
process.env.NODE_ENV === "development") &&
95-
!IS_CLOUD
96-
) {
97-
imageName = "dokploy/monitoring:canary";
98-
}
99-
100-
const settings: ContainerCreateOptions = {
101-
name: containerName,
102-
Env: [`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`],
103-
Image: imageName,
104-
HostConfig: {
105-
// Memory: 100 * 1024 * 1024, // 100MB en bytes
106-
// PidMode: "host",
107-
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
108-
// Privileged: true,
109-
RestartPolicy: {
110-
Name: "always",
111-
},
112-
PortBindings: {
113-
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: [
123+
const settings: CreateServiceOptions = {
124+
Name: serviceName,
125+
TaskTemplate: {
126+
ContainerSpec: {
127+
Image: imageName,
128+
Env: [
129+
`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`,
130+
],
131+
Mounts: [
132+
{
133+
Type: "bind",
134+
Source: "/var/run/docker.sock",
135+
Target: "/var/run/docker.sock",
136+
ReadOnly: true,
137+
},
138+
{
139+
Type: "bind",
140+
Source: "/sys",
141+
Target: "/host/sys",
142+
ReadOnly: true,
143+
},
144+
{
145+
Type: "bind",
146+
Source: "/etc/os-release",
147+
Target: "/etc/os-release",
148+
ReadOnly: true,
149+
},
150+
{
151+
Type: "bind",
152+
Source: "/proc",
153+
Target: "/host/proc",
154+
ReadOnly: true,
155+
},
114156
{
115-
HostPort: webServerSettings?.metricsConfig?.server?.port.toString(),
157+
Type: "bind",
158+
Source: "/etc/dokploy/monitoring/monitoring.db",
159+
Target: "/app/monitoring.db",
116160
},
117161
],
118162
},
119-
Binds: [
120-
"/var/run/docker.sock:/var/run/docker.sock:ro",
121-
"/sys:/host/sys:ro",
122-
"/etc/os-release:/etc/os-release:ro",
123-
"/proc:/host/proc:ro",
124-
"/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
125-
],
126-
// NetworkMode: "host",
163+
Placement: {
164+
Constraints: ["node.role==manager"],
165+
},
127166
},
128-
ExposedPorts: {
129-
[`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: {},
167+
Mode: {
168+
Replicated: {
169+
Replicas: 1,
170+
},
171+
},
172+
EndpointSpec: {
173+
Ports: [
174+
{
175+
TargetPort: port,
176+
PublishedPort: port,
177+
Protocol: "tcp",
178+
PublishMode: "host",
179+
},
180+
],
130181
},
131182
};
132-
const docker = await getRemoteDocker();
133-
try {
134-
await execAsync(
135-
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
136-
);
137-
await pullImage(imageName);
138183

139-
const container = docker.getContainer(containerName);
140-
try {
141-
await container.inspect();
142-
await container.remove({ force: true });
143-
console.log("Removed existing container");
144-
} catch {}
145-
146-
await docker.createContainer(settings);
147-
const newContainer = docker.getContainer(containerName);
148-
await newContainer.start();
184+
const docker = await getRemoteDocker();
149185

150-
console.log("Monitoring Started ");
151-
} catch (error) {
152-
console.log("Monitoring Not Found: Starting ", error);
153-
}
186+
await execAsync(
187+
"mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
188+
);
189+
await pullImage(imageName);
190+
await deployMonitoringService(docker, serviceName, settings);
154191
};

0 commit comments

Comments
 (0)