Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lambda-managed-instances-bedrock-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.js
!src/*.js
*.d.ts
node_modules
cdk.out
package-lock.json
3 changes: 3 additions & 0 deletions lambda-managed-instances-bedrock-cdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.ts
!*.d.ts
cdk.out
85 changes: 85 additions & 0 deletions lambda-managed-instances-bedrock-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Lambda Managed Instances with Amazon Bedrock

This pattern deploys a Lambda function on [Managed Instances](https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html) (EC2-backed compute) that invokes Amazon Bedrock (Claude) for text generation.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-managed-instances-bedrock-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node and NPM](https://nodejs.org/en/download/) installed
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed

## How it works

Lambda Managed Instances combine EC2 flexibility with serverless simplicity:

- **EC2-backed compute**: Your function runs on dedicated EC2 instances in your VPC
- **Serverless management**: AWS handles provisioning, scaling, OS patching, and load balancing
- **Cost optimization**: Use Compute Savings Plans and Reserved Instances for Lambda workloads
- **Specialized hardware**: Access Graviton, GPU, and network-optimized instance types

This pattern adds Bedrock integration to demonstrate a real-world use case beyond hello-world.

## Architecture

```
┌──────────┐ ┌─────────────────────────────────────────────┐
│ Client │──────▶│ Lambda Function (Managed Instances / EC2) │
└──────────┘ │ ARM64 · 2048 MB · nodejs24.x │
└──────────────────┬──────────────────────────┘
│ VPC private subnet
│ NAT Gateway
┌─────────────────────────────────────────────┐
│ Amazon Bedrock (Claude Sonnet) │
│ InvokeModel via inference profile │
└─────────────────────────────────────────────┘
```
Comment on lines +28 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folder structure is not architecture

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair -- replaced with a proper flow diagram showing Client → Lambda (Managed Instances) → Bedrock.


**Note on networking:** This pattern uses a NAT Gateway for internet egress (including Bedrock API calls). For production workloads, consider adding a VPC interface endpoint for Bedrock (`com.amazonaws.{region}.bedrock-runtime`) to keep traffic on the AWS network and eliminate NAT costs.

## Deployment Instructions

1. Clone the repository and navigate to the pattern directory:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/lambda-managed-instances-bedrock-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

## Testing

Invoke the function:

```bash
aws lambda invoke \
--function-name managed-instances-bedrock-cdk --qualifier prod \
--payload '{"prompt":"Explain Lambda Managed Instances in 3 sentences"}' \
--cli-binary-format raw-in-base64-out \
output.json && cat output.json | python3 -m json.tool
```
Comment on lines +68 to +74
Copy link
Copy Markdown
Contributor

@parikhudit parikhudit May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the stack publishes a version (recommended in another comment), this invoke should target the published version

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the invoke command to target the prod alias with --qualifier prod.


## Cleanup

```bash
cdk destroy
```

----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
7 changes: 7 additions & 0 deletions lambda-managed-instances-bedrock-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { LambdaManagedInstancesBedrockStack } from "../lib/lambda-managed-instances-bedrock-stack";

const app = new cdk.App();
new LambdaManagedInstancesBedrockStack(app, "LambdaManagedInstancesBedrockStack");
3 changes: 3 additions & 0 deletions lambda-managed-instances-bedrock-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/app.ts"
}
51 changes: 51 additions & 0 deletions lambda-managed-instances-bedrock-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"title": "Lambda Managed Instances with Amazon Bedrock",
"description": "Run Amazon Bedrock model invocations on Lambda Managed Instances (EC2-backed compute) using AWS CDK, combining serverless simplicity with EC2 flexibility.",
"language": "TypeScript",
"level": "200",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys a Lambda function on Managed Instances (EC2-backed compute) that invokes Amazon Bedrock (Claude) for text generation.",
"Lambda Managed Instances provide EC2 flexibility (Graviton, GPU, specialized hardware) with serverless operational simplicity — AWS handles provisioning, scaling, and maintenance.",
"The function runs on dedicated EC2 capacity in your VPC via a CapacityProvider, enabling access to EC2 pricing models (Savings Plans, Reserved Instances) while maintaining the Lambda programming model."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-managed-instances-bedrock-cdk",
"templateURL": "serverless-patterns/lambda-managed-instances-bedrock-cdk",
"projectFolder": "lambda-managed-instances-bedrock-cdk",
"templateFile": "lib/lambda-managed-instances-bedrock-stack.ts"
}
},
"resources": {
"bullets": [
{ "text": "Lambda Managed Instances documentation", "link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances.html" },
{ "text": "Lambda Managed Instances launch blog", "link": "https://aws.amazon.com/blogs/aws/introducing-aws-lambda-managed-instances-serverless-simplicity-with-ec2-flexibility/" },
{ "text": "Amazon Bedrock documentation", "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/" }
]
},
"deploy": {
"text": ["cdk deploy"]
},
"testing": {
"text": ["See the README for testing instructions."]
},
"cleanup": {
"text": ["cdk destroy"]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
],
"patternArch": {
"icon1": { "x": 20, "y": 50, "service": "lambda", "label": "Lambda (Managed Instances)" },
"icon2": { "x": 80, "y": 50, "service": "bedrock", "label": "Amazon Bedrock" },
"line1": { "from": "icon1", "to": "icon2" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as logs from "aws-cdk-lib/aws-logs";
import { Construct } from "constructs";

export class LambdaManagedInstancesBedrockStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const modelId = new cdk.CfnParameter(this, "BedrockModelId", {
type: "String",
default: "us.anthropic.claude-sonnet-4-20250514-v1:0",
description: "Bedrock inference profile model ID",
});

const functionName = "managed-instances-bedrock-cdk";

const logGroup = new logs.LogGroup(this, "LogGroup", {
logGroupName: `/aws/lambda/${functionName}`,
retention: logs.RetentionDays.TWO_WEEKS,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});

const fn = new lambda.Function(this, "BedrockFn", {
runtime: lambda.Runtime.NODEJS_24_X,
handler: "index.handler",
code: lambda.Code.fromAsset("src"),
timeout: cdk.Duration.minutes(5),
memorySize: 2048, // Managed Instances require >= 2048 MB
architecture: lambda.Architecture.ARM_64,
functionName,
description: "Bedrock invocation on Lambda Managed Instances (EC2-backed)",
loggingFormat: lambda.LoggingFormat.JSON,
logGroup,
environment: { MODEL_ID: modelId.valueAsString },
});

fn.addToRolePolicy(
new iam.PolicyStatement({
actions: ["bedrock:InvokeModel"],
resources: [
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`,
`arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`,
`arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`,
`arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0`,
],
})
);

Comment on lines +42 to +51
Copy link
Copy Markdown
Contributor

@parikhudit parikhudit May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bedrock IAM resource list is too broad. arn:aws:bedrock:*::foundation-model/* permits InvokeModel against every foundation model in every region.
For the default cross-region inference profile us.anthropic.claude-sonnet-4-20250514-v1:0, scope to the destination regions only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point -- scoped it down to the three US regions that the cross-region inference profile routes to (us-east-1, us-east-2, us-west-2) instead of the wildcard.

// Published version required for Managed Instances execution
const version = fn.currentVersion;
const alias = new lambda.Alias(this, "ProdAlias", {
aliasName: "prod",
version,
});

// Lambda Managed Instances: EC2-backed compute with serverless management
const vpc = new ec2.Vpc(this, "ManagedInstancesVpc", {
maxAzs: 2,
natGateways: 1,
});

Comment on lines +60 to +64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VPC creates a NAT gateway and Bedrock traffic egresses through it. Adding interface endpoints removes the NAT requirement and keeps Bedrock traffic on the AWS network.
If endpoints are intentionally omitted to keep the example minimal, please call out the NAT trade-off in the README.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping it minimal intentionally -- added a note in the README calling out the NAT trade-off and recommending a Bedrock VPC endpoint for production.

const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
vpc,
description: "Lambda Managed Instances security group",
});

const capacityProvider = new lambda.CapacityProvider(this, "CapacityProvider", {
subnets: vpc.privateSubnets,
securityGroups: [securityGroup],
architectures: [lambda.Architecture.ARM_64],
});

capacityProvider.addFunction(fn);

new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName });
new cdk.CfnOutput(this, "FunctionArn", { value: fn.functionArn });
Copy link
Copy Markdown
Contributor

@parikhudit parikhudit May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function will not become ACTIVE on the capacity provider until a version is published. Per https://docs.aws.amazon.com/lambda/latest/dg/lambda-managed-instances-getting-started.html step 5: "To run your function on Lambda Managed Instances, you must publish a version."

Without this, cdk deploy may succeed but aws lambda invoke could return the in-place $LATEST and will not execute on Managed Instances.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right -- added fn.currentVersion + a prod alias. Without a published version the function stays on $LATEST and won't actually run on the capacity provider.

new cdk.CfnOutput(this, "AliasArn", { value: alias.functionArn, description: "Invoke this ARN to run on Managed Instances" });
new cdk.CfnOutput(this, "LogGroupName", { value: logGroup.logGroupName });
}
}
20 changes: 20 additions & 0 deletions lambda-managed-instances-bedrock-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "lambda-managed-instances-bedrock-cdk",
"version": "1.0.0",
"bin": {
"app": "bin/app.ts"
},
"scripts": {
"build": "tsc",
"cdk": "cdk"
},
"dependencies": {
"aws-cdk-lib": "^2.257.0",
"constructs": "10.4.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
"ts-node": "^10.9.0",
"typescript": "~5.7.0"
}
}
34 changes: 34 additions & 0 deletions lambda-managed-instances-bedrock-cdk/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");

const client = new BedrockRuntimeClient();
const MODEL_ID = process.env.MODEL_ID;
Copy link
Copy Markdown
Contributor

@parikhudit parikhudit May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If MODEL_ID is unset the handler would proceed with undefined and the SDK call would fail with an opaque ValidationException. Should fail fast at module load?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed -- added a throw at module load so it fails immediately with a clear message instead of a cryptic SDK error.

if (!MODEL_ID) throw new Error("MODEL_ID environment variable is required");

exports.handler = async (event) => {
const prompt = event.prompt || "What are the benefits of Lambda Managed Instances?";

const res = await client.send(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing exception handling in case of failed SDK calls

new InvokeModelCommand({
modelId: MODEL_ID,
contentType: "application/json",
accept: "application/json",
body: JSON.stringify({
anthropic_version: "bedrock-2023-05-31",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
})
);

const body = JSON.parse(new TextDecoder().decode(res.body));

return {
statusCode: 200,
body: JSON.stringify({
prompt,
response: body.content[0].text,
model: MODEL_ID,
usage: body.usage,
}),
};
};
22 changes: 22 additions & 0 deletions lambda-managed-instances-bedrock-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["es2020"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false
},
"exclude": ["cdk.out"]
}