Deploy OpenClaw securely on AWS with a single command. Creates VPC, EC2 (ARM64), Telegram channel, and configurable AI model (Bedrock, Gemini, or any provide...
---
name: openclaw-aws-deploy
description: Deploy OpenClaw securely on AWS with a single command. Creates VPC, EC2 (ARM64), Telegram channel, and configurable AI model (Bedrock, Gemini, or any provider) — SSM-only access, no SSH. Use when setting up OpenClaw on AWS, deploying a new agent instance to EC2, or tearing down an existing AWS deployment.
metadata:
{
"openclaw":
{
"emoji": "☁️",
"requires": { "bins": ["aws", "jq", "openssl"] },
},
}
---
# OpenClaw AWS Deploy Skill
## Quick Start (Minimal Deployment ~$30/mo)
### Prerequisites
- **AWS credentials** — any of these methods:
- `--profile <name>` flag (named AWS CLI profile)
- `.env.aws` file in workspace root or skill directory (optional):
```
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=us-east-1
```
- Existing environment variables, AWS SSO session, or IAM role
- `.env.starfish` in workspace root (recommended) or skill directory:
```
TELEGRAM_BOT_TOKEN=... # from @BotFather (required)
TELEGRAM_USER_ID=... # your Telegram user ID (optional, enables auto-approve pairing)
GEMINI_API_KEY=... # from aistudio.google.com (optional, for Gemini models)
```
- `aws` CLI installed OR Docker for sandboxed access
- `jq`, `openssl` available
### One-Shot Deploy
```bash
# From the skill directory:
./scripts/deploy_minimal.sh --name starfish --region us-east-1 \
--env-dir /path/to/workspace
# Or with cleanup of previous deployment first:
./scripts/deploy_minimal.sh --name starfish --region us-east-1 \
--env-dir /path/to/workspace --cleanup-first
```
This single command:
1. Creates VPC + subnet + IGW + route table
2. Creates security group (NO inbound ports — SSM only)
3. Creates IAM role with minimal permissions (SSM + Parameter Store + Bedrock)
4. Stores secrets in SSM Parameter Store (fetched at each service start — rewritten on each start, never stored in repo or static images)
5. Launches **t4g.medium** ARM64 instance with user-data bootstrap
6. User-data installs Node.js 22 + OpenClaw + configures everything
7. Runs smoke test via SSM
8. Saves all resource IDs to `deploy-output.json`
### After Deploy
1. **Message the Telegram bot** — you'll get a pairing code
2. **Approve pairing** via SSM:
```bash
aws ssm start-session --target <INSTANCE_ID> --region us-east-1
sudo -u openclaw bash
export HOME=/home/openclaw
openclaw pairing approve telegram <CODE>
```
3. Bot is live! ✅
### Teardown
```bash
# Using saved output:
./scripts/teardown.sh --from-output ./deploy-output.json --env-dir /path/to/workspace --yes
# Or by name (discovers via tags):
./scripts/teardown.sh --name starfish --region us-east-1 --env-dir /path/to/workspace --yes
```
## Model Support
### `--model` flag
Pass any model string — it goes directly into `openclaw.json` as `model.primary`:
```bash
# Default (MiniMax M2.1 on Bedrock — no API key needed, uses IAM role)
./scripts/deploy_minimal.sh --name starfish --region us-east-1
# Gemini Flash (needs GEMINI_API_KEY in .env.starfish)
./scripts/deploy_minimal.sh --name starfish --region us-east-1 \
--model google/gemini-2.0-flash
```
### AWS Bedrock
Bedrock IAM permissions (`bedrock:InvokeModel`, `bedrock:InvokeModelWithResponseStream`) are **always added** to the instance role — regardless of which model you choose. This means any deployed instance can use Bedrock models out of the box via IAM role credentials (no API key needed).
Known Bedrock model IDs:
| Model flag | Description |
|------------|-------------|
| `amazon-bedrock/minimax.minimax-m2.1` | MiniMax M2.1 |
| `amazon-bedrock/minimax.minimax-m2` | MiniMax M2 |
| `amazon-bedrock/deepseek.deepseek-r1` | DeepSeek R1 |
| `amazon-bedrock/moonshotai.kimi-k2.5` | Kimi K2.5 |
> **Note:** Bedrock models must be enabled in your AWS account via the Bedrock console before use.
### Gemini
If `GEMINI_API_KEY` is present in `.env.starfish`, it's stored in SSM and written to `auth-profiles.json`. If absent, it's simply skipped — no error.
### `.env.starfish`
```
TELEGRAM_BOT_TOKEN=... # Required — from @BotFather
GEMINI_API_KEY=... # Optional — from aistudio.google.com (needed for Gemini models)
```
## Architecture (Minimal)
```
┌─────────────────────────────────────────────────────┐
│ VPC (10.50.0.0/16) │
│ ┌───────────────────────────────────────────────┐ │
│ │ Public Subnet (10.50.0.0/24) │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ EC2 t4g.medium (ARM64, 4GB) │ │ │
│ │ │ ┌───────────────────────────────────┐ │ │ │
│ │ │ │ OpenClaw Gateway │ │ │ │
│ │ │ │ • Node.js 22.14.0 │ │ │ │
│ │ │ │ • Any model (Bedrock/Gemini/etc) │ │ │ │
│ │ │ │ • Telegram channel │ │ │ │
│ │ │ │ • Encrypted EBS (gp3, 20GB) │ │ │ │
│ │ │ └───────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
↑ ↓
SSM (no SSH/inbound) Outbound HTTPS only
```
## Critical Lessons Learned (22 Issues)
These are baked into the deploy script. See `references/TROUBLESHOOTING.md` for full details.
### Instance Sizing
- **t4g.medium (4GB) required** — t4g.small (2GB) OOMs during npm install + gateway startup
- **ARM64** — better price/performance than x86
### Node.js
- **Node 22+ required** — OpenClaw 2026.x requires Node ≥22.12.0
- **Official tarball install** — NodeSource setup_22.x unreliable on AL2023 ARM64
- **git required** — OpenClaw npm install has git-based dependencies
### npm
- **Use `openclaw@latest`** — bare `openclaw` may resolve to placeholder package (0.0.1)
### Gateway Startup
- **Use `openclaw gateway run --allow-unconfigured`** — NOT `gateway start` (which tries `systemctl --user` and fails)
- **Config file must be `openclaw.json`** — not `config.yaml`
- **`gateway.mode: "local"`** — required or you get "Missing config" error
- **`gateway.auth.mode: "token"`** — `"none"` is invalid
### Telegram
- **`plugins.entries.telegram.enabled: true`** — must be explicit
- **`dmPolicy: "pairing"`** — not `"allowlist"` (blocks everyone without user list)
- **`streamMode: "partial"`** — some models don't support streaming tools, use `"off"` as fallback
### Model
- **Gemini 2.0 Flash** — recommended (free tier: 15 RPM, 1M tokens/day, supports tools)
- **Auth profiles required** — create `auth-profiles.json` in agent dir
- **Bedrock format** — `amazon-bedrock/MODEL_ID` (not `bedrock/`)
- **Bedrock models need console enablement** — Anthropic requires use case form
### Systemd Service
- **Simplified service file** — removed `ProtectHome`, `ReadWritePaths=/tmp/openclaw`, `PrivateTmp` due to namespace issues
- **Use `NODE_OPTIONS="--max-old-space-size=1024"`** — helps prevent OOM
### Security
- **No inbound ports** — SSM Session Manager only
- **Secrets fetched from SSM at runtime** — startup script fetches secrets each time the service starts; config files are ephemeral (rewritten on each start, never stored in repo or static images)
- **Encrypted EBS** — enabled by default in deploy script
- **IMDSv2 required** — `HttpTokens=required`
## File Layout
```
scripts/
deploy_minimal.sh # One-shot deploy (VPC + EC2 + OpenClaw)
teardown.sh # Clean teardown of all resources
setup_deployer_role.sh # Create IAM role/user with minimum permissions
preflight.sh # Pre-deploy validation checks
smoke_test.sh # Post-deploy health verification
references/
TROUBLESHOOTING.md # All 22 issues + solutions
config-templates/ # Ready-to-use config files
gemini-flash.json # OpenClaw config for Gemini Flash
auth-profiles-gemini.json # Auth profile template
openclaw.service.txt # Systemd unit file template
startup.sh # Startup script template
```
## Config Templates
### OpenClaw Config (gemini-flash.json)
See `references/config-templates/gemini-flash.json` — includes all required fields.
### Auth Profiles (auth-profiles-gemini.json)
Create at `~/.openclaw/agents/main/agent/auth-profiles.json`
### Systemd Service (openclaw.service)
Simplified for reliability — security hardening removed due to namespace issues.
## Cost Breakdown (~$30/mo)
| Resource | Cost |
|----------|------|
| t4g.medium (4GB ARM64) | ~$24.53/mo |
| EBS gp3 20GB | ~$1.60/mo |
| Public IP | ~$3.65/mo |
| Gemini Flash | Free tier / ~$0.30/1M tokens |
| **Total** | **~$29.78/mo** |
## Troubleshooting
### "No API key found for amazon-bedrock"
**Cause:** OpenClaw needs `models.providers` config in `openclaw.json` with `"auth": "aws-sdk"`. An `auth-profiles.json` entry alone is NOT sufficient.
**Fix:** Add to `openclaw.json` on the instance:
```bash
sudo -u openclaw bash
cd /home/openclaw/.openclaw
jq '.models = {
"providers": {"amazon-bedrock": {"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com", "api": "bedrock-converse-stream", "auth": "aws-sdk", "models": [{"id": "minimax.minimax-m2.1", "name": "MiniMax M2.1", "input": ["text"], "contextWindow": 128000, "maxTokens": 4096}]}},
"bedrockDiscovery": {"enabled": true, "region": "us-east-1"}
}' openclaw.json > /tmp/oc.json && mv /tmp/oc.json openclaw.json
chown openclaw:openclaw openclaw.json
systemctl restart openclaw
```
### "API rate limit reached" (Gemini)
**Fix:** Switch to Bedrock (default in current version) or redeploy with `--model amazon-bedrock/minimax.minimax-m2.1`.
### Bedrock model returns errors
**Cause:** Model must be enabled in AWS Console → Bedrock → Model access. MiniMax models are auto-authorized; Anthropic/Meta models require use-case approval.
### Bot doesn't respond after deploy
**Fix:** Add `TELEGRAM_USER_ID` to `.env.starfish` for auto-pairing, or use `--pair-user <id>`. Manual: `openclaw pairing approve telegram <CODE>` via SSM.
## Safety Rules
- Never print secrets in logs
- Never open SSH/inbound ports; use SSM Session Manager only
- Use least-privilege IAM policies
- All resources tagged with `Project=<name>` and `DeployId=<unique-id>` for deterministic cleanup
- Encrypted EBS volumes always
don't have the plugin yet? install it then click "run inline in claude" again.
by @clawhub
added explicit decision points for model type, missing credentials, and error handling; separated config generation into a detailed substep; documented all aws permissions and external connections as inputs; added edge cases (user-data size limit, imdsv2, bedrock enablement, gemini free tier); clarified ssm-based secret handling and smoke test validation; preserved original 8-step procedure and all troubleshooting lessons.
Deploy a production-ready OpenClaw gateway on AWS in one command. use this skill when spinning up a new OpenClaw agent instance on EC2, switching AI models (Bedrock, Gemini, etc.), or tearing down an existing deployment. the skill creates a minimal, secure infrastructure stack (VPC, subnet, security group, t4g.medium ARM64 instance) with no inbound ports, encrypts secrets in AWS Systems Manager Parameter Store, bootstraps Node.js 22+, installs OpenClaw, and hands off a live Telegram bot connected to your chosen LLM. total cost runs ~$30/month for the compute + storage layer.
AWS credentials (one of):
--profile <name> flagAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION.env.aws file (workspace root or skill directory):AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=us-east-1
Telegram bot token and (optional) user ID in .env.starfish (workspace root or skill directory):
TELEGRAM_BOT_TOKEN=<token_from_@BotFather>
TELEGRAM_USER_ID=<your_user_id>
GEMINI_API_KEY=<optional_api_key_from_aistudio.google.com>
Local binaries required:
aws CLI (v2.x+)jq (command-line JSON processor)openssl (for secure random generation)git (required by OpenClaw npm dependencies)Alternatively, Docker provides sandboxed access if these are unavailable.
External connections:
TELEGRAM_BOT_TOKEN)Parameters:
--name <string>: deployment name (tagged on all resources, used for teardown discovery)--region <string>: AWS region, e.g. us-east-1 (default: us-east-1)--model <string>: AI model identifier, e.g. google/gemini-2.0-flash or amazon-bedrock/minimax.minimax-m2.1 (default: Bedrock MiniMax M2.1)--env-dir <path>: workspace directory containing .env.starfish and .env.aws (default: current directory)--cleanup-first: destroy any existing deployment with the same --name before deploying (optional)--pair-user <telegram_user_id>: auto-approve Telegram pairing for this user ID (optional)preflight checks , run ./scripts/preflight.sh. verify aws, jq, openssl, git are installed and in PATH. verify AWS credentials are valid. verify .env.starfish exists and TELEGRAM_BOT_TOKEN is set. fail fast if any dependency is missing.
cleanup existing deployment (if requested) , if --cleanup-first flag is set, run ./scripts/teardown.sh --name <name> --region <region> --yes. wait for all resources to be destroyed. skip this step if flag is absent.
create VPC, subnet, internet gateway, and route table , call AWS API to create a VPC (CIDR 10.50.0.0/16), public subnet (10.50.0.0/24), internet gateway, and route table with 0.0.0.0/0 to IGW. tag all resources with Project=<name> and DeployId=<unique-id>.
create security group , create a security group in the VPC with no inbound rules (SSM Session Manager only; no SSH, no HTTP/HTTPS from internet). allow all outbound traffic (required for package installs, API calls, Telegram). tag it.
create IAM role and instance profile , create an IAM role with inline policy granting: ssm:StartSession, ssm:TerminateSession, ssm:GetParameter, ssm:PutParameter, ssm:DescribeParameters, bedrock:InvokeModel, bedrock:InvokeModelWithResponseStream, ec2:DescribeInstances. attach instance profile to the role. tag it.
store secrets in Systems Manager Parameter Store , for each secret in .env.starfish (e.g., TELEGRAM_BOT_TOKEN, GEMINI_API_KEY), create or overwrite an SSM Parameter (String type, not SecureString for speed) at path /openclaw/<name>/<KEY>. these are fetched by the startup script on each service start and written to ephemeral config files (never persisted in AMI or repo).
.env.starfish contents, SSM API/openclaw/starfish/TELEGRAM_BOT_TOKEN)generate user-data bootstrap script , compose a bash script that runs on first boot. the script will: install Node.js 22 from official tarball, install OpenClaw from npm, fetch secrets from SSM Parameter Store, write openclaw.json config and auth-profiles.json, create systemd service file, start the service. see step 13 for config generation.
launch EC2 instance , create an EC2 t4g.medium ARM64 instance (4GB RAM, 20GB gp3 EBS, encrypted) in the public subnet. assign the IAM instance profile. set metadata options to IMDSv2 required. supply the user-data script from step 7. tag it with Project=<name>, DeployId=<unique-id>, Name=openclaw-<name>.
wait for instance state , poll aws ec2 describe-instances every 5 seconds until the instance state is running and status checks reach ok or 2/2. timeout after 5 minutes and fail if not ready.
wait for SSM agent readiness , poll aws ssm describe-instance-information --filters "Key=InstanceIds,Values=<instance-id>" every 5 seconds until the instance appears with PingStatus=Online. timeout after 3 minutes and fail if SSM agent is not reachable.
run smoke test via SSM , call aws ssm start-session --target <instance-id> and execute: sudo -u openclaw openclaw gateway status and sudo -u openclaw cat /home/openclaw/.openclaw/openclaw.json | jq .gateway. verify the gateway is running and config is correct. timeout after 60 seconds per command.
save deployment output , write a JSON file deploy-output.json (in current directory) containing: instance ID, public IP, security group ID, VPC ID, subnet ID, IAM role ARN, region, deployment name, model, Telegram token (masked), DeployId, and timestamp. this file is used by teardown.sh to clean up resources deterministically.
Config generation (step 7, detail):
Generate openclaw.json with these required fields:
{
"gateway": {
"mode": "local",
"auth": {
"mode": "token"
}
},
"plugins": {
"entries": {
"telegram": {
"enabled": true,
"botToken": "${TELEGRAM_BOT_TOKEN}"
}
}
},
"model": {
"primary": "<model_from_--model_flag>"
}
}
If --model is not provided, default to amazon-bedrock/minimax.minimax-m2.1.
If the model starts with amazon-bedrock/, add Bedrock-specific config:
{
"models": {
"providers": {
"amazon-bedrock": {
"baseUrl": "https://bedrock-runtime.<region>.amazonaws.com",
"api": "bedrock-converse-stream",
"auth": "aws-sdk",
"models": [...]
}
}
}
}
If GEMINI_API_KEY is present in .env.starfish and the model is google/*, create auth-profiles.json:
{
"gemini": {
"apiKey": "${GEMINI_API_KEY}"
}
}
If neither condition is met, skip auth-profiles.json (no error).
if --cleanup-first flag is set:
else:
if preflight checks fail (missing binary, invalid credentials, no .env.starfish):
if the --model parameter starts with amazon-bedrock/:
openclaw.json (see config generation detail above).else if the --model parameter starts with google/:
GEMINI_API_KEY in .env.starfish. if present, create auth-profiles.json with the API key.else:
if user-data script exceeds 16 KB:
/openclaw/<name>/startup-script and fetch it during bootstrap. adjust base64 encoding accordingly.if EC2 instance fails to reach running state within 5 minutes:
if SSM agent is not online within 3 minutes:
if smoke test fails (gateway not running, config invalid):
aws ssm start-session --target <instance-id> and inspect logs in /home/openclaw/.openclaw/logs/.if --pair-user flag is provided:
else:
openclaw pairing approve telegram <CODE> after receiving the bot message.if the model requires credentials (e.g., external API key) and they are not found:
file: deploy-output.json (in current working directory)
{
"deploymentName": "<name>",
"deployId": "<unique-id>",
"timestamp": "2025-01-14T12:34:56Z",
"region": "us-east-1",
"instanceId": "i-0123456789abcdef0",
"publicIp": "203.0.113.42",
"vpcId": "vpc-0123456789abcdef0",
"subnetId": "subnet-0123456789abcdef0",
"securityGroupId": "sg-0123456789abcdef0",
"iamRoleArn": "arn:aws:iam::123456789012:role/openclaw-starfish-role",
"model": "amazon-bedrock/minimax.minimax-m2.1",
"telegramBotTokenMasked": "123456789:ABCDEfg***",
"ssm_parameter_prefix": "/openclaw/starfish"
}
AWS resources (all tagged with Project=<name>, DeployId=<deploy-id>):
/openclaw/starfish/TELEGRAM_BOT_TOKEN)service status:
openclaw is active (running) on the instancegateway log: viewable via aws ssm start-session --target <instance-id> then sudo journalctl -u openclaw -f
deploy-output.json exists in the current working directory with all resource IDs populated.
smoke test passes: the script logs "OpenClaw gateway is running" and prints valid JSON from openclaw.json config.
SSM session works: you can connect via aws ssm start-session --target <instance-id> and run commands as the openclaw user.
Telegram bot responds: send a message to the bot (token from TELEGRAM_BOT_TOKEN). within ~10 seconds, the gateway logs show an incoming message and the bot either replies (if configured) or posts a pairing code (if unconfigured).
no SSH access needed: verify that the security group has no inbound SSH rule. confirm that direct SSH to the public IP fails (e.g., ssh -i key.pem ec2-user@<public-ip> times out or is refused).
secrets are not in the AMI or repo: verify that deploy-output.json masks the Telegram token, and the actual token exists only in SSM Parameter Store (fetch it via aws ssm get-parameter --name /openclaw/<name>/TELEGRAM_BOT_TOKEN --region <region>).
cost is ~$30/month: instance billing has started; you can view estimated charges in AWS Billing console (usually appears within 1 hour).