Picking CPU and memory for an app is part measurement, part judgment. Give it too little and it gets throttled or OOM-killed; give it too much and you burn money on idle capacity. This tutorial explains how container resource limits actually work, how to measure what your app needs, and how to map that onto compute tiers.
Requests vs limits: the mental model
Kubernetes — which PandaStack uses under the hood — distinguishes two numbers per resource:
- Request: the guaranteed amount reserved for your container. The scheduler uses it to place the pod on a node.
- Limit: the hard ceiling. Cross it and you get throttled (CPU) or killed (memory).
The two resources behave very differently when you hit the limit:
| Resource | At the limit | Symptom |
|---|---|---|
| CPU | Throttled (compressible) | Slow responses, high latency, but app stays up |
| Memory | OOM-killed (incompressible) | Container restarts, exit code 137 |
This asymmetry is the single most important thing to internalize: CPU pressure makes you slow; memory pressure makes you crash. When in doubt, be more generous with memory than with CPU.
How to measure what you actually need
Guessing leads to waste. Measure instead.
1. Establish a baseline
Deploy with a modest tier and watch metrics under realistic traffic. PandaStack captures server-side metrics (no client SDK needed) so you can see CPU and memory utilization over time in the dashboard.
2. Load test
Drive representative traffic and watch the curves:
# Simple load test with hey
hey -z 60s -c 50 https://your-app.pandastack.app/Watch for: CPU plateauing at 100% of the limit (throttling) or memory climbing toward the ceiling and restarting (OOM).
3. Apply headroom
A common rule of thumb: set requests near your typical (p50) usage and leave roughly 25–50% headroom for memory above your observed peak, because memory spikes are fatal and CPU spikes are merely slow.
Reading the symptoms
| You observe | Diagnosis | Action |
|---|---|---|
Restarts with 137 | OOM kill | Increase memory tier |
| High p99 latency, CPU pinned | CPU throttling | Increase CPU or optimize hot path |
| 10% CPU, 15% memory steady | Over-provisioned | Drop to a smaller tier |
| Memory grows forever, never drops | Likely a leak | Fix the app, not the limit |
That last row matters: throwing memory at a leak only delays the crash. If memory climbs monotonically and never recovers after GC, profile the application.
Mapping needs to PandaStack tiers
PandaStack offers a range of compute tiers from a small free tier up through compute- and memory-optimized options:
- Free — 0.25 CPU / 512 MB, $0/hr. Great for dev, hobby, low-traffic services. Free-tier apps run in a gVisor sandbox on spot nodes with KEDA scale-to-zero, so they cold-start after idling.
- General / compute-optimized (c1, c2) — for CPU-bound work like image processing, compilation-in-request, or high-RPS APIs.
- Memory-optimized (m1, m2) — for caches, in-memory datasets, or runtimes with large heaps.
- Top of range — C2-2XCompute at 8 CPU / 16 GB (~$0.300/hr, roughly $219/mo) for the heaviest workloads.
The choice between compute- and memory-optimized comes down to your bottleneck. If CPU saturates first, go compute-optimized. If memory does, go memory-optimized. (We cover this trade-off in depth in our compute-vs-memory tier guide.)
A worked example
Suppose you run a Node.js API. Under a 50-concurrent load test you observe:
- CPU: peaks at ~0.6 cores
- Memory: steady ~700 MB, peaks at ~900 MB
The free tier (0.25 CPU / 512 MB) is clearly too small — you'd OOM at 900 MB and throttle at 0.6 cores. You'd move to a tier offering at least ~1 CPU and 1.5–2 GB to give memory the fatal-spike headroom it needs. If, after optimizing, memory was the only constraint, a memory-optimized tier would be cheaper per GB than buying CPU you don't use.
Don't forget the Node.js heap caveat
Runtimes with their own memory ceilings need to be told about the container limit. Node's old-space default can exceed a small container's memory before V8 decides to GC, causing an OOM before the heap limit triggers:
# Tell Node to cap old space below the container memory limit
NODE_OPTIONS="--max-old-space-size=1536" # for a ~2GB containerThe JVM has an analogous story with -XX:MaxRAMPercentage. Always reconcile your runtime's heap settings with the container memory limit.
Set it, then revisit
Resource sizing is not set-and-forget. Traffic patterns shift, dependencies bloat, features get added. Revisit utilization monthly and after major releases. The goal is steady-state utilization in a comfortable band — high enough that you're not paying for air, low enough that normal spikes don't crash you.
References
- [Kubernetes: Managing resources for containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)
- [Kubernetes: Resource QoS classes](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/)
- [
heyHTTP load generator](https://github.com/rakyll/hey) - [Node.js
--max-old-space-size](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib) - [gVisor sandbox](https://gvisor.dev/docs/)
---
PandaStack's free tier lets you experiment with sizing at no cost (0.25 CPU / 512 MB, scale-to-zero), and upgrading to a larger tier is a dropdown, not a migration. Start at [dashboard.pandastack.io](https://dashboard.pandastack.io).