diff --git a/components/branding/BrandHomeLink.tsx b/components/branding/BrandHomeLink.tsx
index 1e9f1bdf..a2c92ecd 100644
--- a/components/branding/BrandHomeLink.tsx
+++ b/components/branding/BrandHomeLink.tsx
@@ -38,6 +38,16 @@ export function BrandHomeLink({ showLabel = false }: { showLabel?: boolean }) {
width={26}
height={26}
className="h-[26px] w-[26px] rounded-lg object-contain"
+ // Tenant logos live on the deployment's Supabase Storage host, and
+ // the image optimizer only allows that host when
+ // NEXT_PUBLIC_SUPABASE_URL was known at BUILD time. The generic
+ // Docker image bakes a sentinel and substitutes the real URL at
+ // container start, so on self-hosted installs /_next/image answered
+ // 400 '"url" parameter is not allowed' and the sidebar mark rendered
+ // broken while the favicon (same URL, no optimizer) worked (issue
+ // #2203). The browser fetches the public object directly instead; at
+ // 26px there is nothing to optimize anyway.
+ unoptimized
/>
) : (
)
diff --git a/next.config.ts b/next.config.ts
index ea86644a..b21623ab 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -35,11 +35,16 @@ const supabaseWsUrl =
supabaseUrl.replace(/^http(s?):/, "ws$1:");
// Brand logos (WL-12 slice A3) are served from Supabase Storage public
-// objects and rendered via next/image, which requires the host to be
-// allowlisted. The pattern stays narrow: public storage objects only.
-// try/catch because Docker builds may bake a sentinel value that is not a
-// parseable URL; no hostname simply means no remote images are allowed,
-// exactly as before.
+// objects. The tenant-logo elements (components/branding/) render
+// them `unoptimized` since issue #2203: this allowlist is fixed at build
+// time, and the generic Docker image bakes a sentinel for
+// NEXT_PUBLIC_SUPABASE_URL that docker-entrypoint.sh substitutes only at
+// container start, so the optimizer rejected the runtime host with 400
+// '"url" parameter is not allowed'. The pattern is kept for builds where the
+// real URL is present at build time (hosted, local) so any other remote
+// image from the same public bucket still passes. Narrow on purpose: public
+// storage objects only. try/catch because the sentinel is not a parseable
+// URL; no hostname simply means no remote images are allowed, as before.
let supabaseImageHostname = "";
try {
supabaseImageHostname = supabaseUrl ? new URL(supabaseUrl).hostname : "";