Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
LiteLLMInstance.spec.workload.managed: false(#29) — attaches an instance to a LiteLLM proxy the operator did not deploy, so the entity CRDs (LiteLLMTeam,LiteLLMVirtualKey,LiteLLMBudget,LiteLLMModel, ...) can be used against a proxy owned by a Helm chart, a GitOps pipeline or an internal platform. Workload reconciliation and auto-rollback are skipped entirely: nothing is created, and no existing object is adopted or mutated, replacing the RBAC-denial workaround that left the instance permanentlyDegradedwhile it worked.spec.database.migrationis ignored: an externally-managed proxy owns its own schema, and the migration Job would otherwise runprisma migrate deployfromspec.image.tag(defaulting tolatest) against a database the operator does not own. Health probing, config sync, and finalizer-based cleanup of upstream entities are unaffected.LiteLLMInstance.spec.workload.endpoint— sets the admin API URL explicitly instead of derivinghttp(s)://<metadata.name>.<namespace>.svc:<service.port>, so an unmanaged instance no longer has to be named after a Service it does not own, and can attach to a proxy in another namespace or outside the cluster. Valid only whenmanagedisfalse; rejected by a CEL rule otherwise. Readiness for an unmanaged instance now comes from the admin API answering at that endpoint rather than from a name-matched Deployment, which also makes a StatefulSet-backed or off-cluster proxy work; theReadycondition reportsProxyReachable/ProxyNotReachableand noPodsHealthycondition is set, because the operator owns no pods.status.versionis left empty instead of echoingspec.image.tag, which describes nothing the operator deployed; it is filled in only when the proxy discloseslitellm_versionon/health/readiness(LiteLLM gates that behind its ownallow_public_health_readiness_details).WorkloadUnmanagedstatus condition onLiteLLMInstance— set whenworkload.managedisfalseand the CR still carries spec sections (sso,scim,jwtAuth,oauth2Auth,rbac,security,logging,adminUI,caching,routerSettings,callbacks,passThroughEndpoints,secretManager, or an installed license) that only ever take effect through a managed workload's ConfigMap. Names just the sections actually set, so a clean unmanaged CR stays quiet.masterKey.secretRefis now required whenworkload.managedisfalse, enforced by a CEL rule —autoGenerate's Secret is only ever created by the managed-workload reconcile path, so it previously dangled silently.LiteLLMGuardrailnow reportsReady=False(InstanceUnmanaged) instead ofValidatedwhen itsinstanceRefpoints at an unmanaged instance, since guardrail config renders through that same ConfigMap.
[0.25.0] - 2026-09-10
Changed
- The operator now logs at
infoin JSON instead of DEBUG in console encoding.cmd/main.gocarried the kubebuilder scaffold'szap.Options{Development: true}, which puts the whole operator at DEBUG level. In a real deployment that made a singleV(1)line —license Secret found, emitted once per instance reconcile — about 98% of all log output, roughly 100 lines an hour per instance, burying anything worth reading. Nothing was configurable: the chart'sargswere a hardcoded list with no log-level knob, so the verbosity could not be turned down without patching the Deployment.
Added
logging.level,logging.developmentandlogging.encoderHelm values, mapping to the manager's--zap-log-level,--zap-develand--zap-encoderflags, so verbosity is configurable at install time.logging.levelacceptsdebug/info/erroror a positive integer for V-level logging, where1restores the per-reconcile diagnostics that used to be on by default.- An
extraArgsHelm value for manager flags the chart does not model (e.g.--zap-stacktrace-level=panic), so a new upstream flag no longer requires a chart release to be reachable.
[0.24.0] - 2026-09-10
Added
LiteLLMInstance.spec.podScheduling(#33, #35) — node placement for operator-managed Pods, applied to both the proxy Deployment and the database migration Job so the migration cannot be scheduled somewhere the proxy is not allowed to run. Because a Job's Pod template is immutable, placement is part of the migration Job's name seed: changing it creates a fresh Job instead of leaving a pending one pinned to an unschedulable template.nodeSelector— node labels required by LiteLLM Pods.tolerations— taints LiteLLM Pods may tolerate.affinity— node affinity and pod (anti-)affinity, for rulesnodeSelectorcannot express: set-based matches, soft/preferred placement (e.g. prefer spot capacity, fall back to on-demand), and spreading replicas across nodes or zones.
spec.topologySpreadConstraintsremains where it is, at the top level of the spec and applied to the proxy Deployment only — spread constraints are meaningless for the single-Pod migration Job.
Fixed
An empty
podSchedulingno longer re-runs the database migration. The migration Job's name seed was gated on thepodSchedulingpointer rather than on its contents, so an explicitpodScheduling: {}or emptynodeSelector/tolerations— which a Helm template or GitOps overlay renders easily — hashed differently from omitting the field, despite meaning the same thing. That produced a new Job name and re-ranprisma migrate deploy. Omitted, empty and unset placement now all yield the same Job name. Relatedly, a failure to encode placement for the seed can no longer silently drop it (which would have restored the very Job-name collision the seed guards against).A rotated Secret now reaches the running proxy.
secretKeyRefenv vars and Secret volumes are resolved when the container starts and are never refreshed, so updating a Secret's value left the rendered Deployment byte-identical — the reference had not changed — and nothing rolled. The pod kept serving the old credential indefinitely. The instance controller now stamps a digest of the contents of every Secret the pod consumes onto the pod template (litellm.palena.ai/secret-hash), so a rotation changes the template and rolls the Deployment. The motivating case isLITELLM_LICENSE, where an updated enterprise licence silently never took effect, but the same applied toLITELLM_MASTER_KEY,LITELLM_SALT_KEY,DATABASE_URL, the SSO client secret and every other Secret-backed value. A Secret that does not exist yet is folded into the digest as absent, so installing a licence after the instance was created also rolls it. Only referenced keys are hashed, so an unrelated key changing in a shared Secret does not cause a pointless restart, andimagePullSecretsare excluded because the kubelet reads them at pull time rather than projecting them into the container. A transient read failure surfaces as a reconcile error rather than a digest that looks like the Secret vanished.Note: the first reconcile after upgrading stamps the digest for the first time, which rolls each managed gateway once.
Security
- Bumped
google.golang.org/grpcto v1.83.2 (from v1.82.1), clearing four alerts that shared this one root cause: CVE-2026-84304 (HIGH, Trivy — fragmented HTTP/2 DATA frames stored as separaterecvMsgentries let an unauthenticated remote attacker exhaust process memory via concurrent multiplexed streams), plus Dependabot GHSA-2v4p-qf9q-27wj (HIGH), GHSA-vp52-pcj8-j9qc (HIGH) and GHSA-qc2q-p7wx-3px3 (MEDIUM). - Unbroke the OpenSSF Scorecard workflow by bumping
ossf/scorecard-actionfrom v2.4.0 to v2.4.4. v2.4.0 is the last release whose container is pulled fromgcr.io/openssf/scorecard-action, and that registry began refusing pulls ("This API method requires billing to be enabled" against OpenSSF's own GCP project) — the same commit that passed on 2026-08-31 failed on 2026-09-07 with no change on our side. v2.4.1 moved the image toghcr.io/ossf/scorecard-action, so any version at or above it is unaffected. - The release workflow's licence scan installs from a hash-pinned lock (
.github/scancode-requirements.txt, generated from.github/scancode-requirements.in), resolving the OSSF ScorecardPinnedDependenciesIDfinding on.github/workflows/release.yml.pip install --require-hashespins the whole transitive tree instead of scancode alone — the gap that broke the v0.23.0 release, where an unrelatedclick8.4.2 -> 8.5.0 bump changed the outcome of two otherwise identical runs. Python is now pinned withsetup-pythonto match the interpreter the lock was resolved against.
[0.23.0] - 2026-08-30
Added
LiteLLMInstance.spec.litellmSettings.checkProviderEndpoint(#31) — writeslitellm_settings.check_provider_endpoint, so a wildcard deployment is listed inGET /v1/modelsas the upstream's live model names instead of the literal pattern (or LiteLLM's static built-in list for that provider). Verified end to end on a Kind cluster: it applies to models registered through the API byLiteLLMModel, not only to models declared in the config file. Two caveats are documented: it is a global switch that makes every wildcard deployment call its upstream, and it only works for providers in LiteLLM'smodels_by_providermap — a wildcard backed bylitellm_proxy/*never expands, so front another LiteLLM proxy withopenai/*and its/v1base URL instead.- Raw settings passthrough via
spec.generalSettings.extraandspec.litellmSettings.extra— arbitrary JSON keys written straight intogeneral_settings/litellm_settings, as an escape hatch for LiteLLM settings the CRD does not model yet, so a new upstream setting no longer needs an operator release to be usable. Anything the operator derives from the rest of the spec always wins: a colliding key is ignored and reported as anExtraSettingsIgnoredwarning event naming it, rather than silently dropped. - Annotations and labels on the generated virtual key Secret via
LiteLLMVirtualKey.spec.keySecretTemplate(#27) — lets third-party controllers act on the Secret the operator mints, without an external mutating admission policy. The motivating case is kubernetes-reflector mirroring the key into the namespace where the consuming application runs. Entries are merged, so annotations and labels added by other controllers are preserved; the operator's own labels win on conflict.
Fixed
- The generated key Secret is now reconciled on every pass, not only when the key is minted. Previously the Secret was written once inside the mint branch, which had three consequences: metadata edits never reached an existing Secret, a hand-created Secret was filled in without an
ownerReference(so it was never garbage collected with the CR, leaking the credential), and deleting the Secret left theLiteLLMVirtualKeypermanently unusable because nothing recreated it. - A deleted key Secret now rotates the key instead of failing silently. LiteLLM stores only a hash, so the key material cannot be recovered; the operator deletes the orphaned key, mints a replacement, and emits a
KeySecretMissingwarning event. Absence is confirmed with an uncached read so a stale informer cache cannot destroy a working key. spec.keySecretNameandspec.keySecretTemplateare excluded from the virtual key sync hash, so editing Kubernetes-side Secret plumbing no longer triggers a pointlessPOST /key/update.keySecretNameis also now explicitly pinned bystatus.keySecretRefonce a key has been minted, matching the behaviour it already had, so that editing it cannot orphan the only copy of the key material.
Security
- Bumped the Go toolchain to 1.26.7 (from 1.25.12) in
go.modand theDockerfilebuilder image, clearing 7 HIGH stdlib CVEs reported by the weekly Trivy scan of the published image: CVE-2026-56862 (crypto/tls), CVE-2026-56860 (net/url), CVE-2026-56859 (encoding/xml), CVE-2026-56858 (html/template), CVE-2026-56853 (net/http), CVE-2026-39821 (vendoredgolang.org/x/net/idna) and CVE-2026-33818 (encoding/asn1). All are fixed in Go 1.25.13+/1.26.6+; the 1.25 series is end-of-life now that Go 1.27 has shipped, so the operator moves to the supported 1.26 branch rather than taking another 1.25 patch.
[0.22.0] - 2026-08-01
Added
- ServiceAccount annotations and labels on
LiteLLMInstance.spec.serviceAccount(#22) — enables integrations such as AWS IAM Roles for Service Accounts (IRSA) without an external mutation policy. Configured metadata is reconciled while unrelated labels and annotations added by Kubernetes or other controllers are preserved. - Deployment annotations on
LiteLLMInstance.spec.deployment.annotations(#24) — sets annotations on the operator-managed Deployment's metadata (e.g.reloader.stakater.com/auto: "true"for Stakater Reloader) without an external mutating admission policy. Declared annotations are applied and their values reconciled, while annotations added by other controllers are preserved. Distinct from pod-template annotations.
Fixed
- Redundant Deployment/Service updates that triggered a reconcile loop (#21) — the instance controller now skips updates when the managed spec is already converged and preserves cloud-managed Service fields (
ClusterIP,NodePort, and annotations such as GKE'scloud.google.com/neg-status) instead of replacing the whole object. - External pod-template annotations dropped on reconcile (#25, closes #23) — annotations added by other tools (e.g.
kubectl.kubernetes.io/restartedAtfromkubectl rollout restart) are now carried into the desired pod template, so they survive reconciliation and no longer read as drift.
Security
- Bumped
golang.org/x/textto v0.39.0 (from v0.37.0) to clear GO-2026-5970 / CVE-2026-56852 — an infinite loop innorm.Iteron malformed input, flagged by both govulncheck and the Trivy image scan. - Bumped
github.com/google/cel-goto v0.29.0 (from v0.23.2) to clear GHSA-gcjh-h69q-9w9g. Both are transitive dependencies (viak8s.io/apiserver); no operator code changes.
[0.21.0] - 2026-07-26
Added
LiteLLMInstance.spec.observability.serviceMonitornow supports endpoint authorization (#20) — when the proxy is protected with a master key, the/metricsendpoint rejects unauthenticated scrapes ("Malformed API Key passed in. Ensure Key has a Bearer prefix."), so the operator-managedServiceMonitorcould not scrape an authenticated deployment. AddedserviceMonitor.authorization(mirroring the Prometheus Operator's nativeendpoints[].authorizationblock) withtype(defaultBearer) andcredentials(a Secret name/key). Credentials default to the instance's master key Secret when omitted — the referenced Secret for a user-supplied master key, or the auto-generated<instance>-master-keySecret otherwise — so the common case needs onlyauthorization: {}. Also addedserviceMonitor.pathto override the scrape path (default/metrics).
Security
- Bumped
google.golang.org/grpcto v1.82.1 (from v1.80.0) to clear GHSA-hrxh-6v49-42gf — xDS RBAC and HTTP/2 vulnerabilities in gRPC-Go. Transitive dependency (viak8s.io/apiserver/ controller-runtime metrics filters); no operator code changes. - Bumped the docs site's
viteto 6.4.3 (from 6.4.2) to clear CVE-2026-53571 (server.fs.denybypass on Windows alternate paths) and CVE-2026-53632 (NTLMv2 hash disclosure via UNC path handling), and pinnedpostcssto ≥8.5.18 to clear GHSA-6g55-p6wh-862q / GHSA-r28c-9q8g-f849 (arbitrary.mapfile disclosure via attacker-controlledsourceMappingURL). Build-time dependencies of the VitePress docs only — not shipped in the operator image.
[0.20.0] - 2026-07-21
Added
- Pod-level faults now surface on
LiteLLMInstance.status— when the gateway is unready, the cause is visible from the CR instead of requiring a dig through pod logs. Adds aPodsHealthycondition (independent ofReady, which keeps meaning "at least one replica is serving") and.status.unhealthyPods[]withname,phase,reason, a truncatedmessage, andrestartCount. RecognizesCrashLoopBackOff(reporting the previous container termination — e.g.last exit code 3— rather than the useless back-off timing),ImagePullBackOff,OOMKilled,CreateContainerConfigError(a missing Secret/ConfigMap key), andUnschedulable. Kept cheap and quiet: pods are listed only while not ready (no extra reads when healthy, no pod watch), the list is capped at 3 with 512-char messages to bound the object, and a Warning event fires only when the cause changes so a crash loop can't spam the event stream. Requires new podget;list;watchRBAC.
Security
- Bumped Go to 1.25.12 (go.mod and the
golang:1.25builder image digest) to clear a HIGH standard-library advisory flagged by the image scanner — CVE-2026-39822 (fixed in Go 1.25.12). No code changes; the operator binary is rebuilt against the patched stdlib.
[0.19.1] - 2026-07-14
Fixed
- Enterprise-gated resources gave up permanently instead of retrying, leaving them stuck until manually recreated. When the proxy rejected a
LiteLLMVirtualKey,LiteLLMModel,LiteLLMOrganization,LiteLLMUser,LiteLLMCustomer, orLiteLLMTeamwith a403"enterprise" error (no LiteLLM Enterprise license installed yet), the controller set anEnterpriseLicenseRequiredcondition and returned without a requeue. Because the license is typically installed after the resource is first requested (e.g. a platform activates SSO + virtual keys once the license Secret lands), and re-applying an unchanged spec produces no reconcile event, the resource never synced on its own — the only fix was deleting and recreating the CR. Most visibly, an auto-wiredLiteLLMVirtualKeynever minted its key Secret, so a consumer waiting on it (e.g. a ChatUI) stayed stuck. All six reconcilers now requeue (every 2m) on the enterprise gate, so the resource syncs itself once the license is present.
[0.19.0] - 2026-07-10
Fixed
LiteLLMCredentialreconcile crash-looped the proxy withUniqueViolationErroroncredential_name. The controller decided create-vs-update fromstatus.configured, a subresource that can be lost independently of the sync-hash annotation. When it wasfalsebut the credential already existed in LiteLLM's DB, the operator re-POSTed/credentials→ Prisma unique-constraint violation (returned as HTTP 500, which the old 400-only fallback missed) → the proxy logged the error on every reconcile. Now the decision keys off the sync-hash annotation (the reliable "already pushed" marker), and a create that conflicts (400/409, or a 500 carrying the unique-constraint message) falls back to an idempotentPATCH. Existing stuck credentials self-heal on the next reconcile.
Added
- JWT token-validation env vars on
LiteLLMInstance.spec.jwtAuth:publicKeyUrl,issuer,audience→JWT_PUBLIC_KEY_URL/JWT_ISSUER/JWT_AUDIENCEon the proxy Deployment. LiteLLM reads these from the environment (not fromlitellm_jwtauth), so withoutpublicKeyUrlthe proxy can't fetch signing keys and JWT auth fails even whenlitellm_jwtauthis fully configured.
[0.18.1] - 2026-07-10
Fixed
general_settings.role_permissionscrashed the proxy on startup.spec.rbac.rolePermissionswas rendered as a map keyed by role name, but LiteLLM expects a list of objects each carrying its ownrolefield (it iterates the list and callsRoleBasedPermissions(**item)). Iterating a map yielded string keys →TypeError: argument after ** must be a mapping, not str→ CrashLoopBackOff (exit 3). It now renders a sorted list of{role, models, routes}. Affects every release since the field was introduced; anyone usingspec.rbac.rolePermissionsshould upgrade.
[0.18.0] - 2026-07-10
Added
- Full
litellm_jwtauthsurface onLiteLLMInstance.spec.jwtAuth(enterprise) — the operator now exposes essentially every JWT-auth knob LiteLLM supports:- User/team/org:
userIdUpsert,teamIdUpsert,teamIdDefault,teamAllowedRoutes,teamAliasJwtField,orgAliasJwtField,teamClaimFallback,userAllowedEmailDomain,rolesJwtField. - Enforcement:
enforceTeamBasedModelAccess,enforceScopeBasedAccess,syncUserRoleAndTeams. - Structured mappings:
scopeMappings({scope, models, routes}),roleMappings({role, internalRole}),jwtLitellmRoleMap({jwtRole, litellmRole}), androutingOverrides({iss, clientId, scope, aud, path}). - OIDC UserInfo:
oidcUserinfoEnabled/oidcUserinfoEndpoint/oidcUserinfoCacheTtl. - Virtual-key mapping:
virtualKeyClaimField/virtualKeyMappingCacheTtl. - Custom validation:
customValidate(dotted path to a handler baked into the proxy image).
- User/team/org:
Fixed
spec.jwtAuth.scopeModelMappingswas a no-op and now works. It renderedgeneral_settings.litellm_jwtauth.scope_model_mappings, a key LiteLLM does not read. It's now rendered intoscope_mappings(the list shape LiteLLM actually consumes), merged with the new structuredscopeMappings. ExistingscopeModelMappingsvalues start taking effect on upgrade.
[0.17.0] - 2026-07-09
Added
- JWT role-based model access (RBAC) on
LiteLLMInstance.spec.jwtAuth(enterprise). Four new fields wire a JWT roles claim into LiteLLM'srole_permissions:userRolesJwtField(user_roles_jwt_field— the claim holding a list of roles, distinct from the existing single-valueuserRoleJwtField),userAllowedRoles(user_allowed_roles— roles that map tointernal_user),enforceRbac(litellm_jwtauth.enforce_rbac— the JWT-level RBAC toggle, distinct from thegeneral_settings-levelspec.rbac.enforceRbac), andobjectIdJwtField(object_id_jwt_field). Combined withspec.rbac.rolePermissions, this reproduces the standard LiteLLM JWT-RBAC config (a JWT role →internal_user→ per-role model allow-list) — previously therole_permissionstarget was expressible but the JWT plumbing to feed it was not.
[0.16.2] - 2026-07-07
Fixed
- The operator no longer live-probes models when health checks are disabled — a real cost fix. On every
LiteLLMModelreconcile (~5 min) the operator calledGET /healthto populatestatus.health. Whenbackground_health_checksis off (the LiteLLM default),GET /healthlive-probes every deployment — a billable completion per model — so settingspec.generalSettings.backgroundHealthChecks: falsedisabled LiteLLM's background loop but the operator kept triggering paid probes. The operator now polls/healthonly whenbackgroundHealthChecks: true(when it returns cached results); otherwise it skips the probe and marksstatus.health: unknown. Leaving the field unset also means no probing.
Added
LiteLLMInstance.spec.generalSettings.healthCheckSkipDisabledModels→ rendershealth_check_skip_disabled_background_models, so models withmodelInfo.healthCheck.disableBackgroundHealthCheck: trueare also excluded from the on-demandGET /healthprobe (not just the background loop).
[0.16.1] - 2026-07-04
Fixed
- OLM CSV descriptors for the new
LiteLLMBudgetCRD. The v0.16.0 bundle listedLiteLLMBudgetas a bare stub (nodisplayName,resources, orspecDescriptors), which failed the operator-sdk scorecardolm-crds-have-resourcesandolm-spec-descriptorstests. Added the complete owned-CRD entry to the CSV base. - CI lint cleanups (pre-existing, unrelated to features): extracted the repeated
"ca.crt"Secret key into acaCrtKeyconstant (goconst) and dropped the always-"gw"parameter from thetlsInstancetest helper (unparam). - Hardened CI syft install in
e2e.ymlandrelease.yml: download the pinned installer to a file before executing instead of pipingcurlinto a shell (clears the Semgrepgha-curl-pipe-shellfinding).
[0.16.0] - 2026-07-04
Changed
LiteLLMGuardrail.spec.paramsandLiteLLMCredential.spec.paramsnow accept arbitrary JSON values (map[string]JSONinstead ofmap[string]string). This unblocks structured provider config that couldn't be expressed as strings — e.g. Presidio'spii_entities_config: {CREDIT_CARD: MASK}, per-entity numeric thresholds, or a Vertex AI service-account JSON object as a credential value. Existing string-valued params remain valid (a string is valid JSON), so this is backward-compatible at the YAML level.
Added
- New
LiteLLMBudgetCRD — declares a reusable budget / rate-limit tier via the LiteLLM REST API (/budget/new,/budget/update,/budget/info,/budget/delete). Fields:budgetId(defaults to the object name),maxBudget,softBudget,budgetDuration,tpmLimit,rpmLimit,maxParallelRequests,modelMaxBudget. Other resources reference it bybudget_id(e.g.LiteLLMVirtualKey.spec.budgetId), which previously had no CRD to create the tier — you had to make budgets out-of-band. Short namelb; finalizer-backed delete;status.currentSpendrefreshed from/budget/info. - Cross-CRD access & budget controls on
LiteLLMOrganization,LiteLLMTeam,LiteLLMUser, andLiteLLMVirtualKey(shared, consistent field names):objectPermission— grant access to MCP servers, vector stores, agents, and access groups (object_permission; theLiteLLMCustomertype was generalized into a sharedObjectPermission).softBudget— alert threshold below the hard budget (soft_budget).modelRpmLimit/modelTpmLimit— per-model rate-limit maps (model_rpm_limit/model_tpm_limit).
- Incident-response
blockedflag onLiteLLMTeam,LiteLLMUser, andLiteLLMVirtualKey(spec.blocked: true) — disables all requests from a team/user/key without deleting it. Forwarded to/team/{new,update},/user/{new,update}, and/key/{generate,update}. (LiteLLMCustomeralready had this.) LiteLLMTeam.spec.teamMemberBudget— per-member max budget (team_member_budget), distinct from the team-widemaxBudgetMonthly; reset cadence follows the team'sbudgetDuration.LiteLLMModelprovider/routing additions:litellmParams.dropParams(silently drop params a provider rejects, e.g.temperatureon reasoning models) and first-class Vertex AI auth —vertexProject,vertexLocation, andvertexCredentialsSecretRef(reads the GCP service-account JSON from a Secret and sends it asvertex_credentials, never logged).LiteLLMInstancerouter settings:routerSettings.streamTimeout,routerSettings.enablePreCallChecks(context-window/region pre-filtering),routerSettings.modelGroupAlias; and theroutingStrategyenum now includesusage-based-routing-v2andcost-based-routing.LiteLLMInstancegeneral settings: alerting delivery —generalSettings.alerting,alertingThreshold,alertToWebhookUrl(previously onlyalertTypeswas exposed, so alerts never fired); plusbackgroundHealthChecks,healthCheckInterval,healthCheckDetails.LiteLLMInstance.spec.litellmSettings(new block) withjsonLogsfor structured JSON logging — the home for futurelitellm_settingsknobs.
Fixed
- Three CRD fields that were silently ignored are now rendered. They existed in the API (and passed schema validation) but no controller/resource code ever emitted them, so setting them did nothing:
spec.generalSettings.customKeyGenerate→general_settings.custom_key_generatespec.routerSettings.retryAfter→router_settings.retry_afterspec.database.connectionPool.maxConnections→general_settings.database_connection_pool_limit
Added
LiteLLMModelnow exposes LiteLLM's full per-model config surface. Previously onlymodel, auth,rpm/tpm/timeout/streamTimeout/maxRetriesand threemodelInfofields (maxTokens, cost per token) were configurable — LiteLLM accepts far more.spec.modelInfo.healthCheck— per-model health-check controls, includingdisableBackgroundHealthCheckto turn off background liveness probing for a single deployment (e.g. providers that bill/rate-limit probes, or models that reject the probe request shape). AlsotimeoutSeconds,maxTokens/maxTokensReasoning/maxTokensNonReasoning,reasoningEffort,voice, andmodel(probe target for wildcard routes). These are flattened ontomodel_infoin the/model/newpayload to match LiteLLM's wire format.spec.litellmParamsadditions:weightandorder(weighted / priority load-balancing across deployments in a model group),maxInputTokens(context-window-aware routing/fallbacks), default request paramstemperature/topP/maxTokens/seed, and provider knobsorganization,awsRegionName,extraHeaders.spec.modelInfoadditions:mode(declare model type so the correct health check / routing runs),baseModel(required for accurate Azure cost tracking),tierandregionName(tier-/region-based routing),accessGroupsandsupportedEnvironments(access control / visibility),useInPassThrough, and cost fieldsinputCostPerPixel,inputCostPerSecond,cacheReadInputTokenCost,cacheCreationInputTokenCost.
[0.15.0] - 2026-06-29
Added
- The operator's own admin-API calls now use verified HTTPS when the proxy serves TLS. When
spec.tls.serverCertSecretRefis set,status.endpointbecomeshttps://…— the single URL every controller (model/team/user/key/org/customer/credential sync, config sync) and the health probes use — so all operator→proxy traffic is TLS automatically. The operator validates the serving certificate: it trusts the CA from the server-cert Secret'sca.crt(cert-manager populates it for CA/intermediate issuers), falling back tospec.tls.trustedCASecretRef; a publicly-trusted cert needs neither. Verification is never disabled. AValidationFailedwarning event is emitted if HTTPS is served but no CA is resolvable (operator→proxy calls would then fail verification). This completes the serve-TLS feature from v0.14.0, which switched the listener and probe scheme to HTTPS but left the operator's client onhttp://— it would have lost the ability to reconcile against a TLS-serving gateway.
[0.14.0] - 2026-06-28
Added
LiteLLMInstance.spec.tls— TLS for the proxy pod. Three secret-ref-based knobs, all accepting cert-manager's standardtls.crt/tls.key/ca.crtkeys:serverCertSecretRef— mounts akubernetes.io/tlsSecret and setsSSL_KEYFILE_PATH+SSL_CERTFILE_PATH(together), so uvicorn serves HTTPS on port 4000. When set, the container health probes switch to theHTTPSscheme and the internalPROXY_BASE_URLbecomeshttps://— clients (and any Ingress/Route/HTTPRoute in front) must usehttps://.trustedCASecretRef({ name, key (default ca.crt) }) — mounts a CA bundle and setsSSL_CERT_FILEso outbound provider calls and logging callbacks (e.g. Langfuse) trust a custom CA. This is the documented LiteLLM/httpx knob (SSL_CERT_FILE, notREQUESTS_CA_BUNDLE); being process-level, it also covers the callback HTTP clients (historic Langfuse gap BerriAI/litellm#7046).clientCertSecretRef— mounts a TLS Secret and setsSSL_CERTIFICATEfor outbound mTLS.
LiteLLMInstance.spec.database.tls— PostgreSQL TLS.caSecretRefandclientCertSecretRefmount the Postgres CA bundle (/etc/litellm/db-tls/ca/<key>) and, for mTLS, client cert/key (/etc/litellm/db-tls/client/) on both the proxy Deployment and the migration Job. BecauseDATABASE_URLis sourced from a Secret the operator does not rebuild — and Prisma's native connector reads SSL params from the connection string (usingsslmode=require&sslaccept=strict, not libpq'sverify-fullorPG*env vars) — the operator only mounts the material; the caller adds?sslmode=require&sslaccept=strict&sslrootcert=…(andsslcert/sslkey) to the URL. (NosslModefield is exposed because it cannot be enforced on a Secret-sourced URL and would be misleading.)LiteLLMInstance.spec.extraVolumes/spec.extraVolumeMounts— generic escape hatch to attach arbitrary volumes/mounts to the proxy pod (the env escape hatch,spec.extraEnvVars/spec.extraEnvFrom, already existed).- Validation: the instance controller verifies each referenced TLS Secret exists and that cert Secrets carry both
tls.crtandtls.key, emittingSecretNotFound/SecretKeyMissingwarning events (non-fatal).
Security
- Bumped Go to 1.25.11 (go.mod and the
golang:1.25builder image digest) to clear two HIGH standard-library advisories flagged by the image scanner — CVE-2026-27145 and CVE-2026-42504 (both fixed in Go 1.25.11). No code changes; the operator binary is rebuilt against the patched stdlib.
[0.13.0] - 2026-06-27
Fixed
- Azure models authenticated via
LiteLLMModel.spec.litellmParams.credentialRefnow work at request time. A model that referenced aLiteLLMCredentialwas registered with onlylitellm_credential_nameand no inlineapi_base, on the assumption LiteLLM would resolve the named credential into the DB-stored model at request time. It does not, reliably: LiteLLM hydrates a DB model'slitellm_credential_nameat router-load time, and on a cold start that runs before DB-backed credentials (those created via the/credentialsAPI) are loaded into the in-memorycredential_list— so the lookup returns nothing and the Azure deployment boots with no endpoint, failing every request withAzureException APIError - Must provide one of the base_url or azure_endpoint arguments. It would self-heal on the 30s router resync and break again on the next pod restart. The model controller now resolves the credential'sapi_base/api_version/api_keyand writes them inline on the/model/new//model/updatepayload (LiteLLM only fills fields left unset, so inline always wins and is restart-safe), while still sendinglitellm_credential_namefor Admin UI association and best-effort merge of any extra credential params. The DB-backed credential is unchanged, so secret-rotation-without-restart is preserved — and the model's sync hash now covers the resolved auth material, so a rotated Secret or edited credential re-pushes the model even thoughmodel.Specis unchanged.
Added
LiteLLMModel.spec.litellmParams.apiVersion— sets the providerapi_versioninline (required by most Azure OpenAI / Azure AI Foundry deployments). Previously the API version was only reachable throughcredentialRef; inline Azure models had no way to set it. WhencredentialRefis set, the credential'sapiVersiontakes precedence.
Changed
- Switching a model between
credentialRefand inline auth now deletes and recreates the LiteLLM model./model/updateis a merge and cannot clear provider fields (api_base/api_key/litellm_credential_name) left by the previous auth mode — so flipping modes used to leave stale values on the DB model. The controller now tracks the last-pushed auth mode (litellm.palena.ai/auth-modeannotation) and, on a flip, deletes and re-creates the model for a clean record. In-mode field changes still use/model/update.
[0.12.2] - 2026-06-14
Fixed
- Gateway now actually loads its generated config —
litellm_settings(success/failure callbacks, etc.) are no longer silently dropped. The operator mounted the renderedproxy_server_config.yamlConfigMap at/app/configand setLITELLM_CONFIG_DIR=/app/config, but current LiteLLM does not honorLITELLM_CONFIG_DIR— it only reads its config from theCONFIG_FILE_PATHenv var (or a--configarg). So the file was mounted but never read: completions returned 200 (models still loaded from the DB viaSTORE_MODEL_IN_DB=True, masking the bug) whilesuccess_callback/failure_callbackand every otherlitellm_settingsentry were ignored — no Langfuse traces (or any callback) were ever emitted. The Deployment now setsCONFIG_FILE_PATH=/app/config/proxy_server_config.yaml, derived from the same constants as the volumeMount path and the ConfigMap data key so they cannot drift, and litellm logsInitialized Success Callbacks - [...]on startup. The deadLITELLM_CONFIG_DIRenv var was removed.
Changed
- Default gateway image tag is now
latestinstead ofmain-latest.main-latesttracks LiteLLM'smainbranch (unreleased nightly builds, labeledorg.opencontainers.image.version=main), which is a poor default for a "production-ready" gateway.ghcr.io/berriai/litellm:latesttracks the most recent tagged release (currently resolves tov1.87.0), so newLiteLLMInstances without an explicitspec.image.tagget a released LiteLLM that satisfies the operator's v1.86+ migration assumptions. Existing CRs are unaffected (the value is already persisted); pinspec.image.tagfor reproducible deployments. Applied consistently to the CRD default, the deployment/migration-Job fallbacks, andstatus.version.
[0.12.1] - 2026-06-03
Fixed
RedisReadycondition is no longer permanentlyFalse(#10). The instance health probe parsed aredisboolean offGET /health/readiness, but LiteLLM has never emitted that field — and as of 1.86.x the public readiness payload was reduced to{"status", "db"}— so the absent field decoded to Go's zero value (false), reporting Redis as disconnected on every reconcile and generating continuous spuriousRedisDisconnectedwarning events despite healthy Redis. Redis health is now probed correctly: when response caching is Redis-backed, the operator callsGET /cache/ping(which actively pings Redis and performs a test write) for a genuine connectivity verdict; when Redis is wired only for router coordination (no Redis-backed cache), LiteLLM exposes no runtime signal, so the condition reportsReady=Truewith reasonRedisConfiguredinstead of falsely claiming disconnection. The phantomReadinessResponse.RedisConnected/CacheHealthfields were removed.- CI: license-header check now passes.
.licenserc.yamldeclared the header text asCopyright [year] bitkaio LLC(no trailing period), but every source file's actual header — stamped fromhack/boilerplate.go.txt— readsCopyright [year] bitkaio LLC.(with a period). skywalking-eyes requires the configured text to match, so the mismatch failed every Go file. The check only runs onpull_requestevents (not pushes tomain), so it had been silently broken since it was introduced. Added the trailing period to the config to match the established convention. - CI: operator scorecard
olm-spec-descriptorsnow passes. TheLiteLLMGuardrailfieldsguardrailClassandunreachableFallback(added in v0.12.0) were missing matchingspecDescriptorsin the ClusterServiceVersion, so the bundle's sample CR exercised fields with no descriptor. Added both descriptors to the CSV base (and regenerated bundle). The scorecard job only runs on pushes tomain, so this surfaced after v0.12.0 merged. - Helm chart no longer pins the operator image to the long-stale
v0.5.0.values.yamlhardcodedimage.tag: "v0.5.0", which overrode the intendedappVersionfallback in the image helper — so every defaulthelm installdeployed the v0.5.0 operator regardless of the chart version installed.image.tagnow defaults to empty and the helper resolves tov<appVersion>(matching thev-prefixed tags published by the release workflow), so a default install tracks the chart's release. An explicit--set image.tag=…is still honored verbatim.
[0.12.0] - 2026-06-02
Fixed
- Database migration Job now actually applies LiteLLM's versioned migrations. The Job command has switched from
prisma db push --schema=/app/schema.prisma --accept-data-loss --skip-generatetoprisma migrate deploy --schema=/app/schema.prisma.db pushsyncs the live DB toschema.prismadirectly and ignores LiteLLM's 38+ versioned migration files inlitellm-proxy-extras/migrations— fine for fresh installs, but on upgrades it left_prisma_migrationsout of sync with reality and could drop columns under--accept-data-loss. The previous behavior was masked pre-LiteLLM v1.86 because the proxy itself ran a schema sync on startup; v1.86's componentization PR (#27557) disabled in-pod schema updates, exposing the issue as failed migrations / missing columns at request time.migrate deployis the same command LiteLLM's own componentized Helm chart uses and works against every gateway image v1.85.x and later (the migrations directory is shipped in the image).
Added
spec.database.migration.useDatabaseImage— opt-in toggle to run LiteLLM's dedicatedghcr.io/berriai/litellm-migrationsmigrations image (introduced in v1.86.0) instead of invoking prisma inside the gateway image. Whentrue, only the database image runs and the operator does not override its Command — the image's entrypoint (python3 /app/run.py) wrapsprisma migrate deploywith P3005 baseline / P3009/P3018 idempotent-error recovery and the v2 migration resolver that avoids schema thrashing during rolling deploys. Recommended for v1.86+ and especially for upgrading from operator versions that previously usedprisma db push— the database image's recovery flow heals the resulting_prisma_migrations/schema drift automatically, where the gateway-image path would raise Prisma P3005 on a non-empty database. Tag defaults tospec.image.tagso the migrations image stays version-aligned with the gateway; gateway pull secrets are reused. Tag availability caveat: as of June 2026 the LiteLLM team only publisheslitellm-migrationstags for release candidates (e.g.v1.87.0-rc.1,v1.88.0-rc.1) — no v1.86.x or v1.87.0 stable tag exists yet. If your gateway tag isn't published, override viadatabaseImage.tagor stay on the gateway-image path (which now runs the sameProxyExtrasDBManager.setup_databaserecovery logic inside the gateway image and works on every LiteLLM v1.85+ tag).spec.database.migration.databaseImage— optional repo/tag/pullPolicy override foruseDatabaseImage(e.g. private registry mirror, pinning to a different version). Only consulted whenuseDatabaseImage: true.LiteLLMGuardrail— HTTP/API guardrails viageneric_guardrail_api. You can now point the proxy at any HTTP service you host (e.g. a container running in your cluster) instead of baking a Python class into the proxy image. Setspec.provider: generic_guardrail_apiandspec.apiBaseto your guardrail Service; LiteLLM POSTs request/response content to{apiBase}/beta/litellm_basic_guardrail_apiand acts on the{action, blocked_reason, texts, images}verdict it returns. Newspec.unreachableFallbackfield (fail_closed/fail_open) controls behavior when the endpoint is unreachable,spec.apiKeySecretRefis sent as a Bearer token, andspec.paramsare forwarded underadditional_provider_specific_params. The controller validates thatapiBaseis set for this provider and thatunreachableFallbackis only used with it. Note: this is a BETA LiteLLM feature — its request/response contract may change.LiteLLMGuardrail—custom_guardrailclass path support. Addedspec.guardrailClass, the dotted Python import path to aCustomGuardrailsubclass (e.g.my_pkg.adapters.MyGuardrail). Previouslyprovider: custom_guardrailemitted the literalguardrail: custom_guardrailintoproxy_server_config.yaml, which LiteLLM cannot resolve, so custom guardrails were unusable end-to-end. The operator now writes the class path aslitellm_params.guardrail, and the controller enforces thatguardrailClassis set iff the provider iscustom_guardrail. The class and its dependencies must be present in the proxy image (custom image viaspec.image).
[0.11.3] - 2026-05-26
Security
- Go toolchain bumped to 1.25.10. The previous Dockerfile pin (
cd05a378…) andgo.moddirective (go 1.25.0) both resolved to Go 1.25.9 in practice, which carries five HIGH stdlib CVEs (CVE-2026-33811, -33814, -39820, -39836, -42499) that broke the weekly Trivy scan against:latestand the govulncheck CI step.go.modis nowgo 1.25.10and the Dockerfile pinsgolang:1.25@sha256:c138bff7…(= Go 1.25.10). All sevenactions/setup-goinvocations in CI / E2E / release / scheduled now usecheck-latest: trueso future Go patch releases flow in via the latest matchinggo 1.25.xwithout requiring ago.modedit. golang.org/x/netbumped 0.52.0 → 0.55.0 to clear GO-2026-4918 (HTTP/2 server-push DoS) — the only third-party finding from govulncheck. Pulled in bygo mod tidy;x/sys,x/term,x/text, andx/toolscame along as transitive bumps.
Fixed
LiteLLMCredentialis now actually honored at request time. The credential controller previously rendered credentials intocredential_listinsideproxy_server_config.yaml. LiteLLM only mergescredential_listentries into models defined in the config file'smodel_list; models registered viaPOST /model/new(which is how the operator registers everyLiteLLMModel) are stored in the DB and do not see config-level credentials. Net effect:LiteLLMModel.spec.litellmParams.credentialRefwas silently a no-op for the entire v0.11.x series, and any provider that requiresapi_base/api_version(notably all Azure OpenAI / Azure AI Foundry models) failed withMust provide one of the base_url or azure_endpoint arguments. The controller now reconciles credentials against LiteLLM's/credentialsAPI (POST/PATCH/DELETE/credentials/{name}), which stores them in the DB encrypted withLITELLM_SALT_KEYand merges them into request-timelitellm_params. The credential is now also visible in the Admin UI's Credentials tab.
Changed
- Kubernetes Secret rotation now propagates to LiteLLM in seconds. The credential controller adds a Secret watch: when the referenced
apiKeySecretRefSecret changes, the controller is enqueued immediately, computes a fresh(spec + secret-value)hash, and pushes aPATCH /credentials/{name}if the hash differs from the one stored in thelitellm.palena.ai/sync-hashannotation. No proxy pod restart is required — DB credentials are looked up on each request. - BREAKING (internal):
BuildConfigMap,BuildDeployment, andGenerateProxyConfigininternal/resourcesno longer take a[]LiteLLMCredentialparameter. TheCREDENTIAL_<name>_API_KEYenv var the operator used to inject on the proxy Deployment (so config-levelos.environ/...references resolved at startup) is no longer emitted, and thecredential_listblock inproxy_server_config.yamlis no longer rendered. This is a no-op for end users — credentials still work the same from the CR side — but anyone reading the rendered ConfigMap directly will notice the section is gone. LiteLLMCredentialcontroller now requires a ReadyLiteLLMInstance. Previously the controller could validate a credential against a not-yet-ready instance (because rendering into a config file did not need the proxy reachable). The new flow needs the proxy/credentialsendpoint, so credentials whose instance is not Ready stayConfigured=falsewithreason=InstanceNotReadyuntil the instance reports ready. Order of CR creation does not matter — once the instance is ready, the Secret-watch + periodic resync push the credential automatically.
Notes
- Migrating an existing v0.11.x cluster: on upgrade, the new controller will register each
LiteLLMCredentialagainst/credentialson its first reconcile. The oldcredential_listblock in the proxyConfigMapis dropped on the next instance reconcile, the proxy rolls once (because the ConfigMap hash changes), and from then on credentials are DB-backed. Any existingLiteLLMModelwithcredentialRefcontinues to sendlitellm_credential_namein its params — that wire format is unchanged; only what's behind the lookup moved from config to DB.
0.11.2 - 2026-05-21
Fixed
- Weekly scheduled scans no longer fail on the published OLM bundle. Two unrelated bugs caused the
Scheduled scansworkflow to fail every Monday and auto-open false-positive Trivy CVE issues. (1) Thelitellm-operator-bundleGHCR package was private, so anonymous Trivy andoperator-sdk bundle validatecalls returnedUNAUTHORIZED; the package has been switched to public visibility to match the operator image. (2)operator-sdk bundle validatewas being passed adocker://…reference, but it shells out todocker pullwhich does not understand that scheme — the prefix has been removed. - OLM bundle CSV — empty
iconblock removed. The CSV declared an icon entry with emptybase64data, which madeoperator-sdk bundle validate --select-optional suite=operatorframeworkfail withcsv.Spec.Icon elements should contain both data and mediatype. The block has been removed from both the kustomize base and the generated bundle manifest; a real icon can be added later when an SVG asset is available. - Helm chart — operator ClusterRole synced with the kustomize source. The hand-maintained Helm
clusterrole.yamlhad drifted significantly fromconfig/rbac/role.yaml(generated from+kubebuilder:rbacmarkers). On v0.11.x the operator pod crashed at startup withfailed to wait for caches to syncbecause list/watch onlitellmcredentialsandlitellmguardrails(added in v0.11.x) was forbidden. Several other features were also silently broken for Helm-installed operators: Gateway APIhttproutes, OpenShiftroutes, Prometheusservicemonitors+prometheusrules, and CloudNativePGscheduledbackupswere all missing from the chart's ClusterRole. The template now mirrors the kustomize ruleset 1:1 (verified by tuple-diff). OLM/kustomize installs were not affected.
Security
- Dockerfile base images pinned by digest.
golang:1.25andgcr.io/distroless/static:nonrootare now pinned by SHA256 digest in addition to their tags, addressing the OpenSSF ScorecardPinned-Dependenciesfinding for the operator container. Renovate continues to manage updates via tag rules. - Go toolchain bumped to 1.25.10 (via base-image digest). Repinned
golang:1.25from the 1.25.9 digest to the 1.25.10 digest. This clears five HIGH stdlib CVEs that Trivy was failing the E2E pipeline on:CVE-2026-33811(cgo DNS resolver long-CNAME parsing),CVE-2026-33814(HTTP/2 SETTINGS infinite loop),CVE-2026-39820(net/mailaddress parsing DoS),CVE-2026-39836(WindowsDial/LookupPortNUL-byte panic), andCVE-2026-42499(consumePhraseDoS). - Trivy bumped 0.69.3 → 0.70.0 across
e2e.yml,release.yml, andscheduled.ymlfor up-to-date scanner behaviour and vulnerability DB compatibility.
0.11.1 - 2026-04-17
Fixed
- Release pipeline — GitHub release upload no longer fails on oversized scancode report.
scancode-toolkitin the release workflow previously scanned the entire Go module cache ($(go env GOMODCACHE), ~5 GB of vendored dependencies), which produced a ~50 MBscancode-report.json. The scan took ~100 minutes and the oversized report causedsoftprops/action-gh-releaseto fail withRequest body length does not match content-length header, leaving releases partially published. The scan is now scoped to the project source tree (.) with ignore patterns for build outputs (bin/,dist/,testbin/,release-artifacts/, tarballs). Dependency license coverage remains fully intact viago-licenses(hard-fail on forbidden/restricted) and thesyftCycloneDX SBOM. - CI lint — removed dead
customSSOPackageNameconstant ininternal/resources/deployment.gothat was left over after the custom SSO handler refactor.golangci-lintwas failing the build on theunusedlinter. - CI drift check — regenerated Helm chart CRDs. The
customSsoHandlerfield indeploy/charts/litellm-operator/crds/litellm.palena.ai_litellminstances.yamlwas still the old plain-string shape instead of the union struct introduced in v0.11.0.make sync-helm-crdsnow produces a clean tree.
0.11.0 - 2026-04-17
Added
- SSO logout redirect —
spec.sso.logoutUrlonLiteLLMInstanceis now wired to thePROXY_LOGOUT_URLenv var on the Deployment. When set, the Admin UI's logout action redirects to the IdP's end-session endpoint so users are signed out of both LiteLLM and the IdP in one click. - SSO custom handler (ConfigMap-backed) —
spec.sso.customSsoHandleris now wired togeneral_settings.custom_ssoand supports two modes:module(dotted Python path to a handler baked into a custom image) orconfigMapRef(operator mounts the handler source from a ConfigMap at/app/custom_sso_handlers/and writes the derived module path —custom_sso_handlers.<stem>.<functionName>). Handlers run inside the LiteLLM pod with the gateway's privileges; ConfigMap changes require a pod rollout to take effect. - SSO default-user team auto-assignment —
spec.sso.defaultUserParams.teamsis now emitted underlitellm_settings.default_internal_user_params.teamsin the generatedproxy_server_config.yaml. Each entry mapsteamId→team_id,role→user_role, and optionalmaxBudgetInTeam→max_budget_in_team. New SSO users are auto-enrolled in the listed teams on first login.
Changed
- BREAKING:
spec.sso.customSsoHandlerchanges shape from a plain string (previously unwired) to a union struct ({module | configMapRef}). Anyone who had set the old string form was not getting any behaviour; on upgrade, migrate the value intosso.customSsoHandler.module. extraEnvVarsnow overrides operator-set env vars by name. Previously, putting a variable likePROXY_BASE_URLinspec.extraEnvVarsresulted in two entries with the same name in the Pod spec (operator value first, user value second). The operator now merges user-supplied env vars over operator-derived ones: the user entry replaces the operator entry in place and no duplicates are emitted. Useful for overridingPROXY_BASE_URLwhen exposing the gateway via Gateway API HTTPRoute, OpenShift Route, or an external load balancer — cases where the operator's ingress-based derivation falls back to in-cluster Service DNS.
0.10.0 - 2026-04-13
Added
Admin UI management — new
spec.adminUIfield onLiteLLMInstanceconfigures the built-in Admin UI.disableddisables the UI entirely via theDISABLE_ADMIN_UIenvironment variable.adminOnlyrestricts UI access to proxy admins viaui_access_mode: "admin_only"ingeneral_settings.storeModelInDBenables dynamic model management from the UI without proxy restart viastore_model_in_db.defaultTeamDisabledprevents personal key creation viadefault_team_disabled.apiDocBaseURL,docsURL, androotRedirectURLcustomize API docs and root redirect behavior via environment variables.logoURLsets a custom logo for the Admin UI viaUI_LOGO_PATH.emailLogoURLandemailSupportContactcustomize email notification branding viaEMAIL_LOGO_URLandEMAIL_SUPPORT_CONTACT.colorThemeConfigMapRefmounts a ConfigMap containingenterprise_colors.jsoninto the container for custom UI color themes (Tremor color palette). All settings are optional — whenadminUIis omitted, LiteLLM uses its defaults.Per-team logging (enterprise) — new
spec.loggingfield onLiteLLMTeamconfigures per-team logging destinations and GDPR-compliant logging disable. Each team can have its own logging callbacks routing to separate provider instances (Langfuse, GCS Bucket, LangSmith, Arize) via the/team/{team_id}/callbackAPI. Callback credentials are read from Kubernetes Secrets and passed securely in the API call. Settinglogging.disabled: truecalls/team/{team_id}/disable_loggingto prevent any request/response data from being logged for that team (GDPR compliance). Status fieldsloggingSyncedandloggingDisabledreflect the current state.Instance-level logging controls — new
spec.loggingfield onLiteLLMInstanceconfigures audit logs, global message logging, and spend log retention.auditLogs.enabledwritesstore_audit_logs: truetogeneral_settingswith optionalretentionDays(enterprise).turnOffMessageLoggingdisables logging of request/response content (only metadata is logged).redactUserApiKeyInforedacts API key information from logs.spendLogRetentionconfiguresmaximum_spend_logs_retention_periodandmaximum_spend_logs_retention_intervalingeneral_settings.Role-based access control (RBAC) — new
spec.rbacfield onLiteLLMInstanceconfigures LiteLLM's RBAC enforcement. Supportsenforce_rbac,admin_only_routes(restrict specific routes to proxy admins),allowed_routes(restrict which routes are accessible at all),default_team_disabled(force team-based keys),key_generation_settings(control which roles can generate team/personal keys, enterprise), androle_permissions(per-role route and model access, enterprise). Settings are written togeneral_settingsin the generatedproxy_server_config.yaml. The instance controller sets anEnterpriseFeaturesConfiguredwarning condition when enterprise RBAC features (key_generation_settings,role_permissions) are enabled without a license Secret. Includes unit tests and documentation.JWT/OAuth2 authentication (enterprise) — new
spec.jwtAuthandspec.oauth2Authfields onLiteLLMInstanceconfigure API-level authentication via JWT tokens and OAuth2 machine-to-machine auth. JWT auth (enable_jwt_auth+litellm_jwtauth) validates tokens from identity providers and maps claims to LiteLLM roles, teams, organizations, and end-users. Supports all claim field mappings (teamIdJwtField,teamIdsJwtField,orgIdJwtField,userIdJwtField,userEmailJwtField,userRoleJwtField,endUserIdJwtField), admin scope configuration (adminJwtScope+adminAllowedRoutes), public key TTL, and scope-to-model mappings for fine-grained model access control. OAuth2 auth (enable_oauth2_auth+oauth2_config_mappings) enables service-to-service authentication by mapping JWT fields to LiteLLM attributes (e.g.,client_id→team_id). Both features complement the existing SSO support (which handles Admin UI login) by enabling API-level authentication. The instance controller sets anEnterpriseFeaturesConfiguredwarning condition when JWT/OAuth2 is enabled without a license Secret. Settings are written togeneral_settingsin the generatedproxy_server_config.yaml. Includes sample CR, unit tests, and documentation.External secret manager integration — new
spec.secretManagerfield onLiteLLMInstanceconfigures LiteLLM's native secret manager support. LiteLLM connects to the external provider at runtime to fetch API keys, store generated virtual keys, and read configuration secrets — without the secrets ever being stored in Kubernetes etcd. Supports 6 providers: AWS Secret Manager (aws_secret_manager), AWS KMS (aws_kms), Azure Key Vault (azure_key_vault, enterprise), Google Secret Manager (google_secret_manager), Google KMS (google_kms), and HashiCorp Vault (hashicorp_vault). ConfigurablehostedKeys(env var names resolved from the secret manager),storeVirtualKeys,prefixForStoredVirtualKeys,accessMode(read_only/write_only/read_and_write), andprimarySecretName. Provider credentials are injected viaenvFromfrom a referenced Kubernetes Secret; AWS IRSA and GKE Workload Identity are supported by omitting the credentials Secret and configuring the workload identity token path. Provider-specific settings (AWS region/role/STS endpoint, Azure vault URI/tenant, Vault address/namespace/auth method/mount/prefix/refresh interval) are injected as environment variables. The instance controller validates the credentials Secret and reportsstatus.secretManager.configuredandstatus.secretManager.provider. This is complementary to the External Secrets Operator approach — both patterns are valid and can coexist.
0.9.0 - 2026-04-11
Added
- Guardrails (content moderation / safety) — new
LiteLLMGuardrailCRD (short namelg) declaratively manages guardrail integrations for content moderation, PII detection, jailbreak prevention, and prompt injection defence. Supports 10 providers (aporia,lakera,bedrock,presidio,guardrails_ai,azure,llm_guard,llamaguard,google_text_moderation,custom_guardrail) and all four execution modes (pre_call,post_call,during_call,logging_only). Each guardrail references an optionalapiKeySecretReffor the provider credentials, an optionalapiBase, adefaultOnflag, free-form providerparams, and additionalenvVars. Guardrails are config-level resources materialized by the instance controller: each entry is rendered into theguardrailssection of the generatedproxy_server_config.yaml, and the API key is injected into the pod via asecretKeyRef-backed env var (GUARDRAIL_{NAME}_API_KEY) referenced from config asos.environ/…. The instance controller watchesLiteLLMGuardrailCRs and rebuilds the ConfigMap + Deployment when they change. Newspec.guardrails []stringfield onLiteLLMVirtualKeyandLiteLLMTeamlets keys and teams opt into specific guardrails (enterprise feature) — the list is forwarded to/key/generate,/key/update,/team/new, and/team/update. Includes a dedicated validation controller that checks the instance reference and the API key Secret, sample CR with three example providers (Aporia, local Presidio, AWS Bedrock), RBAC, Helm chart CRD, and unit tests for config generation, env var collection, instance filtering, and env var sanitization. - Credential management — new
LiteLLMCredentialCRD (short namelc) manages reusable provider credentials declaratively. Each credential references a Kubernetes Secret for the API key and an optionalapiBase/apiVersion/ free-formparamsmap, and is materialized into thecredential_listsection of the generatedproxy_server_config.yaml. The instance controller watchesLiteLLMCredentialCRs and rebuilds the ConfigMap + Deployment whenever credentials change; the credential's API key is injected into the pod via asecretKeyRef-backed env var (CREDENTIAL_{NAME}_API_KEY) and referenced from config asos.environ/…so the secret value never lives in the operator's memory.LiteLLMModelgains an optionalcredentialReffield underlitellm_params— when set, the model is registered withlitellm_credential_nameinstead of inlineapiKeySecretRef/apiBase, letting many models share one credential. The model controller watchesLiteLLMCredentialCRs to re-reconcile dependent models when a credential changes, and the credential controller reports.status.referencedByModelsand validates that the referenced Secret exists. Includes RBAC roles, sample CR, Helm chart CRD, and controller/unit tests. - End-users / Customers — new
LiteLLMCustomerCRD (short namelcust) manages external end-users of the AI gateway (e.g., SaaS application customers). UnlikeLiteLLMUser(internal proxy users), a customer represents an external consumer identified by an application-supplied ID that LiteLLM tracks asuser_id/end_user_id. Supports per-customer budgets (maxBudget+budgetDurationor a sharedbudgetIdtier), TPM/RPM rate limits, allowed model list,defaultModel,allowedModelRegion,blockedflag,objectPermission(MCP servers, vector stores, agents, access groups), and metadata. The controller creates/updates/deletes customers via/customer/new,/customer/update,/customer/deleteand refreshes spend from/customer/infoon each reconcile.LiteLLMInstancegainsspec.defaultCustomerBudget(maxBudgetand/orbudgetId) which is written tolitellm_settings.max_end_user_budget/max_end_user_budget_idin the generated proxy config, applying a platform-wide default budget to every customer. Includes RBAC roles (admin/editor/viewer), sample CR, Helm chart CRD, and controller tests. - Organizations (multi-tenancy) — new
LiteLLMOrganizationCRD (short namelo) adds the top-level tenant in LiteLLM's hierarchy: Organization > Team > User > Key. Supports organization alias, model access lists, budget/duration, TPM/RPM limits, member management (add/remove via API), and metadata. Member sync compares spec with API state and adds/removes as needed.LiteLLMTeamgains an optionalorganizationReffield — when set, the team controller resolves the organization's LiteLLM ID and passesorganization_idto team create/update API calls. Includes RBAC roles (admin/editor/viewer), sample CR, and full controller tests. - Advanced budget controls — new budget and concurrency fields across
LiteLLMInstance,LiteLLMTeam, andLiteLLMVirtualKey.spec.generalSettingsgainsmaxBudget,budgetDuration,globalMaxParallelRequests,budgetReschedulerMinTime, andbudgetReschedulerMaxTimefor global proxy budget and concurrency limits.spec.routerSettingsgainsdefaultMaxParallelRequests(per-model-deployment concurrency cap) andproviderBudgetConfig(per-provider spending limits with time periods).LiteLLMTeamgainsmaxParallelRequestsfor team-level concurrency caps.LiteLLMVirtualKeygainsmodelMaxBudget(per-model spending limits per key, enterprise) andmaxParallelRequests(per-key concurrency cap). All new fields are written toproxy_server_config.yamlor passed to the LiteLLM API as appropriate. - Pass-through endpoints — new
spec.passThroughEndpointsfield onLiteLLMInstanceconfigures arbitrary API pass-through proxying. Each endpoint defines a path, target URL, optional LiteLLM authentication (auth), header forwarding (forwardHeaders), sub-path routing (includeSubpath), allowed HTTP methods, static headers, secret-backed headers (headerSecretswith prefix support), and default query parameters. Secret-backed headers are injected as environment variables viasecretKeyRefand referenced in config asos.environ/PASSTHROUGH_{PATH}_{HEADER}. Settings are written togeneral_settings.pass_through_endpointsin the generatedproxy_server_config.yaml. - IP allowlisting (enterprise) — new
spec.security.ipAllowlistfield onLiteLLMInstanceconfigures application-layer IP address filtering. Supports a list of allowed IPs and CIDR ranges (allowedIPs),useXForwardedForfor correct client IP detection behind load balancers, and optionalmaxRequestSizeMB/maxResponseSizeMBlimits. Settings are written togeneral_settingsin the generatedproxy_server_config.yaml. - Tag-based routing — new
enableTagFilteringandtagFilteringMatchAnyfields onspec.routerSettingsenable routing requests to model deployments by tag. Newtagsfield onLiteLLMModelassigns tags to model deployments (passed vialitellm_params.tags). Newtagsfield onLiteLLMTeamassociates tags with teams so keys generated for team members inherit routing tags. Tags are written into the generatedproxy_server_config.yamlrouter settings and included in model/team API create and update calls. - Response caching — new
spec.cachingfield onLiteLLMInstanceconfigures LiteLLM response caching. Supports 6 cache backends:redis,redis-semantic,s3,gcs,qdrant, andlocal(in-memory). Configurable TTL, namespace isolation, call-type filtering (supportedCallTypes), anddefault_offmode. When cache type isredisand no cache-specific Redis config is provided, the operator reuses the instance's existingspec.redisconnection. Secret references for backend credentials (Redis password, AWS credentials, GCS service account, Qdrant API key) are injected as environment variables. - Fallback chains — new
spec.fallbacksfield onLiteLLMInstanceconfigures model fallback routing. SupportsdefaultFallbacks(global fallback list for any error), per-modelmodelFallbacks,contentPolicyFallbacks(on content policy violations),contextWindowFallbacks(on context window exceeded), andmaxFallbacksto limit chain depth. Fallback entries map a primary model to an ordered list of fallback models in the format LiteLLM expects. - Retry policies — new
retryPolicyandmodelGroupRetryPolicyfields onspec.routerSettingsconfigure per-error-type retry counts globally and per model group (e.g.,TimeoutError: 2,RateLimitError: 3). Retries happen on the same model; fallbacks switch to a different model. - Enterprise license management — convention-based LiteLLM Enterprise license activation. The operator detects a well-known Secret (
{instance-name}-licenseper-instance, orlitellm-licensenamespace-wide fallback) and injects theLITELLM_LICENSEenvironment variable into the Deployment viasecretKeyRef(the license value is never read into operator memory). License status is reflected in.status.license. The controller watches license Secrets and triggers reconciliation on create/update/delete. All downstream controllers (Model, Team, User, VirtualKey) detect enterprise-only API errors (403 + "enterprise") and setReason: EnterpriseLicenseRequiredwithout requeueing.
0.7.0 - 2026-04-06
Added
- Namespace-scoped watching — new
--watch-namespacesflag (comma-separated) restricts the operator to only watch and manage resources in the specified namespaces. Also supports theWATCH_NAMESPACEenvironment variable set automatically by OLM forOwnNamespaceandSingleNamespaceinstall modes. Available in the Helm chart viawatchNamespacesvalue. - OpenShift Route support — new
spec.routefield onLiteLLMInstancecreates an OpenShift Route with configurable host and TLS termination (edge,passthrough,reencrypt). Uses unstructured objects to avoid requiring OpenShift API dependencies. - Gateway API HTTPRoute support — new
spec.gatewayHTTPRoutefield onLiteLLMInstancecreates agateway.networking.k8s.io/v1HTTPRoute with configurable parent Gateway references, hostname, and section name. Compatible with any Gateway API implementation (Istio, Envoy Gateway, Cilium, etc.). - CloudNativePG scheduled backups (Level 3: Full Lifecycle) — new
spec.database.cloudnativepg.backupfield creates a CloudNativePGScheduledBackupCR with configurable schedule, retention, method (snapshot/barmanObjectStore), and suspend control. Backup status is reported in.status.backup. Requires the CloudNativePG operator to be installed. - Auto-rollback on failed upgrades (Level 3: Full Lifecycle) — when
spec.upgrade.autoRollback: trueis set, the operator tracks the last successful deployment revision. If a deployment hitsProgressDeadlineExceeded, the operator automatically triggers a rollback and sets a status condition explaining the action. - ServiceMonitor creation (Level 4: Deep Insights) —
spec.observability.serviceMonitor.enabled: truenow actually creates amonitoring.coreos.com/v1ServiceMonitor targeting the LiteLLM proxy's HTTP port with configurable scrape interval and labels. Gracefully degrades if Prometheus Operator CRDs are not installed. - PrometheusRule with default alerts (Level 4: Deep Insights) —
spec.observability.prometheusRule.enabled: truecreates a PrometheusRule with six built-in alerts:LiteLLMInstanceDown(critical),LiteLLMInstanceDegraded,LiteLLMPodRestarting,LiteLLMPodNotReady,LiteLLMHighMemoryUsage, andLiteLLMHighCPUUsage. Each alert includes severity labels, descriptive annotations, and a runbook. Individual alerts can be disabled viaspec.observability.prometheusRule.disabledAlerts. - Grafana dashboard ConfigMap (Level 4: Deep Insights) —
spec.observability.grafanaDashboard.enabled: truecreates a ConfigMap with thegrafana_dashboard: "1"label for auto-discovery by the Grafana sidecar. The dashboard includes panels for ready/desired replicas, pod restarts, CPU/memory usage, network I/O, and deployment conditions. Configurable folder and labels. - E2E test coverage — comprehensive end-to-end tests for the full CRD lifecycle (LiteLLMInstance, LiteLLMModel, LiteLLMTeam, LiteLLMUser, LiteLLMVirtualKey) running against a real Kind cluster with a LiteLLM proxy.
Changed
- Go version updated from 1.24 to 1.25.
- Dockerfile and devcontainer updated to Go 1.25.
- golangci-lint updated to v2.11.4 for Go 1.25 compatibility.
0.6.0 - 2026-04-04
Added
- OpenShift / non-root support — new
spec.security.runAsNonRootfield onLiteLLMInstanceautomatically switches to the officiallitellm-non_rootimage (ghcr.io/berriai/litellm-non_root), setsRunAsNonRoot: true, and runs asnobody(UID 65534). Compatible with OpenShift restricted SCC and Kubernetes Pod Security Standards. - ServiceAccount reconciliation — the
LiteLLMInstancecontroller now creates a ServiceAccount for the LiteLLM pods, preventingCreateContainerConfigErrorwhen the referenced ServiceAccount did not exist. - Helm chart — new Helm chart in
deploy/charts/litellm-operator/as an alternative to OLM-based installation. Includes ClusterRole, ClusterRoleBinding, ServiceAccount, Deployment, leader election RBAC, and all CRD manifests.
Fixed
- Secondary controllers failed with
masterKey.autoGenerate—resolveInstancenow correctly derives the auto-generated master key Secret name ({instance}-master-key) whenspec.masterKey.secretRefis nil andautoGenerate: trueis set. Previously all secondary controllers (Model, Team, User, VirtualKey) failed with"secret ref is nil". - Model update returned 400 "model not found" — the
/model/updateLiteLLM API endpoint requiresmodel_info.idin the request body. AddedIDfield toModelInfoReqand set it in the model update path. - Duplicate resource creation on first sync — all four secondary controllers (Model, Team, User, VirtualKey) could create duplicate resources in LiteLLM because the status subresource (containing the LiteLLM resource ID) was not persisted before the annotation update triggered a re-queue. Fixed by calling
Status().Update()beforeUpdate()in the create path, and setting the sync hash annotation on create (not just on update). - Default resource limits too low — bumped default container resources from 100m/256Mi requests and 1 CPU/512Mi limits to 250m/512Mi requests and 2 CPU/2Gi limits. LiteLLM's Python runtime and Prisma imports require more memory than the previous defaults.
- Container security context too restrictive — removed hardcoded
RunAsNonRoot: true,ReadOnlyRootFilesystem: true, andRunAsUser: 1001from the default container security context. LiteLLM's default image runs as root and writes to the filesystem at startup. Non-root execution is now opt-in viaspec.security.runAsNonRoot. - Migration Job uses correct image and security context — the database migration Job now respects
spec.security.runAsNonRoot, using the non-root image and correct UID (65534) when enabled. - Migration Job command updated — changed from Python
asyncio.run(main())toprisma db pushwhich is the supported migration approach.
Changed
- Pod security context is now conditional: applied only when
spec.security.runAsNonRoot: true, instead of being hardcoded for all deployments. - Image repository selection is automatic:
ghcr.io/berriai/litellmfor default mode,ghcr.io/berriai/litellm-non_rootwhen non-root is enabled. Users can still override viaspec.image.repository.
0.5.0 - 2026-04-01
Added
- LiteLLMInstance CRD — deploy production-ready LiteLLM proxy instances with Deployment, ConfigMap, Service, Ingress, HPA, PDB, NetworkPolicy, and database migration Job management
- LiteLLMModel CRD — register AI models (OpenAI, Anthropic, Azure, etc.) with the LiteLLM proxy via the REST API
- LiteLLMTeam CRD — create and manage teams with budget limits, rate limits, and three member management modes (
crd,sso,mixed) - LiteLLMUser CRD — manage users for non-SSO environments (service accounts, bot users) with team memberships
- LiteLLMVirtualKey CRD — generate scoped API keys stored in Kubernetes Secrets with owner references for automatic garbage collection
- LiteLLM REST API client with interface-based design and mock implementation for testing
- Finalizer-based cleanup on CRD deletion (calls LiteLLM API delete endpoints)
- Spec hash annotations (
litellm.palena.ai/sync-hash) for change detection to avoid unnecessary API calls - Auto-generation of master key and salt key Secrets
- Database migration Job support (runs before Deployment rollout)
- SSO configuration support (Azure Entra ID, Okta, Google, generic OIDC)
- SCIM v2 provisioning configuration
- Redis configuration for caching and routing
- Callback configuration (Langfuse, etc.)
- Observability support (ServiceMonitor for Prometheus)
- Resource generators for all Kubernetes resources (Deployment, ConfigMap, Service, Ingress, HPA, PDB, NetworkPolicy, migration Job)
- Sample CRs for all 5 CRDs in
config/samples/ - GitHub Actions workflows for tests, linting, and releases
- OLM bundle and catalog manifests for OperatorHub distribution
