Proxy on-prem Helm chart deployment
Use this Helm chart to deploy Espresso AI's Proxy Service into an existing Kubernetes cluster.
aws— EKS (or self-managed Kubernetes on AWS) fronted by the AWS Load Balancer Controller (ALB).azure— AKS (or self-managed Kubernetes on Azure) fronted by the Application Gateway Ingress Controller (AGIC).generic— any Kubernetes cluster with an ingress controller you manage yourself (NGINX, Traefik, HAProxy, Contour, etc.), or no ingress at all.
The non-ingress parts of the chart (Deployment, Service, HPA, ServiceAccount) are identical across all three providers.
Prerequisites
Common to all deployments:
Kubernetes cluster access (
kubectlcontext points to the target cluster).An existing Kubernetes Secret containing
ESPRESSO_AI_API_KEY.A container image for the proxy that is reachable from the cluster's nodes.
In the Espresso AI dashboard, go to
Proxy Onboardingand:Provide the information needed for image access (see per-provider notes below).
Copy your customer name.
If running on AWS, copy Espresso AI's AWS Account ID. This is needed for the ECR url.
If running on Azure, copy Espresso AI's Azure Account ID. This is needed for the ACR url.
Generate an API key for Espresso API authentication.
Per-provider additions:
AWS — In the dashboard, enter your AWS account ID so Espresso AI can grant ECR access for the Proxy image. If you plan to expose the proxy via ingress, install the AWS Load Balancer Controller and have an ACM certificate ARN ready.
Azure — Enter your Azure Subscription ID so we can grant ACR access for the Proxy image. We will generate a username and password for you to be able to pull the image from our ACR. The chart's
azureingress provider is specifically for the Application Gateway Ingress Controller (AGIC); install AGIC on your AKS cluster if you plan to use it, and have your Application Gateway SSL certificate name ready if terminating TLS at the gateway. If you front AKS with Azure Front Door (AFD) over a different in-cluster ingress controller (e.g., NGINX), use thegenericprovider instead and point your AFD origin at that ingress — the chart'singress.healthcheck.enabledoption emits a hostless/healthcheckrule that AFD probes can hit, regardless of provider.Generic — Contact Espresso AI for image distribution details. Have the ingress controller of your choice installed and the corresponding
ingressClassNameavailable.
Required values
These are required regardless of provider:
image.repositoryimage.tagcustomer(non-empty)env.PROXY_HOST(non-empty) — your base domain (e.g.example.com), not a full hostname or URLapiKeySecret.name(must reference an existing Kubernetes Secret with keyESPRESSO_AI_API_KEY)
If ingress.enabled: true, also set:
ingress.provider(generic,aws, orazure)ingress.host(the hostname clients will use)For
aws:ingress.aws.certificateArn(recommended)For
azure:ingress.azure.appgwSslCertificate(when terminating TLS at the gateway) oringress.tls(when terminating TLS via a Kubernetes Secret)For
generic:ingress.classNameand, if using TLS,ingress.tls
The API key secret key is fixed to ESPRESSO_AI_API_KEY and is not configurable.
How to deploy
Add/update the chart repository:
Install/upgrade with your values.yaml:
Create the API key secret (example):
Example values per provider
AWS (EKS + ALB)
Azure (AKS + Application Gateway Ingress Controller)
If you prefer to terminate TLS in the cluster instead of at the Application Gateway, omit ingress.azure.appgwSslCertificate and use the ingress.tls shorthand (see below).
Azure Front Door (AFD). The example above assumes AGIC is the edge. If your edge is AFD over an in-cluster ingress controller (e.g., NGINX on AKS), use provider: generic with your controller's className instead — the AGIC-specific annotations don't apply. The ingress.healthcheck block shown above is still useful: AFD origin health probes do not forward the application Host header, so the hostless /healthcheck rule lets them succeed against any provider.
Generic Kubernetes (any ingress controller)
This example uses an NGINX ingress controller and TLS via a Kubernetes Secret.
If you don't need an ingress at all (for example, exposing the Service via a LoadBalancer type or accessing it from inside the cluster), set ingress.enabled: false and service.type to whatever fits your environment.
Core configuration
Image
image.repository
Container image repository for the proxy.
Yes
None
image.tag
Container image tag.
Yes
None
image.pullPolicy
Kubernetes image pull policy.
No
IfNotPresent
Environment
customer
Customer identifier used by the proxy.
Yes
None
env.PROXY_HOST
Your base domain (e.g. example.com), injected as PROXY_HOST. Use the registrable base domain only — not a full hostname (proxy.example.com), scheme, or port.
Yes
None
apiUrl
Base API URL used to derive runtime API_URL (<apiUrl>/<customer> unless overridden).
No
https://api.espressocomputing.com:25831
env.API_URL
Optional full override for API_URL.
No
<apiUrl>/<customer>
env.OTEL_EXPORTER_OTLP_ENDPOINT
Optional telemetry OTLP endpoint override. Defaults to http://localhost:4318 when otelCollector.enabled: true (so the proxy hits the in-pod sidecar) and to https://metrics.espressocomputing.com:443 otherwise.
No
See description
env.EXCLUDE_QUERY_TEXT
Whether to exclude query text on requests to Espresso AI's API. Note: enabling this will limit supported functionality.
No
false
extraEnv
Raw list of Kubernetes env entries injected into the proxy container, rendered after the chart-managed env vars. Use it for values the env map can't express — anything that needs valueFrom (fieldRef, secretKeyRef, configMapKeyRef).
No
[]
Any other key/value pairs you put under env are passed through to the container as environment variables, except for the chart-managed names listed above and ESPRESSO_AI_API_KEY.
env vs extraEnv
env vs extraEnvenv is a simple name: value map for literal values, and is the right place for almost everything. extraEnv is the escape hatch for env vars whose value comes from a non-literal source via valueFrom:
extraEnv entries are rendered after the chart's managed env vars. Kubernetes $(VAR) substitution only resolves variables defined earlier in the same container, so a chart-managed env var cannot reference an extraEnv var via $(VAR). If one extraEnv var must reference another via $(VAR), define both in extraEnv with the source listed before the consumer.
Each entry needs its name and value (or valueFrom) on the same list item. Splitting them across two items — - name: X then - value: Y — produces an entry with no name and fails admission with spec.template.spec.containers[N].env[M].name: Required value.
API key secret
apiKeySecret.name
Existing Kubernetes Secret name that stores the proxy API key.
Yes
None
ESPRESSO_AI_API_KEY
Fixed key the chart reads from the Kubernetes Secret.
Yes (in Secret data)
Fixed
Service
service.type
Service type (ClusterIP, NodePort, LoadBalancer).
No
ClusterIP
service.port
Service port.
No
5050
service.annotations
Extra annotations on the Service (e.g., cloud LB hints when using LoadBalancer).
No
{}
Ingress (common fields)
These fields apply to every provider when ingress.enabled: true.
ingress.enabled
Whether to render an Ingress resource.
No
false
ingress.provider
Ingress flavor: generic, aws, or azure.
Yes (if enabled)
generic
ingress.className
ingressClassName on the Ingress. Defaults: alb for aws, azure/application-gateway for azure, empty for generic.
Conditional
None / provider-specific
ingress.host
Hostname rule for the ingress.
No (recommended)
None
ingress.path
Path for the primary rule.
No
/
ingress.pathType
pathType for the primary rule.
No
Prefix
ingress.annotations
Extra annotations merged onto the Ingress (after provider-specific annotations).
No
{}
ingress.tls
Either a standard ingress TLS list, or a shorthand {secretName, hosts}. When hosts is omitted from the shorthand, ingress.host is used.
No
null
ingress.healthcheck.enabled
Render an extra hostless rule (no host) routing /healthcheck to the Service. Useful for probes that don't send the application's Host header.
No
false
ingress.healthcheck.path
Path used by the hostless healthcheck rule.
No
/healthcheck
ingress.healthcheck.pathType
pathType used by the hostless healthcheck rule.
No
Prefix
If ingress.provider is set to anything other than generic, aws, or azure, the chart fails the install with an explanatory error.
Ingress — AWS (ALB)
When ingress.provider: aws, the chart emits AWS Load Balancer Controller annotations from ingress.aws.*. The previous ingress.alb.* block is still read as a backward-compatible alias if both are present, with ingress.aws.* winning on conflicts.
ingress.aws.certificateArn
ACM certificate ARN for HTTPS listener.
Recommended
None
ingress.aws.scheme
ALB scheme (internet-facing or internal).
No
internet-facing
ingress.aws.targetType
ALB target type.
No
ip
ingress.aws.listenPorts
ALB listen ports JSON.
No
[{"HTTPS":443}]
ingress.aws.sslRedirect
ALB SSL redirect port.
No
"443"
ingress.aws.healthcheckPath
ALB target group health check path.
No
/healthcheck
ingress.aws.annotations
Extra ALB-specific annotations.
No
{}
Ingress — Azure (Application Gateway)
When ingress.provider: azure, the chart emits AGIC annotations from ingress.azure.*.
ingress.azure.healthProbePath
Custom health probe path used by Application Gateway. Renders as appgw.ingress.kubernetes.io/health-probe-path.
No
/healthcheck
ingress.azure.sslRedirect
When true (default), renders appgw.ingress.kubernetes.io/ssl-redirect: "true". Set to false to disable.
No
true
ingress.azure.appgwSslCertificate
Name of an SSL certificate already uploaded to the Application Gateway. Renders as appgw.ingress.kubernetes.io/appgw-ssl-certificate. Use this or ingress.tls, not both.
No
None
ingress.azure.annotations
Extra AGIC annotations.
No
{}
Ingress — Generic
When ingress.provider: generic, no cloud-specific annotations are added. Set ingress.className to your controller's class (e.g., nginx, traefik) and pass any controller-specific configuration through ingress.annotations. TLS works the same way as a stock Kubernetes Ingress, including the shorthand:
Autoscaling
autoscaling.enabled
Whether to render the HPA.
No
true
replicaCount
Initial deployment replica count before HPA adjusts.
No
2
autoscaling.minReplicas
Minimum replicas for HPA.
No
2
autoscaling.maxReplicas
Maximum replicas for HPA.
No
10
autoscaling.targetCPUUtilizationPercentage
CPU utilization target for HPA scaling decisions.
No
70
Probes
Both readiness and liveness probes are enabled by default and hit /healthcheck on the container port. They can be tuned or disabled under probes.readiness and probes.liveness.
Telemetry collector
When otelCollector.enabled: true, the chart adds an OpenTelemetry Collector container to the proxy pod and renders a ConfigMap with a pipeline configuration. The proxy emits OTLP/HTTP to localhost:4318, and the sidecar fans the traffic out to two exporters:
The Espresso exporter always sends to
otelCollector.espresso.endpoint(defaulthttps://metrics.espressocomputing.com:443) for every pipeline. This is how Espresso AI receives your proxy's telemetry, and it is independent of the customer endpoint — leaving the customer exporter unset does not affect it.The optional customer exporter sends to
otelCollector.customer.endpointfor the signals listed inotelCollector.customer.signals. Leave the endpoint empty to disable it entirely (the Espresso pipeline still runs).
otelCollector.enabled
Whether to deploy the OTEL Collector sidecar and ConfigMap.
No
true
otelCollector.image.repository
Collector image repository.
No
otel/opentelemetry-collector-contrib
otelCollector.image.tag
Collector image tag.
No
0.152.0
otelCollector.image.pullPolicy
Kubernetes image pull policy for the collector.
No
IfNotPresent
otelCollector.resources
Standard requests / limits block for the collector container.
No
50m / 128Mi requests, 200m / 256Mi limits
otelCollector.env
Raw list of Kubernetes env entries injected into the collector sidecar container. Reference them inside the collector config with the collector's own ${env:NAME} substitution (see below).
No
[]
otelCollector.espresso.endpoint
OTLP endpoint for Espresso AI's backend. The Espresso exporter always sends here, for every pipeline.
No
https://metrics.espressocomputing.com:443
otelCollector.customer.endpoint
OTLP endpoint for the customer's own observability backend. Leave empty to disable the customer exporter entirely (the Espresso pipeline still runs).
No
""
otelCollector.customer.protocol
Wire protocol for the customer exporter. grpc renders otlp/customer; http renders otlphttp/customer.
No
grpc
otelCollector.customer.signals
Signals to mirror to the customer exporter. Any subset of metrics, logs. Signals not listed here go only to Espresso.
No
[metrics, logs]
otelCollector.customer.authSecret.name
Kubernetes Secret holding the value for the customer endpoint's Authorization header. Leave empty for unauthenticated endpoints.
No
""
otelCollector.customer.authSecret.key
Key within customer.authSecret.name whose value is mounted as CUSTOMER_OTLP_AUTH.
No
authorization
otelCollector.customer.tls.insecure
Disable TLS verification on the customer exporter.
No
false
Example — also mirror metrics (not logs) to your own OTLP backend with bearer-token auth:
Where customer-otlp-auth is a Kubernetes Secret in the proxy namespace whose authorization key contains the full header value (e.g. Bearer eyJ...).
Per-node customer collector
If you run a customer collector on each node (a DaemonSet, say) and want the sidecar to ship to the collector on its own node, the customer endpoint has to resolve to a per-node address. Use otelCollector.env to surface the node's IP, then reference it from otelCollector.customer.endpoint:
Espresso keeps receiving telemetry directly through the Espresso exporter; the customer also gets a copy on each node.
Two substitution layers. Note which one resolves NODE_IP here. There are two distinct layers in play:
$(VAR)is expanded by the kubelet in a container'senvandargs.${env:NAME}is expanded by the OpenTelemetry Collector when it reads its config.
The customer endpoint lives in the collector's ConfigMap, which the kubelet never touches, so a per-node value there must use ${env:NAME} — the collector reads NODE_IP from its own process environment. Unlike the proxy extraEnv $(VAR) case above, ordering within otelCollector.env does not matter, because the collector just reads the environment it was given.
For the full list of metrics, spans, and resource attributes the proxy emits — useful for building dashboards and alerts against the customer exporter — see Proxy telemetry reference.
Resources, scheduling, service account
resources, nodeSelector, tolerations, affinity, and serviceAccount follow standard Helm-chart conventions; see values.yaml for the defaults.
Managed secret note
This chart does not create AWS Secrets Manager, Azure Key Vault, or External Secrets resources by itself. For managed secret sync from a cloud secrets manager, provision your sync resource (e.g., External Secrets Operator with an AWS Secrets Manager or Azure Key Vault SecretStore) separately and set:
apiKeySecret.nameto the Kubernetes Secret generated by the sync (key must beESPRESSO_AI_API_KEY).
Validation checklist
Pods are running:
kubectl -n proxy get podsService exists:
kubectl -n proxy get svcHPA exists (when
autoscaling.enabled):kubectl -n proxy get hpaIngress exists (if enabled):
kubectl -n proxy get ingressApp health endpoint responds on
/healthcheck
Last updated