Skip to content

Branding & Configuration

Nebari Chat reads its runtime configuration — Keycloak connection settings and branding — from a single /config.json that the frontend fetches at startup, before React mounts. This decouples branding from the build: operators can re-brand without rebuilding the image.

Every branding field is optional. When nothing is configured, the app renders the built-in Nebari defaults and looks identical to an unbranded deployment.

{
"keycloak": {
"url": "https://keycloak.example.com",
"realm": "nebari",
"clientId": "nebari-chat"
},
"branding": {
"title": "",
"logoUrl": "",
"logoUrlDark": "",
"faviconUrl": "",
"theme": { "light": {}, "dark": {} }
}
}
FieldDescription
titleBrowser tab title. Empty keeps the default (Nebari Chat).
logoUrlSidebar header logo. Empty uses the built-in Nebari logo.
logoUrlDarkDark-mode logo. Empty falls back to logoUrl, then the built-in dark logo.
faviconUrlFavicon. Empty uses the built-in Nebari favicon.
theme.light / theme.darkCSS variable overrides applied per color scheme.

Theme tokens are the camelCase form of the app’s brand CSS variables; each is converted to a --kebab-case custom property at runtime and applied over the defaults. Common tokens:

bgBrandDefault, bgBrandSecondary, bgNeutralDefault, bgWhite, bdBrandDefault, bdNeutralDefault, textBrandOnBrand, textNeutralDefault, textNeutralSecondary, radius.

Values are validated at runtime for security: any value containing ;, {, }, quotes, url(, expression(, or javascript: is rejected, and logo/favicon URLs must be root-relative paths or http(s) URLs.

/config.json is resolved at runtime in this order (highest wins):

  1. Chart-rendered ConfigMap (Kubernetes) — mounted read-only over /config.json.
  2. Local config file — a file mounted read-only over /config.json.
  3. Environment variables — used to generate /config.json when no file is mounted.
  4. Built-in defaults — empty fields fall back to the app’s title, favicon, logo, and theme.

Set branding under frontend.branding in your Helm values. The chart renders it — together with the keycloak.* values — into a ConfigMap mounted over /config.json.

keycloak:
url: https://keycloak.example.com
realm: nebari
frontend:
branding:
title: "ACME Chat"
logoUrl: "https://cdn.acme.com/logo.svg"
faviconUrl: "https://cdn.acme.com/favicon.ico"
theme:
light:
bgBrandDefault: "#0066cc"
bdBrandDefault: "#004a99"
dark:
bgBrandDefault: "#4da6ff"

Outside Kubernetes there are two options.

The container generates /config.json from environment variables at startup:

Terminal window
docker run -p 8080:8080 \
-e API_URL=http://host.docker.internal:8000 \
-e KEYCLOAK_URL=https://keycloak.example.com \
-e KEYCLOAK_REALM=nebari \
-e KEYCLOAK_CLIENT_ID=nebari-chat \
-e BRANDING_TITLE="ACME Chat" \
-e BRANDING_LOGO_URL="https://cdn.acme.com/logo.svg" \
nebari-chat

Supported variables: KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_CLIENT_ID, BRANDING_TITLE, BRANDING_LOGO_URL, BRANDING_LOGO_URL_DARK, BRANDING_FAVICON_URL, and BRANDING_THEME_LIGHT / BRANDING_THEME_DARK (raw JSON objects).

For full control — including complex theme objects — mount a complete config.json:

Terminal window
docker run -p 8080:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
nebari-chat

A mounted file takes precedence over environment variables. During local development (npm run dev), edit frontend/public/config.json directly.