Architecture
Palena sits between you and the upstream operators (CloudNativePG, LiteLLM Operator, Langfuse Operator), composing them into a single opinionated stack. You write intent; Palena produces the CRs those operators consume, and deploys the rest directly.
Composition graph
Reference graph
The CRDs reference each other in a strict order:
- PalenaModel → PalenaGateway (many-to-many via
gatewayRefs) - PalenaUI → PalenaGateway (one) + PalenaMCPServer (zero or more)
- PalenaObservability → PalenaGateway (one)
- PalenaMCPServer → standalone (no gatewayRef in v1alpha1)
Deletion order
Finalizers ensure child CRs are cleaned up before their parent:
PalenaUI ─┐
PalenaModel ─┤
PalenaObservability ─┼──► PalenaGateway (blocked until children gone)
─┘
PalenaMCPServer ──► independent; owned resources GC'd via ownerRefWhen you delete a PalenaGateway, its finalizer blocks removal until all referencing PalenaModel, PalenaUI, and PalenaObservability CRs have been removed. This prevents orphan LiteLLM models pointing at a deleted instance.
Ownership model
Every Kubernetes resource created by the operator gets an ownerReference pointing at the Palena CR that caused it. This means:
kubectl delete palenagateway productioncascades to the CNPGCluster, RedisStatefulSet,LiteLLMInstance, and NetworkPolicies.kubectl delete palenaui chatcascades to LibreChatDeployment, MongoDBStatefulSet, MeiliSearch, and the auto-generatedlibrechat.yamlConfigMap.
The only resources Palena explicitly does not own are user-managed secrets referenced by SecretKeyRef. Those survive CR deletion — Palena treats them as input, not state.
Labels
Every managed resource is labeled consistently:
labels:
app.kubernetes.io/name: <component> # litellm, librechat, redis, ...
app.kubernetes.io/instance: <cr-name> # production, chat, ...
app.kubernetes.io/component: <role> # gateway, ui, database, mcp
app.kubernetes.io/part-of: palena
app.kubernetes.io/managed-by: palena-operator
operator.palena.ai/gateway: <gateway-name> # only when applicableThe operator.palena.ai/gateway label makes it trivial to list everything tied to one gateway:
kubectl get all -l operator.palena.ai/gateway=productionNaming convention
All resources created by the operator follow:
<cr-name>-palena-<component>For a PalenaGateway named production:
- CNPG Cluster:
production-palena-pg - Redis StatefulSet:
production-palena-redis - LiteLLMInstance:
production-palena-litellm
For a PalenaUI named chat:
- LibreChat Deployment:
chat-palena-librechat - MongoDB StatefulSet:
chat-palena-mongodb - MeiliSearch Deployment:
chat-palena-meilisearch
This convention makes it easy to spot Palena-managed objects and understand which CR produced them.