Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
698104e
fix(docker-logs): fix warning symbol detection
difagume Dec 4, 2025
d465fb4
feat(resources): Add number component to have better UX control over …
divaltor Dec 7, 2025
8c889fc
fix: some fixes in dockerSafeExec()
fir4tozden Dec 10, 2025
c045c53
feat(schedules): add support for all IANA timezones
odedd Dec 12, 2025
d875e08
test(helpers): add tests for handling empty and undefined string vari…
Siumauricio Dec 13, 2025
4c10056
chore
fir4tozden Dec 14, 2025
5d42737
cepte
fir4tozden Dec 14, 2025
19a7a80
[BUG] fix: volume cleaning should not be performed
fir4tozden Dec 14, 2025
ba52830
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 14, 2025
72cc7a2
Merge pull request #3265 from Dokploy/feat/templates-processor-allow-…
Siumauricio Dec 14, 2025
51abf49
chore: update pr id
fir4tozden Dec 14, 2025
371cf83
fix: typing
fir4tozden Dec 14, 2025
669de0f
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 14, 2025
b661569
fix: typing
fir4tozden Dec 14, 2025
2b1a3db
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 14, 2025
b65f53d
fix: return database instance as response on db creation (mongo, mysq…
gosangam Dec 14, 2025
3e356e6
feat: being able to switch environments in sidebar
Bima42 Dec 14, 2025
6bb5404
fix(mongo): use appName instead of localhost for replica set
ayham291 Dec 14, 2025
67f4ca2
fix(auth): update admin check to safely access user property
Siumauricio Dec 15, 2025
5ebcbf8
Merge pull request #3275 from Dokploy/3274-null-server-ip
Siumauricio Dec 15, 2025
8eaf2ab
fix(api): return database object from create endpoints
Divkix Dec 15, 2025
3aeb528
fix: missing switch env for apps
Bima42 Dec 15, 2025
eb4fbff
feat(servers): enhance server management UI with button options
Siumauricio Dec 15, 2025
0ddf6b8
feat(servers): add tooltip for deactivated server status in dashboard
Siumauricio Dec 17, 2025
3a5ac9d
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 17, 2025
54d5266
Merge pull request #3291 from Dokploy/feat/use-cards-in-remote-servers
Siumauricio Dec 17, 2025
38c7e1e
Merge pull request #3276 from Divkix/fix-3268
Siumauricio Dec 17, 2025
d1bc109
feat(registry): enhance registry handling with optional password and …
Siumauricio Dec 17, 2025
acf385a
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 17, 2025
854bd88
Merge pull request #3292 from Dokploy/3261-the-registry-password-is-a…
Siumauricio Dec 17, 2025
72f2cc6
feat(registry): improve server selection by categorizing deploy and b…
Siumauricio Dec 18, 2025
27fa0e8
Merge pull request #3298 from Dokploy/3230-build-server---doesnt-use-…
Siumauricio Dec 18, 2025
0590e78
Merge pull request #3270 from Bima42/3165-add-environment-switch-drop…
Siumauricio Dec 18, 2025
d8514b0
Merge pull request #3273 from ayham291/mongo-replica
Siumauricio Dec 18, 2025
e607220
fix: disabling of previewRequireCollaboratorPermissions
draco-vetus Dec 18, 2025
fd084c6
fix: invalidate query missing
Bima42 Dec 19, 2025
f5d3342
Merge pull request #3309 from Bima42/fix/3308-cannot-update-s3-endpoint
Siumauricio Dec 19, 2025
6685bd6
chore: update dokploy version to v0.26.3 and modify test command
Siumauricio Dec 19, 2025
1b22384
Merge pull request #3267 from fir4tozden/bug-fix/volume-cleaning-shou…
Siumauricio Dec 20, 2025
b476e50
Merge pull request #3229 from fir4tozden/fix/some-fixes-in-dockerSafe…
Siumauricio Dec 20, 2025
97362da
Merge pull request #3303 from draconisNoctis/feature/Fix-Disabling-of…
Siumauricio Dec 20, 2025
dc7af1b
Merge pull request #3269 from gosangam/canary
Siumauricio Dec 20, 2025
db97de2
Merge pull request #3255 from odedd/feat/all-timezones-support
Siumauricio Dec 20, 2025
c065c85
fix: update project handling permissions to include admin role
Siumauricio Dec 20, 2025
7c53a3e
Merge pull request #3316 from Dokploy/feat/add-admin-creation-projects
Siumauricio Dec 20, 2025
53f67c6
Initial plan
Copilot Dec 20, 2025
69d5c6f
Fix Perplexity AI provider by adding hardcoded model list
Copilot Dec 20, 2025
2065372
fix: update test command in package.json to remove specific test target
Siumauricio Dec 20, 2025
6772575
Merge pull request #3318 from Dokploy/copilot/fix-perplexity-ai-model…
Siumauricio Dec 20, 2025
771d0dd
Initial plan
Copilot Dec 20, 2025
44645a6
fix: properly quote registry username in docker login to handle speci…
Copilot Dec 20, 2025
8c7bc82
Merge pull request #3323 from Dokploy/copilot/fix-shell-command-issue
Siumauricio Dec 21, 2025
2bfa464
Merge pull request #3186 from divaltor/slider-resources
Siumauricio Dec 21, 2025
84e0f58
Merge pull request #3164 from difagume/fix/log-warning-detection
Siumauricio Dec 21, 2025
78c9a04
feat(issue-template): add dropdowns for affected areas and git provid…
Siumauricio Dec 21, 2025
e77f276
refactor(issue-template): remove unnecessary dropdowns for git provid…
Siumauricio Dec 21, 2025
babd30a
refactor(settings): migrate user settings to webServerSettings schema…
Siumauricio Dec 21, 2025
b2be5bc
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 21, 2025
1ccb205
fix(admin): add optional chaining to safely access settings properties
Siumauricio Dec 21, 2025
6010643
refactor(server): update server configuration handling to utilize web…
Siumauricio Dec 21, 2025
f1dfa9c
refactor(preview-deployment): remove dynamic import of getWebServerSe…
Siumauricio Dec 21, 2025
10c4f88
Update packages/server/src/services/web-server-settings.ts
Siumauricio Dec 21, 2025
ec56062
fix(settings): update getIp function to return an empty string for cl…
Siumauricio Dec 21, 2025
3abc4cd
refactor(access-log): consolidate web server settings imports and enh…
Siumauricio Dec 21, 2025
f39aa23
fix: pass registry auth to stack deploy
dpulpeiro Dec 23, 2025
b355d44
fix(web-server-settings): use optional chaining for safer ID access i…
Siumauricio Dec 24, 2025
e438407
Merge pull request #3341 from dpulpeiro/fix/stack-registry-auth
Siumauricio Dec 25, 2025
1b5bfe0
chore: uninstall `rotating-file-stream`
bdkopen Dec 27, 2025
260efdc
Merge pull request #3353 from bdkopen/remove-rotating-file-stream
Siumauricio Dec 28, 2025
9e03625
refactor(auth): simplify trustedOrigins logic by removing redundant a…
Siumauricio Dec 28, 2025
58b7520
Merge pull request #3327 from Dokploy/refactor/separate-settings-from…
Siumauricio Dec 28, 2025
d2aa60d
Update package.json
Siumauricio Dec 31, 2025
9498fbe
Update package.json
Siumauricio Dec 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions apps/dokploy/__test__/cluster/upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,38 @@ describe("getRegistryTag", () => {
expect(result).toBe("docker.io/myuser/repo");
});
});

describe("special characters in username", () => {
it("should handle Harbor robot account username with $ (e.g. robot$library+dokploy)", () => {
const registry = createMockRegistry({
username: "robot$library+dokploy",
});
const result = getRegistryTag(registry, "nginx");
expect(result).toBe("docker.io/robot$library+dokploy/nginx");
});

it("should handle username with $ and other special characters", () => {
const registry = createMockRegistry({
username: "robot$test+app",
});
const result = getRegistryTag(registry, "myapp:latest");
expect(result).toBe("docker.io/robot$test+app/myapp:latest");
});

it("should handle username with multiple $ symbols", () => {
const registry = createMockRegistry({
username: "user$name$test",
});
const result = getRegistryTag(registry, "app");
expect(result).toBe("docker.io/user$name$test/app");
});

it("should handle username with + and - symbols", () => {
const registry = createMockRegistry({
username: "robot+test-user",
});
const result = getRegistryTag(registry, "nginx:latest");
expect(result).toBe("docker.io/robot+test-user/nginx:latest");
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Domain } from "@dokploy/server";
import { createDomainLabels } from "@dokploy/server";
import { parse, stringify } from "yaml";
import { describe, expect, it } from "vitest";
import { parse, stringify } from "yaml";

/**
* Regression tests for Traefik Host rule label format.
Expand Down
44 changes: 44 additions & 0 deletions apps/dokploy/__test__/templates/helpers.template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,50 @@ describe("helpers functions", () => {
});
});

describe("Empty string variables", () => {
it("should replace variables with empty string values correctly", () => {
const variables = {
smtp_username: "",
smtp_password: "",
non_empty: "value",
};

const result1 = processValue("${smtp_username}", variables, mockSchema);
expect(result1).toBe("");

const result2 = processValue("${smtp_password}", variables, mockSchema);
expect(result2).toBe("");

const result3 = processValue("${non_empty}", variables, mockSchema);
expect(result3).toBe("value");
});

it("should not replace undefined variables", () => {
const variables = {
defined_var: "",
};

const result = processValue("${undefined_var}", variables, mockSchema);
expect(result).toBe("${undefined_var}");
});

it("should handle mixed empty and non-empty variables in template", () => {
const variables = {
smtp_address: "smtp.example.com",
smtp_port: "2525",
smtp_username: "",
smtp_password: "",
};

const template =
"SMTP_ADDRESS=${smtp_address} SMTP_PORT=${smtp_port} SMTP_USERNAME=${smtp_username} SMTP_PASSWORD=${smtp_password}";
const result = processValue(template, variables, mockSchema);
expect(result).toBe(
"SMTP_ADDRESS=smtp.example.com SMTP_PORT=2525 SMTP_USERNAME= SMTP_PASSWORD=",
);
});
});

describe("${jwt}", () => {
it("should generate a JWT string", () => {
const jwt = processValue("${jwt}", {}, mockSchema);
Expand Down
56 changes: 22 additions & 34 deletions apps/dokploy/__test__/traefik/server/update-server-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ vi.mock("node:fs", () => ({
default: fs,
}));

import type { FileConfig, User } from "@dokploy/server";
import type { FileConfig } from "@dokploy/server";
import {
createDefaultServerTraefikConfig,
loadOrCreateConfig,
updateServerTraefik,
} from "@dokploy/server";
import type { webServerSettings } from "@dokploy/server/db/schema";
import { beforeEach, expect, test, vi } from "vitest";

const baseAdmin: User = {
type WebServerSettings = typeof webServerSettings.$inferSelect;

const baseSettings: WebServerSettings = {
id: "",
https: false,
enablePaidFeatures: false,
allowImpersonation: false,
role: "user",
firstName: "",
lastName: "",
certificateType: "none",
host: null,
serverIp: null,
letsEncryptEmail: null,
sshPrivateKey: null,
enableDockerCleanup: false,
logCleanupCron: null,
metricsConfig: {
containers: {
refreshRate: 20,
Expand All @@ -45,29 +51,8 @@ const baseAdmin: User = {
cleanupCacheApplications: false,
cleanupCacheOnCompose: false,
cleanupCacheOnPreviews: false,
createdAt: new Date(),
serverIp: null,
certificateType: "none",
host: null,
letsEncryptEmail: null,
sshPrivateKey: null,
enableDockerCleanup: false,
logCleanupCron: null,
serversQuantity: 0,
stripeCustomerId: "",
stripeSubscriptionId: "",
banExpires: new Date(),
banned: true,
banReason: "",
email: "",
expirationDate: "",
id: "",
isRegistered: false,
createdAt2: new Date().toISOString(),
emailVerified: false,
image: "",
createdAt: null,
updatedAt: new Date(),
twoFactorEnabled: false,
};

beforeEach(() => {
Expand All @@ -85,7 +70,7 @@ test("Should read the configuration file", () => {
test("Should apply redirect-to-https", () => {
updateServerTraefik(
{
...baseAdmin,
...baseSettings,
https: true,
certificateType: "letsencrypt",
},
Expand All @@ -100,7 +85,7 @@ test("Should apply redirect-to-https", () => {
});

test("Should change only host when no certificate", () => {
updateServerTraefik(baseAdmin, "example.com");
updateServerTraefik(baseSettings, "example.com");

const config: FileConfig = loadOrCreateConfig("dokploy");

Expand All @@ -110,7 +95,7 @@ test("Should change only host when no certificate", () => {
test("Should not touch config without host", () => {
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");

updateServerTraefik(baseAdmin, null);
updateServerTraefik(baseSettings, null);

const config: FileConfig = loadOrCreateConfig("dokploy");

Expand All @@ -119,11 +104,14 @@ test("Should not touch config without host", () => {

test("Should remove websecure if https rollback to http", () => {
updateServerTraefik(
{ ...baseAdmin, certificateType: "letsencrypt" },
{ ...baseSettings, certificateType: "letsencrypt" },
"example.com",
);

updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com");
updateServerTraefik(
{ ...baseSettings, certificateType: "none" },
"example.com",
);

const config: FileConfig = loadOrCreateConfig("dokploy");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
createConverter,
NumberInputWithSteps,
} from "@/components/ui/number-input";
import {
Tooltip,
TooltipContent,
Expand All @@ -30,6 +33,23 @@ import {
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";

const CPU_STEP = 0.25;
const MEMORY_STEP_MB = 256;

const formatNumber = (value: number, decimals = 2): string =>
Number.isInteger(value) ? String(value) : value.toFixed(decimals);

const cpuConverter = createConverter(1_000_000_000, (cpu) =>
cpu <= 0 ? "" : `${formatNumber(cpu)} CPU`,
);

const memoryConverter = createConverter(1024 * 1024, (mb) => {
if (mb <= 0) return "";
return mb >= 1024
? `${formatNumber(mb / 1024)} GB`
: `${formatNumber(mb)} MB`;
});

const addResourcesSchema = z.object({
memoryReservation: z.string().optional(),
cpuLimit: z.string().optional(),
Expand All @@ -51,6 +71,7 @@ interface Props {
}

type AddResources = z.infer<typeof addResourcesSchema>;

export const ShowResources = ({ id, type }: Props) => {
const queryMap = {
postgres: () =>
Expand Down Expand Up @@ -163,16 +184,20 @@ export const ShowResources = ({ id, type }: Props) => {
<TooltipContent>
<p>
Memory hard limit in bytes. Example: 1GB =
1073741824 bytes
1073741824 bytes. Use +/- buttons to adjust by
256 MB.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
<NumberInputWithSteps
value={field.value}
onChange={field.onChange}
placeholder="1073741824 (1GB in bytes)"
{...field}
step={MEMORY_STEP_MB}
converter={memoryConverter}
/>
</FormControl>
<FormMessage />
Expand All @@ -198,16 +223,20 @@ export const ShowResources = ({ id, type }: Props) => {
<TooltipContent>
<p>
Memory soft limit in bytes. Example: 256MB =
268435456 bytes
268435456 bytes. Use +/- buttons to adjust by 256
MB.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
<NumberInputWithSteps
value={field.value}
onChange={field.onChange}
placeholder="268435456 (256MB in bytes)"
{...field}
step={MEMORY_STEP_MB}
converter={memoryConverter}
/>
</FormControl>
<FormMessage />
Expand All @@ -234,17 +263,20 @@ export const ShowResources = ({ id, type }: Props) => {
<TooltipContent>
<p>
CPU quota in units of 10^-9 CPUs. Example: 2
CPUs = 2000000000
CPUs = 2000000000. Use +/- buttons to adjust by
0.25 CPU.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input
<NumberInputWithSteps
value={field.value}
onChange={field.onChange}
placeholder="2000000000 (2 CPUs)"
{...field}
value={field.value?.toString() || ""}
step={CPU_STEP}
converter={cpuConverter}
/>
</FormControl>
<FormMessage />
Expand All @@ -271,14 +303,21 @@ export const ShowResources = ({ id, type }: Props) => {
<TooltipContent>
<p>
CPU shares (relative weight). Example: 1 CPU =
1000000000
1000000000. Use +/- buttons to adjust by 0.25
CPU.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl>
<Input placeholder="1000000000 (1 CPU)" {...field} />
<NumberInputWithSteps
value={field.value}
onChange={field.onChange}
placeholder="1000000000 (1 CPU)"
step={CPU_STEP}
converter={cpuConverter}
/>
</FormControl>
<FormMessage />
</FormItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
previewCertificateType: data.previewCertificateType || "none",
previewCustomCertResolver: data.previewCustomCertResolver || "",
previewRequireCollaboratorPermissions:
data.previewRequireCollaboratorPermissions || true,
data.previewRequireCollaboratorPermissions ?? true,
});
}
}, [data]);
Expand Down
Loading