For years, teams building on Lambda had to make an early, mostly irreversible decision: package as a ZIP archive and get SnapStart's sub-second cold starts, or package as a container image and accept multi-second cold starts in exchange for room to bundle heavy dependencies. AWS closed that gap in 2026, extending SnapStart to container-image functions (aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/, with further coverage from InfoQ in September 2026). It's a small announcement on paper, but it removes a constraint that shaped how a lot of ML and data-heavy serverless workloads got architected.
Why the tradeoff existed in the first place
SnapStart works by initializing your function once, taking a snapshot of the fully-booted execution environment (memory, disk state, loaded classes/modules), and caching that snapshot. On subsequent cold starts, Lambda resumes from the snapshot instead of running your init code from scratch. For JVM-heavy workloads especially, this turned multi-second cold starts into something closer to 200-400ms, because class loading and JIT warmup — the expensive part — already happened before the snapshot was taken.
The catch was packaging. SnapStart originally only supported Lambda's managed ZIP runtimes (Java first, later Python and .NET), and ZIP archives cap out at 250 MB uncompressed. Container images support up to 10 GB. So if your function needed a large ML model, a big native library (OpenCV, a CUDA runtime, a full pandas/numpy/scipy stack with system libs), or anything else that pushed past 250 MB, you had exactly one option: containers, with no SnapStart. Cold start was whatever it was — often several seconds for anything nontrivial — and there was no way around it short of pre-provisioned concurrency.
That split forced an architectural decision early: small and fast, or large and slow to start. Neither option was universally wrong, but plenty of teams ended up over-engineering around it — splitting a single logical function into a small "hot path" Lambda plus a separately-warmed heavier service, just to dodge the ZIP size ceiling.
What changes now
With container-image SnapStart support, that split is no longer necessary for most cases. You can package a 2-3 GB container image with a full model and its dependencies and still get snapshot/resume behavior on cold start. The snapshot captures the container's initialized state — including whatever your init code loaded into memory — the same way it always did for ZIP-packaged Java.
Runtime support isn't uniform, though. AWS's own base images for Java 11+, Python 3.12+, and .NET 8+ get SnapStart working essentially out of the box, matching how ZIP-based SnapStart already worked for those runtimes. Everything else — Node.js, Ruby, Go, or a fully custom runtime — needs an explicit opt-in via a Dockerfile label and, in some cases, runtime hook integration to control what gets snapshotted and when.
Regional coverage is close to complete but not total: as of the September 2026 reporting, it's live in all commercial AWS regions except Asia Pacific (New Zealand) and Asia Pacific (Taipei). Worth checking before you commit an architecture to it if you're running in one of those regions.
A concrete scenario: Python ML inference
Picture a function that serves inference for a fine-tuned sentence-embedding model — a few hundred MB of model weights, plus torch, transformers, and their native dependencies. That easily blows past 250 MB, so before this update, you had to package it as a container and eat a 4-8 second cold start on every scale-out event, or keep provisioned concurrency running 24/7 to avoid cold starts altogether.
With container SnapStart:
- You build the same container image, Python 3.12+ base.
- Model loading and library imports happen once in your init code, outside the handler.
- AWS snapshots that initialized environment after the first invocation.
- Subsequent cold starts resume from the snapshot — model already in memory, libraries already imported — landing in the same sub-second range JVM SnapStart users have had for a while.
The practical effect: you can stop paying for idle provisioned concurrency just to hide cold starts, and you stop needing to shrink your dependency footprint to fit a ZIP archive. For bursty or spiky inference traffic — the classic case where Lambda's pay-per-invocation model actually beats a standing Fargate service — this is a real unlock.
Dockerfile pattern: opting in on a non-Java/Python/.NET runtime
Runtimes outside the "gets it for free" list need to declare SnapStart support explicitly. A representative pattern for a custom or Node.js-based runtime looks like this (illustrative — check current AWS docs for the exact hook contract for your base image):
FROM public.ecr.aws/lambda/nodejs:20
# Opt in to SnapStart for container images
LABEL "aws.lambda.snapstart"="enabled"
# Install dependencies at build time, not cold-start time
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY app/ ./app/
# Register a beforeCheckpoint / afterRestore hook so init-time
# side effects (connections, caches) are handled explicitly
# rather than silently baked into the snapshot
COPY snapstart-hooks.js ./
ENV AWS_LAMBDA_EXEC_WRAPPER=/opt/snapstart-wrapper
CMD ["app/handler.main"]The important part isn't the exact label syntax — that's runtime and base-image specific — it's the concept: something in the image has to explicitly mark it as snapshot-eligible and, for anything with runtime-hook support, register callbacks for the checkpoint and restore lifecycle events. If you skip that, the runtime either won't be snapshotted at all, or worse, will be snapshotted with implicit assumptions your code isn't ready for.
The gotcha: idempotent init code
This is the part teams miss, and it's the same gotcha that existed for ZIP-based SnapStart, just now relevant to a wider set of container workloads. Whatever your init code does before the snapshot is taken gets frozen into that snapshot and replayed on every resume. That's fine for loading a model into memory or importing libraries — those are deterministic and side-effect-free from the outside world's perspective.
It's not fine for:
- Caching secrets or credentials at init time. If you fetch a database password or API key during init and it ends up in the snapshot, every resumed instance replays with that same cached value, even after rotation. Fetch secrets after resume, not before snapshot, or use the
afterRestorehook to refresh them. - Generating unique IDs, tokens, or random seeds at init time. A UUID or nonce generated before the snapshot will be identical across every environment that resumes from it — defeating the whole point of uniqueness. Move that generation into the handler, post-resume.
- Opening network connections that assume a specific point in time. A TCP connection or TLS session established before the snapshot is not valid after resume; it needs to be re-established, typically via a runtime hook.
None of this is new conceptually — it's the same "snapshot equals a paused-and-resumed process" mental model Firecracker-based SnapStart has always had. It's just that container images now bring in workloads (ML pipelines, data processing functions with heavier init) that are more likely to have exactly this kind of init-time side effect, because they tend to do more setup work before the handler runs.
webhani's take: when to reach for this vs. alternatives
We'd reach for container SnapStart when a function has both symptoms at once: dependencies too large or complex for a 250 MB ZIP, and cold start latency that actually matters to the caller (interactive APIs, user-facing inference, anything with a tight SLA). If only one of those is true, there's usually a cheaper answer:
- Large dependencies, cold start doesn't matter (batch jobs, async processing): stick with a plain container image, no SnapStart needed. Don't add snapshot/resume complexity you don't need.
- Cold start matters, dependencies are small: ZIP packaging with SnapStart (Java/Python/.NET) is simpler and has less to debug than a Dockerfile with lifecycle hooks.
- Sustained, predictable high traffic: provisioned concurrency or a standing Fargate service can be more cost-effective than SnapStart once you're not actually experiencing meaningful cold-start rates — SnapStart's value comes from bursty, spiky, or low-frequency invocation patterns where keeping capacity warm 24/7 is wasteful.
- Very long-running or stateful workloads: Lambda's 15-minute execution limit and SnapStart's per-request resume model aren't a fit regardless of packaging; that's Fargate or ECS territory.
Before adopting it, audit init code specifically for the idempotency issues above — that's the one step teams skip and the one that causes production incidents later. It's a short review, but it's the difference between SnapStart being a clean win and it being a source of stale-credential bugs three months down the line.