DEV Community

Cover image for Deploy OpenClaw on AWS EC2 with Amazon Bedrock Using Terraform — No API Keys Required
parmarjatin4911@gmail.com
parmarjatin4911@gmail.com

Posted on

Deploy OpenClaw on AWS EC2 with Amazon Bedrock Using Terraform — No API Keys Required

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

  1. 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.

  1. 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

  1. 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)

  1. 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)