SOCI lazy loading lets a Lambda function on Lambda Managed Instances start before its container image has been fully downloaded. AWS announced it on October 28, 2026, and quotes startup times that are up to 70 percent shorter. I care less about that percentage than about two questions: who actually benefits from the extra build step, and where does it cause you trouble?
Container images are often the escape hatch on Lambda for anything that does not fit into a 250 MB ZIP package: ML libraries, models, PDF tools, entire runtimes. Those images quickly grow to several gigabytes. In this article, we build an example from scratch together: a small book recommendation service for a library, an image with four language models, the SOCI index, a CDK stack with a capacity provider and a script that lets you measure the effect yourself.
In short
SOCI places a table of contents next to your container image, so Lambda on Managed Instances only fetches the files your function actually reads. It pays off most for large images where each function needs only part of the content. The biggest pitfall is the format: Lambda accepts only a SOCI index v2 inside an image index with exactly two entries, and only single-architecture images.
What you will learn in this article
- What Lambda Managed Instances are and when they fit your workload
- How SOCI lazy loading fetches files on demand
- How to create a SOCI index with nerdctl and push it to ECR
- How to set up a capacity provider and function with AWS CDK in TypeScript
- How to measure startup times with and without SOCI yourself
What are Lambda Managed Instances?
Lambda Managed Instances (LMI for short) are a second way to run Lambda functions. Instead of the shared Lambda fleet, your functions run on EC2 instances in your own account. Lambda still takes care of launching, patching, routing and scaling those instances. You only decide where they may run and on which hardware.
The central building block is the capacity provider. It describes the VPC, subnets, security groups, architecture, allowed instance types and an upper limit for vCPUs. You then attach a function to that provider and publish a version. Only when you publish does Lambda launch instances, three by default for spreading across Availability Zones, plus three execution environments, before the version counts as ACTIVE.
How it differs from regular Lambda
| Lambda (default) | Lambda Managed Instances | |
|---|---|---|
| Invocations per environment | one at a time | several at the same time |
| Isolation | Firecracker microVMs on a shared fleet | containers on EC2 Nitro instances in your account |
| Billing | per request and duration | EC2 price plus a 15% management fee |
| Scaling | on demand, down to zero | asynchronously based on CPU utilization, not to zero |
| Good fit for | irregular traffic | high, predictable traffic |
For Node.js, LMI uses worker threads, and initialization runs once per worker thread. That matters for our example: if you load a large model into memory, expect it to sit in memory once per thread. In addition, concurrent requests in the same thread share the global state. Anything you create outside the handler should therefore only be read and never modified.
If you want to refresh the regular Lambda basics first, my article Deploy a Lambda function with CDK and TypeScript covers the standard path with a ZIP package.
Why large images slow down Managed Instances
Without SOCI, Lambda has to download and unpack the complete image on every instance before an execution environment can start there. That does not happen just once when you publish. It also happens whenever the capacity provider launches new instances because load is rising. With a 5 GB image, you are mostly waiting on network and disk during this phase.
The catch: a large part of that data is often never read. An image with PyTorch, several models and half an operating system contains far more than a single invocation needs. This is where SOCI lazy loading helps.
How SOCI lazy loading works
SOCI stands for Seekable OCI. OCI is the open standard for container images. The project comes from AWS and is open source under Apache-2.0. SOCI does not change your image. Instead, it creates an additional index that records for each file in which layer and at which position it is stored. With this table of contents, Lambda can fetch individual files from ECR instead of downloading everything in one go.
On Lambda Managed Instances, the flow looks like this: you push the image and the SOCI index together to ECR under a separate tag. That tag points to an image index with two entries, the regular image manifest and the SOCI index. If your function points to this tag, Lambda detects the index when you publish the version and turns on lazy loading. There are no further settings.
🎬 Startup with and without SOCI – this animated diagram only runs on my site: watch it on sascha-ms.de
When the files are fetched
Files your init code reads are fetched by Lambda during initialization. Anything the handler touches only later gets pulled in during that invocation, which then takes correspondingly longer. According to the documentation, Lambda does not download the rest of the image in the background. That gives you the most important rule for your code: import every library and read every file you need in the init phase. Then your invocation durations stay consistent.
From a plain image to an image index with a SOCI index: five steps to the function.
Prerequisites for SOCI lazy loading
- An AWS account in a Region where Lambda Managed Instances are available, and AWS CLI v2
- Node.js 22 or later, Docker with buildx and AWS CDK v2 (here aws-cdk-lib 2.275.0)
- For the SOCI step, a Linux machine with containerd, for example a small EC2 instance with Amazon Linux 2023 and a Graviton processor
- A function packaged as a container image for one architecture, here
arm64 - The jq command line tool and basic knowledge of TypeScript and CDK
A quick glossary: ECR is the AWS container registry. nerdctl is a Docker-like command line tool for containerd, and the SOCI snapshotter is the containerd extension that can create and read SOCI indexes.
Setting up SOCI lazy loading step by step
Our example is a recommendation service for a library. For every book ID, it returns the five most similar books. It computes the similarity from vectors stored in one binary file per language. The image contains models for German, English, French and Spanish, but the function reads only one of them. I chose this on purpose, because exactly this pattern shows up in real images all the time.
Step 1: Create the project
mkdir lmi-soci-library && cd lmi-soci-library
mkdir -p src scripts lib bin test
npm init -y
Replace the generated package.json with this version. "type": "module" makes Node.js load the compiled files as ES modules, including inside the container.
{
"name": "lmi-soci-library",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.build.json",
"models": "node --experimental-strip-types scripts/make-models.ts",
"test": "node --experimental-strip-types --test test/*.test.ts",
"measure": "node --experimental-strip-types scripts/measure-start.ts"
},
"dependencies": {},
"devDependencies": {
"@aws-sdk/client-lambda": "3.1159.0",
"@types/node": "^22.19.0",
"aws-cdk": "2.1146.1",
"aws-cdk-lib": "2.275.0",
"constructs": "^10.4.2",
"typescript": "7.0.2"
}
}
You run the scripts under scripts/ and the tests directly from TypeScript, using the Node.js 22 option --experimental-strip-types. For the container, on the other hand, you compile to dist/. Two small configuration files handle this:
// tsconfig.json
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"skipLibCheck": true,
"types": ["node"],
"outDir": "dist"
},
"include": ["src", "scripts", "lib", "bin", "test"]
}
// tsconfig.build.json
{
"extends": "./tsconfig.json",
"compilerOptions": { "rootDir": "src", "outDir": "dist" },
"include": ["src"]
}
npm install
Step 2: Load and compare vectors
The module reads a file in the format “magic bytes, count, dimensions, values” and compares books using cosine similarity. Cosine similarity measures how closely two vectors point in the same direction: 1 means very similar, 0 means unrelated.
// src/vectors.ts
import { readFileSync } from 'node:fs';
export interface BookVectors {
count: number;
dims: number;
data: Float32Array;
}
const MAGIC = 'BKV1';
const HEADER_BYTES = 12;
/** Writes vectors in a simple binary format: "BKV1", count, dims, float32 values. */
export function encodeVectors(v: BookVectors): Buffer {
const header = Buffer.alloc(HEADER_BYTES);
header.write(MAGIC, 0, 'ascii');
header.writeUInt32LE(v.count, 4);
header.writeUInt32LE(v.dims, 8);
const body = Buffer.from(v.data.buffer, v.data.byteOffset, v.data.byteLength);
return Buffer.concat([header, body]);
}
export function decodeVectors(buf: Buffer): BookVectors {
if (buf.subarray(0, 4).toString('ascii') !== MAGIC) {
throw new Error('Unknown file format: magic bytes missing');
}
const count = buf.readUInt32LE(4);
const dims = buf.readUInt32LE(8);
const expected = HEADER_BYTES + count * dims * 4;
if (buf.byteLength !== expected) {
throw new Error(`File size ${buf.byteLength} does not match ${expected} bytes`);
}
// Copy into an aligned buffer so Float32Array works on every offset
const data = new Float32Array(count * dims);
data.set(new Float32Array(buf.buffer.slice(buf.byteOffset + HEADER_BYTES, buf.byteOffset + expected)));
return { count, dims, data };
}
export function loadVectors(path: string): BookVectors {
return decodeVectors(readFileSync(path));
}
function cosine(v: BookVectors, a: number, b: number): number {
let dot = 0;
let na = 0;
let nb = 0;
const oa = a * v.dims;
const ob = b * v.dims;
for (let i = 0; i < v.dims; i++) {
const x = v.data[oa + i];
const y = v.data[ob + i];
dot += x * y;
na += x * x;
nb += y * y;
}
return na === 0 || nb === 0 ? 0 : dot / Math.sqrt(na * nb);
}
export interface Recommendation {
bookId: number;
score: number;
}
/** Returns the books most similar to bookId (without bookId itself). */
export function similarBooks(v: BookVectors, bookId: number, limit = 5): Recommendation[] {
if (!Number.isInteger(bookId) || bookId < 0 || bookId >= v.count) {
throw new RangeError(`bookId must be between 0 and ${v.count - 1}`);
}
const best: Recommendation[] = [];
for (let other = 0; other < v.count; other++) {
if (other === bookId) continue;
const score = cosine(v, bookId, other);
if (best.length < limit || score > best[best.length - 1].score) {
best.push({ bookId: other, score });
best.sort((x, y) => y.score - x.score);
if (best.length > limit) best.pop();
}
}
return best.map((r) => ({ bookId: r.bookId, score: Math.round(r.score * 1000) / 1000 }));
}
Step 3: A handler that loads in the init phase
The handler loads the model at the top level, which means during initialization. With SOCI, this is the moment Lambda fetches the file from ECR. If the call to loadVectors lived inside the handler, the first invocation of every environment would take noticeably longer.
// src/handler.ts
import { loadVectors, similarBooks, type Recommendation } from './vectors.ts';
// Load in the init phase: with SOCI, Lambda fetches the file here
// and not during the first invocation.
const MODEL_FILE = process.env.MODEL_FILE ?? '/opt/models/books-de.bin';
const started = Date.now();
const vectors = loadVectors(MODEL_FILE);
console.log(JSON.stringify({ msg: 'model loaded', file: MODEL_FILE, books: vectors.count, ms: Date.now() - started }));
export interface RecommendEvent {
bookId: number;
limit?: number;
}
export interface RecommendResult {
model: string;
recommendations: Recommendation[];
}
// Read-only shared state: safe even when an execution environment
// handles several invocations at the same time.
export async function handler(event: RecommendEvent): Promise<RecommendResult> {
const limit = Math.min(Math.max(event.limit ?? 5, 1), 20);
return { model: MODEL_FILE, recommendations: similarBooks(vectors, event.bookId, limit) };
}
Step 4: Generate test data and check locally
The script creates four model files with random values. With BOOKS=200000, every file grows to roughly 100 MB, and the difference at startup becomes clearly visible. Small files are enough for the first test.
// scripts/make-models.ts
// Generates one vector file per language. Only one of them is used per function –
// exactly the situation in which SOCI lazy loading pays off.
import { mkdirSync, writeFileSync } from 'node:fs';
import { encodeVectors } from '../src/vectors.ts';
const count = Number(process.env.BOOKS ?? 20000);
const dims = Number(process.env.DIMS ?? 128);
const languages = (process.env.LANGS ?? 'de,en,fr,es').split(',');
mkdirSync('models', { recursive: true });
let seed = 42;
const random = () => {
seed = (seed * 1664525 + 1013904223) % 2 ** 32;
return seed / 2 ** 32 - 0.5;
};
for (const lang of languages) {
const data = new Float32Array(count * dims);
for (let i = 0; i < data.length; i++) data[i] = random();
const file = `models/books-${lang}.bin`;
const buf = encodeVectors({ count, dims, data });
writeFileSync(file, buf);
console.log(`${file}: ${(buf.byteLength / 1024 / 1024).toFixed(1)} MB`);
}
BOOKS=5000 npm run models
npm test
npm run build
MODEL_FILE=models/books-de.bin node -e "import('./dist/handler.js').then(async (m) => console.log(await m.handler({ bookId: 7, limit: 3 })))"
Expected result: four files of 2.4 MB each, four passing tests and an output with three recommendations. Book IDs and scores are random; the structure looks like this:
{"msg":"model loaded","file":"models/books-de.bin","books":5000,"ms":4}
{
model: 'models/books-de.bin',
recommendations: [
{ bookId: 37, score: 0.337 },
{ bookId: 1965, score: 0.311 },
{ bookId: 3426, score: 0.274 }
]
}
Step 5: Build the image for arm64 and push it to ECR
The Dockerfile uses the official Lambda base image for Node.js 24. The models end up in /opt/models.
# Dockerfile
FROM public.ecr.aws/lambda/nodejs:24
# Compiled code plus package.json (for "type": "module")
COPY package.json ${LAMBDA_TASK_ROOT}/
COPY dist/ ${LAMBDA_TASK_ROOT}/
# All language models – each function only reads one of them
COPY models/ /opt/models/
CMD ["handler.handler"]
Two build options matter here. --platform linux/arm64, because Lambda accepts only single-architecture images. And --provenance=false, because buildx would otherwise attach an attestation to the image and turn it into an image index with several entries. For the SOCI step, you want a plain image without such extras.
BOOKS=200000 npm run models && npm run build
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=eu-central-1
REPO=library-recommender
REGISTRY=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
aws ecr create-repository --repository-name $REPO --region $REGION
aws ecr get-login-password --region $REGION | docker login -u AWS --password-stdin $REGISTRY
docker buildx build --platform linux/arm64 --provenance=false --sbom=false \
-t $REGISTRY/$REPO:v1 --push .
Step 6: Create the SOCI index
You run this step on a Linux machine with containerd. The easiest option is a small Graviton instance with Amazon Linux 2023, because its architecture already matches the image. The instance needs a role with read and write access to the ECR repository.
sudo yum install -y soci-snapshotter containerd nerdctl
sudo systemctl start soci-snapshotter
sudo systemctl restart containerd
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=eu-central-1
REPO=library-recommender
REGISTRY=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
aws ecr get-login-password --region $REGION | sudo nerdctl login -u AWS --password-stdin $REGISTRY
sudo nerdctl pull --platform linux/arm64 $REGISTRY/$REPO:v1
sudo nerdctl image convert --soci --platform linux/arm64 $REGISTRY/$REPO:v1 $REGISTRY/$REPO:v1-soci
sudo nerdctl push --platform linux/arm64 $REGISTRY/$REPO:v1-soci
nerdctl image convert --soci produces an index in format v2, and that is the only format Lambda accepts. When you follow older guides on the web, check which format they produce: Lambda does not support indexes in format v1.
Step 7: Check the result in ECR
Before you touch the function, take a look at the new tag. The manifest behind v1-soci must have exactly two entries, and one of them must be the SOCI index.
aws ecr batch-get-image --repository-name $REPO --region $REGION \
--image-ids imageTag=v1-soci \
--accepted-media-types application/vnd.oci.image.index.v1+json \
--query 'images[0].imageManifest' --output text | jq '.manifests | length, [.[] | .artifactType // .mediaType]'
Expected result: the first line shows 2, and the list contains an entry with soci in its name. If you see a higher number, attestations or additional platforms are still attached to the image. In that case, rebuild the image as in step 5.
Step 8: Capacity provider and function with CDK
Since version 2.235.0, aws-cdk-lib has a dedicated CapacityProvider construct. If you do not pass your own, it also creates the operator role with the managed policy AWSLambdaManagedEC2ResourceOperator. You attach a function to the provider with addFunction. The function is then published automatically as $LATEST.PUBLISHED, so you do not have to create a version by hand.
// lib/library-stack.ts
import { CfnOutput, Duration, Stack, type StackProps } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import type { Construct } from 'constructs';
export interface LibraryStackProps extends StackProps {
repositoryName: string;
/** Tag of the SOCI-indexed image, e.g. "v1-soci" */
imageTag: string;
/** Optional: original tag (e.g. "v1") for a comparison function without SOCI */
plainImageTag?: string;
}
export class LibraryStack extends Stack {
constructor(scope: Construct, id: string, props: LibraryStackProps) {
super(scope, id, props);
// Private subnets with a NAT gateway so the instances can reach ECR and CloudWatch
const vpc = new ec2.Vpc(this, 'LibraryVpc', { maxAzs: 3, natGateways: 1 });
const sg = new ec2.SecurityGroup(this, 'InstancesSg', { vpc, allowAllOutbound: true });
const capacity = new lambda.CapacityProvider(this, 'LibraryCapacity', {
subnets: vpc.privateSubnets,
securityGroups: [sg],
architectures: [lambda.Architecture.ARM_64],
maxVCpuCount: 12,
});
const repo = ecr.Repository.fromRepositoryName(this, 'Repo', props.repositoryName);
const addRecommender = (id: string, tag: string) => {
const fn = new lambda.DockerImageFunction(this, id, {
code: lambda.DockerImageCode.fromEcr(repo, { tagOrDigest: tag }),
architecture: lambda.Architecture.ARM_64,
memorySize: 4096,
timeout: Duration.seconds(30),
environment: { MODEL_FILE: '/opt/models/books-de.bin' },
});
capacity.addFunction(fn, {
perExecutionEnvironmentMaxConcurrency: 8,
latestPublishedScalingConfig: { minExecutionEnvironments: 3, maxExecutionEnvironments: 6 },
});
new CfnOutput(this, `${id}Name`, { value: fn.functionName });
return fn;
};
addRecommender('Recommender', props.imageTag);
if (props.plainImageTag) {
addRecommender('RecommenderPlain', props.plainImageTag);
}
}
}
// bin/app.ts
import { App } from 'aws-cdk-lib';
import { LibraryStack } from '../lib/library-stack.ts';
const app = new App();
new LibraryStack(app, 'LibraryRecommender', {
repositoryName: app.node.tryGetContext('repo') ?? 'library-recommender',
imageTag: app.node.tryGetContext('tag') ?? 'v1-soci',
plainImageTag: app.node.tryGetContext('plainTag'),
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
// cdk.json
{
"app": "node --experimental-strip-types bin/app.ts"
}
A few values in the stack are deliberate: according to CDK, maxVCpuCount must be between 12 and 15,000. perExecutionEnvironmentMaxConcurrency: 8 allows eight concurrent invocations per environment, which is rather generous for our compute-heavy handler. For pure CPU work, more concurrency per vCPU does not help much; more memory, and with it more vCPUs per function, helps more.
npx cdk bootstrap
npx cdk deploy -c repo=library-recommender -c tag=v1-soci -c plainTag=v1
With -c plainTag=v1, the stack creates a second function that points to the image without a SOCI index. You need it for the comparison in the next step. The deployment takes a few minutes, because Lambda launches the instances.
Step 9: Measure startup time with and without SOCI
The script publishes a new version for each function and measures the time until it is Active. It is a rough but honest measurement: it includes pulling the image and initializing the environments.
// scripts/measure-start.ts
// Publishes a new version per function and measures the time until it is "Active".
// Usage: npm run measure -- recommender-plain recommender-soci
import {
GetFunctionConfigurationCommand,
LambdaClient,
PublishVersionCommand,
} from '@aws-sdk/client-lambda';
import { setTimeout as sleep } from 'node:timers/promises';
const client = new LambdaClient({});
async function timeToActive(functionName: string): Promise<number> {
const start = Date.now();
const { Version } = await client.send(new PublishVersionCommand({ FunctionName: functionName }));
for (;;) {
const cfg = await client.send(
new GetFunctionConfigurationCommand({ FunctionName: functionName, Qualifier: Version }),
);
if (cfg.State === 'Active') return (Date.now() - start) / 1000;
if (cfg.State === 'Failed') throw new Error(`${functionName}:${Version} failed: ${cfg.StateReason}`);
await sleep(5000);
}
}
const names = process.argv.slice(2);
if (names.length === 0) {
console.error('Please pass at least one function name');
process.exit(1);
}
for (const name of names) {
const seconds = await timeToActive(name);
console.log(`${name}: active after ${seconds.toFixed(0)} s`);
}
npm run measure -- <name of the plain function> <name of the SOCI function>
You will find the function names in the outputs of cdk deploy. Measure several times and compare the averages, because network and placement vary. It is also worth checking CloudWatch Logs for the model loaded line and the duration of each first invocation. If you measure numbers with your own image, I would love to read about them in the comments.
Common mistakes with SOCI lazy loading
-
The function points to the old tag. The image URI must point to
v1-soci, not tov1. Otherwise everything works, just without lazy loading. -
Index in the wrong format. Lambda supports only SOCI Index Manifest v2. Check for the media type
application/vnd.amazon.soci.index.v2+json. - Too many entries in the image index. Attestations, SBOMs or a multi-arch image lead to more than two entries in the index.
- Files read only in the handler. Then the first invocation of every environment pays the loading time, and your latency metrics get outliers.
-
Mismatched architecture. Capacity provider, function and image must share the same architecture, here
arm64everywhere. -
New image, old index. Every change to the image needs a new SOCI tag, for example
v2-soci, and an updated image URI.
Costs and security
The announcement does not mention a separate price for SOCI itself. The index is stored as an additional artifact in ECR and is small compared to the image. The real costs come from Lambda Managed Instances: you pay the EC2 price of the instances plus a 15 percent management fee. Savings Plans and Reserved Instances apply only to the EC2 part. In this example, you also pay for a NAT gateway and the EC2 instance used for the SOCI step. Clean up after testing:
npx cdk destroy
aws ecr delete-repository --repository-name library-recommender --region eu-central-1 --force
On the security side, the capacity provider is the security boundary. Functions that share a provider run as containers on the same instances. Separate functions with different protection needs by giving them their own capacity providers. The operator role needs only the managed policy, and the role of the build instance needs access to just the one repository.
My take
SOCI lazy loading solves a real problem, but a narrow one. It helps only container image functions on Lambda Managed Instances, not the classic Lambda most of us use every day. If you run large images there, an extra build step gets you faster starts when publishing and when scaling out. That is mainly interesting for ML workloads, where an image ships many models or libraries.
Where I am cautious: the build process gets more complicated. You need containerd and nerdctl, a Linux machine with the matching architecture and a second tagging scheme. That belongs in the CI pipeline, not on someone’s laptop. And lazy loading is no substitute for lean images. If your function ends up reading almost everything anyway, you gain little. My advice: clean up the image first, then measure, then add SOCI. By the way, for Java functions on regular Lambda, SnapStart remains the tool of choice for startup times.
FAQ
Does SOCI lazy loading work with regular Lambda?
No. As of late October 2026, Lambda supports SOCI only for functions that are packaged as container images and run on Lambda Managed Instances.
Do I have to change my function code for SOCI?
Not necessarily. You should, however, load all libraries and files in the init phase, so the first invocation of an environment is not slowed down by on-demand fetching.
Can I create the SOCI index with Docker?
The AWS documentation shows the path with containerd, nerdctl and the SOCI snapshotter. Docker alone does not create a SOCI index, so you need an environment with containerd for this step.
How much faster does my function start with SOCI?
AWS quotes startup times that are up to 70 percent shorter. How much you gain depends on which share of the image is actually read. Measure it with and without the index.
Your next step
Take your largest container image and find out which share of it is really read at startup. If it is less than half, and you are considering Lambda Managed Instances anyway, SOCI is a good candidate for an experiment. What makes your Lambda images so large: models, libraries or something else entirely?
Sources
- AWS What’s New: AWS Lambda supports SOCI lazy loading for Lambda Managed Instances (Oct 28, 2026)
- AWS docs: Lazy-load container images with Seekable OCI (SOCI)
- AWS docs: Lambda Managed Instances
- AWS docs: Getting started with Lambda Managed Instances
- AWS docs: Node.js runtime for Lambda Managed Instances
- AWS docs: Lambda Managed Instances runtimes
- AWS Compute Blog: Build high-performance apps with AWS Lambda Managed Instances
- GitHub: awslabs/soci-snapshotter
- AWS CDK API Reference: aws_lambda.CapacityProvider
This post was created with AI assistance.
👉 Next step: Curious what your own Lambda functions cost? Try my Lambda cost calculator – it runs right in your browser.
Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de/en

Top comments (0)