Deploy OpenClaw on AWS EC2 with Amazon Bedrock Using Terraform — No API Keys Required
TL;DR: A complete Terraform setup to deploy OpenClaw — the open-source multi-channel AI gateway — on an EC2 instance, wired to Amazon Bedrock's Claude models via IAM instance roles. Zero API key management. Full CI/CD pipeline included.
Why This Matters
If you've ever wanted a personal AI coding assistant you can message from WhatsApp, Telegram, or Discord — without relying on a hosted service — OpenClaw is the answer. It's an open-source, self-hosted gateway that bridges your chat apps to AI agents.
The typical setup requires managing API keys for your LLM provider. But when you run on AWS, there's a cleaner approach: Amazon Bedrock with IAM instance roles. The EC2 instance authenticates to Bedrock automatically through instance metadata — no secrets to rotate, no environment variables to leak.
This article walks through a Terraform project that automates the entire deployment, from VPC security groups to a running OpenClaw gateway, including a GitHub Actions CI/CD pipeline with OIDC authentication.
Repository: github.com/Yash-Kavaiya/openclaw-bedrock-ec2
What is OpenClaw?
OpenClaw is a self-hosted gateway that connects chat applications — WhatsApp, Telegram, Discord, iMessage, and others — to AI coding agents. You run a single Gateway process on your machine or server, and it becomes the bridge between your messaging apps and an always-available AI assistant.
Key characteristics:
Self-hosted: Runs on your hardware, under your control
Multi-channel: One Gateway serves WhatsApp, Telegram, Discord simultaneously
Agent-native: Built for coding agents with tool use, sessions, memory, and multi-agent routing
Open source: MIT licensed
The Gateway listens on port 18789 and handles session management, routing, and channel connections.
Architecture Overview
text
┌─────────────────────────────────────────────┐
│ EC2 (Amazon Linux 2023, t3.small) │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ OpenClaw Gateway :18789 │ │
│ │ Primary: Claude Sonnet 4.6 │ │
│ │ Fallback: Haiku 3.5 → Opus 4.6 │ │
│ └──────────────┬──────────────────────┘ │
│ │ IAM Instance Role │
└─────────────────┼───────────────────────────┘
│
Amazon Bedrock (us-east-1)
AmazonBedrockFullAccess
The setup provisions:
An EC2 instance running Amazon Linux 2023 on t3.small
An IAM instance profile with AmazonBedrockFullAccess
A security group allowing SSH (22), HTTP (80), HTTPS (443), and the OpenClaw gateway port (18789)
A user data script that bootstraps Node.js 22, installs OpenClaw, writes the config, and starts it as a systemd service
A templated OpenClaw config (openclaw.json) that registers three Bedrock Claude models with automatic discovery enabled
Prerequisites
Before you begin, ensure you have:
Terraform >= 1.5 installed (install guide)
AWS CLI configured with credentials (aws configure)
An EC2 key pair created in your AWS account
Bedrock model access enabled in your AWS region
Navigate to: AWS Console → Bedrock → Model access → Enable Claude models
The Terraform Code, Explained
Project Structure
text
openclaw-bedrock-ec2/
├── main.tf # Provider, data sources, SG, EC2 instance
├── iam.tf # IAM role + instance profile for Bedrock
├── variables.tf # Input variables
├── outputs.tf # Gateway URL and SSH command outputs
├── versions.tf # Terraform + provider version constraints
├── openclaw.json.tpl # OpenClaw config template
├── user_data.sh.tpl # EC2 bootstrap script template
├── terraform.tfvars.example # Example variables file
├── github-oidc.tf # GitHub Actions OIDC role
└── .github/workflows/
└── terraform.yml # CI/CD pipeline
- IAM: Keyless Bedrock Access The core idea is using an IAM instance role instead of API keys. The iam.tf file creates:
hcl
resource "aws_iam_role" "openclaw_bedrock" {
name = "openclaw-bedrock-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}
]
})
}
resource "aws_iam_role_policy_attachment" "bedrock_full_access" {
role = aws_iam_role.openclaw_bedrock.name
policy_arn = "arn:aws:iam::aws:policy/AmazonBedrockFullAccess"
}
resource "aws_iam_instance_profile" "openclaw_bedrock" {
name = "openclaw-bedrock-ec2-profile"
role = aws_iam_role.openclaw_bedrock.name
}
The managed policy AmazonBedrockFullAccess grants:
bedrock:InvokeModel
bedrock:InvokeModelWithResponseStream
bedrock:ListFoundationModels
OpenClaw's config uses "auth": "aws-sdk", which automatically picks up the instance metadata credentials. No keys in environment variables, no secrets to manage.
- OpenClaw Configuration Template The openclaw.json.tpl template wires up three Claude models through Bedrock:
json
{
"models": {
"mode": "merge",
"bedrockDiscovery": {
"enabled": true,
"region": "${region}",
"providerFilter": ["anthropic"],
"refreshInterval": 3600,
"defaultContextWindow": 200000,
"defaultMaxTokens": 8192
},
"providers": {
"amazon-bedrock": {
"baseUrl": "https://bedrock-runtime.${region}.amazonaws.com",
"api": "bedrock-converse-stream",
"auth": "aws-sdk",
"models": [
{
"id": "us.anthropic.claude-sonnet-4-6-v1:0",
"name": "Claude Sonnet 4.6 (Bedrock)",
"reasoning": false,
"contextWindow": 200000,
"maxTokens": 8192
},
{
"id": "us.anthropic.claude-haiku-3-5-v1:0",
"name": "Claude Haiku 3.5 (Bedrock)",
"reasoning": false
},
{
"id": "us.anthropic.claude-opus-4-6-v1:0",
"name": "Claude Opus 4.6 (Bedrock)",
"reasoning": true
}
]
}
}
},
"agents": {
"defaults": {
"model": {
"primary": "amazon-bedrock/us.anthropic.claude-sonnet-4-6-v1:0",
"fallback": [
"amazon-bedrock/us.anthropic.claude-haiku-3-5-v1:0",
"amazon-bedrock/us.anthropic.claude-opus-4-6-v1:0"
]
}
}
}
}
Notable design choices:
Primary model: Claude Sonnet 4.6 — strong balance of capability and speed
Fallback chain: Haiku 3.5 (fast/cheap) → Opus 4.6 (most capable, with reasoning)
Auto-discovery: bedrockDiscovery.enabled = true means any additional Anthropic models you enable in Bedrock are automatically available
Cost fields set to 0: Bedrock billing is handled by AWS, not by token-based API keys
- EC2 Bootstrap Script The user_data.sh.tpl script runs on first boot:
bash
!/bin/bash
set -euo pipefail
exec > /var/log/openclaw-bootstrap.log 2>&1
1. System update
dnf update -y
2. Install Node.js 22
curl -fsSL https://rpm.nodesource.com/setup_22.x | bash -
dnf install -y nodejs
3. Install OpenClaw globally
npm install -g openclaw
4. Write config to ~/.openclaw/openclaw.json
mkdir -p /root/.openclaw
cat > /root/.openclaw/openclaw.json << 'OPENCLAW_CONFIG'
${openclaw_config}
OPENCLAW_CONFIG
5. Create systemd service and start
systemctl daemon-reload
systemctl enable openclaw
systemctl start openclaw
The script creates a proper systemd service so OpenClaw:
Starts automatically on boot
Restarts on failure (with a 10-second backoff)
Logs to journald (viewable via journalctl -u openclaw -f)
- Variables
Variable Required Default Description
key_name
Yes
—
EC2 key pair name
openclaw_gateway_token
Yes
—
Secret auth token for the gateway
region
No
us-east-1
AWS region
instance_type
No
t3.small
EC2 instance type
ami_id
No
Latest AL2023
Override AMI
allowed_ssh_cidr
No
0.0.0.0/0
Restrict SSH access
aws_bearer_token_bedrock
No
""
Bearer token (alternative auth)
Step-by-Step Deployment
Step 1: Clone the Repository
bash
git clone https://github.com/Yash-Kavaiya/openclaw-bedrock-ec2
cd openclaw-bedrock-ec2
Step 2: Configure Variables
bash
cp terraform.tfvars.example terraform.tfvars
Edit terraform.tfvars:
hcl
key_name = "my-ec2-keypair"
openclaw_gateway_token = "a-strong-random-token-here"
region = "us-east-1"
Important: Never commit terraform.tfvars — it's already in .gitignore.
Step 3: Initialize and Deploy
bash
terraform init
terraform plan # Review what will be created
terraform apply # Type 'yes' to confirm
Step 4: Connect
After terraform apply completes, the outputs show:
text
openclaw_gateway_url = http://:18789
ssh_command = ssh -i ~/.ssh/my-key.pem ec2-user@
Configure the OpenClaw CLI to point at your gateway:
bash
openclaw config set gateway.url http://:18789
openclaw config set gateway.auth.token
openclaw status
Or monitor the bootstrap via SSH:
bash
ssh -i ~/.ssh/my-key.pem ec2-user@
sudo tail -f /var/log/openclaw-bootstrap.log
sudo journalctl -u openclaw -f
CI/CD Pipeline with GitHub Actions
The repository includes a GitHub Actions workflow (.github/workflows/terraform.yml) that automates the entire lifecycle using OIDC authentication — no long-lived AWS access keys stored in GitHub.
Pipeline Behavior
Trigger What Happens
Pull Request
Lint + Validate → Plan (posted as PR comment)
Push to master
Lint + Validate → Manual approval → Apply
workflow_dispatch
Manual approval → Destroy
How OIDC Auth Works
Instead of storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as GitHub secrets, the pipeline uses OpenID Connect:
GitHub Actions requests a short-lived JWT from GitHub's OIDC provider
AWS STS exchanges that JWT for temporary AWS credentials
The temporary credentials are scoped to only the permissions the IAM role allows
This is set up via github-oidc.tf, which creates an IAM OIDC provider and a role that trusts GitHub's identity provider.
Setup Steps
Bootstrap the OIDC role from your local machine:
bash
terraform apply \
-target=aws_iam_openid_connect_provider.github \
-target=aws_iam_role.github_actions \
-target=aws_iam_role_policy.github_actions_terraform \
-var="key_name=any" \
-var="openclaw_gateway_token=placeholder"
terraform output github_actions_role_arn
Add GitHub Secrets (repo → Settings → Secrets → Actions):
Secret Value
AWS_ROLE_ARN
ARN from step 1
TF_KEY_NAME
Your EC2 key pair name
OPENCLAW_GATEWAY_TOKEN
Your gateway auth token
Create a production environment (repo → Settings → Environments) and add yourself as a required reviewer. This gates the apply and destroy jobs behind manual approval.
Security Considerations
A few things to keep in mind for production use:
Port 18789 is open to 0.0.0.0/0 by default. For production, restrict the CIDR or place the instance behind a VPN, Tailscale, or an ALB with TLS termination.
The gateway token is embedded in user data. For production, store it in AWS Secrets Manager and fetch it during bootstrap.
SSH access is open by default. Set allowed_ssh_cidr to your IP range.
AmazonBedrockFullAccess is broad. For production, create a custom policy limited to bedrock:InvokeModel* and bedrock:ListFoundationModels.
Cost Estimate
Running costs are modest:
EC2 t3.small: ~$15/month (on-demand), less with Reserved or Spot
Bedrock usage: Pay-per-token based on your Claude model usage (no upfront commitment)
Storage: 20 GB gp3 EBS — ~$1.60/month
Tear Down
When you're done:
bash
terraform destroy
Or trigger the Destroy workflow from GitHub Actions → Run workflow (requires manual approval).
Wrapping Up
This Terraform project eliminates two common friction points in self-hosting AI agents:
API key management — IAM instance roles handle authentication to Bedrock automatically
Manual server setup — Terraform + user data scripts automate the entire provisioning and configuration
The result is a reproducible, one-command deployment of OpenClaw on AWS with Claude models, complete with CI/CD and proper infrastructure lifecycle management.
Get started: github.com/Yash-Kavaiya/openclaw-bedrock-ec2
Tags: AWS, Terraform, Amazon Bedrock, Claude, OpenClaw, Infrastructure as Code, AI Agents, DevOps, GitHub Actions, OIDC
Top comments (0)