TypeScript Deployment to Amazon Bedrock AgentCore Runtime
This guide covers deploying TypeScript-based Strands agents to Amazon Bedrock AgentCore Runtime using Express and Docker.
Prerequisites
Section titled “Prerequisites”- Node.js 20+
- Docker installed and running
- AWS CLI configured with valid credentials
- AWS account with appropriate permissions
- ECR repository access
Step 1: Project Setup
Section titled “Step 1: Project Setup”Create Project Structure
Section titled “Create Project Structure”mkdir my-agent-service && cd my-agent-servicenpm init -yInstall Dependencies
Section titled “Install Dependencies”Create or update your package.json with the following configuration and dependencies:
{ "name": "my-agent-service", "version": "1.0.0", "type": "module", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsc && node dist/index.js" }, "dependencies": { "@strands-agents/sdk": "latest", "@aws-sdk/client-bedrock-agentcore": "latest", "express": "^4.18.2", "zod": "^4.1.12" }, "devDependencies": { "@types/express": "^4.17.21", "typescript": "^5.3.3" }}Then install all dependencies:
npm installConfigure TypeScript
Section titled “Configure TypeScript”Create tsconfig.json:
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "outDir": "./dist", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["*.ts"], "exclude": ["node_modules", "dist"]}Step 2: Create Your Agent
Section titled “Step 2: Create Your Agent”Create index.ts with your agent implementation:
import { z } from 'zod'import * as strands from '@strands-agents/sdk'import express, { type Request, type Response } from 'express'
const PORT = process.env.PORT || 8080
// Define a custom toolconst calculatorTool = strands.tool({ name: 'calculator', description: 'Performs basic arithmetic operations', inputSchema: z.object({ operation: z.enum(['add', 'subtract', 'multiply', 'divide']), a: z.number(), b: z.number(), }), callback: (input): number => { switch (input.operation) { case 'add': return input.a + input.b case 'subtract': return input.a - input.b case 'multiply': return input.a * input.b case 'divide': return input.a / input.b } },})
// Configure the agent with Amazon Bedrockconst agent = new strands.Agent({ model: new strands.BedrockModel({ region: 'ap-southeast-2', // Change to your preferred region }), tools: [calculatorTool],})
const app = express()
// Health check endpoint (REQUIRED)app.get('/ping', (_, res) => res.json({ status: 'Healthy', time_of_last_update: Math.floor(Date.now() / 1000), }))
// Agent invocation endpoint (REQUIRED)// AWS sends binary payload, so we use express.raw middlewareapp.post('/invocations', express.raw({ type: '*/*' }), async (req, res) => { try { // Decode binary payload from AWS SDK const prompt = new TextDecoder().decode(req.body)
// Invoke the agent const response = await agent.invoke(prompt)
// Return response return res.json({ response }) } catch (err) { console.error('Error processing request:', err) return res.status(500).json({ error: 'Internal server error' }) }})
// Start serverapp.listen(PORT, () => { console.log(`🚀 AgentCore Runtime server listening on port ${PORT}`) console.log(`📍 Endpoints:`) console.log(` POST http://0.0.0.0:${PORT}/invocations`) console.log(` GET http://0.0.0.0:${PORT}/ping`)})Understanding the Endpoints
AgentCore Runtime requires your service to expose two HTTP endpoints, /ping and /invocations. See HTTP protocol contract for more details.
Step 3: Test Locally
Section titled “Step 3: Test Locally”Compile & Start server
npm run build
npm startTest health check
curl http://localhost:8080/pingTest invocation
echo -n "What is 5 plus 3?" | curl -X POST http://localhost:8080/invocations \ -H "Content-Type: application/octet-stream" \ --data-binary @-Step 4: Create Dockerfile
Section titled “Step 4: Create Dockerfile”Create a Dockerfile for deployment:
FROM --platform=linux/arm64 public.ecr.aws/docker/library/node:latest
WORKDIR /app
# Copy source codeCOPY . ./
# Install dependenciesRUN npm install
# Build TypeScriptRUN npm run build
# Expose portEXPOSE 8080
# Start the applicationCMD ["npm", "start"]Test Docker Build Locally
Section titled “Test Docker Build Locally”Build the image
docker build -t my-agent-service .Run the container
docker run -p 8081:8080 my-agent-serviceTest in another terminal
curl http://localhost:8081/pingStep 5: Create IAM Role
Section titled “Step 5: Create IAM Role”The agent runtime needs an IAM role with permissions to access Bedrock and other AWS services.
Option 1: Using a Script (Recommended)
Section titled “Option 1: Using a Script (Recommended)”The easiest way to create the IAM role is to use the provided script that automates the entire process.
Create a file create-iam-role.sh:
#!/bin/bash
# Script to create IAM role for AWS Bedrock AgentCore Runtime# Based on the CloudFormation AgentCoreRuntimeExecutionRole
set -e
# Get AWS Account ID and RegionACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)REGION=${AWS_REGION:-ap-southeast-2}
echo "Creating IAM role for Bedrock AgentCore Runtime..."echo "Account ID: ${ACCOUNT_ID}"echo "Region: ${REGION}"
# Role nameROLE_NAME="BedrockAgentCoreRuntimeRole"
# Create trust policy documentTRUST_POLICY=$(cat <<EOF{ "Version": "2012-10-17", "Statement": [ { "Sid": "AssumeRolePolicy", "Effect": "Allow", "Principal": { "Service": "bedrock-agentcore.amazonaws.com" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:SourceAccount": "${ACCOUNT_ID}" }, "ArnLike": { "aws:SourceArn": "arn:aws:bedrock-agentcore:${REGION}:${ACCOUNT_ID}:*" } } } ]}EOF)
# Create permissions policy documentPERMISSIONS_POLICY=$(cat <<EOF{ "Version": "2012-10-17", "Statement": [ { "Sid": "ECRImageAccess", "Effect": "Allow", "Action": [ "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer" ], "Resource": "arn:aws:ecr:${REGION}:${ACCOUNT_ID}:repository/*" }, { "Sid": "ECRTokenAccess", "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:DescribeLogStreams", "logs:CreateLogGroup" ], "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*" }, { "Effect": "Allow", "Action": "logs:DescribeLogGroups", "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*" }, { "Effect": "Allow", "Action": [ "xray:PutTraceSegments", "xray:PutTelemetryRecords", "xray:GetSamplingRules", "xray:GetSamplingTargets" ], "Resource": "*" }, { "Effect": "Allow", "Action": "cloudwatch:PutMetricData", "Resource": "*", "Condition": { "StringEquals": { "cloudwatch:namespace": "bedrock-agentcore" } } }, { "Sid": "BedrockModelAccess", "Effect": "Allow", "Action": [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream" ], "Resource": [ "arn:aws:bedrock:*::foundation-model/*", "arn:aws:bedrock:${REGION}:${ACCOUNT_ID}:*" ] } ]}EOF)
# Check if role already existsif aws iam get-role --role-name ${ROLE_NAME} 2>/dev/null; then echo "Role ${ROLE_NAME} already exists." echo "Role ARN: $(aws iam get-role --role-name ${ROLE_NAME} --query 'Role.Arn' --output text)" exit 0fi
# Create the IAM roleecho "Creating IAM role: ${ROLE_NAME}"aws iam create-role \ --role-name ${ROLE_NAME} \ --assume-role-policy-document "${TRUST_POLICY}" \ --description "Service role for AWS Bedrock AgentCore Runtime" \ --tags Key=ManagedBy,Value=Script Key=Purpose,Value=BedrockAgentCore
echo "Attaching permissions policy to role..."aws iam put-role-policy \ --role-name ${ROLE_NAME} \ --policy-name AgentCoreRuntimeExecutionPolicy \ --policy-document "${PERMISSIONS_POLICY}"
# Get the role ARNROLE_ARN=$(aws iam get-role --role-name ${ROLE_NAME} --query 'Role.Arn' --output text)
echo ""echo "✅ IAM Role created successfully!"echo ""echo "Role Name: ${ROLE_NAME}"echo "Role ARN: ${ROLE_ARN}"echo ""echo "Use this ARN in your create-agent-runtime command:"echo " --role-arn ${ROLE_ARN}"echo ""echo "You can also set it as an environment variable:"echo " export ROLE_ARN=${ROLE_ARN}"Make the script executable
chmod +x create-iam-role.shRun the script
./create-iam-role.shOr specify a different region
AWS_REGION=us-east-1 ./create-iam-role.shThe script will output the role ARN. Save this for the deployment steps.
Option 2: Using AWS Console
Section titled “Option 2: Using AWS Console”-
Go to IAM Console → Roles → Create Role
-
Select “Custom trust policy” and paste the trust policy above
-
Attach the required policies:
- AmazonBedrockFullAccess
- CloudWatchLogsFullAccess
- AWSXRayDaemonWriteAccess
-
Name the role
BedrockAgentCoreRuntimeRole
Step 6: Deploy to AWS
Section titled “Step 6: Deploy to AWS”Set Environment Variables
export ACCOUNTID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION=ap-southeast-2
// Set the IAM Role ARNexport ROLE_ARN=$(aws iam get-role \ --role-name BedrockAgentCoreRuntimeRole \ --query 'Role.Arn' \ --output text)
// New or Existing ECR repository nameexport ECR_REPO=my-agent-serviceCreate ECR Repository
Create a new ECR repo if it doesn’t yet exist
aws ecr create-repository \ --repository-name ${ECR_REPO} \ --region ${AWS_REGION}Build and Push Docker Image:
Login to ECR
aws ecr get-login-password --region ${AWS_REGION} | \ docker login --username AWS --password-stdin \ ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.comBuild, Tag, and Push
docker build -t ${ECR_REPO} .
docker tag ${ECR_REPO}:latest \ ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latest
docker push ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latestCreate AgentCore Runtime
aws bedrock-agentcore-control create-agent-runtime \ --agent-runtime-name my_agent_service \ --agent-runtime-artifact containerConfiguration={containerUri=${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latest} \ --role-arn ${ROLE_ARN} \ --network-configuration networkMode=PUBLIC \ --protocol-configuration serverProtocol=HTTP \ --region ${AWS_REGION}Verify Deployment Status
Section titled “Verify Deployment Status”Wait a minute for the runtime to reach “READY” status.
Get runtime ID from the create command output, then check status
aws bedrock-agentcore-control get-agent-runtime \ --agent-runtime-id my-agent-service-XXXXXXXXXX \ --region ${AWS_REGION} \ --query 'status' \ --output textYou can list all runtimes if needed:
aws bedrock-agentcore-control list-agent-runtimes --region ${AWS_REGION}Step 7: Test Your Deployment
Section titled “Step 7: Test Your Deployment”Create Test Script
Section titled “Create Test Script”Create invoke.ts:
Update the
YOUR_ACCOUNT_IDand theagentRuntimeArnto the variables we just saw
import { BedrockAgentCoreClient, InvokeAgentRuntimeCommand,} from '@aws-sdk/client-bedrock-agentcore'
const input_text = 'Calculate 5 plus 3 using the calculator tool'
const client = new BedrockAgentCoreClient({ region: 'ap-southeast-2',})
const input = { // Generate unique session ID runtimeSessionId: 'test-session-' + Date.now() + '-' + Math.random().toString(36).substring(7), // Replace with your actual runtime ARN agentRuntimeArn: 'arn:aws:bedrock-agentcore:ap-southeast-2:YOUR_ACCOUNT_ID:runtime/my-agent-service-XXXXXXXXXX', qualifier: 'DEFAULT', payload: new TextEncoder().encode(input_text),}
const command = new InvokeAgentRuntimeCommand(input)const response = await client.send(command)const textResponse = await response.response.transformToString()
console.log('Response:', textResponse)Run the Test
Section titled “Run the Test”npx tsx invoke.tsExpected output:
Response: {"response":{"type":"agentResult","stopReason":"endTurn","lastMessage":{"type":"message","role":"assistant","content":[{"type":"textBlock","text":"The result of 5 plus 3 is **8**."}]}}}Step 8: Update Your Deployment
Section titled “Step 8: Update Your Deployment”After making code changes, use this workflow to update your deployed agent.
Build TypeScript
npm run buildSet Environment Variables
export ACCOUNTID=$(aws sts get-caller-identity --query Account --output text)
export AWS_REGION=ap-southeast-2
export ECR_REPO=my-agent-serviceGet the IAM Role ARN
export ROLE_ARN=$(aws iam get-role --role-name BedrockAgentCoreRuntimeRole --query 'Role.Arn' --output text)Build new image
docker build -t ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latest . --no-cachePush to ECR
docker push ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latestUpdate runtime
(replace XXXXXXXXXX with your runtime ID)
aws bedrock-agentcore-control update-agent-runtime \ --agent-runtime-id "my-agent-service-XXXXXXXXXX" \ --agent-runtime-artifact "{\"containerConfiguration\": {\"containerUri\": \"${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPO}:latest\"}}" \ --role-arn "${ROLE_ARN}" \ --network-configuration "{\"networkMode\": \"PUBLIC\"}" \ --protocol-configuration serverProtocol=HTTP \ --region ${AWS_REGION}Wait a minute for the update to complete, then test with npx tsx invoke.ts.
Best Practices
Section titled “Best Practices”Development
- Test locally with Docker before deploying
- Use TypeScript strict mode for better type safety
- Include error handling in all endpoints
- Log important events for debugging
Deployment
- Keep IAM permissions minimal (least privilege)
- Monitor CloudWatch logs after deployment
- Test thoroughly after each update
Troubleshooting
Section titled “Troubleshooting”Build Errors
Section titled “Build Errors”TypeScript compilation fails:
Clean, install and build
rm -rf dist node_modules
npm install
npm run buildDocker build fails:
Ensure Docker is running
docker infoTry building without cache
docker build --no-cache -t my-agent-service .Deployment Errors
Section titled “Deployment Errors”“Access Denied” errors:
- Verify IAM role trust policy includes your account ID
- Check role has required permissions
- Ensure you have permissions to create AgentCore runtimes
ECR authentication expired:
// Re-authenticateaws ecr get-login-password --region ${AWS_REGION} | \ docker login --username AWS --password-stdin \ ${ACCOUNTID}.dkr.ecr.${AWS_REGION}.amazonaws.comRuntime Errors
Section titled “Runtime Errors”Check CloudWatch logs
aws logs tail /aws/bedrock-agentcore/runtimes/my-agent-service-XXXXXXXXXX-DEFAULT \ --region ${AWS_REGION} \ --since 5m \ --followObservability
Section titled “Observability”Amazon Bedrock AgentCore provides built-in observability through CloudWatch.
View Recent Logs
Section titled “View Recent Logs”aws logs tail /aws/bedrock-agentcore/runtimes/my-agent-service-XXXXXXXXXX-DEFAULT \ --region ${AWS_REGION} \ --since 1h