diff --git a/helm/litellm/templates/ui/deployment.yaml b/helm/litellm/templates/ui/deployment.yaml index 91d6de39ea6..5ed5cfb516a 100644 --- a/helm/litellm/templates/ui/deployment.yaml +++ b/helm/litellm/templates/ui/deployment.yaml @@ -53,6 +53,10 @@ spec: - name: LITELLM_BACKEND_URL value: {{ .Values.ui.backendUrl | quote }} {{- end }} + {{- with .Values.ui.listenIPv6 }} + - name: NGINX_LISTEN_IPV6 + value: {{ . | quote }} + {{- end }} {{- with .Values.ui.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/helm/litellm/tests/ui_listen_ipv6_tests.yaml b/helm/litellm/tests/ui_listen_ipv6_tests.yaml new file mode 100644 index 00000000000..e4314e3c536 --- /dev/null +++ b/helm/litellm/tests/ui_listen_ipv6_tests.yaml @@ -0,0 +1,33 @@ +suite: test ui IPv6 listener toggle +templates: + - ui/deployment.yaml +values: + - ./values/required.yaml +tests: + - it: renders no NGINX_LISTEN_IPV6 env by default + asserts: + - notContains: + path: spec.template.spec.containers[0].env + any: true + content: + name: NGINX_LISTEN_IPV6 + + - it: renders NGINX_LISTEN_IPV6=auto when listenIPv6 is auto + set: + ui.listenIPv6: auto + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: NGINX_LISTEN_IPV6 + value: auto + + - it: renders NGINX_LISTEN_IPV6=true when listenIPv6 forces the listener on + set: + ui.listenIPv6: "true" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: NGINX_LISTEN_IPV6 + value: "true" diff --git a/helm/litellm/values.yaml b/helm/litellm/values.yaml index 06ba72d84b3..abe51ca0664 100644 --- a/helm/litellm/values.yaml +++ b/helm/litellm/values.yaml @@ -380,6 +380,11 @@ backend: ui: enabled: true logLevel: INFO + # IPv6 listener for the ui nginx, rendered as NGINX_LISTEN_IPV6 on the + # container: "auto" listens on [::]:3000 when the pod has an IPv6 stack + # (dual-stack clusters), "true" forces it, "false"/empty keeps today's + # IPv4-only bind. Leave empty unless kubelet probes reach the pod over IPv6. + listenIPv6: "" extraEnv: [] envConfigMaps: [] envSecrets: [] diff --git a/ui/Dockerfile b/ui/Dockerfile index 24140093270..efe0dda6ed2 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -27,7 +27,10 @@ RUN npm run build FROM nginx:${NGINX_VERSION} AS runtime # Drop the upstream default :80 server; we own the config. -RUN rm -f /etc/nginx/conf.d/default.conf +RUN rm -f /etc/nginx/conf.d/default.conf && mkdir /etc/nginx/listen-ipv6 + +# Opt-in IPv6 listen (NGINX_LISTEN_IPV6=true|auto) — see the script header. +COPY --chmod=755 ui/docker-entrypoint.d/15-listen-on-ipv6.sh /docker-entrypoint.d/ # Static export → web root. COPY --from=builder /app/out /usr/share/nginx/html diff --git a/ui/docker-entrypoint.d/15-listen-on-ipv6.sh b/ui/docker-entrypoint.d/15-listen-on-ipv6.sh new file mode 100755 index 00000000000..ecdb00f38d8 --- /dev/null +++ b/ui/docker-entrypoint.d/15-listen-on-ipv6.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# Renders the UI server's IPv6 listen directive, opt-in via NGINX_LISTEN_IPV6: +# "true" forces it on, "auto" enables it only when the container has an IPv6 +# stack (same /proc/net/if_inet6 gate as the stock +# 10-listen-on-ipv6-by-default.sh), anything else keeps today's IPv4-only bind. + +set -eu + +ME=$(basename "$0") +SNIPPET="/etc/nginx/listen-ipv6/enabled.conf" + +entrypoint_log() { + if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$ME: $*" + fi +} + +case "${NGINX_LISTEN_IPV6:-}" in + true|on|1) + ;; + auto) + if [ ! -f /proc/net/if_inet6 ]; then + entrypoint_log "info: NGINX_LISTEN_IPV6=auto and ipv6 not available, keeping IPv4 only" + exit 0 + fi + ;; + *) + exit 0 + ;; +esac + +if ! touch "$SNIPPET" 2>/dev/null; then + entrypoint_log "info: can not write $SNIPPET (read-only file system?), keeping IPv4 only" + exit 0 +fi + +echo "listen [::]:3000 default_server;" > "$SNIPPET" +entrypoint_log "info: enabled listen on [::]:3000" diff --git a/ui/nginx.conf b/ui/nginx.conf index 235cb9c501e..54170071197 100644 --- a/ui/nginx.conf +++ b/ui/nginx.conf @@ -36,6 +36,9 @@ http { server { listen 3000 default_server; + # Populated by docker-entrypoint.d/15-listen-on-ipv6.sh when + # NGINX_LISTEN_IPV6 opts in; empty by default (IPv4 only). + include /etc/nginx/listen-ipv6/*.conf; server_name _; root /usr/share/nginx/html;