Skip to content

Commit a7d8dff

Browse files
authored
Merge pull request #5624 from Dokploy/feat/logs-and-metrics
feat(logs-and-metrics): ship container logs and metrics to external providers via Vector
2 parents 931f5c1 + b329595 commit a7d8dff

76 files changed

Lines changed: 18326 additions & 74 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Dokploy includes multiple features to make your life easier.
2626
- **Templates**: Deploy open-source templates (Plausible, Pocketbase, Calcom, etc.) with a single click.
2727
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
2828
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage for every resource.
29+
- **Logs & Metrics**: Ship container logs and host/container metrics to Grafana (Loki, Prometheus/Mimir), Datadog, New Relic, InfluxDB, VictoriaMetrics, Better Stack, Elasticsearch/OpenSearch, Splunk, or AWS CloudWatch, tagged by project/environment/application.
2930
- **Docker Management**: Easily deploy and manage Docker containers.
3031
- **CLI/API**: Manage your applications and databases using the command line or through the API.
3132
- **Notifications**: Get notified when your deployments succeed or fail (via Slack, Discord, Telegram, Email, etc.).

‎apps/dokploy/__test__/compose/domain/raw-compose.test.ts‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,16 @@ services:
3333
volumes:
3434
test-data:
3535
`,
36+
composeId: "compose-1",
37+
name: "raw-stack",
3638
composePath: "./docker-compose.yml",
3739
composeType: "stack",
40+
environment: {
41+
environmentId: "env-1",
42+
name: "production",
43+
projectId: "project-1",
44+
project: { name: "shop", organizationId: "org-1" },
45+
},
3846
isolatedDeployment: false,
3947
isolatedDeploymentsVolume: false,
4048
randomize: false,
@@ -54,6 +62,72 @@ volumes:
5462
},
5563
]);
5664
expect(converted?.services?.test?.tmpfs).toEqual(["/cache"]);
65+
expect(converted?.services?.test?.labels).toEqual(
66+
expect.arrayContaining([
67+
"dokploy.organization.id=org-1",
68+
"dokploy.project=shop",
69+
"dokploy.service=test",
70+
]),
71+
);
72+
expect(converted?.services?.test?.deploy?.labels).toEqual(
73+
expect.arrayContaining(["dokploy.organization.id=org-1"]),
74+
);
5775
expect(execAsyncRemote).not.toHaveBeenCalled();
5876
});
77+
78+
it("escapes `$` in the Dokploy label values so compose keeps them literal", async () => {
79+
const compose = {
80+
appName: "dollar-stack",
81+
composeFile: `
82+
services:
83+
mapped:
84+
image: alpine:latest
85+
labels:
86+
custom: keep
87+
deploy:
88+
labels:
89+
custom: keep
90+
listed:
91+
image: alpine:latest
92+
labels:
93+
- custom=keep
94+
`,
95+
composeId: "compose-1",
96+
name: "app $DB_PASSWORD",
97+
composePath: "./docker-compose.yml",
98+
composeType: "stack",
99+
environment: {
100+
environmentId: "env-1",
101+
name: "env ${HOME}",
102+
projectId: "project-1",
103+
project: { name: "Budget $100", organizationId: "org-1" },
104+
},
105+
isolatedDeployment: false,
106+
isolatedDeploymentsVolume: false,
107+
randomize: false,
108+
serverId: null,
109+
sourceType: "raw",
110+
suffix: "",
111+
} as unknown as Parameters<typeof addDomainToCompose>[0];
112+
113+
const converted = await addDomainToCompose(compose, []);
114+
const escaped = {
115+
"dokploy.project": "Budget $$100",
116+
"dokploy.environment": "env $${HOME}",
117+
"dokploy.application": "app $$DB_PASSWORD",
118+
};
119+
120+
const mapped = converted?.services?.mapped;
121+
expect(mapped?.labels).toMatchObject({ custom: "keep", ...escaped });
122+
expect(mapped?.deploy?.labels).toMatchObject({
123+
custom: "keep",
124+
...escaped,
125+
});
126+
const listed = converted?.services?.listed;
127+
const asList = Object.entries(escaped).map(([k, v]) => `${k}=${v}`);
128+
expect(listed?.labels).toEqual(
129+
expect.arrayContaining(["custom=keep", ...asList]),
130+
);
131+
expect(listed?.deploy?.labels).toEqual(expect.arrayContaining(asList));
132+
});
59133
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const mocks = vi.hoisted(() => ({
4+
findOwnedOrganizations: vi.fn(),
5+
transaction: vi.fn(),
6+
removeVectorAgents: vi.fn(),
7+
calls: [] as string[],
8+
}));
9+
10+
vi.mock("@dokploy/server/db", () => ({
11+
db: {
12+
query: { organization: { findMany: mocks.findOwnedOrganizations } },
13+
transaction: mocks.transaction,
14+
},
15+
}));
16+
vi.mock("@dokploy/server/services/organization", () => ({
17+
removeVectorAgents: mocks.removeVectorAgents,
18+
}));
19+
20+
const { deleteUserAccountData } = await import(
21+
"@dokploy/server/services/account-deletion"
22+
);
23+
24+
beforeEach(() => {
25+
mocks.calls.length = 0;
26+
mocks.findOwnedOrganizations.mockReset();
27+
mocks.removeVectorAgents
28+
.mockReset()
29+
.mockImplementation(async (id: string) => mocks.calls.push(`agents:${id}`));
30+
mocks.transaction.mockReset().mockImplementation(async () => {
31+
mocks.calls.push("delete");
32+
return { email: "a@b.c", organizationsDeleted: 2 };
33+
});
34+
});
35+
36+
describe("deleteUserAccountData", () => {
37+
it("removes the Vector agents of every owned organization before deleting the user", async () => {
38+
mocks.findOwnedOrganizations.mockResolvedValue([
39+
{ id: "org-a" },
40+
{ id: "org-b" },
41+
]);
42+
43+
await deleteUserAccountData("user-1");
44+
45+
expect(mocks.calls).toEqual(["agents:org-a", "agents:org-b", "delete"]);
46+
});
47+
48+
it("deletes the user without touching agents when it owns no organization", async () => {
49+
mocks.findOwnedOrganizations.mockResolvedValue([]);
50+
51+
await deleteUserAccountData("user-1");
52+
53+
expect(mocks.removeVectorAgents).not.toHaveBeenCalled();
54+
expect(mocks.calls).toEqual(["delete"]);
55+
});
56+
});

0 commit comments

Comments
 (0)