,

HPA vs VPA: When to Use Each, and Can You Use Both?

HPA scales the number of pod replicas; VPA scales the CPU and memory each pod requests. Use HPA for stateless services that scale out, VPA for right-sizing requests, and combine them carefully.

Kunal Das Avatar
HPA vs VPA Featured Image

Key Takeaways

  • HPA adds or removes pod replicas based on load. VPA adjusts CPU and memory requests per pod. These solve two different autoscaling problems.
  • HPA fits stateless workloads: web APIs, microservices, and traffic-variable jobs that benefit from running more instances under load.
  • VPA fits rightsizing use cases: stateful apps, JVM services, and ML inference servers where adding replicas doesn’t address the root problem.
  • Running both safely requires metric separation. HPA on RPS or queue depth with VPA in Off mode works. HPA on CPU while VPA also controls CPU breaks.
  • Cast AI Workload Optimization rightsizes pods in-place via Linux cgroup modification, without evictions, and supports DaemonSets – which VPA’s Updater does not touch.

HPA vs VPA: A Side-by-Side Comparison

Choosing between HPA and VPA starts with understanding what each one scales. HPA responds to traffic by adding or removing pod replicas – a horizontal solution for workloads that run in parallel. VPA fine-tunes individual pod resource requests – a vertical solution for getting per-pod allocation right.

HPAVPA
What it scalesPod replica countCPU and memory requests per pod
How it worksController adjusts Deployment or StatefulSet replicas every 15 secondsRecommender, Updater, and Admission Controller adjust resource specs
Metric typesCPU, memory, custom metrics, external metricsHistorical CPU and memory usage per container
Update modeContinuous near-real-time scalingOff, Initial, Recreate, InPlaceOrRecreate (K8s 1.33+)
Disruption riskLow – adds or removes replicas without touching running podsMedium to high with Recreate; low with InPlaceOrRecreate on K8s 1.33+
Best forStateless workloads, variable traffic patternsRightsizing requests, stateful apps, JVM and ML workloads
LimitationsRequires resource requests set; no native scale-to-zeroNeeds separate install; requires 24-48h of data; Recreate mode causes restarts; no DaemonSet support
Kubernetes requirementBuilt-in (autoscaling/v2 stable since K8s 1.26)External CRD from kubernetes/autoscaler repo

When to Use HPA

HPA is the right tool when your workload scales by running more instances. Stateless services – web APIs, REST microservices, and gRPC servers – fit this model well. The HPA controller checks metrics every 15 seconds and adjusts replica count using this formula:

desiredReplicas = ceil(currentReplicas x currentMetricValue / desiredMetricValue)

HPA ignores metric deviations within 10% of the target (the default tolerance window) to prevent constant scaling on minor fluctuations. Tune this with --horizontal-pod-autoscaler-tolerance on the controller. For HPA to function, metrics-server must run in the cluster and resource requests must be set on the container spec – without requests, HPA has no baseline for CPU percentage calculations.

Here is a production-ready HPA manifest using autoscaling/v2:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-server-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-server
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Pods
        value: 4
        periodSeconds: 60
      - type: Percent
        value: 100
        periodSeconds: 60
      selectPolicy: Max

The scaleDown stabilization window prevents thrashing when utilization briefly dips between bursts. The scaleUp policy caps scale-up at the larger of 4 pods or 100% of current replicas per minute, preventing a traffic spike from overwhelming downstream services.

Queue Consumers and KEDA

Queue consumers fit HPA well, but native HPA cannot scale to zero when a queue empties and only supports CPU and memory metrics natively. KEDA adds 60+ external metric sources – Kafka lag, SQS queue depth, Prometheus queries – and enables scale-to-zero. For event-driven workloads, evaluate KEDA alongside native HPA.

For a deeper look at HPA mechanics and cost implications, see How HPA Can Help You Save on the Cloud.

When to Use VPA

VPA answers a different question: are your pod resource requests actually correct? According to the Cast AI 2026 State of Kubernetes Optimization Report, 69% of requested CPU goes unused across production clusters, average utilization sits at 8%, and memory overprovisioning reaches 79%. Teams set conservative requests at deployment, workloads stabilize, and nobody revisits the numbers.

VPA’s Recommender watches historical usage and generates resource suggestions. Three workload types get the most value from it:

Stateful Workloads, JVM Services, and ML Inference

Stateful workloads that can’t scale horizontally. A single-replica database or cache cannot simply add replicas to handle load. VPA adjusts requests so the pod receives the CPU and memory it actually uses rather than what was guessed at deployment time.

One gotcha: VPA’s Updater skips pods when a Deployment has only 1 replica, because evicting the single pod would cause downtime. To override this default, set --min-replicas=1 on the VPA Updater. Alternatively, use updateMode: Initial for zero-disruption recommendations on startup only.

JVM-based services. Java heap behavior makes resource sizing difficult to estimate upfront. VPA observes real usage and adjusts accordingly, typically stabilizing after 24-48 hours of data collection.

ML inference servers. Model serving carries variable memory footprints depending on batch size and model weight loading. VPA rightsizes these pods based on observed patterns rather than worst-case estimates.

Note: VPA is not built into Kubernetes. Install it from the kubernetes/autoscaler GitHub repository. It adds three components: Recommender (generates suggestions), Updater (evicts pods needing resource changes), and Admission Controller (applies new requests at pod creation).

DaemonSet limitation: VPA does not support DaemonSets. For DaemonSet workloads, set resource requests manually or use VPA in Off mode to gather baseline data without automated changes.

VPA Update Modes

VPA offers four update modes. Choosing the right one matters for production stability:

  • Off: Generates recommendations but makes no changes. Use this for observation before committing to automated updates.
  • Initial: Applies recommendations only at pod creation. Running pods remain unchanged. This works for workloads with frequent restarts.
  • Recreate: Evicts pods when requests need adjustment and recreates them with updated specs. Configure a PodDisruptionBudget (PDB) before enabling this in production. Without a PDB, VPA can evict all pods simultaneously. A PDB with minAvailable: 1 ensures at least one pod stays available.
  • InPlaceOrRecreate: Introduced in K8s 1.33. Applies changes in-place when supported, falls back to Recreate when not. GA expected in K8s 1.35.

Start with Off Mode

Before enabling automated updates, run VPA in Off mode to collect recommendations without touching running pods. Here is a minimal example:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: payment-api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api
  updatePolicy:
    updateMode: "Off"

Inspect VPA Recommendations

# Check VPA-generated recommendations
kubectl describe vpa <vpa-name> -n <namespace>

# See recommended, lower bound, and upper bound
kubectl get vpa <vpa-name> -n <namespace> -o json | jq '.status.recommendation'

Run these commands 24-48 hours after enabling VPA in Off mode to see initial recommendations before applying them. The output includes lowerBound, target, and upperBound per container. Use target as the baseline for your Deployment resource requests.

For teams that want automated rightsizing without VPA’s operational complexity, see Cast AI Automated Workload Rightsizing with PrecisionPack.

Running HPA and VPA Together

Using both autoscalers in the same cluster is possible – but the metric each controller watches determines whether the combination is safe or actively harmful. Understanding the failure mode makes the line clear.

Safe Combinations

HPA on custom or external metrics + VPA in Off mode. HPA scales replicas based on RPS or queue depth. VPA generates recommendations your team reviews and applies during maintenance windows. The controllers don’t interact because they watch different signals.

HPA on CPU + VPA on memory only. HPA adds replicas when CPU utilization exceeds your threshold. VPA independently adjusts memory requests. CPU is fully HPA’s domain; memory belongs to VPA. To enforce this boundary, use the controlledResources field in the VPA manifest:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: payment-api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api
  updatePolicy:
    updateMode: "Recreate"
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      controlledResources: ["memory"]
      controlledValues: RequestsOnly
      minAllowed:
        memory: 128Mi
      maxAllowed:
        memory: 2Gi

Note: Use updateMode: Recreate (not Auto, which is deprecated since VPA 1.4.0). The controlledValues: RequestsOnly field ensures VPA adjusts only memory requests, leaving memory limits unchanged to prevent unexpected throttling.

The controlledResources: ["memory"] field restricts VPA to memory adjustments only, ensuring HPA’s CPU metric remains stable.

The CPU Feedback Loop – Why It Breaks

HPA on CPU and VPA also controlling CPU creates oscillation. Here is the failure sequence:

  1. CPU utilization rises across pods. HPA adds replicas to distribute the load.
  2. With more replicas handling the same traffic, per-pod CPU usage drops. VPA’s Recommender observes lower utilization and recommends reducing CPU requests.
  3. VPA applies the lower request. HPA recalculates utilization as actual CPU divided by the request – now smaller, so measured utilization spikes back up.
  4. HPA responds by adding more replicas. The cycle repeats. Neither controller converges.

This is a predictable outcome of two feedback loops sharing the same input signal. The fix: don’t let both controllers watch CPU simultaneously.

Best Practice for Teams Wanting Both

Start with VPA in Off mode and collect 48 hours of usage data. Apply recommendations as a one-time adjustment to your Deployment specs, then enable HPA on RPS or queue depth. This gives you rightsized requests before autoscaling begins – HPA scales from an accurate baseline, not an overprovisioned one.

For continuous VPA updates, use Initial or InPlaceOrRecreate mode with controlledResources: ["memory"]. Keep HPA on CPU or application metrics, and review VPA recommendations quarterly.

Conclusion: Autoscaling That Handles Both Dimensions

HPA and VPA each solve half of the Kubernetes resource efficiency problem. HPA removes idle replicas when traffic drops. VPA removes wasted resource requests when pod specs are set too high. Together, they cover both dimensions – as long as metric ownership stays clean.

Both autoscalers operate at the pod level. For pod-level changes to reduce your cloud bill, a node-level autoscaler (Cluster Autoscaler or Karpenter) must also run. When pods scale down or request less CPU, the node autoscaler consolidates workloads and removes underutilized nodes. HPA and VPA optimize pod resources; the node autoscaler translates that into infrastructure savings.

For teams that want both problems solved without managing two separate controllers, Cast AI Workload Optimization is worth a close look. Unlike VPA, Cast AI’s Workload Optimization applies resource changes in-place – modifying the container’s cgroup limits without pod eviction. It respects PodDisruptionBudgets, works with single-replica Deployments, and supports DaemonSets, which VPA’s Updater does not touch. Combined with Cast AI’s node autoscaler, it addresses per-pod resource fit and cluster-level node count as one continuous optimization loop.

For a broader look at how HPA, VPA, and cluster autoscaling fit together, read the Kubernetes Autoscaling Guide for Cloud Cost Optimization.

Frequently Asked Questions

What is the difference between HPA and VPA in Kubernetes?

HPA (Horizontal Pod Autoscaler) changes the number of pod replicas based on metrics like CPU utilization, memory, or custom application metrics. VPA (Vertical Pod Autoscaler) changes the CPU and memory requests assigned to each pod based on historical usage data. HPA scales out by running more pods; VPA scales up by giving each pod more resources. Both address autoscaling across different dimensions: replica count versus per-pod resource allocation.

Can I use HPA and VPA at the same time?

Yes, but the combination requires metric separation to avoid controller conflicts. Safe options include HPA on application metrics (RPS, queue depth) with VPA in Off mode, or HPA on CPU with VPA managing memory only via controlledResources: ["memory"]. The unsafe combination is both controllers watching CPU simultaneously – this creates a feedback loop causing oscillation in replica count and resource requests. Keep their metric domains separate and the combination works well.

Which autoscaler is better for Kubernetes cost optimization?

Both reduce cost in different ways. HPA prevents running excess replicas when traffic drops. VPA prevents allocating compute that pods never use – the Cast AI 2026 State of Kubernetes Optimization Report shows 69% of requested CPU goes unused and memory overprovisioning reaches 79% in production clusters. HPA removes idle replicas; VPA removes idle resource allocations per pod. Pair both with a node-level autoscaler (Cluster Autoscaler or Karpenter) and pod-level savings translate into node reduction and lower cloud bills.

Cast AIBlogHPA vs VPA: When to Use Each, and Can You Use Both?