Skip to content
Open
113 changes: 113 additions & 0 deletions apps/dokploy/__test__/queues/redis-connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { afterEach, describe, expect, it, vi } from "vitest";
Comment thread
satoukouga marked this conversation as resolved.
Outdated
Comment thread
satoukouga marked this conversation as resolved.
Outdated
import fs from "node:fs";

// Mock fs for readSecret tests
vi.mock("node:fs");

// Mock dockerode to verify service settings
const mockCreateService = vi.fn().mockResolvedValue({});
const mockGetService = vi.fn().mockReturnValue({
inspect: vi.fn().mockRejectedValue(new Error("Not found")),
update: vi.fn().mockResolvedValue({}),
});

vi.mock("dockerode", () => {
return {
default: vi.fn().mockImplementation(function() {
return {
createService: mockCreateService,
getService: mockGetService,
pull: vi.fn().mockResolvedValue({}),
};
}),
};
});

describe("redis-connection", () => {
afterEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
vi.clearAllMocks();
});

it("should use REDIS_URL if provided", async () => {
vi.stubEnv("REDIS_URL", "redis://user:pass@remote-host:6379/1");

const { redisConfig } = await import("../../server/queues/redis-connection");

expect(redisConfig).toEqual({
url: "redis://user:pass@remote-host:6379/1",
});
}, 30000);

it("should use individual env vars if REDIS_URL is not provided", async () => {
vi.stubEnv("REDIS_HOST", "custom-host");
vi.stubEnv("REDIS_PORT", "1234");
vi.stubEnv("REDIS_DB_INDEX", "2");
vi.stubEnv("REDIS_PASSWORD", "secret");
vi.stubEnv("REDIS_USERNAME", "admin");

const { redisConfig } = await import("../../server/queues/redis-connection");

expect(redisConfig).toEqual({
host: "custom-host",
port: 1234,
db: 2,
password: "secret",
username: "admin",
});
});

it("should read password from REDIS_PASSWORD_FILE if provided", async () => {
vi.stubEnv("REDIS_PASSWORD_FILE", "/tmp/password.txt");
vi.mocked(fs.readFileSync).mockReturnValue("file-secret\n");

const { redisConfig } = await import("../../server/queues/redis-connection");

expect(redisConfig.password).toBe("file-secret");
expect(fs.readFileSync).toHaveBeenCalledWith("/tmp/password.txt", "utf8");
});

it("should fallback to defaults in development", async () => {
vi.stubEnv("NODE_ENV", "development");

const { redisConfig } = await import("../../server/queues/redis-connection");

expect(redisConfig).toEqual({
host: "127.0.0.1",
port: 6379,
db: 0,
});
});

it("should fallback to production defaults", async () => {
vi.stubEnv("NODE_ENV", "production");

const { redisConfig } = await import("../../server/queues/redis-connection");

expect(redisConfig).toEqual({
host: "dokploy-redis",
port: 6379,
db: 0,
});
});

it("should verify initializeRedis creates service with correct Command and Args when password is set", async () => {
vi.stubEnv("REDIS_PASSWORD", "test-pass");
vi.stubEnv("NODE_ENV", "production");

// We need to import initializeRedis AFTER stubbing the env
const { initializeRedis } = await import("@dokploy/server/setup/redis-setup");

await initializeRedis();

Comment thread
satoukouga marked this conversation as resolved.
Outdated
expect(mockCreateService).toHaveBeenCalledWith(expect.objectContaining({
TaskTemplate: expect.objectContaining({
ContainerSpec: expect.objectContaining({
Command: ["redis-server"],
Args: ["--requirepass", "test-pass"],
})
})
}));
}, 30000);
});
8 changes: 2 additions & 6 deletions apps/dokploy/server/queues/redis-connection.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { redisConfig as sharedRedisConfig } from "@dokploy/server";
import type { ConnectionOptions } from "bullmq";

export const redisConfig: ConnectionOptions = {
host:
process.env.NODE_ENV === "production"
? process.env.REDIS_HOST || "dokploy-redis"
: "127.0.0.1",
};
export const redisConfig = sharedRedisConfig as ConnectionOptions;
Comment thread
satoukouga marked this conversation as resolved.
Outdated
5 changes: 2 additions & 3 deletions apps/schedules/src/queue.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Queue, type RepeatableJob } from "bullmq";
import { redisConfig } from "@dokploy/server";
import { logger } from "./logger.js";
import type { QueueJob } from "./schema.js";

export const jobQueue = new Queue("backupQueue", {
connection: {
url: process.env.REDIS_URL!,
},
connection: redisConfig,
Comment thread
satoukouga marked this conversation as resolved.
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: true,
Expand Down
13 changes: 4 additions & 9 deletions apps/schedules/src/workers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Job, Worker } from "bullmq";
import { redisConfig } from "@dokploy/server";
Comment thread
satoukouga marked this conversation as resolved.
Outdated
import { logger } from "./logger.js";
import type { QueueJob } from "./schema.js";
import { runJobs } from "./utils.js";
Expand All @@ -11,9 +12,7 @@ export const firstWorker = new Worker(
},
{
concurrency: 100,
connection: {
url: process.env.REDIS_URL!,
},
connection: redisConfig,
},
);
export const secondWorker = new Worker(
Expand All @@ -24,9 +23,7 @@ export const secondWorker = new Worker(
},
{
concurrency: 100,
connection: {
url: process.env.REDIS_URL!,
},
connection: redisConfig,
},
);

Expand All @@ -38,8 +35,6 @@ export const thirdWorker = new Worker(
},
{
concurrency: 100,
connection: {
url: process.env.REDIS_URL!,
},
connection: redisConfig,
},
);
23 changes: 23 additions & 0 deletions packages/server/src/db/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export const {
POSTGRES_DB = "dokploy",
POSTGRES_HOST = "dokploy-postgres",
POSTGRES_PORT = "5432",
REDIS_URL,
REDIS_HOST,
REDIS_PORT,
REDIS_PASSWORD,
REDIS_PASSWORD_FILE,
REDIS_DB_INDEX,
REDIS_USERNAME,
} = process.env;

export function readSecret(path: string): string {
Expand Down Expand Up @@ -45,3 +52,19 @@ if (DATABASE_URL) {
"postgres://dokploy:amukds4wi9001583845717ad2@localhost:5432/dokploy";
}
}

const redisPassword = REDIS_PASSWORD_FILE
? readSecret(REDIS_PASSWORD_FILE)
: REDIS_PASSWORD;

export const redisConfig = REDIS_URL
? { url: REDIS_URL }
: {
Comment thread
satoukouga marked this conversation as resolved.
Outdated
host:
REDIS_HOST ||
(process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1"),
port: REDIS_PORT ? Number.parseInt(REDIS_PORT, 10) : 6379,
db: REDIS_DB_INDEX ? Number.parseInt(REDIS_DB_INDEX, 10) : 0,
Comment thread
satoukouga marked this conversation as resolved.
Outdated
...(redisPassword && { password: redisPassword }),
...(REDIS_USERNAME && { username: REDIS_USERNAME }),
};
9 changes: 9 additions & 0 deletions packages/server/src/setup/redis-setup.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { CreateServiceOptions } from "dockerode";
import { docker } from "../constants";
import { REDIS_PASSWORD, REDIS_PASSWORD_FILE, readSecret } from "../db/constants";
import { pullImage } from "../utils/docker/utils";
Comment thread
satoukouga marked this conversation as resolved.

export const initializeRedis = async () => {
const imageName = "redis:7";
const containerName = "dokploy-redis";

const redisPassword = REDIS_PASSWORD_FILE
? readSecret(REDIS_PASSWORD_FILE)
: REDIS_PASSWORD;

const settings: CreateServiceOptions = {
Name: containerName,
TaskTemplate: {
Expand All @@ -18,6 +23,10 @@ export const initializeRedis = async () => {
Target: "/data",
},
],
...(redisPassword && {
Command: ["redis-server"],
Args: ["--requirepass", redisPassword],
Comment thread
satoukouga marked this conversation as resolved.
Outdated
}),
},
Networks: [{ Target: "dokploy-network" }],
Placement: {
Expand Down
9 changes: 5 additions & 4 deletions packages/server/src/utils/databases/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ export const buildRedis = async (redis: RedisNested) => {
Args: args,
}),
}
: {
Command: ["/bin/sh"],
Args: ["-c", `redis-server --requirepass ${databasePassword}`],
: {
Command: ["redis-server"],
Args: ["--requirepass", databasePassword],
}),
...(Ulimits && { Ulimits }),
...(Ulimits && { Ulimits }),

Labels,
},
Networks,
Expand Down
Loading