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 .zip deployments 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 later
  • public.ecr.aws/lambda/python โ€” 3.12 or later
  • public.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:

ConstraintDetail
RuntimeJava 11+, Python 3.12+, .NET 8+ (managed runtimes or the matching AWS base images)
DeploymentPublished versions only โ€” SnapStart never applies to $LATEST
Provisioned concurrencyMutually exclusive with SnapStart on the same function
Amazon EFSNot supported
Ephemeral storage/tmp must be 512 MB or less
RegionsAll 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:

  1. Implements the SnapStart Runtime API contract itself โ€” call GET /runtime/restore/next after init to trigger snapshotting, then run after-restore logic before entering the invoke loop, gated on AWS_LAMBDA_INITIALIZATION_TYPE == "snap-start".
  2. 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:

ChargeApplies toNotes
CachingPython, .NETPer published version with SnapStart on. Priced by memory size. Billed for a minimum of 3 hours and continues as long as the version exists.
RestorationPython, .NETCharged each time an execution environment is resumed from a snapshot. Priced by memory size.
Caching / RestorationJavaNo additional charge โ€” Java SnapStart is billed like a normal function.
DurationAllYou 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

SnapStartProvisioned concurrency
Cold startSub-second in good cases, still variableDouble-digit milliseconds, consistent
Idle costSnapshot caching only (zero for Java)Full price for every provisioned environment, 24/7
Scale spikesCovered automaticallyOnly up to the number you provisioned
Best forLatency-sensitive APIs and data pipelines that scale up and downHard 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 / Random seeded 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 /tmp cap. Functions using large ephemeral storage will reject the SnapStart config.

Troubleshooting

SymptomCauseFix
publish-version fails on a custom imageNo /restore/next implementation and no snapstart="Allow" labelAdd the Dockerfile label or implement the Runtime API contract
OptimizationStatus: Off on a versionVersion predates enabling SnapStart, or you're checking $LATESTPublish a new version and check that one
SnapStartNotReadyException (Java)Version had no invokes for 14 days; snapshot was deletedRetry after the version returns to Active; keep a warmer or synthetic canary hitting it
Version stuck Inactive or FailedInit code threw while Lambda was regenerating the snapshot for patchingFix the init failure; check CloudWatch logs for the publish-time init run
Latency didn't improveInit 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

PointDetail
Available sinceSeptember 2, 2026
Works withContainer images on AWS base images for Java 11+, Python 3.12+, .NET 8+
Enable with--snap-start ApplyOn=PublishedVersions, then publish-version
Verify withOptimizationStatus: On + State: Active on the version
CostsCaching + restoration on Python/.NET (3h minimum caching); free on Java
Biggest pitfallShared snapshot state โ€” reseed RNGs, refresh credentials, reopen connections
Not compatible withProvisioned concurrency, EFS, /tmp > 512 MB, $LATEST

Further Reading