Yes — Kubernetes can change the CPU and memory of a running pod without deleting it and, in most cases, without restarting its containers. The feature is called In-Place Pod Resize (the KEP calls it In-Place Pod Vertical Scaling), it has been stable since Kubernetes 1.35, and on any cluster at 1.33 or later it is already on by default. You trigger it by patching the pod’s resize subresource:
kubectl patch pod checkout-7d9f8c6b5-x2lqp --subresource resize --patch \
'{"spec":{"containers":[{"name":"app","resources":{"requests":{"cpu":"1"},"limits":{"cpu":"2"}}}]}}'The kubelet updates the container’s cgroup, the pod keeps its name, IP, node and in-memory state, and restartCount stays where it was. That is the short answer. The long answer — which this article is about — is that “without restarting” comes with conditions: which resource you are changing, what resizePolicy says, whether the node has room, what QoS class the pod was born with, and whether the runtime inside the container can actually use the new numbers. And the moment you want this to happen automatically, you are in Vertical Pod Autoscaler territory, where the new InPlaceOrRecreate and InPlace modes finally change the old “VPA means evictions” trade-off — and where running HPA and VPA together still needs care.
If you need the fundamentals first, the VPA introduction covers when vertical scaling makes sense at all, and Kubernetes resource requests and limits covers what the numbers you are about to change actually do.
Why Resource Changes Used to Mean a Restart
For most of Kubernetes’ history, spec.containers[*].resources was immutable on a Pod. Requests and limits were decided at admission, the scheduler placed the pod against them, the kubelet wrote them into the container’s cgroup, and that was it for the life of the pod. If you wanted more CPU, you changed the Deployment template, the Deployment controller created a new ReplicaSet, and the old pods were terminated one by one.
That is fine for a stateless web tier with twenty replicas and a fast readiness probe. It is painful for:
- stateful workloads where a restart means cache warm-up or a replica resync;
- long-running batch or ML jobs where a restart throws away hours of progress;
- JVM services that need a lot of CPU for thirty seconds of startup and very little afterwards.
It is also why the Vertical Pod Autoscaler had a reputation problem. Its only way to apply a new recommendation to a running pod was to evict it and let the admission webhook stamp the new values on the replacement. Many teams ran VPA in recommendation-only mode forever because they did not want an autoscaler that deletes pods.
Feature History: From Alpha to Stable
In-Place Pod Resize took more than six years from conception to GA. The version history matters because behavior changed along the way, and a lot of blog posts still describe the alpha or beta semantics.
| Kubernetes version | State | What changed |
|---|---|---|
| 1.27 | Alpha | InPlacePodVerticalScaling feature gate, off by default. Resources became mutable directly on spec, progress reported in status.resize. |
| 1.33 | Beta, on by default | New resize subresource, status.resize replaced by the PodResizePending and PodResizeInProgress conditions. |
| 1.35 (Dec 2025) | Stable, locked on | Memory limit decreases allowed (best-effort OOM protection), prioritized retry of deferred resizes, new kubelet metrics and pod events. Pod-level resources resize added as alpha. |
| 1.36 | — | In-place resize of pod-level resources (spec.resources) promoted to beta, on by default. |
| 1.37 | — | Alpha: scheduler preemption for deferred resizes (InPlacePodVerticalScalingSchedulerPreemption) and resize of memory-backed emptyDir size limits (InPlacePodVerticalScalingMemoryBackedVolumes, cgroup v2 only). |
On 1.35+ the feature cannot be turned off, and your kubectl client needs to be 1.32 or newer to use --subresource resize; older clients fail with an “invalid subresource” error even against a new API server.
How In-Place Resize Works
Three fields carry the whole mechanism, and reading them correctly saves most of the debugging time.
spec.containers[*].resources is the desired state. For CPU and memory, these fields are now mutable — but only through the resize subresource. A normal kubectl edit pod or kubectl apply against the main pod resource is still rejected for resource changes.
status.containerStatuses[*].resources is the actual state. It reflects what the kubelet has really configured in the container’s cgroup. When desired and actual differ, a resize is pending or in progress.
status.containerStatuses[*].allocatedResources is what the kubelet has admitted. It is mostly for scheduling. While a resize is incomplete, the scheduler accounts for the pod using the maximum of desired, allocated and actual requests, so a node never gets overcommitted in the gap.
The flow is:
- You (or an autoscaler) patch the pod’s
resizesubresource with new requests and limits. - The API server validates the change — QoS class preserved, only CPU and memory touched, no resource removed.
- The kubelet on the pod’s node checks whether the new requests fit in the node’s allocatable capacity.
- If they fit, the kubelet admits the resize, updates the cgroup through the container runtime (containerd or CRI-O via the CRI
UpdateContainerResourcescall), and — depending onresizePolicy— restarts the container or not. - When the runtime confirms,
status.containerStatuses[*].resourcesmatches the spec and the conditions clear.
A Complete Example
Create a pod with an explicit resize policy:
apiVersion: v1
kind: Pod
metadata:
name: resize-demo
namespace: resize-lab
spec:
containers:
- name: app
image: registry.k8s.io/pause:3.9
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired # default, explicit here
- resourceName: memory
restartPolicy: RestartContainer
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mikubectl create namespace resize-lab
kubectl apply -f resize-demo.yamlThis pod is Burstable (requests differ from limits). Now double the CPU:
kubectl -n resize-lab patch pod resize-demo --subresource resize --patch \
'{"spec":{"containers":[{"name":"app","resources":{"requests":{"cpu":"1"},"limits":{"cpu":"2"}}}]}}'Check desired versus actual, and the restart count:
kubectl -n resize-lab get pod resize-demo \
-o jsonpath='{.spec.containers[0].resources}{"\n"}{.status.containerStatuses[0].resources}{"\n"}{.status.containerStatuses[0].restartCount}{"\n"}'Both resource blocks should show cpu: 1 / cpu: 2, and restartCount is still 0. Now change the memory limit:
kubectl -n resize-lab patch pod resize-demo --subresource resize --patch \
'{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"768Mi"}}}]}}'This time restartCount goes to 1, because the memory policy is RestartContainer. The pod is the same pod — same UID, same IP, same node — but the container process was restarted to pick up the new value.
resizePolicy: NotRequired vs RestartContainer
resizePolicy is a per-container, per-resource setting with two values:
NotRequired(the default when unset): apply the new value to the running container’s cgroup without restarting it.RestartContainer: restart the container so the process starts with the new value.
The rule when several resources change at once is simple: if any changed resource has RestartContainer, the container restarts. With the policy above, a CPU-only change is in place, a memory-only change restarts, and a CPU-and-memory change restarts.
One restriction: if the pod’s overall restartPolicy is Never (typical for Jobs that must not rerun a container), every resizePolicy entry must be NotRequired. You cannot declare a resize that would require a restart the pod is not allowed to do.
Which policy should you choose?
CPU: almost always NotRequired. Changing CPU requests and limits changes CFS shares and quota in the cgroup. The kernel applies it on the next scheduling period and the process does not need to know. The one caveat is runtimes that size thread pools from the CPU count at startup — the JVM’s GC threads and ForkJoinPool.commonPool() are the classic example. The process gets more CPU time immediately, but it will not create more worker threads. (Go is the pleasant exception: since Go 1.25 the runtime reads the cgroup CPU limit and adjusts GOMAXPROCS while running, unless you pinned it with an environment variable.) For most services that is fine; for heavily parallel compute, a restart may still be worth it.
Memory: depends on whether the process can use it. Raising a cgroup memory limit is harmless, but a JVM started with -XX:MaxRAMPercentage=75 computed its max heap from the old limit and will not grow the heap past it. Python and most interpreted runtimes do not re-read limits either — the Kubernetes GA announcement itself notes that Java and Python do not support resizing memory without a restart. For these, RestartContainer on memory is the honest setting: you still avoid rescheduling and keep the pod’s identity, you just restart the process. Go with GOMEMLIMIT set from the downward API, or native services that allocate on demand, can use NotRequired.
Memory Decreases: Allowed, but Best-Effort
Until 1.35, decreasing a container’s memory limit in place was simply forbidden. The GA release lifted that restriction with a safeguard: when the memory restartPolicy is NotRequired, the kubelet checks current memory usage before applying a lower limit. If usage is already above the new limit, the resize is not applied and stays in progress rather than triggering an immediate OOM kill.
The check is explicitly best-effort. There is a race: usage can spike between the check and the cgroup write, and then the kernel OOM-kills the container. Treat memory decreases on NotRequired containers as something you do with margin — shrink to well above the observed working set, not to it. Moving the check into the container runtime to close that race is listed as future work.
Watching a Resize: Conditions, Deferred and Infeasible
When the kubelet cannot apply a resize immediately, it tells you through pod conditions.
PodResizePending means the kubelet has not admitted the resize. The reason field says why:
Infeasible: the request can never fit on this node — for example, asking for 16 CPUs on an 8-CPU node, or a change the node’s configuration forbids. It will not be retried until you change the request.Deferred: the request does not fit right now, but could if other pods leave. The kubelet retries automatically.
PodResizeInProgress means the kubelet has admitted the resize and allocated the resources but the runtime has not finished applying them. It is usually brief. If actuation fails, the condition carries reason: Error and a message.
To see them:
kubectl -n resize-lab get pod resize-demo \
-o jsonpath='{range .status.conditions[?(@.type=="PodResizePending")]}{.reason}{": "}{.message}{"\n"}{end}'
kubectl -n resize-lab describe pod resize-demo | sed -n '/Conditions:/,/Volumes:/p'Since 1.35 the pod also emits dedicated events for resizes, and status.observedGeneration tells you which version of the spec the kubelet has acknowledged — useful when several resizes are issued in a row and you need to know which one the status refers to.
When several resizes on a node are Deferred, they are retried in priority order: higher PriorityClass first, then Guaranteed before Burstable, then the longest-waiting first. A deferred high-priority resize does not block lower-priority ones that happen to fit. And with the 1.37 alpha InPlacePodVerticalScalingSchedulerPreemption gate, the scheduler can go further and evict lower-priority pods to make room for a higher-priority pod’s deferred resize — the same preemption semantics as scheduling, applied to growth.
Limitations You Will Hit
As of Kubernetes 1.37, the official list of restrictions is:
| Limitation | What it means in practice |
|---|---|
| Only CPU and memory | Ephemeral storage, GPUs, hugepages and other extended resources are still immutable. |
| QoS class is fixed | The QoS class decided at creation must survive the resize. Guaranteed pods must keep requests equal to limits; Burstable pods cannot make requests equal limits for both CPU and memory; BestEffort pods cannot gain requests or limits. |
| No removal | Once a request or limit is set, you can change it but not delete it. |
| Container types | Regular containers and sidecars (restartable init containers) can be resized; classic init containers and ephemeral debug containers cannot. |
| Windows | Windows pods do not support in-place resize. |
| Static CPU / Memory Manager | Pods pinned by the kubelet’s static CPU manager or static memory manager policies cannot be resized in place. |
| Swap | Pods using swap can only change memory requests if the memory resize policy is RestartContainer. |
| Memory decrease | Best-effort OOM protection, as described above. |
The other restriction that is not in the table but matters more than all of them: in-place resize is a Pod operation, not a Deployment operation. If you change resources in a Deployment, StatefulSet or DaemonSet template, the controller still performs a normal rolling update with new pods. Workload controllers do not propagate template changes in place. Resizing in place means patching individual pods — by hand, with a script, or with an autoscaler that does it for you. And whatever you patch onto a pod is lost the next time the controller replaces it, because new pods are created from the template.
Pod-Level Resources Resize
Kubernetes also supports resources at the pod level — spec.resources as an aggregate budget shared by all containers — and since 1.36 those can be resized in place too (beta, on by default; alpha in 1.35 under InPlacePodLevelResourcesVerticalScaling). The same resize subresource, conditions and retry logic apply:
kubectl patch pod pod-level-demo --subresource resize --patch \
'{"spec":{"resources":{"requests":{"cpu":"300m"},"limits":{"cpu":"300m"}}}}'Two rules are specific to pod-level resize. The new pod-level requests must be at least the sum of the container requests, and no individual container limit may exceed the new pod-level limit. And there is no pod-level resizePolicy: the pod cgroup is always changed in place, but containers without their own limit inherit the pod limit as their effective ceiling, so their container-level resizePolicy still decides whether they restart. A container with restartPolicy: RestartContainer for CPU and no CPU limit will restart when you raise the pod’s CPU limit.
Automating It: VPA InPlaceOrRecreate and InPlace
Manual resizes are useful for incidents and experiments. The production use of this feature is the Vertical Pod Autoscaler, which as of VPA 1.8 has six update modes:
| updateMode | Behavior | Status |
|---|---|---|
Off | Recommendations only, never touches pods | Stable |
Initial | Applies recommendations only when pods are created | Stable |
Recreate | Evicts pods to apply recommendations (the default) | Stable |
Auto | Same as Recreate | Deprecated — use an explicit mode |
InPlaceOrRecreate | Tries an in-place resize; falls back to eviction | Alpha in VPA 1.4, beta in 1.5, GA in VPA 1.6 |
InPlace | In-place only; never evicts, retries later instead | Alpha in VPA 1.7 (feature gate InPlace) |
InPlaceOrRecreate is the mode most teams should move to:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: checkout
namespace: shop
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout
updatePolicy:
updateMode: InPlaceOrRecreate
resourcePolicy:
containerPolicies:
- containerName: app
controlledResources: ["cpu", "memory"]
minAllowed:
cpu: 250m
memory: 256Mi
maxAllowed:
cpu: "4"
memory: 4GiThe VPA updater attempts an in-place update when the pod’s requests fall outside the recommended bounds, after a quick OOM, or for pods running more than 12 hours whose recommendation differs by more than 10%. It falls back to eviction when the resize is Infeasible, has been Deferred for more than 5 minutes, has been in progress for more than an hour, would change the pod’s QoS class, or requires a memory limit decrease the policy does not allow. All containers of a pod are updated together.
A few operational details worth knowing:
- In-place updates still respect VPA’s eviction tolerance and
minReplicasby default. If every container usesNotRequiredfor both resources, the updater flag--in-place-skip-disruption-budgetlets VPA skip those checks for updates that genuinely do not restart anything. - The updater exports
vpa_updater_in_place_updated_pods_totalandvpa_updater_failed_in_place_update_attempts_total, among others. Alert on the failure counter — a steady stream of failed in-place attempts usually means nodes are too full and you are silently back to evictions. InPlacemode is for workloads where any eviction is unacceptable. The price is that a resize that never fits never happens; VPA tracks infeasible attempts and only retries when the recommendation drops below what was already rejected.
CPU Startup Boost: The JVM Use Case
The most compelling use of in-place resize is the startup spike. A Spring Boot or Quarkus-on-JVM service can burn two or three cores for the first 30–60 seconds (class loading, JIT compilation, connection pool warm-up) and then settle at 200 millicores. Sizing requests for the startup means paying for idle cores all day; sizing for steady state means slow starts, failing startup probes and an HPA that adds pods too late to help.
VPA 1.7 introduced CPU Startup Boost (alpha, CPUStartupBoost feature gate), built directly on in-place resize:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: orders
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: orders
updatePolicy:
updateMode: InPlaceOrRecreate
startupBoost:
cpu:
type: Factor
factor: 3
durationSeconds: 60The admission controller multiplies the CPU of new pods by the factor, and once the pod is Ready and durationSeconds has elapsed, the updater scales CPU back down in place — to the VPA recommendation if VPA controls the container, otherwise to the original spec. VPA 1.8 added reactive unboosting on top. Boost can also be set per container under containerPolicies, or as a fixed Quantity instead of a factor.
Remember the caveat from the resizePolicy section: the JVM sizes GC and common pool threads at startup, so it keeps the thread counts it chose during the boost — usually a benefit, not a problem.
Using HPA and VPA Together Without Conflicts
“Can I use HPA and VPA together?” is the other question people search for, and in-place resize changes the answer only partially.
The classic conflict: an HPA with a CPU or memory utilization target computes utilization as usage divided by requests. VPA changes requests. When VPA raises the CPU request, utilization drops, and the HPA scales in; with fewer pods, usage per pod rises, VPA raises requests again. The two controllers fight over the same signal from opposite directions. In-place resize makes that loop faster, not safer, because VPA no longer has to wait for evictions to take effect.
The rule remains: never let HPA and VPA act on the same resource metric. The patterns that work:
HPA on a custom or external metric, VPA on resources. Scale out on requests per second, queue depth or consumer lag (via the custom metrics API, Prometheus Adapter or KEDA), and let VPA InPlaceOrRecreate right-size CPU and memory per pod. Each controller owns a different dimension. This is the pattern I recommend for most services that need both.
HPA on CPU, VPA on memory only. Set controlledResources: ["memory"] in the VPA’s container policy. HPA owns CPU-driven horizontal scale, VPA only sizes memory. Works well for services whose memory footprint depends on configuration and data size rather than load.
HPA on CPU, VPA in Off mode. Use VPA purely as a recommendation engine and feed its numbers into the Deployment template through your GitOps pipeline. No live conflict, and you still get data-driven requests.
If your HPA scales on CPU and you are also tuning its behavior, the HPA best practices guide and the HPA memory examples cover the scaling policies. For HPA on event-driven metrics, see KEDA and event-driven autoscaling.
Interaction With Cluster Autoscaling
In-place resize never moves a pod. If a node is full, a resize goes Deferred, and neither Cluster Autoscaler nor Karpenter will add a node for it: they react to unschedulable pods, and a pod with a deferred resize is scheduled and running. The pod stays at its old size until something on the node shrinks or leaves.
That is why InPlaceOrRecreate falls back to eviction after five minutes deferred: an evicted pod with larger requests becomes pending, and pending pods do trigger node provisioning. If you run VPA in pure InPlace mode, plan headroom on nodes explicitly, or accept that some resizes will wait. The 1.37 preemption alpha helps high-priority pods but does not create capacity. For how node autoscalers make those decisions, see Cluster Autoscaler vs Karpenter, and if you are adopting several of these newer features at once, Kubernetes features that hurt in production is a useful sanity check on rollout order.
Frequently Asked Questions
Can Kubernetes scale a pod vertically without restarting the pod?
Yes. Since Kubernetes 1.33 (beta, on by default) and as a stable feature since 1.35, you can change a running pod’s CPU and memory requests and limits through the pod’s resize subresource, for example with kubectl patch pod <name> --subresource resize. The pod keeps its UID, IP and node. Whether the container process restarts depends on the container’s resizePolicy: with NotRequired, the default, the new values are applied to the cgroup live; with RestartContainer, the container is restarted inside the same pod.
Which Kubernetes version made in-place pod resize GA?
Kubernetes 1.35, released in December 2025. The feature was alpha in 1.27 behind the InPlacePodVerticalScaling feature gate and beta, enabled by default, in 1.33. In 1.35 the gate is locked on, memory limit decreases became allowed, and deferred resizes are retried by priority. Resizing pod-level resources is beta since 1.36.
Why does my pod show PodResizePending with reason Deferred?
The node does not currently have enough allocatable CPU or memory for the new requests. The kubelet will retry automatically when capacity frees up, prioritizing by PriorityClass, then QoS class, then waiting time. In-place resize never moves the pod to another node, and cluster autoscalers do not add nodes for deferred resizes, so either free capacity on that node or recreate the pod so the scheduler can place it elsewhere.
Can I change a pod’s QoS class with an in-place resize?
No. The QoS class is fixed at pod creation. A Guaranteed pod must keep requests equal to limits for CPU and memory, a Burstable pod cannot make them equal for both resources, and a BestEffort pod cannot gain requests or limits. Resizes that would change the QoS class are rejected by the API server, and VPA’s InPlaceOrRecreate mode falls back to evicting the pod in that case.
Does changing resources in a Deployment resize its pods in place?
No. Changing resources in a Deployment, StatefulSet or DaemonSet template still triggers a normal rolling update with new pods. In-place resize works on individual pods through the resize subresource. To resize a Deployment’s pods in place automatically, use the Vertical Pod Autoscaler with updateMode: InPlaceOrRecreate or InPlace, and keep in mind that new pods created by the controller always start from the template values.
Can I use HPA and VPA together?
Yes, as long as they do not act on the same resource metric. An HPA with a CPU or memory utilization target divides usage by requests, and VPA changes requests, so the two controllers fight. Safe combinations are HPA on a custom or external metric such as requests per second or queue depth with VPA managing CPU and memory, HPA on CPU with VPA controlling only memory, or VPA in Off mode as a recommendation source for an HPA-managed workload.
Does in-place resize work for JVM applications?
For CPU, yes: the JVM receives more or less CPU time immediately, although thread pools and GC threads sized at startup keep their original counts. For memory, the JVM computes its maximum heap at startup, so raising the limit in place does not give the heap more room. Use RestartContainer for memory on JVM workloads, and consider VPA’s CPU Startup Boost to give JVMs extra CPU during startup and scale back down in place once they are ready.
Conclusion
In-place pod resize turns CPU and memory from birth certificates into dials. On Kubernetes 1.35+ you can grow or shrink a running pod through the resize subresource, and for CPU that genuinely means no restart. Memory is more nuanced: the cgroup changes live, but many runtimes only read their memory budget at startup, so RestartContainer is often the right policy — still far cheaper than rescheduling.
The bigger shift is for autoscaling. VPA’s InPlaceOrRecreate mode, GA since VPA 1.6, removes the main reason teams kept VPA in recommendation-only mode, and CPU Startup Boost finally gives JVM services a clean answer to the startup spike. What has not changed is the rule for combining autoscalers: give HPA and VPA different signals, keep the QoS class in mind from day one, and remember that anything you patch onto a pod lives only until the next rollout.