AWS Lambda SnapStart for Container Images: Setup Guide and Cost Tradeoffs
AWS Lambda SnapStart for Container Images: Setup Guide and Cost Tradeoffs
If you package a Lambda function as a container image and it runs Java, Python, or .NET, you have probably eaten cold starts of several seconds every time traffic scaled up. SnapStart was the fix for that on .zip functions since 2022, but until now it flatly refused to work with container images. On September 2, 2026 AWS closed that gap: SnapStart now supports functions deployed as container images built on the AWS Lambda base images for Java 11+, Python 3.12+, and .NET 8+. This post covers exactly how to turn it on, what it costs, and the snapshot behavior that will bite you if you enable it blindly.
What actually changed
Nothing about how SnapStart works changed. What changed is the deployment package restriction was lifted. Before September 2, 2026:
- SnapStart worked only with
.zipdeployments on managed Java, Python, and .NET runtimes. - Container image functions had exactly two cold-start levers: provisioned concurrency (expensive, always-on) and hand optimization of your init code.
Now the same SnapStart you already know works when your function's PackageType is Image, as long as the image is built FROM one of these base images:
public.ecr.aws/lambda/javaโ 11 or laterpublic.ecr.aws/lambda/pythonโ 3.12 or laterpublic.ecr.aws/lambda/dotnetโ 8 or later
Custom base images, Runtime Interface Clients, and Lambda's provided.al2023, Node.js, and Ruby base images are not automatically supported โ there is a manual path for those, covered below.
How SnapStart works, briefly
When you publish a function version with SnapStart enabled, Lambda runs your initialization code once, then takes a Firecracker microVM snapshot of the memory and disk state of that initialized execution environment. It encrypts the snapshot, replicates it for durability, and caches it. On the first invoke and on every scale-up after that, Lambda resumes a fresh execution environment from the cached snapshot instead of running your init code again. Loading dependencies, warming a framework, building a connection pool โ all of that is already done inside the snapshot.
The key mental model: your init code runs at publish time, not invoke time. Everything downstream of that follows from it.
Requirements and limits
Before you enable it, check that your function fits inside these constraints:
| Constraint | Detail |
|---|---|
| Runtime | Java 11+, Python 3.12+, .NET 8+ (managed runtimes or the matching AWS base images) |
| Deployment | Published versions only โ SnapStart never applies to $LATEST |
| Provisioned concurrency | Mutually exclusive with SnapStart on the same function |
| Amazon EFS | Not supported |
| Ephemeral storage | /tmp must be 512 MB or less |
| Regions | All commercial Regions except Asia Pacific (New Zealand) and Asia Pacific (Taipei) |
Step-by-step: enable SnapStart on a container image function
1. Turn on SnapStart
For an existing function:
1aws lambda update-function-configuration \
2 --function-name my-image-fn \
3 --snap-start ApplyOn=PublishedVersions
Or set it at create time with create-function --snap-start ApplyOn=PublishedVersions. In AWS SAM the property lives on the function resource:
1Resources:
2 MyImageFn:
3 Type: AWS::Serverless::Function
4 Properties:
5 PackageType: Image
6 ImageUri: 111122223333.dkr.ecr.ap-south-1.amazonaws.com/my-image-fn:latest
7 MemorySize: 1024
8 SnapStart:
9 ApplyOn: PublishedVersions
CloudFormation uses the SnapStart entity on AWS::Lambda::Function; CDK uses CfnFunction.SnapStartProperty (or the snapStart prop on the L2 Function construct).
2. Publish a version
1aws lambda publish-version --function-name my-image-fn
Publishing is what triggers snapshot creation. The version sits in Pending state while Lambda runs your init code and snapshots it. Any invoke against the version during Pending fails, so treat publish as an async step in your pipeline, not instant.
3. Verify the snapshot is live
1aws lambda get-function-configuration \
2 --function-name my-image-fn:1
You want to see both of these in the response:
1"SnapStart": {
2 "ApplyOn": "PublishedVersions",
3 "OptimizationStatus": "On"
4},
5"State": "Active",
OptimizationStatus: On with State: Active means the snapshot exists and the version is servable. If you see OptimizationStatus: Off, you are looking at $LATEST or an old version published before you enabled SnapStart โ publish again.
4. Point your alias at the new version
Invoke the version or an alias that targets it. Invoking $LATEST gets you the old cold-start path with none of the benefit and none of the cost โ which is a subtle way to think SnapStart "did nothing."
Custom and unsupported base images
If your image is built on provided.al2023, a Node.js or Ruby base image, a Runtime Interface Client, or a fully custom base image, publish-version will fail outright unless the image does one of two things:
- Implements the SnapStart Runtime API contract itself โ call
GET /runtime/restore/nextafter init to trigger snapshotting, then run after-restore logic before entering the invoke loop, gated onAWS_LAMBDA_INITIALIZATION_TYPE == "snap-start". - If you do not need before-snapshot or after-restore hooks, add this label to your Dockerfile so Lambda knows the image is snapshot-safe:
1LABEL com.amazonaws.lambda.feature.snapstart="Allow"
An image that neither implements /restore/next nor carries the label cannot publish a SnapStart version. The init phase plus before-snapshot hooks share a timeout of max(function_timeout, 130s).
Runtime hooks and priming
For the supported managed base images, Lambda coordinates the snapshot lifecycle for you, and you register hooks through the normal runtime hooks API (Crac Resource for Java, snapstart hook decorators for Python, ISnapstartCallback for .NET). Use them for two things:
- Re-establish anything that must be per-environment unique or fresh after restore: reseed RNGs, drop cached temporary credentials, reopen non-SDK network connections.
- Prime hot paths before the snapshot: make one dummy call through your ORM, HTTP client, or serializer during init so the class loading and JIT warmup are captured in the snapshot. This is where most of the real latency win beyond raw init comes from.
The pricing model
SnapStart is not free on Python and .NET, and the cost structure is different from anything else in Lambda:
| Charge | Applies to | Notes |
|---|---|---|
| Caching | Python, .NET | Per published version with SnapStart on. Priced by memory size. Billed for a minimum of 3 hours and continues as long as the version exists. |
| Restoration | Python, .NET | Charged each time an execution environment is resumed from a snapshot. Priced by memory size. |
| Caching / Restoration | Java | No additional charge โ Java SnapStart is billed like a normal function. |
| Duration | All | You pay duration for init code, runtime load, and any runtime-hook code. You also pay again each time Lambda re-runs init to apply patches to the snapshot. |
The practical consequence: stale published versions cost real money on Python and .NET. Every version you publish and forget keeps its snapshot cached and billed. Wire a cleanup step into your deploy pipeline โ ListVersionsByFunction then DeleteFunction on anything an alias no longer points at.
SnapStart vs provisioned concurrency
| SnapStart | Provisioned concurrency | |
|---|---|---|
| Cold start | Sub-second in good cases, still variable | Double-digit milliseconds, consistent |
| Idle cost | Snapshot caching only (zero for Java) | Full price for every provisioned environment, 24/7 |
| Scale spikes | Covered automatically | Only up to the number you provisioned |
| Best for | Latency-sensitive APIs and data pipelines that scale up and down | Hard p99 cold-start SLAs that SnapStart can't meet |
They cannot both be on the same function. Most teams should try SnapStart first and reach for provisioned concurrency only when a measured p99 still misses the target.
Common mistakes to avoid
- Testing against
$LATEST. SnapStart only exists on published versions. Benchmark the version or alias. - Assuming init still runs per environment. Anything you did in init โ read a timestamp, generate a request ID, open a socket โ is frozen in the snapshot and shared across every resumed environment until you refresh it in a hook or the handler.
- Leaving
Math.random()/random/Randomseeded in init. Every environment restored from that snapshot produces the same sequence. Use a CSPRNG or reseed after restore. - Ignoring version sprawl on Python/.NET. Caching charges accrue silently per version.
- Forgetting the 512 MB
/tmpcap. Functions using large ephemeral storage will reject the SnapStart config.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
publish-version fails on a custom image | No /restore/next implementation and no snapstart="Allow" label | Add the Dockerfile label or implement the Runtime API contract |
OptimizationStatus: Off on a version | Version predates enabling SnapStart, or you're checking $LATEST | Publish a new version and check that one |
SnapStartNotReadyException (Java) | Version had no invokes for 14 days; snapshot was deleted | Retry after the version returns to Active; keep a warmer or synthetic canary hitting it |
Version stuck Inactive or Failed | Init code threw while Lambda was regenerating the snapshot for patching | Fix the init failure; check CloudWatch logs for the publish-time init run |
| Latency didn't improve | Init work is cheap; the cost is elsewhere (VPC ENI, first DB call in handler) | Move first-call warmup into init so it lands in the snapshot |
FAQ
Does SnapStart change my function code? Usually not. You may need runtime hooks if your init code creates unique state or non-SDK connections, but many functions work unchanged.
Is there a 14-day snapshot expiry for Python and .NET container images too? The documented 14-day delete-after-no-invocations behavior is called out specifically for Java runtimes. Regardless of runtime, keep a low-rate canary on production versions so you never resume a cold snapshot under real traffic.
Can I use SnapStart with a function URL or ALB target? Yes. Point the integration at an alias that targets the SnapStart-enabled version.
Does SnapStart help a function that's invoked a few times a day? Not much. SnapStart shines when environments scale up frequently. A rarely-invoked function still pays snapshot restore latency on most calls and, on Python/.NET, the caching charge.
Will my VPC-attached function get sub-second cold starts? SnapStart removes init latency, not ENI attachment latency. Modern Lambda VPC networking is fast, but measure your own p99 rather than assuming.
Key takeaways
| Point | Detail |
|---|---|
| Available since | September 2, 2026 |
| Works with | Container images on AWS base images for Java 11+, Python 3.12+, .NET 8+ |
| Enable with | --snap-start ApplyOn=PublishedVersions, then publish-version |
| Verify with | OptimizationStatus: On + State: Active on the version |
| Costs | Caching + restoration on Python/.NET (3h minimum caching); free on Java |
| Biggest pitfall | Shared snapshot state โ reseed RNGs, refresh credentials, reopen connections |
| Not compatible with | Provisioned concurrency, EFS, /tmp > 512 MB, $LATEST |
Further Reading
- AWS Lambda Response Streaming: Blazing Fast Serverless Apps in 2026!
- AWS Lambda Just Got a HUGE Upgrade: 32GB Memory & 16 vCPUs!
- Migrate AWS ECR From One AWS Region to Another AWS Region
- Official docs: Improving startup performance with Lambda SnapStart
- Official docs: Implementing SnapStart hooks for container images