Deploy AI app infrastructure on AWS Bedrock with Pulumi

Provision a minimal HTTP endpoint, cloud logs, secure config, and least-privilege Amazon Bedrock invocation for an AI app on AWS Bedrock.

Switch variant

Choose a different cloud.

Download blueprint

Get this AWS Bedrock blueprint project as a zip. Switch Pulumi language here to keep the download aligned with the install commands and blueprint program on the page.

Download the TypeScript blueprint with the matching Pulumi program, dependency files, and README.

Download TypeScript blueprint

Download the Python blueprint with the matching Pulumi program, dependency files, and README.

Download Python blueprint

This solution deploys the infrastructure around a small HTTP AI application: a runtime endpoint, logs, secure configuration, and a runtime identity allowed to invoke one managed model service. It keeps the blueprint small so you can swap in your own handler code without first adopting an application framework.

Use it when you need a provider-native starting point for a production-facing AI request path, not a data or training platform. The app receives a JSON request, forwards the prompt to the selected managed model service, returns the model text, and writes platform logs for operations.

Architecture

The stack has four pieces:

  1. An HTTP endpoint on AWS Lambda with a Function URL.
  2. A runtime identity with the smallest model-service permission practical for Amazon Bedrock.
  3. Secure configuration through Pulumi config and Lambda environment variables.
  4. Native request and application logs in Amazon CloudWatch Logs.

The generated app code is small. Replace the sample prompt handler with your application logic after the stack proves that identity, model access, logs, and endpoint routing work in your account.

AWS Bedrock notes

This variant uses AWS Bedrock as the managed model backend and keeps the application endpoint provider-native. Account-specific model access is configured outside source code.

Prerequisites

You need:

  • a Pulumi account and the Pulumi CLI
  • an AWS account with Bedrock model access enabled for the configured model and permission to create Lambda, IAM, and CloudWatch Logs resources
  • local cloud credentials for the selected provider

Download the blueprint

Use the Download blueprint button at the top of this page to grab the AWS Bedrock zip for the language selected in the chooser. Each zip contains:

  • index.ts as the Pulumi entrypoint
  • components/ai-app.ts as the reusable component
  • runtime app code under the provider-specific folder
  • package.json and tsconfig.json for the Pulumi project
  • __main__.py as the Pulumi entrypoint
  • components/ai_app.py as the reusable component
  • runtime app code under the provider-specific folder
  • requirements.txt for the Pulumi project

Unzip, change into the directory, and continue with the quickstart below.

Quickstart

Install Pulumi project dependencies, configure the stack, and deploy. This solution currently ships TypeScript and Python starters.

# 1. Install Pulumi project dependencies
npm install

# 2. Initialize and configure the stack
pulumi stack init dev
pulumi config set aws:region us-east-1
pulumi config set modelId anthropic.claude-haiku-4-5-20251001-v1:0

# 3. Deploy
pulumi up
# 1. Install Pulumi project dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# 2. Initialize and configure the stack
pulumi stack init dev
pulumi config set aws:region us-east-1
pulumi config set modelId anthropic.claude-haiku-4-5-20251001-v1:0

# 3. Deploy
pulumi up

Bedrock model access is regional. The default model ID is an Anthropic Claude model commonly available through Amazon Bedrock in us-east-1; confirm access in the Bedrock console before pulumi up.

Walk through the stack

The component creates AWS Lambda with a Function URL and exports endpointUrl so you can test the request path immediately after pulumi up. The sample handler accepts a JSON body with a prompt field and returns a JSON response with generated text or an operational error from the provider SDK.

Lambda invokes Bedrock through the runtime role using bedrock:InvokeModel scoped to the configured foundation-model ARN.

Keep prompts and generated output out of stack outputs. Runtime values belong in request bodies and platform logs, not Pulumi state.

Sample request handler

lambda/index.mjs

The handler the Lambda Function URL invokes. It forwards the prompt to Amazon Bedrock with InvokeModel and returns the model text. This same lambda/ directory ships in the TypeScript and Python starters.

import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
const client = new BedrockRuntimeClient({ region: process.env.AWS_REGION });
export const handler = async (event) => {
  const body = event.body ? JSON.parse(event.body) : {};
  const prompt = body.prompt || "Say hello from Bedrock.";
  const payload = { anthropic_version: "bedrock-2023-05-31", max_tokens: 256, messages: [{ role: "user", content: [{ type: "text", text: prompt }] }] };
  const response = await client.send(new InvokeModelCommand({ modelId: process.env.MODEL_ID, contentType: "application/json", accept: "application/json", body: JSON.stringify(payload) }));
  const result = JSON.parse(new TextDecoder().decode(response.body));
  return { statusCode: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ text: result.content?.[0]?.text ?? "" }) };
};

Operate the endpoint

After deployment:

curl -X POST "$(pulumi stack output endpointUrl)" \
  -H "content-type: application/json" \
  -d '{"prompt":"Write one sentence about infrastructure as code."}'

Open endpointUrl, send a small JSON body, and inspect logResource in CloudWatch Logs.

Warning: The generated endpoint is public and unauthenticated by default. Anyone with the URL can call it, and leaked URLs can create model and token spend. Before production use, add auth, request validation, rate limits, and provider quota alarms.

This blueprint stays focused on runtime infrastructure and model invocation permissions.

Blueprint Pulumi program

The entrypoint reads stack config, creates the Amazon Bedrock application component, and exports the HTTP endpoint plus observability handles.

import * as pulumi from "@pulumi/pulumi";
import { AiApp } from "./components/ai-app";
const config = new pulumi.Config();
const tags = { "pulumi:template": "ai-app-infrastructure", "pulumi:cloud": "aws-bedrock", "pulumi:language": "typescript" };
const app = new AiApp("ai-app", { namePrefix: `${pulumi.getProject()}-${pulumi.getStack()}`, modelId: config.get("modelId") || "anthropic.claude-haiku-4-5-20251001-v1:0",  tags });
export const endpointUrl = app.endpointUrl;
export const logResource = app.logResource;
export const runtimeIdentity = app.runtimeIdentity;
import pulumi
from components.ai_app import AiApp
config = pulumi.Config()
tags = {"pulumi:template": "ai-app-infrastructure", "pulumi:cloud": "aws-bedrock", "pulumi:language": "python"}
app = AiApp("ai-app", name_prefix=f"{pulumi.get_project()}-{pulumi.get_stack()}", model_id=config.get("modelId") or "anthropic.claude-haiku-4-5-20251001-v1:0",  tags=tags)
pulumi.export("endpointUrl", app.endpoint_url)
pulumi.export("logResource", app.log_resource)
pulumi.export("runtimeIdentity", app.runtime_identity)

Reusable AI application component

The component provisions AWS Lambda with a Function URL, Amazon CloudWatch Logs, secure configuration, and least-privilege runtime access to Amazon Bedrock.

components/ai-app.ts

Creates AWS Lambda with a Function URL, Amazon CloudWatch Logs, secure config, and model invocation IAM for AWS Bedrock.

import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
export interface AiAppArgs { namePrefix: string; modelId: string; tags: Record<string, string>; }
export class AiApp extends pulumi.ComponentResource {
  public readonly endpointUrl: pulumi.Output<string>; public readonly logResource: pulumi.Output<string>; public readonly runtimeIdentity: pulumi.Output<string>;
  constructor(name: string, args: AiAppArgs, opts?: pulumi.ComponentResourceOptions) {
    super("guides:aiAppInfrastructure:AwsBedrock", name, {}, opts);
    const functionName = `${args.namePrefix}-ai`;
    const logGroup = new aws.cloudwatch.LogGroup(`${name}-logs`, { name: `/aws/lambda/${functionName}`, retentionInDays: 14, tags: args.tags }, { parent: this });
    const role = new aws.iam.Role(`${name}-role`, { assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "lambda.amazonaws.com" }), tags: args.tags }, { parent: this });
    new aws.iam.RolePolicyAttachment(`${name}-basic`, { role: role.name, policyArn: aws.iam.ManagedPolicy.AWSLambdaBasicExecutionRole }, { parent: this });
    const modelArn = pulumi.interpolate`arn:aws:bedrock:${aws.config.region}:*:foundation-model/${args.modelId}`;
    new aws.iam.RolePolicy(`${name}-bedrock`, { role: role.id, policy: pulumi.jsonStringify({ Version: "2012-10-17", Statement: [{ Effect: "Allow", Action: "bedrock:InvokeModel", Resource: modelArn }] }) }, { parent: this });
    const fn = new aws.lambda.Function(`${name}-function`, { name: functionName, role: role.arn, runtime: aws.lambda.Runtime.NodeJS20dX, handler: "index.handler", timeout: 60, memorySize: 512, code: new pulumi.asset.FileArchive("lambda"), environment: { variables: { MODEL_ID: args.modelId } }, tags: args.tags }, { parent: this, dependsOn: [logGroup] });
    const url = new aws.lambda.FunctionUrl(`${name}-url`, { functionName: fn.name, authorizationType: "NONE", cors: { allowOrigins: ["*"], allowMethods: ["POST"], allowHeaders: ["content-type"] } }, { parent: this });
    this.endpointUrl = url.functionUrl; this.logResource = logGroup.name; this.runtimeIdentity = role.arn; this.registerOutputs({ endpointUrl: this.endpointUrl, logResource: this.logResource, runtimeIdentity: this.runtimeIdentity });
  }
}

components/ai_app.py

Creates AWS Lambda with a Function URL, Amazon CloudWatch Logs, secure config, and model invocation IAM for AWS Bedrock.

import json
import pulumi
import pulumi_aws as aws
class AiApp(pulumi.ComponentResource):
    def __init__(self, name, name_prefix, model_id, tags, opts=None):
        super().__init__("guides:aiAppInfrastructure:AwsBedrock", name, None, opts)
        function_name = f"{name_prefix}-ai"
        log_group = aws.cloudwatch.LogGroup(f"{name}-logs", name=f"/aws/lambda/{function_name}", retention_in_days=14, tags=tags, opts=pulumi.ResourceOptions(parent=self))
        role = aws.iam.Role(f"{name}-role", assume_role_policy=json.dumps({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}), tags=tags, opts=pulumi.ResourceOptions(parent=self))
        aws.iam.RolePolicyAttachment(f"{name}-basic", role=role.name, policy_arn=aws.iam.ManagedPolicy.AWS_LAMBDA_BASIC_EXECUTION_ROLE, opts=pulumi.ResourceOptions(parent=self))
        model_arn = pulumi.Output.concat("arn:aws:bedrock:", aws.config.region, ":*:foundation-model/", model_id)
        aws.iam.RolePolicy(f"{name}-bedrock", role=role.id, policy=pulumi.Output.json_dumps({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"bedrock:InvokeModel","Resource":model_arn}]}), opts=pulumi.ResourceOptions(parent=self))
        fn = aws.lambda_.Function(f"{name}-function", name=function_name, role=role.arn, runtime=aws.lambda_.Runtime.NODE_JS20D_X, handler="index.handler", timeout=60, memory_size=512, code=pulumi.FileArchive("lambda"), environment=aws.lambda_.FunctionEnvironmentArgs(variables={"MODEL_ID": model_id}), tags=tags, opts=pulumi.ResourceOptions(parent=self, depends_on=[log_group]))
        function_url = aws.lambda_.FunctionUrl(f"{name}-url", function_name=fn.name, authorization_type="NONE", cors=aws.lambda_.FunctionUrlCorsArgs(allow_origins=["*"], allow_methods=["POST"], allow_headers=["content-type"]), opts=pulumi.ResourceOptions(parent=self))
        self.endpoint_url = function_url.function_url
        self.log_resource = log_group.name
        self.runtime_identity = role.arn
        self.register_outputs({"endpointUrl": self.endpoint_url, "logResource": self.log_resource, "runtimeIdentity": self.runtime_identity})

Frequently asked questions

Does this deploy model training or data pipelines?
No. The blueprint only deploys runtime infrastructure for invoking a managed foundation model from an HTTP endpoint.
Where do model identifiers and endpoints come from?
Each starter reads provider-specific Pulumi config. Defaults are safe examples where the provider has a public publisher model; account-specific endpoints stay in config or managed secrets.
Does the Azure variant create an Azure OpenAI account?
No. Azure OpenAI access, quota, and deployments are account-specific, so the starter stores references to an existing endpoint and deployment name in Key Vault and injects them into Azure Functions.
How are logs collected?
The runtime uses native platform logging. Lambda writes to CloudWatch Logs, Azure Functions writes through Application Insights, and Cloud Run writes to Cloud Logging.
How do I clean it up?
Run pulumi destroy from the same stack. Provider-managed log retention or externally supplied model deployments may need separate retention review.