
Deploying Amazon Bedrock AgentCore Runtime in a VPC: A Step-by-Step Guide
This tutorial demonstrates how to deploy Amazon Bedrock AgentCore Runtime agents in a VPC with secure access to private resources. Using a simple calculator agent example, you'll learn to set up VPC infrastructure with NAT Gateway for internet access, configure network security, and deploy agents that can communicate with both private resources and AWS services like Bedrock and CloudWatch.
Introduction
Amazon Bedrock AgentCore Runtime provides a server-less platform for deploying AI agents at scale. While AgentCore can run in AWS-managed infrastructure, many organizations require their agents to access private resources like databases, internal APIs, or services within their Virtual Private Cloud (VPC). In this post, I'll walk you through deploying an AgentCore Runtime agent in a VPC with full internet access, enabling secure communication with both private resources and public APIs.
Why Deploy AgentCore in a VPC?
Deploying AgentCore Runtime in a VPC offers several key benefits:
- Secure access to private resources: Connect to Amazon RDS databases, ElastiCache clusters, or internal APIs without exposing them to the internet
- Network isolation: Keep your agent's network traffic within your organization's security boundaries
- Compliance requirements: Meet regulatory requirements that mandate private network connectivity
- Cost optimization: Use VPC endpoints to reduce data transfer costs for AWS service calls
Architecture Overview
Our deployment architecture includes:
- VPC : A dedicated Virtual Private Cloud with DNS resolution enabled
- Private Subnets : Two private subnets across different Availability Zones for high availability
- Public Subnet: Hosts the NAT Gateway for outbound internet access
- NAT Gateway : Enables agents in private subnets to access AWS services and the internet
- Internet Gateway : Provides internet connectivity for the NAT Gateway
- Security Group : Controls outbound traffic from the agent (HTTPS to AWS services)
- IAM Execution Role : Grants the agent permissions to invoke Bedrock models and write CloudWatch logs
NAT Gateway vs VPC Endpoints
This tutorial uses a NAT Gateway for simplicity and flexibility. Alternatively, you can use VPC endpoints if your agent only needs AWS service access (no public internet). We'll briefly cover this option later in the post.
Prerequisites
Before you begin, ensure you have:
- AWS CLI installed and configured with appropriate credentials
- Python 3.9 or later
pippackage manager- Sufficient IAM permissions to create VPC resources, IAM roles, and AgentCore runtimes
- The
agentcore-starter-toolkitinstalled:pip install bedrock-agentcore-starter-toolkit
Step 1: Create Your Agent Code
First, let's create a simple calculator agent using the Strands framework . This agent will use the built-in calculator tool to perform mathematical operations.
Create a file
src/agent.py:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""Strands calculator agent with AgentCore Runtime integration."""
import os
os.environ["BYPASS_TOOL_CONSENT"] = "true"
from strands import Agent
from strands_tools import calculator
from bedrock_agentcore.runtime import BedrockAgentCoreApp
app = BedrockAgentCoreApp()
def agent_invocation(payload, context):
"""Handler for agent invocation.
Args:
payload: Request payload containing the prompt
context: AgentCore context information
Returns:
dict: Response containing the agent's result
"""
# Extract prompt from payload
user_message = payload.get("prompt", "No prompt provided")
# Create agent with built-in calculator tool
agent = Agent(tools=[calculator])
# Process the request
result = agent(user_message)
# Return structured response
return {"result": result.message}
if __name__ == "__main__":
app.run()
Create
src/requirements.txt with the necessary dependencies:1
2
strands-agents
strands-agents-tools
Step 2: Set Up VPC Infrastructure
The VPC infrastructure setup is the most critical step. We need to create all networking components that enable secure, private connectivity while maintaining internet access for Bedrock API calls.
Key Infrastructure Components
DNS Configuration: AgentCore requires DNS hostnames and DNS support to be enabled in the VPC for proper service discovery and VPC endpoint resolution.
Subnet Strategy:
- Private subnets (2): Host the AgentCore Runtime network interfaces across different AZs for high availability
- Public subnet (1): Hosts the NAT Gateway
Internet Access Architecture:
- AgentCore in private subnets → NAT Gateway in public subnet → Internet Gateway → AWS Services
- This architecture enables AgentCore to access Bedrock (for models), and CloudWatch (for logs)
Here's the infrastructure setup script (
setup_infrastructure.sh):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# One-time infrastructure setup for AgentCore Runtime
set -e
# Configuration
REGION="${AWS_REGION:-us-east-1}"
PROJECT_NAME="${PROJECT_NAME:-strands-calculator}"
VPC_CIDR="10.0.0.0/16"
PUBLIC_SUBNET_CIDR="10.0.1.0/24"
PRIVATE_SUBNET_1_CIDR="10.0.2.0/24"
PRIVATE_SUBNET_2_CIDR="10.0.3.0/24"
# Get availability zones
AZ1=$(aws ec2 describe-availability-zones --region $REGION \
--query 'AvailabilityZones[0].ZoneName' --output text)
AZ2=$(aws ec2 describe-availability-zones --region $REGION \
--query 'AvailabilityZones[1].ZoneName' --output text)
# Create VPC
VPC_ID=$(aws ec2 create-vpc \
--cidr-block $VPC_CIDR \
--region $REGION \
--tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=$PROJECT_NAME-vpc}]" \
--query 'Vpc.VpcId' \
--output text)
# Enable DNS (required for VPC endpoint resolution)
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames --region $REGION
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support --region $REGION
# Create Internet Gateway
IGW_ID=$(aws ec2 create-internet-gateway \
--region $REGION \
--tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=$PROJECT_NAME-igw}]" \
--query 'InternetGateway.InternetGatewayId' \
--output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID --region $REGION
# Create Subnets
PUBLIC_SUBNET_ID=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block $PUBLIC_SUBNET_CIDR \
--availability-zone $AZ1 \
--region $REGION \
--tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$PROJECT_NAME-public}]" \
--query 'Subnet.SubnetId' \
--output text)
PRIVATE_SUBNET_1_ID=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block $PRIVATE_SUBNET_1_CIDR \
--availability-zone $AZ1 \
--region $REGION \
--tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$PROJECT_NAME-private-1}]" \
--query 'Subnet.SubnetId' \
--output text)
PRIVATE_SUBNET_2_ID=$(aws ec2 create-subnet \
--vpc-id $VPC_ID \
--cidr-block $PRIVATE_SUBNET_2_CIDR \
--availability-zone $AZ2 \
--region $REGION \
--tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$PROJECT_NAME-private-2}]" \
--query 'Subnet.SubnetId' \
--output text)
# Create NAT Gateway
EIP_ALLOC_ID=$(aws ec2 allocate-address \
--domain vpc \
--region $REGION \
--query 'AllocationId' \
--output text)
NAT_GW_ID=$(aws ec2 create-nat-gateway \
--subnet-id $PUBLIC_SUBNET_ID \
--allocation-id $EIP_ALLOC_ID \
--region $REGION \
--query 'NatGateway.NatGatewayId' \
--output text)
# Wait for NAT Gateway to become available
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW_ID --region $REGION
# Create Route Tables
PUBLIC_RT_ID=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--region $REGION \
--query 'RouteTable.RouteTableId' \
--output text)
aws ec2 create-route --route-table-id $PUBLIC_RT_ID \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID --region $REGION
aws ec2 associate-route-table --route-table-id $PUBLIC_RT_ID \
--subnet-id $PUBLIC_SUBNET_ID --region $REGION
PRIVATE_RT_ID=$(aws ec2 create-route-table \
--vpc-id $VPC_ID \
--region $REGION \
--query 'RouteTable.RouteTableId' \
--output text)
aws ec2 create-route --route-table-id $PRIVATE_RT_ID \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_GW_ID --region $REGION
aws ec2 associate-route-table --route-table-id $PRIVATE_RT_ID \
--subnet-id $PRIVATE_SUBNET_1_ID --region $REGION
aws ec2 associate-route-table --route-table-id $PRIVATE_RT_ID \
--subnet-id $PRIVATE_SUBNET_2_ID --region $REGION
# Create Security Group
SG_ID=$(aws ec2 create-security-group \
--group-name "$PROJECT_NAME-agentcore-sg" \
--description "Security group for AgentCore Runtime" \
--vpc-id $VPC_ID \
--region $REGION \
--query 'GroupId' \
--output text)
# Allow HTTPS outbound (required for Bedrock API calls)
aws ec2 authorize-security-group-egress \
--group-id $SG_ID \
--ip-permissions IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0}]' \
--region $REGION
# Create IAM Execution Role
ROLE_NAME="$PROJECT_NAME-agentcore-execution-role"
cat > /tmp/trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "bedrock-agentcore.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document file:///tmp/trust-policy.json \
--region $REGION
# Attach required policies
aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/AmazonBedrockFullAccess \
--region $REGION
aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess \
--region $REGION
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}"
# Wait for IAM role to propagate
sleep 10
# Save configuration for deployment script
cat > infrastructure.env <<EOF
export VPC_ID=$VPC_ID
export PRIVATE_SUBNET_1_ID=$PRIVATE_SUBNET_1_ID
export PRIVATE_SUBNET_2_ID=$PRIVATE_SUBNET_2_ID
export SECURITY_GROUP_ID=$SG_ID
export EXECUTION_ROLE_ARN=$ROLE_ARN
export REGION=$REGION
export PROJECT_NAME=$PROJECT_NAME
EOF
echo "Infrastructure setup complete!"
Run the script:
1
2
chmod +x setup_infrastructure.sh
./setup_infrastructure.sh
This creates all necessary infrastructure and saves the configuration to
infrastructure.env for use in the deployment step.Step 3: Deploy the Agent to AgentCore Runtime
Now that our infrastructure is ready, we can deploy the agent using the AgentCore CLI using direct code deployment . The deployment script (
deploy_agent.sh) handles the entire process:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Agent deployment to AgentCore Runtime
set -e
# Load infrastructure configuration
source infrastructure.env
# Install dependencies
pip install strands-agents strands-agents-tools bedrock-agentcore-starter-toolkit boto3
# Configure agent with VPC settings
agentcore configure \
--entrypoint src/agent.py \
--name calculator_agent \
--region $REGION \
--execution-role $EXECUTION_ROLE_ARN \
--vpc \
--subnets $PRIVATE_SUBNET_1_ID,$PRIVATE_SUBNET_2_ID \
--security-groups $SECURITY_GROUP_ID \
--disable-memory \
--non-interactive
# Launch agent to AgentCore Runtime
agentcore launch
echo "Agent deployment complete!"
Key deployment parameters:
--vpc: Enables VPC mode for the runtime--subnets: Specifies the private subnets where network interfaces will be created--security-groups: Applies the security group to control traffic--disable-memory: Disables AgentCore Memory (optional, reduces costs for simple agents)--execution-role: IAM role for Bedrock and CloudWatch access
Run the deployment:
1
2
chmod +x deploy_agent.sh
./deploy_agent.sh
The deployment process takes 5-10 minutes as AWS provisions the container infrastructure, creates network interfaces in your VPC, and deploys your agent code.
Step 4: Test Your Agent
Once deployed, test your agent using the AgentCore CLI:
1
agentcore invoke '{"prompt": "What is 25 plus 17?"}'
Expected response:
1
2
3
{
"result": "25 plus 17 equals 42."
}
You can also test more complex calculations:
1
agentcore invoke '{"prompt": "Calculate the square root of 144 and multiply it by 5"}'
Step 5: Monitor Your Agent
View real-time logs using CloudWatch:
1
aws logs tail /aws/bedrock/agentcore/runtime/src_agent --follow --region us-east-1
The logs show:
- Agent invocations
- Tool usage (calculator calls)
- Execution context
- Any errors or warnings
Understanding VPC Connectivity
How AgentCore Accesses Resources
When you deploy AgentCore in a VPC:
- Network Interfaces: AWS creates Elastic Network Interfaces (ENIs) in your specified private subnets
- Private IP Assignment: Each ENI receives a private IP address from your subnet CIDR range
- Outbound Traffic Flow:
- Agent → Private Subnet → NAT Gateway → Internet Gateway → Bedrock APIs
- Security Group Control: The security group acts as a firewall, controlling what the agent can access
Important Considerations
Public Subnets Don't Provide Internet Access: A common misconception is that placing AgentCore in a public subnet provides internet access. It doesn't. Always use private subnets with a NAT Gateway for internet connectivity.
DNS Resolution is Required: Enable DNS hostnames and DNS support in your VPC. AgentCore needs this for service discovery and VPC endpoint resolution.
Availability Zone Support: AgentCore only supports specific Availability Zones in each region. For us-east-1, use use1-az1, use1-az2, or use1-az4. Check the AgentCore VPC documentation for your region's supported AZs.
Connecting to Private Resources
To connect your agent to private resources like RDS databases:
1. Update Security Groups
Agent Security Group (outbound rule):
1
2
3
aws ec2 authorize-security-group-egress \
--group-id $SECURITY_GROUP_ID \
--ip-permissions IpProtocol=tcp,FromPort=3306,ToPort=3306,IpRanges='[{CidrIp=10.0.0.0/16}]'
RDS Security Group (inbound rule):
1
2
3
4
5
aws ec2 authorize-security-group-ingress \
--group-id $RDS_SECURITY_GROUP_ID \
--protocol tcp \
--port 3306 \
--source-group $SECURITY_GROUP_ID
2. Update Agent Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pymysql
def agent_invocation(payload, context):
# Connect to private RDS instance
connection = pymysql.connect(
host='mydb.cluster-abc123.us-east-1.rds.amazonaws.com',
user='admin',
password=os.environ['DB_PASSWORD'],
database='myapp'
)
# Use the connection in your agent logic
# ...
Alternative: Using VPC Endpoints Instead of NAT Gateway
Alternatively, if your agent doesn't need public internet access, you can replace the NAT Gateway with VPC endpoints. AgentCore needs to access ECR, S3, Bedrock, CloudWatch Logs, and the AgentCore API. You can create interface endpoints for these services (except S3, which uses a free gateway endpoint). This approach eliminates NAT Gateway costs but requires creating and managing multiple endpoints. VPC endpoints are ideal for production workloads with high traffic volumes where the lower data processing fees offset the endpoint costs. For details, see the AgentCore VPC endpoints documentation .
Cleanup
When you're done testing, clean up all resources to avoid ongoing charges:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# cleanup.sh
source infrastructure.env
# Delete AgentCore Runtime
agentcore delete --non-interactive
# Delete NAT Gateway
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_GATEWAY_ID --region $REGION
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT_GATEWAY_ID --region $REGION
# Release Elastic IP
EIP_ALLOC_ID=$(aws ec2 describe-addresses --region $REGION \
--filters "Name=tag:Project,Values=$PROJECT_NAME" \
--query 'Addresses[0].AllocationId' --output text)
aws ec2 release-address --allocation-id $EIP_ALLOC_ID --region $REGION
# Delete remaining resources
aws ec2 detach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID --region $REGION
aws ec2 delete-internet-gateway --internet-gateway-id $IGW_ID --region $REGION
aws ec2 delete-subnet --subnet-id $PUBLIC_SUBNET_ID --region $REGION
aws ec2 delete-subnet --subnet-id $PRIVATE_SUBNET_1_ID --region $REGION
aws ec2 delete-subnet --subnet-id $PRIVATE_SUBNET_2_ID --region $REGION
aws ec2 delete-security-group --group-id $SECURITY_GROUP_ID --region $REGION
aws ec2 delete-vpc --vpc-id $VPC_ID --region $REGION
# Delete IAM role
ROLE_NAME="$PROJECT_NAME-agentcore-execution-role"
aws iam detach-role-policy --role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/AmazonBedrockFullAccess
aws iam detach-role-policy --role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
aws iam delete-role --role-name $ROLE_NAME --region $REGION
rm -f infrastructure.env
Run cleanup:
1
2
chmod +x cleanup.sh
./cleanup.sh
Conclusion
Deploying Amazon Bedrock AgentCore Runtime in a VPC enables secure access to private resources while maintaining the serverless benefits of AgentCore. The architecture we've built provides:
- Security: Agents run in private subnets with controlled network access
- Reliability: Multi-AZ deployment ensures high availability
- Flexibility: Can access both private resources and public APIs
- Observability: CloudWatch integration for monitoring and debugging
Additional Resources
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article