This is an automated email from the ASF dual-hosted git repository.

lhotari pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-helm-chart.git


The following commit(s) were added to refs/heads/master by this push:
     new f18d020  Add support for deploying pulsar as standalone (#674)
f18d020 is described below

commit f18d0206e088ba2a1d6f68d5408d88592b6b52a1
Author: Shaun Becker <[email protected]>
AuthorDate: Fri Apr 24 09:26:54 2026 -0400

    Add support for deploying pulsar as standalone (#674)
---
 .../clusters/values-standalone.yaml                |  46 ++--
 .ci/helm.sh                                        | 150 ++++++++-----
 .ci/values-common.yaml                             |   5 +
 .github/workflows/pulsar-helm-chart-ci.yaml        |   7 +
 charts/pulsar/templates/_standalone.tpl            |  68 ++++++
 .../pulsar/templates/autorecovery-configmap.yaml   |   2 +-
 .../pulsar/templates/autorecovery-podmonitor.yaml  |   2 +-
 .../templates/autorecovery-service-account.yaml    |   2 +-
 charts/pulsar/templates/autorecovery-service.yaml  |   2 +-
 .../pulsar/templates/autorecovery-statefulset.yaml |   2 +-
 .../templates/bookkeeper-cluster-initialize.yaml   |   2 +-
 charts/pulsar/templates/bookkeeper-configmap.yaml  |   2 +-
 charts/pulsar/templates/bookkeeper-pdb.yaml        |   2 +-
 charts/pulsar/templates/bookkeeper-podmonitor.yaml |   2 +-
 .../templates/bookkeeper-service-account.yaml      |   2 +-
 charts/pulsar/templates/bookkeeper-service.yaml    |   2 +-
 .../pulsar/templates/bookkeeper-statefulset.yaml   |   2 +-
 .../pulsar/templates/bookkeeper-storageclass.yaml  |   2 +-
 .../templates/broker-cluster-role-binding.yaml     |   2 +-
 charts/pulsar/templates/broker-configmap.yaml      |   2 +-
 .../pulsar/templates/broker-headless-service.yaml  |   2 +-
 charts/pulsar/templates/broker-hpa.yaml            |   2 +-
 charts/pulsar/templates/broker-pdb.yaml            |   2 +-
 charts/pulsar/templates/broker-podmonitor.yaml     |   2 +-
 charts/pulsar/templates/broker-rbac.yaml           |   6 +-
 .../pulsar/templates/broker-service-account.yaml   |   4 +-
 charts/pulsar/templates/broker-service.yaml        |   2 +-
 .../templates/broker-statefulset-upgrade.yaml      |   2 +-
 charts/pulsar/templates/broker-statefulset.yaml    |   2 +-
 charts/pulsar/templates/proxy-configmap.yaml       |  26 ++-
 charts/pulsar/templates/proxy-statefulset.yaml     |  14 +-
 .../templates/pulsar-cluster-initialize.yaml       |   2 +-
 charts/pulsar/templates/standalone-configmap.yaml  |  93 +++++++++
 charts/pulsar/templates/standalone-deployment.yaml | 232 +++++++++++++++++++++
 ...-podmonitor.yaml => standalone-podmonitor.yaml} |   7 +-
 ...ccount.yaml => standalone-service-account.yaml} |   8 +-
 charts/pulsar/templates/standalone-service.yaml    |  77 +++++++
 charts/pulsar/templates/tls-certs-internal.yaml    |   7 +
 charts/pulsar/templates/toolset-configmap.yaml     |  20 +-
 charts/pulsar/templates/zookeeper-configmap.yaml   |   2 +-
 .../templates/zookeeper-headless-service.yaml      |   2 +-
 charts/pulsar/templates/zookeeper-pdb.yaml         |   2 +-
 charts/pulsar/templates/zookeeper-podmonitor.yaml  |   2 +-
 .../templates/zookeeper-service-account.yaml       |   2 +-
 charts/pulsar/templates/zookeeper-service.yaml     |   2 +-
 .../templates/zookeeper-statefulset-upgrade.yaml   |   2 +-
 charts/pulsar/templates/zookeeper-statefulset.yaml |   2 +-
 .../pulsar/templates/zookeeper-storageclass.yaml   |   2 +-
 charts/pulsar/values.yaml                          |  90 ++++++++
 .../values-standalone.yaml                         |  31 +--
 hack/kind-cluster-build.sh                         |   7 +-
 51 files changed, 807 insertions(+), 155 deletions(-)

diff --git a/charts/pulsar/templates/autorecovery-service.yaml 
b/.ci/clusters/values-standalone.yaml
similarity index 53%
copy from charts/pulsar/templates/autorecovery-service.yaml
copy to .ci/clusters/values-standalone.yaml
index 1021a3b..f38bd08 100644
--- a/charts/pulsar/templates/autorecovery-service.yaml
+++ b/.ci/clusters/values-standalone.yaml
@@ -16,27 +16,29 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+standalone:
+  enabled: true
 
-{{- if .Values.components.autorecovery }}
-apiVersion: v1
-kind: Service
-metadata:
-  name: "{{ template "pulsar.fullname" . }}-{{ .Values.autorecovery.component 
}}"
-  namespace: {{ template "pulsar.namespace" . }}
-  labels:
-    {{- include "pulsar.standardLabels" . | nindent 4 }}
-    component: {{ .Values.autorecovery.component }}
-{{- with .Values.autorecovery.service.annotations }}
-  annotations:
-{{ toYaml . | indent 4 }}
-{{- end }}
-spec:
-  ports:
-  - name: http
-    port: {{ .Values.autorecovery.ports.http }}
-  clusterIP: None
-  selector:
-    {{- include "pulsar.matchLabels" . | nindent 4 }}
-    component: {{ .Values.autorecovery.component }}
-{{- end }}
+auth:
+  authentication:
+    enabled: true
+    jwt:
+      # Enable JWT authentication
+      enabled: true
+      # If the token is generated by a secret key, set the usingSecretKey as 
true.
+      # If the token is generated by a private key, set the usingSecretKey as 
false.
+      usingSecretKey: false
+      generateSecrets:
+        enabled: true
+  authorization:
+    enabled: true
+  superUsers:
+    # broker to broker communication
+    broker: "broker-admin"
+    # proxy to broker communication
+    proxy: "proxy-admin"
+    # pulsar-admin client to broker/proxy communication
+    client: "admin"
+    # pulsar-manager to broker communication
+    manager: "manager-admin"
 
diff --git a/.ci/helm.sh b/.ci/helm.sh
index 574e602..480c1df 100755
--- a/.ci/helm.sh
+++ b/.ci/helm.sh
@@ -160,9 +160,8 @@ function ci::install_pulsar_chart() {
       if [[ "${AUTHENTICATION_PROVIDER}" == "openid" ]]; then
           ci::create_openid_resources
       fi
-    else
-      install_args+=("--wait" "--wait-for-jobs" "--timeout=360s" "--debug")
     fi
+    install_args+=("--wait" "--wait-for-jobs" 
"--timeout=${HELM_INSTALL_TIMEOUT:-6m}" "--debug")
 
     declare -a CHART_ARGS
     if [[ "${PULSAR_CHART_VERSION}" == "local" ]]; then
@@ -184,65 +183,97 @@ function ci::install_pulsar_chart() {
     ${HELM} "${install_type}" --values "${common_value_file}" --values 
"${value_file}" "${extra_values[@]}" --namespace="${NAMESPACE}" "${CLUSTER}" 
"${CHART_ARGS[@]}" "${install_args[@]}"
     set +x
 
+    local standalone
+    standalone=$(ci::helm_values_for_deployment | yq .standalone.enabled)
+
     if [[ "${install_type}" == "install" ]]; then
-      echo "wait until broker is alive"
-      # shellcheck disable=SC2126
-      WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-broker | wc -l)
-      counter=1
-      while [[ ${WC} -lt 1 ]]; do
-        ((counter++))
-        echo "${WC}";
-        sleep 15
-        ${KUBECTL} get pods,jobs -n "${NAMESPACE}"
-        ${KUBECTL} get events --sort-by=.lastTimestamp -A | tail -n 30 || true
-        if [[ $((counter % 20)) -eq 0 ]]; then
-          ci::print_pod_logs
-          if [[ $counter -gt 100 ]]; then
-            echo >&2 "Timeout waiting..."
-            exit 1
-          fi
-        fi
+      if [[ "${standalone}" == "true" ]]; then
+        echo "wait until standalone is alive"
         # shellcheck disable=SC2126
-        WC=$(${KUBECTL} get pods -n "${NAMESPACE}" | grep "${CLUSTER}"-broker 
| wc -l)
-        if [[ ${WC} -gt 1 ]]; then
-          ${KUBECTL} describe pod -n "${NAMESPACE}" pulsar-ci-broker-0
-          ${KUBECTL} logs -n "${NAMESPACE}" pulsar-ci-broker-0
-        fi
+        WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-standalone | wc -l)
+        counter=1
+        while [[ ${WC} -lt 1 ]]; do
+          ((counter++))
+          echo "${WC}";
+          sleep 15
+          ${KUBECTL} get pods,jobs -n "${NAMESPACE}"
+          ${KUBECTL} get events --sort-by=.lastTimestamp -A | tail -n 30 || 
true
+          if [[ $((counter % 20)) -eq 0 ]]; then
+            ci::print_pod_logs
+            if [[ $counter -gt 100 ]]; then
+              echo >&2 "Timeout waiting..."
+              exit 1
+            fi
+          fi
+          # shellcheck disable=SC2126
+          WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-standalone | wc -l)
+        done
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-standalone; do 
sleep 3; done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+      else
+        echo "wait until broker is alive"
         # shellcheck disable=SC2126
         WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-broker | wc -l)
-      done
-      timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until nslookup pulsar-ci-broker; do sleep 3; done' || { echo >&2 
"Timeout waiting..."; ci::print_pod_logs; exit 1; }
-      # shellcheck disable=SC2016
-      timeout 120s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until [ "$(curl -s -L http://pulsar-ci-broker:8080/status.html)" == 
"OK" ]; do sleep 3; done' || { echo >&2 "Timeout waiting..."; 
ci::print_pod_logs; exit 1; }
-
-      # shellcheck disable=SC2126
-      WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-proxy | wc -l)
-      counter=1
-      while [[ ${WC} -lt 1 ]]; do
-        ((counter++))
-        echo "${WC}";
-        sleep 15
-        ${KUBECTL} get pods,jobs -n "${NAMESPACE}"
-        ${KUBECTL} get events --sort-by=.lastTimestamp -A | tail -n 30 || true
-        if [[ $((counter % 8)) -eq 0 ]]; then
-          ci::print_pod_logs
-          if [[ $counter -gt 16 ]]; then
-            echo >&2 "Timeout waiting..."
-            exit 1
+        counter=1
+        while [[ ${WC} -lt 1 ]]; do
+          ((counter++))
+          echo "${WC}";
+          sleep 15
+          ${KUBECTL} get pods,jobs -n "${NAMESPACE}"
+          ${KUBECTL} get events --sort-by=.lastTimestamp -A | tail -n 30 || 
true
+          if [[ $((counter % 20)) -eq 0 ]]; then
+            ci::print_pod_logs
+            if [[ $counter -gt 100 ]]; then
+              echo >&2 "Timeout waiting..."
+              exit 1
+            fi
           fi
-        fi
+          # shellcheck disable=SC2126
+          WC=$(${KUBECTL} get pods -n "${NAMESPACE}" | grep 
"${CLUSTER}"-broker | wc -l)
+          if [[ ${WC} -gt 1 ]]; then
+            ${KUBECTL} describe pod -n "${NAMESPACE}" pulsar-ci-broker-0
+            ${KUBECTL} logs -n "${NAMESPACE}" pulsar-ci-broker-0
+          fi
+          # shellcheck disable=SC2126
+          WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-broker | wc -l)
+        done
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-broker; do sleep 3; 
done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+        # shellcheck disable=SC2016
+        timeout 120s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until [ "$(curl -s -L 
http://pulsar-ci-broker:8080/status.html)" == "OK" ]; do sleep 3; done' || { 
echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+
         # shellcheck disable=SC2126
-        WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep ${CLUSTER}-proxy | wc -l)
-      done
-      timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until nslookup pulsar-ci-proxy; do sleep 3; done' || { echo >&2 
"Timeout waiting..."; ci::print_pod_logs; exit 1; }
+        WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep "${CLUSTER}"-proxy | wc -l)
+        counter=1
+        while [[ ${WC} -lt 1 ]]; do
+          ((counter++))
+          echo "${WC}";
+          sleep 15
+          ${KUBECTL} get pods,jobs -n "${NAMESPACE}"
+          ${KUBECTL} get events --sort-by=.lastTimestamp -A | tail -n 30 || 
true
+          if [[ $((counter % 8)) -eq 0 ]]; then
+            ci::print_pod_logs
+            if [[ $counter -gt 16 ]]; then
+              echo >&2 "Timeout waiting..."
+              exit 1
+            fi
+          fi
+          # shellcheck disable=SC2126
+          WC=$(${KUBECTL} get pods -n "${NAMESPACE}" 
--field-selector=status.phase=Running | grep ${CLUSTER}-proxy | wc -l)
+        done
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-proxy; do sleep 3; 
done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+      fi
       echo "Install complete"
     else
-      echo "wait until broker is alive"
-      timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until nslookup pulsar-ci-broker; do sleep 3; done' || { echo >&2 
"Timeout waiting..."; ci::print_pod_logs; exit 1; }
-      # shellcheck disable=SC2016
-      timeout 120s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until [ "$(curl -s -L http://pulsar-ci-broker:8080/status.html)" == 
"OK" ]; do sleep 3; done' || { echo >&2 "Timeout waiting..."; 
ci::print_pod_logs; exit 1; }
-      echo "wait until proxy is alive"
-      timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 
-- bash -c 'until nslookup pulsar-ci-proxy; do sleep 3; done' || { echo >&2 
"Timeout waiting..."; ci::print_pod_logs; exit 1; }
+      if [[ "${standalone}" == "true" ]]; then
+        echo "wait until standalone is alive"
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-standalone; do 
sleep 3; done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+      else
+        echo "wait until broker is alive"
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-broker; do sleep 3; 
done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+        # shellcheck disable=SC2016
+        timeout 120s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until [ "$(curl -s -L 
http://pulsar-ci-broker:8080/status.html)" == "OK" ]; do sleep 3; done' || { 
echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+        echo "wait until proxy is alive"
+        timeout 300s "${KUBECTL}" exec -n "${NAMESPACE}" 
"${CLUSTER}"-toolset-0 -- bash -c 'until nslookup pulsar-ci-proxy; do sleep 3; 
done' || { echo >&2 "Timeout waiting..."; ci::print_pod_logs; exit 1; }
+      fi
       echo "Upgrade complete"
     fi
 }
@@ -257,6 +288,11 @@ function ci::helm_values_for_deployment() {
 }
 
 function ci::check_pulsar_environment() {
+    if [[ "$(ci::helm_values_for_deployment | yq .standalone.enabled)" == 
"true" ]]; then
+        echo "Wait until pulsar-ci-standalone is ready"
+        ${KUBECTL} exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 -- bash -c 
'until nslookup pulsar-ci-standalone; do sleep 3; done'
+        return
+    fi
     echo "Wait until pulsar-ci-broker is ready"
     ${KUBECTL} exec -n "${NAMESPACE}" "${CLUSTER}"-toolset-0 -- bash -c 'until 
nslookup pulsar-ci-broker; do sleep 3; done'
     echo "Wait until pulsar-ci-proxy is ready"
@@ -303,7 +339,13 @@ function ci::test_create_test_namespace() {
 function ci::test_pulsar_producer_consumer() {
     action="${1:-"produce-consume"}"
     echo "Testing with ${action}"
-    if [[ "$(ci::helm_values_for_deployment | yq .tls.proxy.enabled)" == 
"true" ]]; then
+    if [[ "$(ci::helm_values_for_deployment | yq .standalone.enabled)" == 
"true" ]]; then
+      if [[ "$(ci::helm_values_for_deployment | yq .tls.enabled)" == "true" 
]]; then
+        PROXY_URL="pulsar+ssl://pulsar-ci-standalone:6651"
+      else
+        PROXY_URL="pulsar://pulsar-ci-standalone:6650"
+      fi
+    elif [[ "$(ci::helm_values_for_deployment | yq .tls.proxy.enabled)" == 
"true" ]]; then
       PROXY_URL="pulsar+ssl://pulsar-ci-proxy:6651"
     else
       PROXY_URL="pulsar://pulsar-ci-proxy:6650"
diff --git a/.ci/values-common.yaml b/.ci/values-common.yaml
index b9b109b..f0b1fde 100644
--- a/.ci/values-common.yaml
+++ b/.ci/values-common.yaml
@@ -120,3 +120,8 @@ oxia:
   server:
     podMonitor:
       enabled: false
+
+standalone:
+  # Disable pod monitor since we're disabling CRD installation
+  podMonitor:
+    enabled: false
diff --git a/.github/workflows/pulsar-helm-chart-ci.yaml 
b/.github/workflows/pulsar-helm-chart-ci.yaml
index 94d1d53..eff01f5 100644
--- a/.github/workflows/pulsar-helm-chart-ci.yaml
+++ b/.github/workflows/pulsar-helm-chart-ci.yaml
@@ -242,6 +242,9 @@ jobs:
           - name: CA certificates
             values_file: .ci/clusters/values-cacerts.yaml
             shortname: cacerts
+          - name: Standalone
+            values_file: .ci/clusters/values-standalone.yaml
+            shortname: standalone
         include:
           - k8sVersion:
               version: "1.25.16"
@@ -305,6 +308,10 @@ jobs:
             "jwt-generated")
               export SKIP_PREPARE_RELEASE="1"
               ;;
+            "standalone")
+              export SKIP_PREPARE_RELEASE="1"
+              export HELM_INSTALL_TIMEOUT="10m"
+              ;;
             "openid")
               export AUTHENTICATION_PROVIDER=openid
               ;;
diff --git a/charts/pulsar/templates/_standalone.tpl 
b/charts/pulsar/templates/_standalone.tpl
new file mode 100644
index 0000000..69733b9
--- /dev/null
+++ b/charts/pulsar/templates/_standalone.tpl
@@ -0,0 +1,68 @@
+{{/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/}}
+
+{{/*
+Define the standalone service
+*/}}
+{{- define "pulsar.standalone.service" -}}
+{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component }}
+{{- end }}
+
+{{/*
+Define the standalone headless service
+*/}}
+{{- define "pulsar.standalone.service.headless" -}}
+{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component }}-headless
+{{- end }}
+
+{{/*
+Define standalone tls certs mounts
+*/}}
+{{- define "pulsar.standalone.certs.volumeMounts" -}}
+{{- if .Values.tls.enabled }}
+- name: standalone-certs
+  mountPath: "/pulsar/certs/broker"
+  readOnly: true
+- name: ca
+  mountPath: "/pulsar/certs/ca"
+  readOnly: true
+{{- end }}
+{{- end }}
+
+{{/*
+Define standalone tls certs volumes
+*/}}
+{{- define "pulsar.standalone.certs.volumes" -}}
+{{- if .Values.tls.enabled }}
+- name: standalone-certs
+  secret:
+    secretName: "{{ .Release.Name }}-{{ .Values.tls.standalone.cert_name }}"
+    items:
+    - key: tls.crt
+      path: tls.crt
+    - key: tls.key
+      path: tls.key
+- name: ca
+  secret:
+    secretName: "{{ template "pulsar.certs.issuers.ca.secretName" . }}"
+    items:
+    - key: ca.crt
+      path: ca.crt
+{{- end }}
+{{- end }}
diff --git a/charts/pulsar/templates/autorecovery-configmap.yaml 
b/charts/pulsar/templates/autorecovery-configmap.yaml
index 18395c2..9d592f0 100644
--- a/charts/pulsar/templates/autorecovery-configmap.yaml
+++ b/charts/pulsar/templates/autorecovery-configmap.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.autorecovery }}
+{{- if and .Values.components.autorecovery (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/charts/pulsar/templates/autorecovery-podmonitor.yaml 
b/charts/pulsar/templates/autorecovery-podmonitor.yaml
index 2ba909d..a141b37 100644
--- a/charts/pulsar/templates/autorecovery-podmonitor.yaml
+++ b/charts/pulsar/templates/autorecovery-podmonitor.yaml
@@ -18,6 +18,6 @@
 #
 
 # deploy autorecovery PodMonitor only when 
`$.Values.autorecovery.podMonitor.enabled` is true
-{{- if $.Values.autorecovery.podMonitor.enabled }}
+{{- if and $.Values.autorecovery.podMonitor.enabled (not 
$.Values.standalone.enabled) }}
 {{- include "pulsar.podMonitor" (list . "autorecovery" (printf "component: %s" 
.Values.autorecovery.component)) }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/pulsar/templates/autorecovery-service-account.yaml 
b/charts/pulsar/templates/autorecovery-service-account.yaml
index e72580b..6c06776 100644
--- a/charts/pulsar/templates/autorecovery-service-account.yaml
+++ b/charts/pulsar/templates/autorecovery-service-account.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.autorecovery }}
+{{- if and .Values.components.autorecovery (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/autorecovery-service.yaml 
b/charts/pulsar/templates/autorecovery-service.yaml
index 1021a3b..2a765c1 100644
--- a/charts/pulsar/templates/autorecovery-service.yaml
+++ b/charts/pulsar/templates/autorecovery-service.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.autorecovery }}
+{{- if and .Values.components.autorecovery (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/autorecovery-statefulset.yaml 
b/charts/pulsar/templates/autorecovery-statefulset.yaml
index 14ea073..75d0f69 100644
--- a/charts/pulsar/templates/autorecovery-statefulset.yaml
+++ b/charts/pulsar/templates/autorecovery-statefulset.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.autorecovery }}
+{{- if and .Values.components.autorecovery (not .Values.standalone.enabled) }}
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-cluster-initialize.yaml 
b/charts/pulsar/templates/bookkeeper-cluster-initialize.yaml
index 735bf9d..db7f6cb 100755
--- a/charts/pulsar/templates/bookkeeper-cluster-initialize.yaml
+++ b/charts/pulsar/templates/bookkeeper-cluster-initialize.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 {{- if or (and .Values.useReleaseStatus .Release.IsInstall) .Values.initialize 
}}
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 apiVersion: batch/v1
 kind: Job
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-configmap.yaml 
b/charts/pulsar/templates/bookkeeper-configmap.yaml
index db5e52f..cd82dfe 100644
--- a/charts/pulsar/templates/bookkeeper-configmap.yaml
+++ b/charts/pulsar/templates/bookkeeper-configmap.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-pdb.yaml 
b/charts/pulsar/templates/bookkeeper-pdb.yaml
index 3af8290..409036d 100644
--- a/charts/pulsar/templates/bookkeeper-pdb.yaml
+++ b/charts/pulsar/templates/bookkeeper-pdb.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 {{- if .Values.bookkeeper.pdb.usePolicy }}
 # pdb version detection
 {{- if semverCompare "<1.21-0" .Capabilities.KubeVersion.Version }}
diff --git a/charts/pulsar/templates/bookkeeper-podmonitor.yaml 
b/charts/pulsar/templates/bookkeeper-podmonitor.yaml
index 8bd63ca..005e22b 100644
--- a/charts/pulsar/templates/bookkeeper-podmonitor.yaml
+++ b/charts/pulsar/templates/bookkeeper-podmonitor.yaml
@@ -18,6 +18,6 @@
 #
 
 # deploy bookkeeper PodMonitor only when 
`$.Values.bookkeeper.podMonitor.enabled` is true
-{{- if $.Values.bookkeeper.podMonitor.enabled }}
+{{- if and $.Values.bookkeeper.podMonitor.enabled (not 
$.Values.standalone.enabled) }}
 {{- include "pulsar.podMonitor" (list . "bookkeeper" (printf "component: %s" 
.Values.bookkeeper.component)) }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/pulsar/templates/bookkeeper-service-account.yaml 
b/charts/pulsar/templates/bookkeeper-service-account.yaml
index 5779fba..926b483 100644
--- a/charts/pulsar/templates/bookkeeper-service-account.yaml
+++ b/charts/pulsar/templates/bookkeeper-service-account.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-service.yaml 
b/charts/pulsar/templates/bookkeeper-service.yaml
index 9ab1b0f..0c05559 100644
--- a/charts/pulsar/templates/bookkeeper-service.yaml
+++ b/charts/pulsar/templates/bookkeeper-service.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-statefulset.yaml 
b/charts/pulsar/templates/bookkeeper-statefulset.yaml
index 414fabd..c7cb5f6 100644
--- a/charts/pulsar/templates/bookkeeper-statefulset.yaml
+++ b/charts/pulsar/templates/bookkeeper-statefulset.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
diff --git a/charts/pulsar/templates/bookkeeper-storageclass.yaml 
b/charts/pulsar/templates/bookkeeper-storageclass.yaml
index 2d60070..f9f2a71 100644
--- a/charts/pulsar/templates/bookkeeper-storageclass.yaml
+++ b/charts/pulsar/templates/bookkeeper-storageclass.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if and .Values.components.bookkeeper (not .Values.standalone.enabled) }}
 {{- if and (and .Values.persistence .Values.volumes.persistence) 
.Values.bookkeeper.volumes.persistence }}
 {{- if not .Values.volumes.local_storage }}
 
diff --git a/charts/pulsar/templates/broker-cluster-role-binding.yaml 
b/charts/pulsar/templates/broker-cluster-role-binding.yaml
index a64984a..382614c 100644
--- a/charts/pulsar/templates/broker-cluster-role-binding.yaml
+++ b/charts/pulsar/templates/broker-cluster-role-binding.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 ## TODO create our own cluster role with less privledges than admin
 apiVersion: rbac.authorization.k8s.io/v1
 {{- if .Values.rbac.limit_to_namespace }}
diff --git a/charts/pulsar/templates/broker-configmap.yaml 
b/charts/pulsar/templates/broker-configmap.yaml
index 2fde143..177b4d8 100644
--- a/charts/pulsar/templates/broker-configmap.yaml
+++ b/charts/pulsar/templates/broker-configmap.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/charts/pulsar/templates/broker-headless-service.yaml 
b/charts/pulsar/templates/broker-headless-service.yaml
index 9780f2c..6ec4e99 100644
--- a/charts/pulsar/templates/broker-headless-service.yaml
+++ b/charts/pulsar/templates/broker-headless-service.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy broker only when `components.broker` is true
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/broker-hpa.yaml 
b/charts/pulsar/templates/broker-hpa.yaml
index 83a24aa..d165c33 100644
--- a/charts/pulsar/templates/broker-hpa.yaml
+++ b/charts/pulsar/templates/broker-hpa.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.broker.autoscaling.enabled }}
+{{- if and .Values.broker.autoscaling.enabled (not .Values.standalone.enabled) 
}}
 {{- if (semverCompare "<1.23-0" .Capabilities.KubeVersion.Version) }}
 apiVersion: autoscaling/v2beta2
 {{- else }}
diff --git a/charts/pulsar/templates/broker-pdb.yaml 
b/charts/pulsar/templates/broker-pdb.yaml
index ec7555c..9c36310 100644
--- a/charts/pulsar/templates/broker-pdb.yaml
+++ b/charts/pulsar/templates/broker-pdb.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 {{- if .Values.broker.pdb.usePolicy }}
 # pdb version detection
 {{- if semverCompare "<1.21-0" .Capabilities.KubeVersion.Version }}
diff --git a/charts/pulsar/templates/broker-podmonitor.yaml 
b/charts/pulsar/templates/broker-podmonitor.yaml
index 79f72a6..16e332d 100644
--- a/charts/pulsar/templates/broker-podmonitor.yaml
+++ b/charts/pulsar/templates/broker-podmonitor.yaml
@@ -18,6 +18,6 @@
 #
 
 # deploy broker PodMonitor only when `$.Values.broker.podMonitor.enabled` is 
true
-{{- if $.Values.broker.podMonitor.enabled }}
+{{- if and $.Values.broker.podMonitor.enabled (not 
$.Values.standalone.enabled) }}
 {{- include "pulsar.podMonitor" (list . "broker" (printf "component: %s" 
.Values.broker.component)) }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/pulsar/templates/broker-rbac.yaml 
b/charts/pulsar/templates/broker-rbac.yaml
index 3d6dc6e..80d69b2 100644
--- a/charts/pulsar/templates/broker-rbac.yaml
+++ b/charts/pulsar/templates/broker-rbac.yaml
@@ -18,6 +18,7 @@
 #
 
 {{- if .Values.components.functions }}
+{{- $subjectSA := ternary (printf "%s-%s-acct" (include "pulsar.fullname" .) 
.Values.standalone.component) (printf "%s-%s" (include "pulsar.fullname" .) 
.Values.functions.component) .Values.standalone.enabled }}
 apiVersion: rbac.authorization.k8s.io/v1
 {{- if .Values.functions.rbac.limit_to_namespace }}
 kind: Role
@@ -64,7 +65,10 @@ roleRef:
   name: "{{ template "pulsar.fullname" . }}-{{ .Values.functions.component }}"
 {{- end}}
 subjects:
+# In standalone mode the function worker runs inside the standalone pod, so
+# bind the functions role to the standalone service account instead of the
+# dedicated functions-worker SA (which isn't created in standalone mode).
 - kind: ServiceAccount
-  name: "{{ template "pulsar.fullname" . }}-{{ .Values.functions.component }}"
+  name: "{{ $subjectSA }}"
   namespace: {{ template "pulsar.namespace" . }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/pulsar/templates/broker-service-account.yaml 
b/charts/pulsar/templates/broker-service-account.yaml
index 5e23976..ec0e001 100644
--- a/charts/pulsar/templates/broker-service-account.yaml
+++ b/charts/pulsar/templates/broker-service-account.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
@@ -33,7 +33,7 @@ metadata:
 ---
 {{- end }}
 
-{{- if .Values.components.functions }}
+{{- if and .Values.components.functions (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/broker-service.yaml 
b/charts/pulsar/templates/broker-service.yaml
index 3fd2474..a761af6 100644
--- a/charts/pulsar/templates/broker-service.yaml
+++ b/charts/pulsar/templates/broker-service.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/broker-statefulset-upgrade.yaml 
b/charts/pulsar/templates/broker-statefulset-upgrade.yaml
index 656c1d6..b0d9984 100644
--- a/charts/pulsar/templates/broker-statefulset-upgrade.yaml
+++ b/charts/pulsar/templates/broker-statefulset-upgrade.yaml
@@ -21,7 +21,7 @@
 # this pre-upgrade hook job will be created to clean up the old broker 
statefulset if the existing statefulset is created 
 # by a chart older than 4.6.0, which has a different headless service name and 
will cause issue if not deleted before
 # the new statefulset is created.
-{{- if and .Values.components.broker .Values.broker.statefulsetUpgrade.enabled 
}}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) 
.Values.broker.statefulsetUpgrade.enabled }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/broker-statefulset.yaml 
b/charts/pulsar/templates/broker-statefulset.yaml
index 534db04..3a1d700 100644
--- a/charts/pulsar/templates/broker-statefulset.yaml
+++ b/charts/pulsar/templates/broker-statefulset.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
diff --git a/charts/pulsar/templates/proxy-configmap.yaml 
b/charts/pulsar/templates/proxy-configmap.yaml
index 0e85271..6dd2d56 100644
--- a/charts/pulsar/templates/proxy-configmap.yaml
+++ b/charts/pulsar/templates/proxy-configmap.yaml
@@ -18,6 +18,14 @@
 #
 
 {{- if .Values.components.proxy }}
+{{- /*
+  The proxy can sit in front of either the distributed broker or the
+  standalone pod. Resolve backend host/ports and whether the backend speaks
+  TLS once, so the URL blocks below stay readable.
+*/}}
+{{- $backendHost := ternary (include "pulsar.standalone.service" .) (printf 
"%s-%s" (include "pulsar.fullname" .) .Values.broker.component) 
.Values.standalone.enabled }}
+{{- $backendPorts := ternary .Values.standalone.ports .Values.broker.ports 
.Values.standalone.enabled }}
+{{- $backendTls := ternary .Values.tls.enabled (and .Values.tls.enabled 
.Values.tls.broker.enabled) .Values.standalone.enabled }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
@@ -33,8 +41,8 @@ data:
   webServicePort: "{{ .Values.proxy.ports.containerPorts.http }}"
   {{- if or (not .Values.tls.enabled) (not .Values.tls.proxy.enabled) }}
   servicePort: "{{ .Values.proxy.ports.pulsar }}"
-  brokerServiceURL: pulsar://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.pulsar }}
-  brokerWebServiceURL: http://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.http }}
+  brokerServiceURL: pulsar://{{ $backendHost }}:{{ $backendPorts.pulsar }}
+  brokerWebServiceURL: http://{{ $backendHost }}:{{ $backendPorts.http }}
   {{- end }}
   {{- if and .Values.tls.enabled .Values.tls.proxy.enabled }}
   tlsEnabledInProxy: "true"
@@ -43,17 +51,17 @@ data:
   tlsCertificateFilePath: "/pulsar/certs/proxy/tls.crt"
   tlsKeyFilePath: "/pulsar/certs/proxy/tls.key"
   tlsTrustCertsFilePath: {{ ternary "/pulsar/certs/cacerts/ca-combined.pem" 
"/pulsar/certs/ca/ca.crt" .Values.tls.proxy.cacerts.enabled | quote }}
-  {{- if and .Values.tls.enabled .Values.tls.broker.enabled }}
-  # if broker enables TLS, configure proxy to talk to broker using TLS
-  brokerServiceURLTLS: pulsar+ssl://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.pulsarssl }}
-  brokerWebServiceURLTLS: https://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.https }}
+  {{- if $backendTls }}
+  # backend speaks TLS, so configure proxy to talk to it using TLS
+  brokerServiceURLTLS: pulsar+ssl://{{ $backendHost }}:{{ 
$backendPorts.pulsarssl }}
+  brokerWebServiceURLTLS: https://{{ $backendHost }}:{{ $backendPorts.https }}
   tlsEnabledWithBroker: "true"
   tlsCertRefreshCheckDurationSec: "300"
   brokerClientTrustCertsFilePath: {{ ternary 
"/pulsar/certs/cacerts/ca-combined.pem" "/pulsar/certs/ca/ca.crt" 
.Values.tls.proxy.cacerts.enabled | quote }}
   {{- end }}
-  {{- if not (and .Values.tls.enabled .Values.tls.broker.enabled) }}
-  brokerServiceURL: pulsar://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.pulsar }}
-  brokerWebServiceURL: http://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.http }}
+  {{- if not $backendTls }}
+  brokerServiceURL: pulsar://{{ $backendHost }}:{{ $backendPorts.pulsar }}
+  brokerWebServiceURL: http://{{ $backendHost }}:{{ $backendPorts.http }}
   {{- end }}
   {{- end }}
 
diff --git a/charts/pulsar/templates/proxy-statefulset.yaml 
b/charts/pulsar/templates/proxy-statefulset.yaml
index 9c220e0..4df1dfa 100644
--- a/charts/pulsar/templates/proxy-statefulset.yaml
+++ b/charts/pulsar/templates/proxy-statefulset.yaml
@@ -123,7 +123,7 @@ spec:
         volumeMounts:
         {{- include "pulsar.proxy.certs.volumeMounts" . | nindent 8 }}
       {{- end }}
-      {{- if and .Values.components.zookeeper 
.Values.proxy.waitZookeeperTimeout (gt (.Values.proxy.waitZookeeperTimeout | 
int) 0) }}
+      {{- if and .Values.components.zookeeper (not .Values.standalone.enabled) 
.Values.proxy.waitZookeeperTimeout (gt (.Values.proxy.waitZookeeperTimeout | 
int) 0) }}
       # This init container will wait for zookeeper to be ready before
       # deploying the bookies
       - name: wait-zookeeper-ready
@@ -148,7 +148,7 @@ spec:
             done;
             {{- end}}
       {{- end}}
-      {{- if and .Values.components.oxia .Values.proxy.waitOxiaTimeout (gt 
(.Values.proxy.waitOxiaTimeout | int) 0) }}
+      {{- if and .Values.components.oxia (not .Values.standalone.enabled) 
.Values.proxy.waitOxiaTimeout (gt (.Values.proxy.waitOxiaTimeout | int) 0) }}
       - name: wait-oxia-ready
         image: "{{ template "pulsar.imageFullName" (dict "image" 
.Values.images.proxy "root" .) }}"
         imagePullPolicy: "{{ template "pulsar.imagePullPolicy" (dict "image" 
.Values.images.proxy "root" .) }}"
@@ -161,8 +161,10 @@ spec:
             done;
       {{- end }}      
       {{- if and .Values.proxy.waitBrokerTimeout (gt 
(.Values.proxy.waitBrokerTimeout | int) 0) }}
-      # This init container will wait for at least one broker to be ready 
before
-      # deploying the proxy
+      {{- $backendService := ternary (include "pulsar.standalone.service" .) 
(printf "%s-%s" (include "pulsar.fullname" .) .Values.broker.component) 
.Values.standalone.enabled }}
+      # This init container waits for the broker backend (either the broker
+      # StatefulSet or the standalone pod) to be reachable before starting
+      # the proxy.
       - name: wait-broker-ready
         image: "{{ template "pulsar.imageFullName" (dict "image" 
.Values.images.proxy "root" .) }}"
         imagePullPolicy: "{{ template "pulsar.imagePullPolicy" (dict "image" 
.Values.images.proxy "root" .) }}"
@@ -171,11 +173,11 @@ spec:
         args:
           - |
             set -e;
-            brokerServiceNumber="$(nslookup -timeout=10 {{ template 
"pulsar.fullname" . }}-{{ .Values.broker.component }} | grep Name | wc -l)";
+            brokerServiceNumber="$(nslookup -timeout=10 {{ $backendService }} 
| grep Name | wc -l)";
             until [ ${brokerServiceNumber} -ge 1 ]; do
               echo "pulsar cluster {{ template "pulsar.cluster.name" . }} 
isn't initialized yet ... check in 10 seconds ...";
               sleep 10;
-              brokerServiceNumber="$(nslookup -timeout=10 {{ template 
"pulsar.fullname" . }}-{{ .Values.broker.component }} | grep Name | wc -l)";
+              brokerServiceNumber="$(nslookup -timeout=10 {{ $backendService 
}} | grep Name | wc -l)";
             done;
       {{- end}}
       {{- if .Values.proxy.initContainers }}
diff --git a/charts/pulsar/templates/pulsar-cluster-initialize.yaml 
b/charts/pulsar/templates/pulsar-cluster-initialize.yaml
index 74599da..dfbab23 100755
--- a/charts/pulsar/templates/pulsar-cluster-initialize.yaml
+++ b/charts/pulsar/templates/pulsar-cluster-initialize.yaml
@@ -18,7 +18,7 @@
 #
 
 {{- if or (and .Values.useReleaseStatus .Release.IsInstall) .Values.initialize 
}}
-{{- if .Values.components.broker }}
+{{- if and .Values.components.broker (not .Values.standalone.enabled) }}
 apiVersion: batch/v1
 kind: Job
 metadata:
diff --git a/charts/pulsar/templates/standalone-configmap.yaml 
b/charts/pulsar/templates/standalone-configmap.yaml
new file mode 100644
index 0000000..ee6aa5b
--- /dev/null
+++ b/charts/pulsar/templates/standalone-configmap.yaml
@@ -0,0 +1,93 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+{{- if .Values.standalone.enabled }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: "{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component }}"
+  namespace: {{ template "pulsar.namespace" . }}
+  labels:
+    {{- include "pulsar.standardLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+data:
+  clusterName: {{ template "pulsar.cluster.name" . }}
+
+  # Standalone-specific defaults: single-node replication
+  managedLedgerDefaultEnsembleSize: "1"
+  managedLedgerDefaultWriteQuorum: "1"
+  managedLedgerDefaultAckQuorum: "1"
+
+  # Disable load balancer in standalone mode
+  loadBalancerEnabled: "false"
+
+  # Enable WebSocket service
+  webSocketServiceEnabled: "true"
+
+  # Relaxed disk thresholds for standalone
+  diskUsageThreshold: "0.99"
+  diskUsageWarnThreshold: "0.99"
+
+  # Metrics
+  exposeTopicLevelMetricsInPrometheus: "true"
+  exposeConsumerLevelMetricsInPrometheus: "false"
+  exposeProducerLevelMetricsInPrometheus: "false"
+
+  {{- if .Values.tls.enabled }}
+  # TLS Settings
+  # standalone.conf lacks these as properties (only in a comment), so use
+  # PULSAR_PREFIX_ to ensure apply-config-from-env.py injects them.
+  PULSAR_PREFIX_brokerServicePortTls: "{{ .Values.standalone.ports.pulsarssl 
}}"
+  PULSAR_PREFIX_webServicePortTls: "{{ .Values.standalone.ports.https }}"
+  PULSAR_PREFIX_tlsCertificateFilePath: "/pulsar/certs/broker/tls.crt"
+  PULSAR_PREFIX_tlsKeyFilePath: "/pulsar/certs/broker/tls.key"
+  PULSAR_PREFIX_tlsTrustCertsFilePath: "/pulsar/certs/ca/ca.crt"
+  # Disable the non-TLS Pulsar wire-protocol listener. Without this, the
+  # standalone process keeps listening on 6650 in parallel with 6651.
+  PULSAR_PREFIX_brokerServicePort: ""
+  {{- end }}
+
+  # Authentication Settings
+  {{- if .Values.auth.authentication.enabled }}
+  authenticationEnabled: "true"
+  {{- if .Values.auth.authorization.enabled }}
+  authorizationEnabled: "true"
+  superUserRoles: {{ .Values.auth.superUsers | values | compact | sortAlpha | 
join "," }}
+  {{- end }}
+  {{- if .Values.auth.authentication.jwt.enabled }}
+  {{- if .Values.auth.authentication.openid.enabled }}
+  authenticationProviders: 
"org.apache.pulsar.broker.authentication.AuthenticationProviderToken,org.apache.pulsar.broker.authentication.oidc.AuthenticationProviderOpenID"
+  {{- else }}
+  authenticationProviders: 
"org.apache.pulsar.broker.authentication.AuthenticationProviderToken"
+  {{- end }}
+  brokerClientAuthenticationParameters: "file:///pulsar/tokens/client/token"
+  brokerClientAuthenticationPlugin: 
"org.apache.pulsar.client.impl.auth.AuthenticationToken"
+  {{- if .Values.auth.authentication.jwt.usingSecretKey }}
+  tokenSecretKey: "file:///pulsar/keys/token/secret.key"
+  {{- else }}
+  tokenPublicKey: "file:///pulsar/keys/token/public.key"
+  {{- end }}
+  {{- end }}
+  {{- end }}
+
+  # User-provided configuration overrides
+  {{- if .Values.standalone.configData }}
+  {{- toYaml .Values.standalone.configData | nindent 2 }}
+  {{- end }}
+{{- end }}
diff --git a/charts/pulsar/templates/standalone-deployment.yaml 
b/charts/pulsar/templates/standalone-deployment.yaml
new file mode 100644
index 0000000..574367c
--- /dev/null
+++ b/charts/pulsar/templates/standalone-deployment.yaml
@@ -0,0 +1,232 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+{{- if .Values.standalone.enabled }}
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: "{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component 
}}-{{ .Values.standalone.volumes.data.name }}"
+  namespace: {{ template "pulsar.namespace" . }}
+  annotations:
+    # Preserve standalone data on `helm uninstall`
+    helm.sh/resource-policy: keep
+  labels:
+    {{- include "pulsar.standardLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+spec:
+  accessModes: [ "ReadWriteOnce" ]
+  resources:
+    requests:
+      storage: {{ .Values.standalone.volumes.data.size }}
+{{- if or .Values.standalone.volumes.data.storageClassName 
.Values.volumes.local_storage }}
+  {{- if .Values.standalone.volumes.data.storageClassName }}
+  storageClassName: "{{ .Values.standalone.volumes.data.storageClassName }}"
+  {{- end }}
+  {{- if .Values.volumes.local_storage }}
+  storageClassName: "local-storage"
+  {{- end }}
+{{- end }}
+{{- if .Values.standalone.volumes.data.selector }}
+  selector:
+{{ toYaml .Values.standalone.volumes.data.selector | indent 4 }}
+{{- end }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: "{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component }}"
+  namespace: {{ template "pulsar.namespace" . }}
+  annotations: {{ .Values.standalone.appAnnotations | toYaml | nindent 4 }}
+  labels:
+    {{- include "pulsar.standardLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+spec:
+  replicas: 1
+  # Recreate releases the ReadWriteOnce PVC before the new pod starts; a
+  # rolling update would deadlock because the replacement pod cannot attach
+  # the volume while the outgoing pod still holds it.
+  strategy:
+    type: Recreate
+  selector:
+    matchLabels:
+      {{- include "pulsar.matchLabels" . | nindent 6 }}
+      component: {{ .Values.standalone.component }}
+  template:
+    metadata:
+      labels:
+        {{- include "pulsar.template.labels" . | nindent 8 }}
+        component: {{ .Values.standalone.component }}
+      annotations:
+        {{- if not .Values.standalone.podMonitor.enabled }}
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "{{ .Values.standalone.ports.http }}"
+        {{- end }}
+        {{- if .Values.standalone.restartPodsOnConfigMapChange }}
+        checksum/config: {{ include (print $.Template.BasePath 
"/standalone-configmap.yaml") . | sha256sum }}
+        {{- end }}
+{{- with .Values.standalone.annotations }}
+{{ toYaml . | indent 8 }}
+{{- end }}
+    spec:
+      # hostname + subdomain give the pod a headless-service FQDN that
+      # `hostname -f` will return and DNS will resolve back to the pod,
+      # which is what --advertised-address needs to publish.
+      hostname: {{ .Values.standalone.component }}
+      subdomain: "{{ template "pulsar.standalone.service.headless" . }}"
+      serviceAccountName: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}-acct"
+    {{- if .Values.standalone.nodeSelector }}
+      nodeSelector:
+{{ toYaml .Values.standalone.nodeSelector | indent 8 }}
+    {{- end }}
+    {{- if .Values.standalone.tolerations }}
+      tolerations:
+{{ toYaml .Values.standalone.tolerations | indent 8 }}
+    {{- end }}
+      terminationGracePeriodSeconds: {{ .Values.standalone.gracePeriod }}
+      {{- if .Values.standalone.initContainers }}
+      initContainers:
+        {{- toYaml .Values.standalone.initContainers | nindent 6 }}
+      {{- end }}
+      containers:
+      - name: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}"
+        image: "{{ template "pulsar.imageFullName" (dict "image" 
.Values.images.standalone "root" .) }}"
+        imagePullPolicy: "{{ template "pulsar.imagePullPolicy" (dict "image" 
.Values.images.standalone "root" .) }}"
+        {{- if .Values.standalone.probe.liveness.enabled }}
+        livenessProbe:
+          tcpSocket:
+            port: {{ .Values.standalone.ports.http }}
+          initialDelaySeconds: {{ 
.Values.standalone.probe.liveness.initialDelaySeconds }}
+          periodSeconds: {{ .Values.standalone.probe.liveness.periodSeconds }}
+          timeoutSeconds: {{ .Values.standalone.probe.liveness.timeoutSeconds 
}}
+          failureThreshold: {{ 
.Values.standalone.probe.liveness.failureThreshold }}
+        {{- end }}
+        {{- if .Values.standalone.probe.readiness.enabled }}
+        readinessProbe:
+          tcpSocket:
+            port: {{ .Values.standalone.ports.http }}
+          initialDelaySeconds: {{ 
.Values.standalone.probe.readiness.initialDelaySeconds }}
+          periodSeconds: {{ .Values.standalone.probe.readiness.periodSeconds }}
+          timeoutSeconds: {{ .Values.standalone.probe.readiness.timeoutSeconds 
}}
+          failureThreshold: {{ 
.Values.standalone.probe.readiness.failureThreshold }}
+        {{- end }}
+        {{- if .Values.standalone.probe.startup.enabled }}
+        startupProbe:
+          tcpSocket:
+            port: {{ .Values.standalone.ports.http }}
+          initialDelaySeconds: {{ 
.Values.standalone.probe.startup.initialDelaySeconds }}
+          periodSeconds: {{ .Values.standalone.probe.startup.periodSeconds }}
+          timeoutSeconds: {{ .Values.standalone.probe.startup.timeoutSeconds }}
+          failureThreshold: {{ 
.Values.standalone.probe.startup.failureThreshold }}
+        {{- end }}
+      {{- if .Values.standalone.resources }}
+        resources:
+{{ toYaml .Values.standalone.resources | indent 10 }}
+      {{- end }}
+        command: ["sh", "-c"]
+        args:
+        - |
+        {{- if .Values.standalone.additionalCommand }}
+          {{ .Values.standalone.additionalCommand }}
+        {{- end }}
+          bin/apply-config-from-env.py conf/standalone.conf;
+          bin/gen-yml-from-env.py conf/functions_worker.yml;
+          echo "OK" > "${statusFilePath:-status}";
+          # --advertised-address is REQUIRED: Pulsar no longer defaults this
+          # to the FQDN since apache/pulsar#25523 reverted apache/pulsar#25238.
+          # Without it, client lookups return a non-resolvable address and
+          # connections fail.
+          OPTS="${OPTS} -Dlog4j2.formatMsgNoLookups=true" exec bin/pulsar 
standalone \
+            --advertised-address "$(hostname -f)" \
+            {{- if not .Values.components.functions }}
+            --no-functions-worker \
+            --no-stream-storage \
+            {{- end }}
+            ;
+        ports:
+        - name: http
+          containerPort: {{ .Values.standalone.ports.http }}
+        {{- if not .Values.tls.enabled }}
+        - name: "{{ .Values.tcpPrefix }}pulsar"
+          containerPort: {{ .Values.standalone.ports.pulsar }}
+        {{- end }}
+        {{- if .Values.tls.enabled }}
+        - name: https
+          containerPort: {{ .Values.standalone.ports.https }}
+        - name: "{{ .Values.tlsPrefix }}pulsarssl"
+          containerPort: {{ .Values.standalone.ports.pulsarssl }}
+        {{- end }}
+        envFrom:
+        - configMapRef:
+            name: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}"
+        volumeMounts:
+          - name: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}-{{ .Values.standalone.volumes.data.name }}"
+            mountPath: /pulsar/data
+          {{- if .Values.auth.authentication.enabled }}
+          {{- if .Values.auth.authentication.jwt.enabled }}
+          - mountPath: "/pulsar/keys"
+            name: token-keys
+            readOnly: true
+          - mountPath: "/pulsar/tokens"
+            name: client-token
+            readOnly: true
+          {{- end }}
+          {{- end }}
+          {{- if .Values.standalone.extraVolumeMounts }}
+{{ toYaml .Values.standalone.extraVolumeMounts | indent 10 }}
+          {{- end }}
+          {{- include "pulsar.standalone.certs.volumeMounts" . | nindent 10 }}
+        {{- if .Values.standalone.extraEnvs }}
+{{- toYaml .Values.standalone.extraEnvs | nindent 10 }}
+        {{- end }}
+      volumes:
+      - name: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}-{{ .Values.standalone.volumes.data.name }}"
+        persistentVolumeClaim:
+          claimName: "{{ template "pulsar.fullname" . }}-{{ 
.Values.standalone.component }}-{{ .Values.standalone.volumes.data.name }}"
+      {{- if .Values.standalone.extraVolumes }}
+{{ toYaml .Values.standalone.extraVolumes | indent 6 }}
+      {{- end }}
+      {{- if .Values.auth.authentication.enabled }}
+      {{- if .Values.auth.authentication.jwt.enabled }}
+      - name: token-keys
+        secret:
+          {{- if not .Values.auth.authentication.jwt.usingSecretKey }}
+          secretName: "{{ .Release.Name }}-token-asymmetric-key"
+          {{- end}}
+          {{- if .Values.auth.authentication.jwt.usingSecretKey }}
+          secretName: "{{ .Release.Name }}-token-symmetric-key"
+          {{- end}}
+          items:
+            {{- if .Values.auth.authentication.jwt.usingSecretKey }}
+            - key: SECRETKEY
+              path: token/secret.key
+            {{- else }}
+            - key: PUBLICKEY
+              path: token/public.key
+            {{- end}}
+      - name: client-token
+        secret:
+          secretName: "{{ .Release.Name }}-token-{{ 
.Values.auth.superUsers.client }}"
+          items:
+            - key: TOKEN
+              path: client/token
+      {{- end}}
+      {{- end}}
+      {{- include "pulsar.standalone.certs.volumes" . | nindent 6 }}
+      {{- include "pulsar.imagePullSecrets" . | nindent 6}}
+{{- end }}
diff --git a/charts/pulsar/templates/broker-podmonitor.yaml 
b/charts/pulsar/templates/standalone-podmonitor.yaml
similarity index 76%
copy from charts/pulsar/templates/broker-podmonitor.yaml
copy to charts/pulsar/templates/standalone-podmonitor.yaml
index 79f72a6..5aa4a1b 100644
--- a/charts/pulsar/templates/broker-podmonitor.yaml
+++ b/charts/pulsar/templates/standalone-podmonitor.yaml
@@ -17,7 +17,6 @@
 # under the License.
 #
 
-# deploy broker PodMonitor only when `$.Values.broker.podMonitor.enabled` is 
true
-{{- if $.Values.broker.podMonitor.enabled }}
-{{- include "pulsar.podMonitor" (list . "broker" (printf "component: %s" 
.Values.broker.component)) }}
-{{- end }}
\ No newline at end of file
+{{- if and $.Values.standalone.enabled $.Values.standalone.podMonitor.enabled 
}}
+{{- include "pulsar.podMonitor" (list . "standalone" (printf "component: %s" 
.Values.standalone.component)) }}
+{{- end }}
diff --git a/charts/pulsar/templates/bookkeeper-service-account.yaml 
b/charts/pulsar/templates/standalone-service-account.yaml
similarity index 81%
copy from charts/pulsar/templates/bookkeeper-service-account.yaml
copy to charts/pulsar/templates/standalone-service-account.yaml
index 5779fba..8df7cdf 100644
--- a/charts/pulsar/templates/bookkeeper-service-account.yaml
+++ b/charts/pulsar/templates/standalone-service-account.yaml
@@ -17,16 +17,16 @@
 # under the License.
 #
 
-{{- if .Values.components.bookkeeper }}
+{{- if .Values.standalone.enabled }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
-  name: "{{ template "pulsar.fullname" . }}-{{ .Values.bookkeeper.component }}"
+  name: "{{ template "pulsar.fullname" . }}-{{ .Values.standalone.component 
}}-acct"
   namespace: {{ template "pulsar.namespace" . }}
   labels:
     {{- include "pulsar.standardLabels" . | nindent 4 }}
-    component: {{ .Values.bookkeeper.component }}
-{{- with .Values.bookkeeper.service_account.annotations }}
+    component: {{ .Values.standalone.component }}
+{{- with .Values.standalone.service_account.annotations }}
   annotations:
 {{ toYaml . | indent 4 }}
 {{- end }}
diff --git a/charts/pulsar/templates/standalone-service.yaml 
b/charts/pulsar/templates/standalone-service.yaml
new file mode 100644
index 0000000..e33e2ae
--- /dev/null
+++ b/charts/pulsar/templates/standalone-service.yaml
@@ -0,0 +1,77 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+{{- if .Values.standalone.enabled }}
+apiVersion: v1
+kind: Service
+metadata:
+  name: "{{ template "pulsar.standalone.service" . }}"
+  namespace: {{ template "pulsar.namespace" . }}
+  labels:
+    {{- include "pulsar.standardLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+{{- with .Values.standalone.service.annotations }}
+  annotations:
+{{ toYaml . | indent 4 }}
+{{- end }}
+spec:
+  type: {{ .Values.standalone.service.type }}
+  ports:
+  - name: http
+    port: {{ .Values.standalone.ports.http }}
+  {{- if not (.Values.tls.enabled) }}
+  - name: "{{ .Values.tcpPrefix }}pulsar"
+    port: {{ .Values.standalone.ports.pulsar }}
+  {{- end }}
+  {{- if .Values.tls.enabled }}
+  - name: https
+    port: {{ .Values.standalone.ports.https }}
+  - name: "{{ .Values.tlsPrefix }}pulsarssl"
+    port: {{ .Values.standalone.ports.pulsarssl }}
+  {{- end }}
+  selector:
+    {{- include "pulsar.matchLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: "{{ template "pulsar.standalone.service.headless" . }}"
+  namespace: {{ template "pulsar.namespace" . }}
+  labels:
+    {{- include "pulsar.standardLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+spec:
+  clusterIP: None
+  publishNotReadyAddresses: true
+  ports:
+  - name: http
+    port: {{ .Values.standalone.ports.http }}
+  - name: "{{ .Values.tcpPrefix }}pulsar"
+    port: {{ .Values.standalone.ports.pulsar }}
+  {{- if .Values.tls.enabled }}
+  - name: https
+    port: {{ .Values.standalone.ports.https }}
+  - name: "{{ .Values.tlsPrefix }}pulsarssl"
+    port: {{ .Values.standalone.ports.pulsarssl }}
+  {{- end }}
+  selector:
+    {{- include "pulsar.matchLabels" . | nindent 4 }}
+    component: {{ .Values.standalone.component }}
+{{- end }}
diff --git a/charts/pulsar/templates/tls-certs-internal.yaml 
b/charts/pulsar/templates/tls-certs-internal.yaml
index 08fd2b3..409e808 100644
--- a/charts/pulsar/templates/tls-certs-internal.yaml
+++ b/charts/pulsar/templates/tls-certs-internal.yaml
@@ -27,9 +27,16 @@
 {{- end }}
 
 {{- if or .Values.tls.broker.enabled (or .Values.tls.bookie.enabled 
.Values.tls.zookeeper.enabled) }}
+{{- if not .Values.standalone.enabled }}
 {{ include "pulsar.cert.template" (dict "root" . "componentConfig" 
.Values.broker "tlsConfig" .Values.tls.broker) }}
 ---
 {{- end }}
+{{- end }}
+
+{{- if .Values.standalone.enabled }}
+{{ include "pulsar.cert.template" (dict "root" . "componentConfig" 
.Values.standalone "tlsConfig" .Values.tls.standalone) }}
+---
+{{- end }}
 
 {{- if or .Values.tls.bookie.enabled .Values.tls.zookeeper.enabled }}
 {{ include "pulsar.cert.template" (dict "root" . "componentConfig" 
.Values.bookkeeper "tlsConfig" .Values.tls.bookie) }}
diff --git a/charts/pulsar/templates/toolset-configmap.yaml 
b/charts/pulsar/templates/toolset-configmap.yaml
index 16f12c6..1a787c1 100644
--- a/charts/pulsar/templates/toolset-configmap.yaml
+++ b/charts/pulsar/templates/toolset-configmap.yaml
@@ -29,22 +29,34 @@ metadata:
 data:
   BOOKIE_LOG_APPENDER: "RollingFile"
   {{- include "pulsar.bookkeeper.config.common" . | nindent 2 }}
-  {{- if not .Values.toolset.useProxy }}
+  {{- if .Values.standalone.enabled }}
+  # talk to standalone
+  {{- if .Values.tls.enabled }}
+  webServiceUrl: "https://{{ template "pulsar.standalone.service" . }}:{{ 
.Values.standalone.ports.https }}/"
+  brokerServiceUrl: "pulsar+ssl://{{ template "pulsar.standalone.service" . 
}}:{{ .Values.standalone.ports.pulsarssl }}/"
+  useTls: "true"
+  tlsAllowInsecureConnection: "false"
+  tlsTrustCertsFilePath: {{ ternary "/pulsar/certs/cacerts/ca-combined.pem" 
"/pulsar/certs/ca/ca.crt" .Values.tls.toolset.cacerts.enabled | quote }}
+  tlsEnableHostnameVerification: "false"
+  {{- else }}
+  webServiceUrl: "http://{{ template "pulsar.standalone.service" . }}:{{ 
.Values.standalone.ports.http }}/"
+  brokerServiceUrl: "pulsar://{{ template "pulsar.standalone.service" . }}:{{ 
.Values.standalone.ports.pulsar }}/"
+  {{- end }}
+  {{- else if not .Values.toolset.useProxy }}
   # talk to broker
   {{- if and .Values.tls.enabled .Values.tls.broker.enabled }}
   webServiceUrl: "https://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.https }}/"
   brokerServiceUrl: "pulsar+ssl://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.pulsarssl }}/"
   useTls: "true"
   tlsAllowInsecureConnection: "false"
-  tlsTrustCertsFilePath: {{ ternary "/pulsar/certs/cacerts/ca-combined.pem" 
"/pulsar/certs/ca/ca.crt" .Values.tls.toolset.cacerts.enabled | quote }}
+  tlsTrustCertsFilePath: "{{ ternary "/pulsar/certs/cacerts/ca-combined.pem" 
"/pulsar/certs/ca/ca.crt" .Values.tls.toolset.cacerts.enabled }}"
   tlsEnableHostnameVerification: "false"
   {{- end }}
   {{- if not (and .Values.tls.enabled .Values.tls.broker.enabled) }}
   webServiceUrl: "http://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.http }}/"
   brokerServiceUrl: "pulsar://{{ template "pulsar.fullname" . }}-{{ 
.Values.broker.component }}:{{ .Values.broker.ports.pulsar }}/"
   {{- end }}
-  {{- end }}
-  {{- if .Values.toolset.useProxy }}
+  {{- else if .Values.toolset.useProxy }}
   # talk to proxy
   {{- if and .Values.tls.enabled .Values.tls.proxy.enabled }}
   webServiceUrl: "https://{{ template "pulsar.fullname" . }}-{{ 
.Values.proxy.component }}:{{ .Values.proxy.ports.https }}/"
diff --git a/charts/pulsar/templates/zookeeper-configmap.yaml 
b/charts/pulsar/templates/zookeeper-configmap.yaml
index cbb1ab6..91ec4c8 100755
--- a/charts/pulsar/templates/zookeeper-configmap.yaml
+++ b/charts/pulsar/templates/zookeeper-configmap.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-headless-service.yaml 
b/charts/pulsar/templates/zookeeper-headless-service.yaml
index 8c99fa6..4279788 100644
--- a/charts/pulsar/templates/zookeeper-headless-service.yaml
+++ b/charts/pulsar/templates/zookeeper-headless-service.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-pdb.yaml 
b/charts/pulsar/templates/zookeeper-pdb.yaml
index 70f88f9..38cb52c 100644
--- a/charts/pulsar/templates/zookeeper-pdb.yaml
+++ b/charts/pulsar/templates/zookeeper-pdb.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 {{- if .Values.zookeeper.pdb.usePolicy }}
 # pdb version detection
 {{- if semverCompare "<1.21-0" .Capabilities.KubeVersion.Version }}
diff --git a/charts/pulsar/templates/zookeeper-podmonitor.yaml 
b/charts/pulsar/templates/zookeeper-podmonitor.yaml
index 0a3e869..7161f31 100644
--- a/charts/pulsar/templates/zookeeper-podmonitor.yaml
+++ b/charts/pulsar/templates/zookeeper-podmonitor.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper PodMonitor only when 
`$.Values.zookeeper.podMonitor.enabled` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 {{- if $.Values.zookeeper.podMonitor.enabled }}
 {{- include "pulsar.podMonitor" (list . "zookeeper" (printf "component: %s" 
.Values.zookeeper.component)) }}
 {{- end }}
diff --git a/charts/pulsar/templates/zookeeper-service-account.yaml 
b/charts/pulsar/templates/zookeeper-service-account.yaml
index e347677..f7ff9e7 100644
--- a/charts/pulsar/templates/zookeeper-service-account.yaml
+++ b/charts/pulsar/templates/zookeeper-service-account.yaml
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-service.yaml 
b/charts/pulsar/templates/zookeeper-service.yaml
index da3a02e..8e10e3b 100644
--- a/charts/pulsar/templates/zookeeper-service.yaml
+++ b/charts/pulsar/templates/zookeeper-service.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-statefulset-upgrade.yaml 
b/charts/pulsar/templates/zookeeper-statefulset-upgrade.yaml
index 51b114e..fa6ed66 100644
--- a/charts/pulsar/templates/zookeeper-statefulset-upgrade.yaml
+++ b/charts/pulsar/templates/zookeeper-statefulset-upgrade.yaml
@@ -21,7 +21,7 @@
 # this pre-upgrade hook job will be created to clean up the old zookeeper 
statefulset if the existing statefulset is created 
 # by a chart older than 4.6.0, which has a different headless service name and 
will cause issue if not deleted before
 # the new statefulset is created.
-{{- if and .Values.components.zookeeper 
.Values.zookeeper.statefulsetUpgrade.enabled }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) 
.Values.zookeeper.statefulsetUpgrade.enabled }}
 apiVersion: v1
 kind: ServiceAccount
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-statefulset.yaml 
b/charts/pulsar/templates/zookeeper-statefulset.yaml
index 53eff6f..9450bd3 100755
--- a/charts/pulsar/templates/zookeeper-statefulset.yaml
+++ b/charts/pulsar/templates/zookeeper-statefulset.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
diff --git a/charts/pulsar/templates/zookeeper-storageclass.yaml 
b/charts/pulsar/templates/zookeeper-storageclass.yaml
index ff2af9f..ba80027 100644
--- a/charts/pulsar/templates/zookeeper-storageclass.yaml
+++ b/charts/pulsar/templates/zookeeper-storageclass.yaml
@@ -18,7 +18,7 @@
 #
 
 # deploy zookeeper only when `components.zookeeper` is true
-{{- if .Values.components.zookeeper }}
+{{- if and .Values.components.zookeeper (not .Values.standalone.enabled) }}
 {{- if and (and .Values.persistence .Values.volumes.persistence) 
.Values.zookeeper.volumes.persistence }}
 
 # define the storage class for data directory
diff --git a/charts/pulsar/values.yaml b/charts/pulsar/values.yaml
index 11494fe..40d4e75 100755
--- a/charts/pulsar/values.yaml
+++ b/charts/pulsar/values.yaml
@@ -219,6 +219,13 @@ images:
     repository: oxia/oxia
     tag: 0.16.0
     pullPolicy:
+  standalone:
+    # uses defaultPulsarImageRepository when unspecified
+    repository:
+    # uses defaultPulsarImageTag when unspecified
+    tag:
+    # uses defaultPullPolicy when unspecified
+    pullPolicy:
   kubectl:
     repository: alpine/k8s
     tag: 1.32.12
@@ -336,6 +343,11 @@ tls:
         #     - ca.crt
     # Annotations to apply to the secrets generated by the cert-manager 
certificate
     # secretAnnotations: {}
+  # settings for standalone TLS cert (used when tls.enabled is true and 
standalone.enabled is true)
+  standalone:
+    cert_name: tls-standalone
+    # Annotations to apply to the secrets generated by the cert-manager 
certificate
+    # secretAnnotations: {}
   # TLS setting for function runtime instance
   function_instance:
     # controls the use of TLS for function runtime connections towards brokers
@@ -1141,6 +1153,84 @@ pulsar_metadata:
 # Can be used to run extra commands in the initialization jobs e.g. to quit 
istio sidecars etc.
 extraInitCommand: ""
 
+## Pulsar: Standalone mode
+## templates/standalone-deployment.yaml
+##
+## Runs all Pulsar components (ZooKeeper, BookKeeper, Broker) in a single 
process.
+## When enabled, distributed components (zookeeper, bookkeeper, broker, 
autorecovery) are
+## automatically suppressed and a single standalone Deployment is deployed 
instead.
+## Always runs at replicas: 1 with strategy: Recreate — standalone has no
+## sharding or peer model, so multiple replicas would contend for the same
+## ClusterIP and RWO volume.
+standalone:
+  enabled: false
+  component: standalone
+  initContainers: []
+  podMonitor:
+    enabled: true
+    interval: 60s
+    scrapeTimeout: 60s
+  restartPodsOnConfigMapChange: false
+  ports:
+    http: 8080
+    https: 8443
+    pulsar: 6650
+    pulsarssl: 6651
+  # nodeSelector:
+    # cloud.google.com/gke-nodepool: default-pool
+  probe:
+    liveness:
+      enabled: true
+      failureThreshold: 10
+      initialDelaySeconds: 30
+      periodSeconds: 10
+      timeoutSeconds: 5
+    readiness:
+      enabled: true
+      failureThreshold: 10
+      initialDelaySeconds: 30
+      periodSeconds: 10
+      timeoutSeconds: 5
+    startup:
+      enabled: true
+      failureThreshold: 30
+      initialDelaySeconds: 30
+      periodSeconds: 10
+      timeoutSeconds: 5
+  affinity:
+    anti_affinity: false
+    anti_affinity_topology_key: kubernetes.io/hostname
+    type: preferredDuringSchedulingIgnoredDuringExecution
+  appAnnotations: {}
+  annotations: {}
+  tolerations: []
+  gracePeriod: 30
+  resources:
+    requests:
+      memory: 1Gi
+      cpu: 0.5
+  extraVolumes: []
+  extraVolumeMounts: []
+  extraEnvs: []
+  service:
+    annotations: {}
+    type: ClusterIP
+  service_account:
+    annotations: {}
+  additionalCommand: ""
+  volumes:
+    data:
+      name: data
+      size: 10Gi
+      # storageClassName:
+      selector: {}
+  configData:
+    PULSAR_MEM: >
+      -Xms256m -Xmx512m -XX:MaxDirectMemorySize=512m
+    PULSAR_GC: >
+      -XX:+UseG1GC
+      -XX:MaxGCPauseMillis=10
+
 ## Pulsar: Broker cluster
 ## templates/broker-statefulset.yaml
 ##
diff --git a/charts/pulsar/templates/autorecovery-configmap.yaml 
b/examples/values-standalone.yaml
similarity index 57%
copy from charts/pulsar/templates/autorecovery-configmap.yaml
copy to examples/values-standalone.yaml
index 18395c2..584496a 100644
--- a/charts/pulsar/templates/autorecovery-configmap.yaml
+++ b/examples/values-standalone.yaml
@@ -17,17 +17,20 @@
 # under the License.
 #
 
-{{- if .Values.components.autorecovery }}
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: "{{ template "pulsar.fullname" . }}-{{ .Values.autorecovery.component 
}}"
-  namespace: {{ template "pulsar.namespace" . }}
-  labels:
-    {{- include "pulsar.standardLabels" . | nindent 4 }}
-    component: {{ .Values.autorecovery.component }}
-data:
-  # common config
-  {{- include "pulsar.bookkeeper.config.common" . | nindent 2 }}
-{{ toYaml .Values.autorecovery.configData | indent 2 }}
-{{- end }}
+# Standalone mode: runs all Pulsar components in a single process.
+# Suitable for development, testing, and small deployments.
+# Distributed components (zookeeper, bookkeeper, broker, autorecovery) are
+# automatically suppressed when standalone is enabled.
+
+standalone:
+  enabled: true
+
+# Disable anti-affinity (standalone is a single instance)
+affinity:
+  anti_affinity: false
+
+# Pulsar Proxy in front of standalone is a valid topology (TLS termination
+# at the edge, stable ingress endpoint, not exposing the standalone
+# listeners directly). Uncomment to enable:
+# components:
+#   proxy: true
diff --git a/hack/kind-cluster-build.sh b/hack/kind-cluster-build.sh
index 5dc8648..180b388 100755
--- a/hack/kind-cluster-build.sh
+++ b/hack/kind-cluster-build.sh
@@ -18,6 +18,8 @@
 # under the License.
 #
 
+set -euo pipefail
+
 PULSAR_CHART_HOME=$(unset CDPATH && cd "$(dirname "${BASH_SOURCE[0]}")/.." && 
pwd)
 cd "${PULSAR_CHART_HOME}" || exit
 
@@ -86,7 +88,7 @@ clusterName=${clusterName:-pulsar-dev}
 nodeNum=${nodeNum:-6}
 # k8sVersion must be compatible with the used kind version
 # see https://github.com/kubernetes-sigs/kind/releases/tag/v0.22.0 for the 
list of supported k8s versions for kind 0.22.0
-k8sVersion=${k8sVersion:-v1.23.17@sha256:14d0a9a892b943866d7e6be119a06871291c517d279aedb816a4b4bc0ec0a5b3}
+k8sVersion=${k8sVersion:-v1.25.16@sha256:6110314339b3b44d10da7d27881849a87e092124afab5956f2e10ecdb463b025}
 volumeNum=${volumeNum:-9}
 
 echo "clusterName: ${clusterName}"
@@ -150,8 +152,7 @@ EOF
     done
 done
 
-matchedCluster=$(kind get clusters | grep "${clusterName}")
-if [[ "${matchedCluster}" == "${clusterName}" ]]; then
+if kind get clusters | grep -qx "${clusterName}"; then
     echo "Kind cluster ${clusterName} already exists"
     kind delete cluster --name="${clusterName}"
 fi

Reply via email to