diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
index d1a03fedc..f82fdeb6b 100644
--- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
+++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts
@@ -62,6 +62,10 @@ const baseSettings: WebServerSettings = {
errorPageDescription: null,
ogImageUrl: null,
footerText: null,
+ logoLightUrl: null,
+ loginLogoLightUrl: null,
+ faviconLightUrl: null,
+ dualModeBranding: false,
},
cleanupCacheApplications: false,
cleanupCacheOnCompose: false,
diff --git a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx
index 5ab262ec8..8a66b5270 100644
--- a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx
+++ b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx
@@ -186,6 +186,7 @@ export const ImpersonationBar = () => {
{!isImpersonating ? (
diff --git a/apps/dokploy/components/layouts/onboarding-layout.tsx b/apps/dokploy/components/layouts/onboarding-layout.tsx
index ed46c1930..3776c2db5 100644
--- a/apps/dokploy/components/layouts/onboarding-layout.tsx
+++ b/apps/dokploy/components/layouts/onboarding-layout.tsx
@@ -19,6 +19,10 @@ export const OnboardingLayout = ({ children, leftPanel }: Props) => {
"\u201CThe Open Source alternative to Netlify, Vercel, Heroku.\u201D";
const logoUrl =
whitelabeling?.loginLogoUrl || whitelabeling?.logoUrl || undefined;
+ const logoLightUrl =
+ whitelabeling?.loginLogoLightUrl ||
+ whitelabeling?.logoLightUrl ||
+ undefined;
return (
@@ -28,7 +32,11 @@ export const OnboardingLayout = ({ children, leftPanel }: Props) => {
href="/"
className="relative z-20 flex items-center text-lg font-medium gap-4 text-primary"
>
-
+
{appName}
diff --git a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx
index 3ba9e80bd..c4ce523a7 100644
--- a/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx
+++ b/apps/dokploy/components/proprietary/whitelabeling/whitelabeling-settings.tsx
@@ -26,6 +26,7 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
+import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { WhitelabelingPreview } from "./whitelabeling-preview";
@@ -49,6 +50,10 @@ const formSchema = z.object({
errorPageDescription: z.string(),
footerText: z.string(),
ogImageUrl: safeUrlField,
+ logoLightUrl: safeUrlField,
+ loginLogoLightUrl: safeUrlField,
+ faviconLightUrl: safeUrlField,
+ dualModeBranding: z.boolean(),
});
type FormSchema = z.infer
;
@@ -195,6 +200,10 @@ export function WhitelabelingSettings() {
errorPageDescription: "",
footerText: "",
ogImageUrl: "",
+ logoLightUrl: "",
+ loginLogoLightUrl: "",
+ faviconLightUrl: "",
+ dualModeBranding: false,
},
resolver: zodResolver(formSchema),
});
@@ -214,6 +223,10 @@ export function WhitelabelingSettings() {
errorPageDescription: data.errorPageDescription ?? "",
footerText: data.footerText ?? "",
ogImageUrl: data.ogImageUrl ?? "",
+ logoLightUrl: data.logoLightUrl ?? "",
+ loginLogoLightUrl: data.loginLogoLightUrl ?? "",
+ faviconLightUrl: data.faviconLightUrl ?? "",
+ dualModeBranding: data.dualModeBranding ?? false,
});
}
}, [data, form]);
@@ -244,6 +257,10 @@ export function WhitelabelingSettings() {
errorPageDescription: values.errorPageDescription || null,
footerText: values.footerText || null,
ogImageUrl: values.ogImageUrl || null,
+ logoLightUrl: values.logoLightUrl || null,
+ loginLogoLightUrl: values.loginLogoLightUrl || null,
+ faviconLightUrl: values.faviconLightUrl || null,
+ dualModeBranding: values.dualModeBranding,
},
})
.then(async () => {
@@ -412,6 +429,101 @@ export function WhitelabelingSettings() {
+ {/* Light Mode Logos Section */}
+
+
+
+
+ Light Mode Logos
+
+ Use separate logos for light and dark themes. When enabled,
+ the logos in Branding are used for dark mode.
+
+
+
(
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
{/* Appearance Section */}
diff --git a/apps/dokploy/components/shared/logo.tsx b/apps/dokploy/components/shared/logo.tsx
index 2c316e22f..500925df3 100644
--- a/apps/dokploy/components/shared/logo.tsx
+++ b/apps/dokploy/components/shared/logo.tsx
@@ -1,16 +1,25 @@
+import { useTheme } from "next-themes";
import { cn } from "@/lib/utils";
interface Props {
className?: string;
logoUrl?: string;
+ logoLightUrl?: string;
}
-export const Logo = ({ className = "size-14", logoUrl }: Props) => {
- if (logoUrl) {
+export const Logo = ({
+ className = "size-14",
+ logoUrl,
+ logoLightUrl,
+}: Props) => {
+ const { resolvedTheme } = useTheme();
+ const effectiveUrl =
+ resolvedTheme === "light" && logoLightUrl ? logoLightUrl : logoUrl;
+
+ if (effectiveUrl) {
return (
- // biome-ignore lint/performance/noImgElement: this is for dynamic logo loading
diff --git a/apps/dokploy/pages/_document.tsx b/apps/dokploy/pages/_document.tsx
index a8f3b5931..5e6b6948b 100644
--- a/apps/dokploy/pages/_document.tsx
+++ b/apps/dokploy/pages/_document.tsx
@@ -13,6 +13,7 @@ interface WhitelabelingDocumentProps {
appDescription: string | null;
ogImageUrl: string | null;
faviconHref: string | null;
+ faviconLightHref: string | null;
customCss: string | null;
baseUrl: string;
}
@@ -22,6 +23,7 @@ export default function Document({
appDescription,
ogImageUrl,
faviconHref,
+ faviconLightHref,
customCss,
baseUrl,
}: WhitelabelingDocumentProps) {
@@ -44,7 +46,22 @@ export default function Document({
-
+ {faviconLightHref ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
{customCss && (