
September 02, 2026
9 min read
Table of Contents
By Kokil Thapa | Last reviewed: September 2026
Setting accurate CPU and memory requests is one of the hardest operational challenges in Kubernetes, yet getting it wrong leads directly to wasted budget or application crashes. The Vertical Pod Autoscaler (VPA) solves this by analyzing historical usage and automatically adjusting resource requests to match actual demand. Unlike horizontal scaling which adds more replicas, VPA optimizes the size of existing pods to improve cluster efficiency and stability.
If you are managing infrastructure for cost-sensitive projects, understanding the distinction between vertical and horizontal scaling is critical before touching production manifests. While I often discuss horizontal pod autoscaling in Kubernetes for handling traffic spikes, VPA serves a different purpose: long-term efficiency optimization. Before deploying VPA, ensure you have a solid grasp of Kubernetes resource limits and requests, as VPA modifies these values directly and misconfiguration can cause immediate outages.
How does Vertical Pod Autoscaler (VPA) actually work?
VPA is not a single controller but a system of three distinct components that form a feedback loop. Understanding this architecture prevents the most common debugging failures when recommendations seem stale or updates fail to apply.
The Recommender
This component runs as a standalone deployment and polls the Metrics Server every minute. It builds a histogram of resource usage over a configurable window (default 8 days) and calculates target percentiles. The Recommender writes its output to the VPA Custom Resource status field; it never touches running pods directly. If your Metrics Server is misconfigured or missing data, the Recommender will produce empty or zero recommendations.
The Updater
The Updater watches VPA objects and compares current pod specs against recommendations. When running in Auto or Recreate mode, it issues eviction API calls to pods whose requests deviate beyond the configured threshold. This is the component responsible for the disruptive part of VPA: it kills pods so they can be recreated with new values. In production, this means you must have proper PodDisruptionBudgets and readiness probes configured before enabling Auto mode.
The Admission Controller
This is a webhook that intercepts pod creation requests. When a new pod is scheduled (either from a fresh deployment or after an Updater eviction), the Admission Controller rewrites the resource requests in-flight based on the latest VPA recommendation. Without this component, newly created pods would use the original manifest values until the next Updater cycle. This component is mandatory for Initial and Auto modes to function correctly.
What are the VPA update modes and when should you use each?
Choosing the wrong update mode is the most frequent cause of VPA-related incidents. Each mode represents a different trade-off between automation safety and operational overhead.
| Mode | Behavior | Pod Disruption | Best For |
|---|---|---|---|
| Off | Generates recommendations only; never modifies pods | None | Initial assessment, auditing, non-production testing |
| Initial | Sets resources only at pod creation time; no evictions | Only on natural restarts | Stateful sets, databases, jobs where restarts are expensive |
| Auto (Recreate) | Applies recommendations on create AND evicts running pods | Yes — proactive evictions | Stateless web apps, microservices with fast startup times |
Starting safely with Off mode
I always deploy VPA in Off mode first on any workload I haven't previously profiled. This lets you observe recommendations for several days without risk. Check the VPA status object:
kubectl get vpa my-app-vpa -o jsonpath='{.status.recommendation}' If the recommended values look reasonable compared to your monitoring dashboards, you can graduate to Initial or Auto. On legal-tech portals I've maintained, where document processing jobs have highly variable memory profiles, spending two weeks in Off mode prevented us from setting aggressive limits that would have killed batch jobs mid-execution.
When Auto mode requires guardrails
Auto mode is powerful but destructive. The Updater will evict pods whenever usage drifts outside the update policy thresholds. Without a PodDisruptionBudget, it can evict all replicas simultaneously during a recommendation shift. Always pair Auto mode with:
- A PDB allowing at least one unavailable pod
- Readiness probes that prevent traffic routing until the new pod is warm
- Graceful shutdown handlers in your application code
- Minimum replica count of 2+ for any service receiving live traffic
How do you configure Vertical Pod Autoscaler for production workloads?
A minimal VPA manifest is deceptively simple, but production configurations require explicit boundaries to prevent runaway scaling or resource starvation.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: laravel-api-vpa
namespace: production
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: laravel-api
updatePolicy:
updateMode: "Auto"
minReplicas: 2
resourcePolicy:
containerPolicies:
- containerName: php-fpm
minAllowed:
cpu: 100m
memory: 256Mi
maxAllowed:
cpu: 2000m
memory: 4Gi
controlledResources: ["cpu", "memory"]
mode: "Auto"
- containerName: nginx-sidecar
mode: "Off" Setting minAllowed and maxAllowed boundaries
Never deploy VPA without resource boundaries. The Recommender uses percentile-based algorithms that can suggest extremely low values during quiet periods or extremely high values after a transient spike. The minAllowed floor prevents the scheduler from placing pods on nodes too small to run them, while maxAllowed caps costs and prevents a single pod from consuming an entire node. For PHP-FPM containers in my experience, setting minAllowed.memory to at least 256Mi avoids OOM kills during composer autoload initialization, even if median usage suggests 180Mi is sufficient.
Excluding sidecars and init containers
VPA applies recommendations per-container. If your pod includes a logging sidecar, proxy, or cache container, explicitly set their policy to Off or define separate boundaries. Letting VPA auto-scale a Redis sidecar alongside your application container creates coupling that makes debugging nearly impossible when one gets starved.
Why shouldn't you combine VPA and HPA on the same metric?
This is the single most important constraint to internalize: never configure VPA and HPA to scale on the same resource metric for the same workload. Both controllers watch the same Metrics Server data and issue conflicting adjustments, creating an oscillation loop that destabilizes the cluster.
The feedback loop problem
Imagine a deployment where HPA scales on CPU utilization and VPA also manages CPU requests. When load increases, HPA adds replicas. The additional replicas distribute the work, lowering per-pod CPU usage. VPA observes lower usage and reduces requests. With smaller requests, each pod handles less work efficiently, causing utilization to spike again. HPA responds by adding more replicas. This cycle continues indefinitely, causing constant scaling churn and unpredictable performance.
Safe combination patterns
You can safely combine VPA and HPA if they operate on orthogonal dimensions:
- HPA on custom metrics + VPA on CPU/memory: Use HPA to scale replica count based on queue depth, request rate, or business metrics while VPA right-sizes individual pod resources.
- HPA on CPU + VPA on memory only: Set VPA's
controlledResourcesto["memory"]exclusively, letting HPA own CPU-based horizontal scaling. - Separate workloads entirely: Use VPA for batch processors and stateful services; use HPA for stateless web frontends.
For a deeper comparison of when each autoscaler applies, refer to the broader Kubernetes autoscaling guide covering HPA, VPA, and Cluster Autoscaler.
What are the common pitfalls when adopting Vertical Pod Autoscaler?
VPA has been stable since 2020, but its default behaviors still surprise teams migrating from static resource definitions. These are the issues I encounter repeatedly in production audits.
Cold start penalties after eviction
When the Updater evicts a pod, the replacement starts with zero cache, empty JIT compilation buffers, and cold connection pools. For JVM applications, Laravel with opcache, or Node.js services with heavy module initialization, this can mean 30–60 seconds of degraded latency. Configure updatePolicy.controlledValues to RequestsOnly if your application is sensitive to startup latency, and ensure your load balancer respects readiness gates strictly.
OOM kills during recommendation transitions
VPA sets requests, not limits. If your deployment defines hard memory limits and VPA recommends requests near those limits, normal variance pushes the pod into OOM territory. Either remove memory limits entirely when using VPA (letting requests act as the soft boundary) or set limits to at least 2× the VPA maxAllowed value. On a recent e-commerce project, we saw repeated OOM kills because VPA recommended 1.8Gi requests against a 2Gi limit during flash sales; raising the limit to 4Gi resolved it immediately.
Ignoring the 8-day learning window
The Recommender needs approximately 8 days of metrics to produce stable recommendations. During this period, suggestions may be volatile or absent. Do not enable Auto mode on day one. Run in Off mode, monitor the recommendation quality through at least one full business cycle (including weekends or month-end processing if relevant), then graduate gradually.
Stateful workloads and singleton pods
VPA in Auto mode is fundamentally incompatible with single-replica stateful applications. Evicting a database primary, a message broker leader, or a distributed lock holder causes immediate service disruption. For these workloads, use Initial mode exclusively, or better yet, manage resources manually with periodic review. VPA excels at stateless, horizontally replicated services where individual pod lifecycle is disposable.
Making Vertical Pod Autoscaler work in practice
Vertical Pod Autoscaler (VPA) explained properly is not about installing a controller and forgetting it; it is about establishing a continuous right-sizing discipline. Start every adoption in Off mode, define explicit resource boundaries, respect the learning window, and never let VPA and HPA fight over the same metric. When configured correctly, VPA typically recovers 15–30% of over-provisioned cluster capacity within the first month of operation.
If you are evaluating Kubernetes autoscaling for a production system and need hands-on guidance tailored to your specific workload patterns, reach out to discuss your infrastructure requirements. Getting the initial configuration right prevents weeks of troubleshooting later.









