# How to Reduce Cold Start Times in Containerized Apps
Cold starts are the tax you pay for elastic, cost-efficient infrastructure. When a scaled-to-zero service wakes up, or an autoscaler adds a fresh instance under load, the first request waits while the container boots. This article breaks a cold start into its phases and shows how to attack each one.
Anatomy of a cold start
A cold start isn't one thing — it's a sequence, and you optimize it by knowing where the time goes.
| Phase | What happens | Biggest lever |
|---|---|---|
| Scheduling | Pod placed on a node | Resource requests, node availability |
| Image pull | Container image fetched to the node | Image size, layer caching |
| Container start | Runtime/process boots | Base image, runtime choice |
| App initialization | Framework, DB pools, caches warm up | Lazy loading, lean startup |
| First request | Code paths run for the first time | JIT warmup, precomputation |
Measure before optimizing. If 80% of your cold start is image pull, shaving startup code won't help much.
Lever 1: Shrink your image
Image pull is often the single largest contributor, and it scales directly with image size. Every technique from our Dockerfile and multi-stage guides pays off here:
- Use a slim or distroless base instead of a full OS image.
- Use multi-stage builds so the runtime image contains only what's needed.
- Remove dev dependencies, build caches, and unused files.
# Multi-stage: ship only the artifact on a tiny base
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server
FROM gcr.io/distroless/static-debian12
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]Going from a ~1GB image to ~50MB can cut seconds off every cold start.
Lever 2: Speed up application initialization
Many apps do expensive work at startup that delays readiness: eager-loading huge modules, opening every connection pool, pre-warming caches, running heavy framework bootstrapping. Two strategies:
Lazy-load what you can. Defer initializing rarely-used components until first use, not at boot.
// Eager: blocks startup even if rarely used
const heavyClient = createExpensiveClient();
// Lazy: initialize on first use
let _client;
function getClient() {
if (!_client) _client = createExpensiveClient();
return _client;
}Make readiness honest but minimal. Your readiness probe should pass as soon as the app can serve traffic — don't gate it on warming up optional caches that can fill lazily.
Lever 3: Choose a fast-starting runtime
Runtimes differ enormously in startup cost. A compiled Go or Rust binary starts in milliseconds. A JVM app with a large classpath can take seconds to warm up. Interpreted runtimes sit in between. You don't have to rewrite your app, but if cold start is critical and you're choosing a stack, this matters. For existing JVM apps, technologies like ahead-of-time compilation (GraalVM native image) can dramatically reduce startup.
Lever 4: Right-size resources
Under-provisioned CPU slows initialization — many runtimes do CPU-heavy work at boot (parsing, JIT compilation). Giving a container slightly more CPU during startup can shorten the cold start even if steady-state needs are modest. Conversely, requesting huge resources can slow *scheduling* because the scheduler needs to find a node with room. Tune to the sweet spot.
Lever 5: Keep something warm (when it matters)
The most reliable way to avoid a cold start is to not be cold. If a workload is latency-critical, keep a minimum number of warm instances instead of scaling fully to zero.
# Don't scale below one warm instance for latency-critical paths
minReplicas: 1
maxReplicas: 20This trades the zero-cost idle state for consistent latency. It's the right call for user-facing production paths; scale-to-zero remains great for dev, internal, and bursty workloads. (Our scale-to-zero article covers this trade-off in depth.)
Lever 6: Cache the image on the node
If an image is already cached on a node, the pull phase nearly disappears. Pre-pulling popular images, or keeping warm nodes that already have your image, removes a big chunk of cold-start time. This is more of a platform-level concern, but it's worth knowing it exists.
A prioritized plan
- 1Measure where your cold-start time actually goes.
- 2Shrink the image — usually the biggest, easiest win.
- 3Trim startup work — lazy-load, minimal readiness.
- 4Right-size CPU for the startup burst.
- 5Keep warm capacity for anything latency-critical.
Cold starts on PandaStack
Cold starts are directly relevant on PandaStack because free-tier apps use KEDA scale-to-zero on preemptible nodes — that's exactly the cost/latency trade-off that makes a free tier viable, with the honest caveat that those apps cold-start after idle. The levers above apply cleanly: PandaStack builds with rootless BuildKit and pushes to Artifact Registry, so smaller multi-stage images pull faster on wake. Lean application startup and honest readiness checks shorten the init phase.
When a workload can't tolerate cold starts — a latency-sensitive production path — the answer is to run it on a paid tier with warm capacity rather than scale-to-zero, and to pick a compute tier (up through the C2/memory-optimized range) that gives startup enough CPU. In short: use scale-to-zero where idle savings matter, optimize the image and startup everywhere, and keep warm what your users feel.
References
- [AWS: Operating Lambda — performance optimization (cold starts)](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/)
- [Knative: Configuring autoscaling and scale-to-zero](https://knative.dev/docs/serving/autoscaling/)
- [Google: Distroless images](https://github.com/GoogleContainerTools/distroless)
- [GraalVM Native Image](https://www.graalvm.org/latest/reference-manual/native-image/)
Deploy lean, fast-starting apps on PandaStack — with scale-to-zero free-tier apps and warm paid tiers when you need them. Start at [dashboard.pandastack.io](https://dashboard.pandastack.io).