Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,7 @@ dmypy.json
cython_debug/

# macOS temporary files
.DS_Store
.DS_Store

# Auto-generated working files
ISSUE_CONTEXT.md
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The chart supports multiple ChromaDB versions from 0.4.3 to 1.0.x with version-s
- **Authentication**: Supported in versions < 1.0.0 (token auth from 0.4.8+, basic auth from 0.4.7+)
- **Logging Configuration**: Custom log levels and config maps (versions < 1.0.0)
- **Cache Management**: LRU cache policy configuration (versions < 1.0.0)
- **CORS Configuration**: List-based CORS origins (wildcard not supported in 1.0.0+)
- **CORS Configuration**: List-based CORS origins (wildcard supported for all versions)
- **Telemetry**: OTEL telemetry support with configurable endpoints

### GitHub Workflows
Expand Down
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ helm install chroma chroma/chromadb --set chromadb.allowReset="true"

| Key | Type | Default | Description |
|-----------------------------------------------------|---------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `chromadb.apiVersion` | string | `1.0.10` (Chart app version) | The ChromaDB version. Supported version `0.4.3` - `1.0.x` |
| `chromadb.apiVersion` | string | `1.5.0` (Chart app version) | The ChromaDB version. Supported version `0.4.3` - `1.x` |
| `chromadb.allowReset` | boolean | `false` | Allows resetting the index (delete all data) |
| `chromadb.isPersistent` | boolean | `true` | A flag to control whether data is persisted |
| `chromadb.persistDirectory` | string | `/data` | The location to store the index data. This configure both chromadb and underlying persistent volume |
| `chromadb.anonymizedTelemetry` | boolean | `false` | The flag to send anonymized stats using posthog. By default this is enabled in the chromadb however for user's privacy we have disabled it so it is opt-in |
| `chromadb.corsAllowOrigins` | list | N/A | The CORS config. Wildcard ["*"] is not supported in version 1.0.0 or later. |
| `chromadb.corsAllowOrigins` | list | `[]` | List of allowed CORS origins. Wildcard `["*"]` is supported. |
| `chromadb.apiImpl` | string | `- "chromadb.api.segment.SegmentAPI"` | The default API impl. It uses SegmentAPI however FastAPI is also available. Note: FastAPI seems to be bugging so we discourage users to use it in releases prior or equal to 0.4.3 Deprecated in since 0.1.23 (will be removed in 0.2.0) |
| `chromadb.serverHost` | string | `0.0.0.0` | The API server host. |
| `chromadb.serverHttpPort` | int | `8000` | The API server port. |
Expand Down Expand Up @@ -103,6 +103,7 @@ helm install chroma chroma/chromadb --set chromadb.allowReset="true"
| `chromadb.telemetry.serviceName` | string | `chroma` | The service name that will show up in traces. |
| `imagePullSecrets` | list | `[]` | List of image pull secrets for the ChromaDB pod (e.g. `[{name: "my-secret"}]`). |
| `global.imagePullSecrets` | list | `[]` | Global image pull secrets shared across all subcharts. Merged with `imagePullSecrets`. |
| `chromadb.extraConfig` | object | `{}` | Extra config keys merged into the v1 server config (>= 1.0.0). Overrides chart-managed keys. See [Extra Config](#extra-config). |
| `commonLabels` | object | `{}` | Additional labels applied to all chart resources (StatefulSet, Service, Ingress, ConfigMaps, Secrets, PVCs, test Jobs). |
| `podLabels` | object | `{}` | Additional labels applied to pods only. Does not affect `matchLabels`. |

Expand Down Expand Up @@ -243,6 +244,29 @@ Then, run `helm dependency update` to install the chart.

When using as a subchart, `global.imagePullSecrets` lets you define pull secrets once in the parent chart and have them propagated to all subcharts (including ChromaDB). Chart-level `imagePullSecrets` only applies to this chart. Both lists are merged, so there is no conflict if the same secret appears in both — though it may appear as a duplicate, Kubernetes handles this gracefully.

## Extra Config

For Chroma >= 1.0.0 (Rust server), `chromadb.extraConfig` lets you inject arbitrary config keys into the server's YAML
config file. This is useful for setting options not yet exposed as dedicated chart values.

```yaml
chromadb:
extraConfig:
circuit_breaker:
requests: 500
sqlite_filename: "custom.db"
open_telemetry:
filters:
- crate_name: "chroma_frontend"
filter_level: "info"
```

> [!WARNING]
> Keys in `extraConfig` override chart-managed keys of the same name. Overriding `port` or
> `listen_address` via `extraConfig` is **not allowed** and will cause template rendering to fail.
> Use `chromadb.serverHttpPort` and `chromadb.serverHost` instead so that the Service, container
> port, and health probes remain in sync.

## References

- Chroma: https://docs.trychroma.com/docs/overview/getting-started
Expand Down
40 changes: 40 additions & 0 deletions charts/chromadb-chart/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,46 @@ Get the chroma api version
{{- end }}
{{- end }}

{{/*
Build the server config dict for the v1-config ConfigMap.
*/}}
{{- define "chromadb.serverConfig" -}}
{{- $port := .Values.chromadb.serverHttpPort | int -}}
{{- if le $port 0 -}}
{{- fail (printf "chromadb.serverHttpPort must be a positive integer, got: %v" .Values.chromadb.serverHttpPort) -}}
{{- end -}}
{{- $maxPayload := .Values.chromadb.maxPayloadSizeBytes | int64 -}}

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation for maxPayloadSizeBytes at line 132 checks if the value is <= 0, but the conversion to int64 happens before the check. If the user provides a non-numeric value, the int64 function may return 0, which would then fail the validation with a potentially confusing error message saying "got: ". Consider adding explicit type checking or updating the error message to clarify that the value must be numeric.

Suggested change
{{- $maxPayload := .Values.chromadb.maxPayloadSizeBytes | int64 -}}
{{- $rawMaxPayload := .Values.chromadb.maxPayloadSizeBytes -}}
{{- if not (regexMatch "^[0-9]+$" (toString $rawMaxPayload)) -}}
{{- fail (printf "chromadb.maxPayloadSizeBytes must be a positive integer (numeric), got non-numeric value: %v" $rawMaxPayload) -}}
{{- end -}}
{{- $maxPayload := $rawMaxPayload | int64 -}}

Copilot uses AI. Check for mistakes.
{{- if le $maxPayload 0 -}}
{{- fail (printf "chromadb.maxPayloadSizeBytes must be a positive integer, got: %v" .Values.chromadb.maxPayloadSizeBytes) -}}
{{- end -}}
{{- $config := dict -}}
{{- $_ := set $config "port" $port -}}
{{- $_ := set $config "listen_address" .Values.chromadb.serverHost -}}
{{- $_ := set $config "max_payload_size_bytes" $maxPayload -}}
{{- $_ := set $config "persist_path" .Values.chromadb.persistDirectory -}}
{{- $_ := set $config "allow_reset" .Values.chromadb.allowReset -}}
{{- if .Values.chromadb.corsAllowOrigins -}}
{{- $_ := set $config "cors_allow_origins" .Values.chromadb.corsAllowOrigins -}}
{{- end -}}
{{- if .Values.chromadb.telemetry.enabled -}}
{{- if not .Values.chromadb.telemetry.endpoint -}}
{{- fail "chromadb.telemetry.endpoint must be set when chromadb.telemetry.enabled is true" -}}
{{- end -}}
{{- $otel := dict "service_name" .Values.chromadb.telemetry.serviceName "endpoint" .Values.chromadb.telemetry.endpoint -}}
{{- $_ := set $config "open_telemetry" $otel -}}
{{- end -}}
{{- with .Values.chromadb.extraConfig -}}

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of mergeOverwrite allows users to inject arbitrary YAML keys into the server configuration through extraConfig. While this is intentional and documented, it could potentially allow users to override security-sensitive configuration options (beyond just port and listen_address which are explicitly blocked). Consider documenting any other keys that should not be overridden for security reasons, or implement additional validation to prevent overriding other sensitive keys like authentication-related settings if they exist in the v1 config format.

Suggested change
{{- with .Values.chromadb.extraConfig -}}
{{- with .Values.chromadb.extraConfig -}}
{{- $disallowedKeys := list "auth" "authentication" "auth_token" "jwt" "security" -}}
{{- range $k := $disallowedKeys -}}
{{- if hasKey . $k -}}
{{- fail (printf "chromadb.extraConfig cannot set '%s' because it is security-sensitive; use chromadb.auth.* Helm values instead" $k) -}}
{{- end -}}
{{- end -}}

Copilot uses AI. Check for mistakes.
{{- $config = mergeOverwrite $config . -}}
{{- end -}}
{{- if ne (get $config "port" | int) ($port) -}}
{{- fail (printf "extraConfig.port (%v) conflicts with chromadb.serverHttpPort (%v) — update serverHttpPort instead" (get $config "port") $.Values.chromadb.serverHttpPort) -}}

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation logic for extraConfig.port has a type conversion issue. Line 154 converts the config value to int, but if a user provides a string value in extraConfig (e.g., "9999"), the comparison ne (get $config "port" | int) ($port) may behave unexpectedly. The left side converts the potentially overridden value to int, while $port is already an int. This works, but the error message on line 155 uses (get $config "port") without the int conversion, which could display a string in the error message while comparing against an int. For consistency, either use (get $config "port" | int) in the error message or ensure type consistency throughout.

Suggested change
{{- fail (printf "extraConfig.port (%v) conflicts with chromadb.serverHttpPort (%v) — update serverHttpPort instead" (get $config "port") $.Values.chromadb.serverHttpPort) -}}
{{- fail (printf "extraConfig.port (%v) conflicts with chromadb.serverHttpPort (%v) — update serverHttpPort instead" (get $config "port" | int) $.Values.chromadb.serverHttpPort) -}}

Copilot uses AI. Check for mistakes.
{{- end -}}
{{- if ne (get $config "listen_address") .Values.chromadb.serverHost -}}
{{- fail (printf "extraConfig.listen_address (%s) conflicts with chromadb.serverHost (%s) — update serverHost instead" (get $config "listen_address") .Values.chromadb.serverHost) -}}
{{- end -}}
{{- $config | toYaml -}}
{{- end -}}

{{/*
Get the Chroma auth token header type
*/}}
Expand Down
17 changes: 1 addition & 16 deletions charts/chromadb-chart/templates/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,4 @@ metadata:
"helm.sh/hook-weight": "-5"
data:
config.yaml: |-
{{- if .Values.chromadb.telemetry.enabled }}
open_telemetry:
service_name: {{ .Values.chromadb.telemetry.serviceName }}
endpoint: {{ .Values.chromadb.telemetry.endpoint }}
{{- end }}
port: {{ .Values.chromadb.serverHttpPort }}
listen_address: {{ .Values.chromadb.serverHost }}
max_payload_size_bytes: {{ .Values.chromadb.maxPayloadSizeBytes | int64 }}
{{- if .Values.chromadb.corsAllowOrigins }}
{{- if and (eq (len .Values.chromadb.corsAllowOrigins) 1) (eq (index .Values.chromadb.corsAllowOrigins 0) "*") }}
{{ fail "cors_allow_origins must not be set to '*' when only one origin is allowed" }}
{{- end }}
cors_allow_origins: {{ .Values.chromadb.corsAllowOrigins | toJson }}
{{- end }}
persist_path: {{ .Values.chromadb.persistDirectory }}
allow_reset: {{ .Values.chromadb.allowReset }}
{{- include "chromadb.serverConfig" . | nindent 4 }}
7 changes: 6 additions & 1 deletion charts/chromadb-chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ chromadb:
chromadb: "DEBUG"
uvicorn: "INFO"
anonymizedTelemetry: false
corsAllowOrigins: [] # as of version 1.0.x * is not allowed
corsAllowOrigins: []
serverHost: "0.0.0.0"
serverHttpPort: 8000
maxPayloadSizeBytes: "41943040"
Expand All @@ -150,4 +150,9 @@ chromadb:
token:
headerType: "Authorization" #possible values Authorization, X-Chroma-Token
value: null # The string used as the token (value). Only used if value not null, otherwise a random string will be generated and used.
# Extra config keys merged into the v1 server config (>= 1.0.0). Overrides chart-managed keys.
extraConfig: {}
# circuit_breaker:
# requests: 500
# sqlite_filename: "chroma.sqlite3"

135 changes: 135 additions & 0 deletions tests/test_v1_config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
set -euo pipefail

CHART_DIR="$(cd "$(dirname "$0")/../charts/chromadb-chart" && pwd)"
PASS=0
FAIL=0

assert_config_key() {
local desc="$1" yaml="$2" key="$3" expected="$4"
actual=$(echo "$yaml" | yq eval ".$key" -)
if [ "$actual" = "$expected" ]; then
echo " PASS: $desc"
PASS=$((PASS+1))
else
echo " FAIL: $desc (expected '$expected', got '$actual')"
FAIL=$((FAIL+1))
fi
}

assert_config_key_missing() {
local desc="$1" yaml="$2" key="$3"
actual=$(echo "$yaml" | yq eval ".$key" -)
if [ "$actual" = "null" ]; then
echo " PASS: $desc"
PASS=$((PASS+1))
else
echo " FAIL: $desc (expected key '$key' to be absent, got '$actual')"
FAIL=$((FAIL+1))
fi
}

get_v1_config() {
local output
output=$(helm template test "$CHART_DIR" "$@" 2>&1) || {
echo "TEMPLATE_ERROR: $output" >&2
return 1
}
echo "$output" | yq eval 'select(.metadata.name == "v1-config") | .data["config.yaml"]' -
}

assert_template_fails() {
local desc="$1"; shift
local output
if output=$(helm template test "$CHART_DIR" "$@" 2>&1); then
echo " FAIL: $desc (expected template to fail, but it succeeded)"
FAIL=$((FAIL+1))
else
echo " PASS: $desc"
PASS=$((PASS+1))
fi
}

# --- Test suite ---

echo "=== v1-config template tests ==="

echo ""
echo "1. Default values"
config=$(get_v1_config)
assert_config_key "port defaults to 8000" "$config" "port" "8000"
assert_config_key "listen_address defaults to 0.0.0.0" "$config" "listen_address" "0.0.0.0"
assert_config_key "max_payload_size_bytes defaults to 41943040" "$config" "max_payload_size_bytes" "41943040"
assert_config_key "persist_path defaults to /data" "$config" "persist_path" "/data"
assert_config_key "allow_reset defaults to false" "$config" "allow_reset" "false"
assert_config_key_missing "cors_allow_origins absent when empty" "$config" "cors_allow_origins"
assert_config_key_missing "open_telemetry absent when disabled" "$config" "open_telemetry"

echo ""
echo "2. CORS wildcard (should work)"
config=$(get_v1_config --set 'chromadb.corsAllowOrigins={*}')
assert_config_key "cors_allow_origins contains wildcard" "$config" "cors_allow_origins[0]" "*"

echo ""
echo "3. CORS multiple origins"
config=$(get_v1_config --set 'chromadb.corsAllowOrigins={https://a.com,https://b.com}')
assert_config_key "first origin" "$config" "cors_allow_origins[0]" "https://a.com"
assert_config_key "second origin" "$config" "cors_allow_origins[1]" "https://b.com"

echo ""
echo "4. OpenTelemetry enabled"
config=$(get_v1_config \
--set 'chromadb.telemetry.enabled=true' \
--set 'chromadb.telemetry.endpoint=http://otel:4317' \
--set 'chromadb.telemetry.serviceName=my-chroma')
assert_config_key "otel endpoint" "$config" "open_telemetry.endpoint" "http://otel:4317"
assert_config_key "otel service_name" "$config" "open_telemetry.service_name" "my-chroma"

echo ""
echo "5. Custom server settings"
config=$(get_v1_config \
--set 'chromadb.serverHttpPort=9000' \
--set 'chromadb.serverHost=127.0.0.1' \
--set 'chromadb.allowReset=true' \
--set 'chromadb.persistDirectory=/mnt/data' \
--set 'chromadb.maxPayloadSizeBytes=52428800')
assert_config_key "custom port" "$config" "port" "9000"
assert_config_key "custom listen_address" "$config" "listen_address" "127.0.0.1"
assert_config_key "allow_reset true" "$config" "allow_reset" "true"
assert_config_key "custom persist_path" "$config" "persist_path" "/mnt/data"
assert_config_key "custom max_payload_size_bytes" "$config" "max_payload_size_bytes" "52428800"

echo ""
echo "6. extraConfig merge"
config=$(get_v1_config \
--set 'chromadb.extraConfig.circuit_breaker.requests=500' \
--set 'chromadb.extraConfig.sqlite_filename=custom.db')
assert_config_key "circuit_breaker.requests from extraConfig" "$config" "circuit_breaker.requests" "500"
assert_config_key "sqlite_filename from extraConfig" "$config" "sqlite_filename" "custom.db"
assert_config_key "port still present after merge" "$config" "port" "8000"

echo ""
echo "7. extraConfig override of port fails"
assert_template_fails "extraConfig.port override rejected" \
--set 'chromadb.extraConfig.port=9999'

echo ""
echo "8. extraConfig override of listen_address fails"
assert_template_fails "extraConfig.listen_address override rejected" \
--set 'chromadb.extraConfig.listen_address=127.0.0.1'

echo ""
echo "9. telemetry enabled without endpoint fails"
assert_template_fails "telemetry.enabled without endpoint rejected" \
--set 'chromadb.telemetry.enabled=true'

echo ""

Copilot AI Feb 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test 10 validates that CORS wildcard renders in v1-config for apiVersion 0.6.3 (pre-1.0), but this test is not meaningful because v1-config is only mounted and used for versions >= 1.0.0 (see statefulset.yaml lines 175-179 and 220-225). While the ConfigMap will render for all versions, it's effectively unused for < 1.0.0. This test gives false confidence that wildcard CORS works for pre-1.0 versions when in reality those versions use a different configuration mechanism. Consider either removing this test or adding a comment explaining that it only validates template rendering, not actual functionality for pre-1.0 versions.

Suggested change
echo ""
echo ""
# NOTE: v1-config is only mounted and used for apiVersion >= 1.0.0.
# For apiVersion < 1.0.0 (e.g. 0.6.3), this test only validates that the
# Helm template renders the expected CORS wildcard in the ConfigMap; it
# does NOT verify actual runtime CORS behavior for pre-1.0.0, which uses
# a different configuration mechanism.

Copilot uses AI. Check for mistakes.
# v1-config is only mounted for >= 1.0.0; this test validates template
# rendering only, not runtime CORS behavior for pre-1.0 versions.
echo "10. CORS wildcard on < 1.0.0 (ConfigMap renders)"
config=$(get_v1_config --set 'chromadb.corsAllowOrigins={*}' --set 'chromadb.apiVersion=0.6.3')
assert_config_key "cors_allow_origins wildcard for pre-1.0" "$config" "cors_allow_origins[0]" "*"

echo ""
echo "--- Results: $PASS passed, $FAIL failed ---"
[ "$FAIL" -eq 0 ] || exit 1
Loading