{"id":44611719,"url":"https://github.com/zxkane/openhands-infra","last_synced_at":"2026-04-09T07:10:41.042Z","repository":{"id":334014530,"uuid":"1137352845","full_name":"zxkane/openhands-infra","owner":"zxkane","description":"An AWS CDK (TypeScript) infrastructure project for deploying OpenHands, an AI‑driven development platform, on AWS.","archived":false,"fork":false,"pushed_at":"2026-02-12T15:47:54.000Z","size":636,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-12T17:48:34.716Z","etag":null,"topics":["aws","aws-cdk","code-agent","iac","openhands"],"latest_commit_sha":null,"homepage":"https://kane.mx/posts/2026/deploying-openhands-on-aws-with-cdk/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zxkane.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"security-check.sh","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-19T09:02:48.000Z","updated_at":"2026-02-03T07:16:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zxkane/openhands-infra","commit_stats":null,"previous_names":["zxkane/openhands-infra"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/zxkane/openhands-infra","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zxkane%2Fopenhands-infra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zxkane%2Fopenhands-infra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zxkane%2Fopenhands-infra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zxkane%2Fopenhands-infra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zxkane","download_url":"https://codeload.github.com/zxkane/openhands-infra/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zxkane%2Fopenhands-infra/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29443468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T10:51:12.367Z","status":"ssl_error","status_checked_at":"2026-02-14T10:50:52.088Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-cdk","code-agent","iac","openhands"],"created_at":"2026-02-14T12:00:52.284Z","updated_at":"2026-04-09T07:10:41.028Z","avatar_url":"https://github.com/zxkane.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🚀 OpenHands on AWS\n\n### Self-host your AI coding agent — fully serverless, zero idle cost\n\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![CI](https://github.com/zxkane/openhands-infra/actions/workflows/ci.yml/badge.svg)](https://github.com/zxkane/openhands-infra/actions/workflows/ci.yml)\n[![CDK](https://img.shields.io/badge/AWS_CDK-TypeScript-orange)](https://aws.amazon.com/cdk/)\n[![OpenHands](https://img.shields.io/badge/OpenHands-Compatible-green)](https://github.com/All-Hands-AI/OpenHands)\n\nDeploy [OpenHands](https://github.com/All-Hands-AI/OpenHands) on AWS with **production-grade infrastructure** in minutes.\nECS Fargate • Bedrock LLM • Per-conversation isolation • Self-healing architecture.\n\n[**Getting Started**](#-quick-start) · [**Architecture**](#architecture-overview) · [**Cost Estimate**](#cost-estimate) · [**Blog Post**](https://kane.mx/posts/2026/serverless-multi-tenant-openhands-on-aws/)\n\n\u003c/div\u003e\n\n---\n\n## Why This Project?\n\nRunning OpenHands locally is great for trying it out. Running it for a **team** or in **production** is a different story:\n\n| Challenge | How This Project Solves It |\n|-----------|---------------------------|\n| **\"I don't want to manage servers\"** | Fully serverless — ECS Fargate, Aurora Serverless, no EC2 instances |\n| **\"Idle cost is too high\"** | Sandboxes scale to zero when not in use; pay only for active conversations |\n| **\"Multi-user access control\"** | Cognito authentication with 30-day sessions, per-user conversation isolation |\n| **\"My conversations disappear on restart\"** | Self-healing: Aurora + S3 + EFS persist everything across Fargate task replacements |\n| **\"I need AWS access from the AI agent\"** | Optional scoped IAM credentials for sandbox containers (least-privilege) |\n| **\"Setting up infra is painful\"** | One `cdk deploy --all` command — 10 stacks deployed in the right order automatically |\n\n## ✨ Key Features\n\n- **🏗️ Fully Serverless** — ECS Fargate (ARM64) for compute, Aurora Serverless v2 for database, no instances to patch\n- **💰 Zero Idle Cost** — Sandbox containers spin up per-conversation and stop automatically after idle timeout\n- **🔒 Per-Conversation Isolation** — Each sandbox gets a dedicated EFS access point; no cross-conversation access\n- **🔄 Self-Healing Architecture** — Conversations resume seamlessly after Fargate task replacement (Aurora + S3 + EFS)\n- **🤖 AWS Bedrock** — LLM inference via IAM Role, no API keys to manage\n- **🌐 Multi-Domain Support** — Share one backend across multiple CloudFront distributions and domains\n- **🔐 Enterprise Security** — Cognito auth, WAF, VPC Endpoints, private subnets, KMS encryption, Secrets Manager\n- **🚀 Runtime Subdomain** — Agent-built apps accessible via `{port}-{convId}.runtime.{subdomain}.{domain}`\n- **📊 Observability** — CloudWatch Logs, Alarms, Container Insights, AWS Backup (14-day retention)\n- **🏎️ Warm Pool** — Pre-warmed sandbox tasks for instant conversation starts\n\n## Architecture Overview\n\n```\nUser → CloudFront (WAF+Lambda@Edge Auth) → ALB (origin verified) → ECS Fargate (App + OpenResty)\n           │                                                                  ↓\n           └── Cognito (OAuth2, Managed Login v2)                  Cloud Map → Sandbox Fargate Tasks\n                                                                              ↓\n                                                        VPC Endpoints → Bedrock / CloudWatch Logs\n                                                                              ↓\n                                                        RDS Proxy → Aurora Serverless v2 PostgreSQL\n\nSandbox Orchestration:\nApp → Orchestrator Lambda → DynamoDB Registry → Sandbox Fargate Tasks (per-conversation EFS isolation)\n\nRuntime Apps:\n{port}-{convId}.runtime.{subdomain}.{domain} → CloudFront → Lambda@Edge → OpenResty → Sandbox Fargate Task\n```\n\n\u003e 📐 For a detailed architecture deep dive (10-stack breakdown, data flows, sandbox lifecycle), see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).\n\n## 🚀 Quick Start\n\n### Prerequisites\n\n- AWS CLI configured with appropriate credentials\n- Node.js 22+ and npm\n- Existing VPC with private subnets and NAT Gateway\n- Existing Route 53 Hosted Zone\n\n### 1. Install Dependencies\n\n```bash\ngit clone https://github.com/zxkane/openhands-infra.git\ncd openhands-infra\nnpm install\n```\n\n### 2. Bootstrap CDK (First Time Only)\n\n```bash\nnpx cdk bootstrap --region \u003cyour-main-region\u003e\nnpx cdk bootstrap --region us-east-1  # Required for Lambda@Edge and CloudFront\n```\n\n### 3. Create Sandbox Secret Key (First Time Only)\n\n```bash\naws secretsmanager create-secret \\\n  --name openhands/sandbox-secret-key \\\n  --secret-string \"$(openssl rand -base64 32)\" \\\n  --region \u003cyour-main-region\u003e \\\n  --description \"OpenHands sandbox secret key for session encryption\"\n```\n\n\u003e **Note**: This secret must exist in each region where you deploy.\n\n### 4. Deploy\n\n```bash\nnpx cdk deploy --all \\\n  --context vpcId=\u003cvpc-id\u003e \\\n  --context hostedZoneId=\u003chosted-zone-id\u003e \\\n  --context domainName=\u003cdomain-name\u003e \\\n  --context subDomain=\u003csubdomain\u003e \\\n  --context region=\u003cregion\u003e \\\n  --require-approval never\n```\n\nThat's it! Access OpenHands at `https://\u003csubdomain\u003e.\u003cdomain-name\u003e` 🎉\n\n## Configuration\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e📋 All Context Parameters\u003c/strong\u003e\u003c/summary\u003e\n\n| Parameter | Description | Example |\n|-----------|-------------|---------|\n| `vpcId` | Existing VPC ID | `vpc-0123456789abcdef0` |\n| `hostedZoneId` | Route 53 Hosted Zone ID | `Z0123456789ABCDEFGHIJ` |\n| `domainName` | Domain name | `example.com` |\n| `subDomain` | Subdomain for OpenHands | `openhands` |\n| `region` | AWS region (optional, defaults to us-east-1) | `us-west-2` |\n| `siteName` | Cognito managed login site name (optional) | `Openhands on AWS` |\n| `authCallbackDomains` | Extra OAuth callback domains for shared Cognito client (optional; JSON array or comma-separated) | `[\"openhands.example.com\",\"openhands.test.example.com\"]` |\n| `authDomainPrefixSuffix` | Suffix for Cognito domain prefix (optional; avoids collisions) | `shared` |\n| `edgeStackSuffix` | Suffix for Edge stack name in us-east-1 (optional; enables multiple Edge stacks) | `my-project` |\n| `sandboxAwsAccess` | Enable sandbox AWS access (optional, defaults to false) | `true` |\n| `sandboxAwsPolicyFile` | Path to custom IAM policy JSON for sandbox (optional) | `config/sandbox-aws-policy.json` |\n| `skipS3Endpoint` | Skip S3 Gateway endpoint if VPC already has one (optional) | `true` |\n| `warmPoolSize` | Number of pre-warmed sandbox Fargate tasks (optional, default: 2) | `3` |\n| `idleTimeoutMinutes` | Minutes before idle sandbox is stopped (optional, default: 30, staging: 10) | `15` |\n| `sandboxSociImageUri` | SOCI v2 image URI for Fargate lazy loading (optional, see AGENTS.md) | `\u003cecr-uri\u003e:tag-soci` |\n\n\u003c/details\u003e\n\n## Stack Structure\n\nThe project deploys **10 stacks** with automatic dependency resolution:\n\n| Stack | Region | Description |\n|-------|--------|-------------|\n| `OpenHands-Auth` | us-east-1 | Cognito User Pool + Managed Login v2 branding |\n| `OpenHands-Network` | Main | VPC import, VPC Endpoints |\n| `OpenHands-Monitoring` | Main | CloudWatch Logs, Alarms, S3 Data Bucket, Backup |\n| `OpenHands-Security` | Main | IAM Roles, Security Groups, KMS key |\n| `OpenHands-Database` | Main | Aurora Serverless v2 PostgreSQL with RDS Proxy |\n| `OpenHands-UserConfig` | Main | User Configuration API Lambda (MCP, Secrets, Integrations) |\n| `OpenHands-Cluster` | Main | Shared ECS Cluster + Cloud Map namespace |\n| `OpenHands-Sandbox` | Main | Sandbox Fargate tasks, DynamoDB registry, Orchestrator Lambda |\n| `OpenHands-Compute` | Main | Fargate services (App + OpenResty), ALB, EFS |\n| `OpenHands-Edge-*` | us-east-1 | Lambda@Edge, CloudFront, WAF, Route 53 (per domain/environment) |\n\n**Deployment Order** (handled automatically by CDK):\n0. Auth → 1. Network → 2. Monitoring → 3. Security → 4. Database → 5. UserConfig → 6. Cluster → 7. Sandbox → 8. Compute → 9. Edge\n\n## Cost Estimate\n\n### Base Infrastructure (~$250-350/month)\n\n| Component | Monthly Cost (USD) | Notes |\n|-----------|--------------------|-------|\n| Fargate App Service (1 vCPU / 2 GB ARM64) | ~$30 | Auto-scales 1-3 |\n| Fargate OpenResty Service (0.25 vCPU / 512 MB) | ~$8 | Auto-scales 1-3 |\n| Fargate Sandbox Tasks | ~$0-50 | On-demand, per-conversation |\n| Aurora Serverless v2 | ~$43-80 | 0.5-4 ACU |\n| RDS Proxy | ~$18 | |\n| CloudFront | ~$85 | 1TB data transfer |\n| VPC Endpoints (10) | ~$60 | |\n| ALB | ~$25 | |\n| Other (EFS, S3, NAT, CW, R53, DDB) | ~$10-50 | Usage-dependent |\n\n### Bedrock LLM Cost (Variable)\n\n| Model | Input (per 1M tokens) | Output (per 1M tokens) |\n|-------|----------------------|------------------------|\n| Claude Opus 4.5 | $5 | $25 |\n| Claude Sonnet 4.5 | $3 | $15 |\n| Claude Haiku 4.5 | $1 | $5 |\n\n**Example**: 10M input + 2M output tokens/month with Claude Sonnet 4.5 ≈ **$60/month**\n\n## Advanced Topics\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🌐 Multi-Domain Deployment\u003c/strong\u003e\u003c/summary\u003e\n\nYou can deploy multiple OpenHands instances on different domains, all sharing the same backend infrastructure.\n\n### Architecture\n\n```\n                                 ┌─────────────────────────────────┐\n                                 │      AuthStack (us-east-1)      │\n                                 │  Shared Cognito User Pool       │\n                                 │  - Multi-domain callbacks       │\n                                 └─────────────────────────────────┘\n                                              │\n              ┌───────────────────────────────┼───────────────────────────────┐\n              │                               │                               │\n              ▼                               ▼                               ▼\n┌─────────────────────────┐   ┌─────────────────────────┐   ┌─────────────────────────┐\n│  EdgeStack-Domain1      │   │  EdgeStack-Domain2      │   │  EdgeStack-DomainN      │\n│  (us-east-1)            │   │  (us-east-1)            │   │  (us-east-1)            │\n│  - CloudFront           │   │  - CloudFront           │   │  - CloudFront           │\n│  - Lambda@Edge          │   │  - Lambda@Edge          │   │  - Lambda@Edge          │\n│  - WAF                  │   │  - WAF                  │   │  - WAF                  │\n│  - Route 53 records     │   │  - Route 53 records     │   │  - Route 53 records     │\n│  - ACM Certificate      │   │  - ACM Certificate      │   │  - ACM Certificate      │\n└─────────────────────────┘   └─────────────────────────┘   └─────────────────────────┘\n              │                               │                               │\n              └───────────────────────────────┼───────────────────────────────┘\n                                              │\n                                              ▼\n                           ┌─────────────────────────────────────┐\n                           │     ComputeStack (main region)      │\n                           │  - ALB with origin verification     │\n                           │  - Fargate services (App+OpenResty) │\n                           │  - SSM parameters in us-east-1      │\n                           └─────────────────────────────────────┘\n                                              │\n                           ┌──────────────────┴──────────────────┐\n                           ▼                                     ▼\n              ┌─────────────────────────┐          ┌─────────────────────────┐\n              │     DatabaseStack       │          │    MonitoringStack      │\n              │  Aurora PostgreSQL      │          │  S3, CloudWatch         │\n              └─────────────────────────┘          └─────────────────────────┘\n```\n\n### Step 1: Configure Shared Authentication\n\n```bash\nnpx cdk deploy OpenHands-Auth \\\n  --context vpcId=\u003cvpc-id\u003e \\\n  --context hostedZoneId=\u003cprimary-hosted-zone-id\u003e \\\n  --context domainName=\u003cprimary-domain\u003e \\\n  --context subDomain=openhands \\\n  --context region=\u003cmain-region\u003e \\\n  --context authCallbackDomains='[\"openhands.domain1.com\",\"openhands.domain2.com\"]' \\\n  --require-approval never\n```\n\n### Step 2: Deploy Backend Infrastructure\n\n```bash\nnpx cdk deploy OpenHands-Network OpenHands-Monitoring OpenHands-Security \\\n  OpenHands-Database OpenHands-UserConfig OpenHands-Cluster \\\n  OpenHands-Sandbox OpenHands-Compute \\\n  --context vpcId=\u003cvpc-id\u003e \\\n  --context hostedZoneId=\u003cprimary-hosted-zone-id\u003e \\\n  --context domainName=\u003cprimary-domain\u003e \\\n  --context subDomain=openhands \\\n  --context region=\u003cmain-region\u003e \\\n  --require-approval never\n```\n\n### Step 3: Deploy Edge Stacks for Each Domain\n\n```bash\n# Domain 1\nnpx cdk deploy OpenHands-Edge-Test \\\n  --context vpcId=\u003cvpc-id\u003e \\\n  --context hostedZoneId=\u003chosted-zone-for-test-example-com\u003e \\\n  --context domainName=test.example.com \\\n  --context subDomain=openhands \\\n  --context region=\u003cmain-region\u003e \\\n  --context edgeStackSuffix=Test \\\n  --exclusively \\\n  --require-approval never\n\n# Domain 2\nnpx cdk deploy OpenHands-Edge-Prod \\\n  --context vpcId=\u003cvpc-id\u003e \\\n  --context hostedZoneId=\u003chosted-zone-for-prod-example-com\u003e \\\n  --context domainName=prod.example.com \\\n  --context subDomain=openhands \\\n  --context region=\u003cmain-region\u003e \\\n  --context edgeStackSuffix=Prod \\\n  --exclusively \\\n  --require-approval never\n```\n\n**Important**: Use `--exclusively` flag when deploying individual Edge stacks to avoid redeploying the backend stacks with different domain context.\n\n### Managing Domains\n\n**Adding a new domain:**\n1. Update Auth stack with the new callback domain\n2. Deploy a new Edge stack with `--context edgeStackSuffix=\u003cName\u003e --exclusively`\n\n**Removing a domain:**\n1. `aws cloudformation delete-stack --stack-name OpenHands-Edge-\u003cSuffix\u003e --region us-east-1`\n2. Optionally update Auth stack to remove the callback domain\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔄 Conversation Resume (Self-Healing)\u003c/strong\u003e\u003c/summary\u003e\n\nWhen sandbox Fargate tasks stop (idle timeout, crash, or deployment), conversations become `ARCHIVED`. All data is preserved:\n\n| Data | Storage | Survives Task Stop |\n|------|---------|-------------------|\n| Conversation metadata | Aurora PostgreSQL | ✅ |\n| Conversation events/history | S3 | ✅ |\n| Workspace files | EFS (per-conversation access point) | ✅ |\n\n**Auto-Resume Flow:**\n\n```\nUser clicks archived conversation\n    ↓\nFrontend detects ARCHIVED status\n    ↓\nCalls POST /api/v1/app-conversations/{id}/resume\n    ↓\nApp → Orchestrator Lambda:\n  - Creates new EFS access point for conversation\n  - Registers new task definition with access point\n  - Launches Fargate sandbox task\n  - Updates DynamoDB registry\n    ↓\nPage reloads → conversation is usable again\n```\n\nWorkspace files on EFS are preserved via the access point, so code and files from the previous session remain available after resume.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔐 Sandbox AWS Access\u003c/strong\u003e\u003c/summary\u003e\n\nEnable AI agents in sandbox containers to access AWS services with scoped IAM credentials:\n\n```bash\nnpx cdk deploy --all \\\n  --context sandboxAwsAccess=true \\\n  --context sandboxAwsPolicyFile=config/sandbox-aws-policy.json \\\n  ...\n```\n\n### ⚠️ Customize the Policy File\n\nThe default `config/sandbox-aws-policy.json` grants broad permissions. **Customize this for your use case!**\n\n**Example: Purpose-built policy for S3 and DynamoDB only:**\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"AllowS3Access\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:ListBucket\"],\n      \"Resource\": [\"arn:aws:s3:::my-bucket\", \"arn:aws:s3:::my-bucket/*\"]\n    },\n    {\n      \"Sid\": \"AllowDynamoDB\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\"dynamodb:GetItem\", \"dynamodb:PutItem\", \"dynamodb:Query\"],\n      \"Resource\": \"arn:aws:dynamodb:*:*:table/my-table\"\n    }\n  ]\n}\n```\n\n### Hardcoded Explicit Denies\n\nThese actions are **always denied** regardless of your policy:\n\n| Category | Denied Actions |\n|----------|----------------|\n| IAM Users | `iam:CreateUser`, `iam:DeleteUser`, `iam:CreateAccessKey` |\n| IAM Policies | `iam:AttachUserPolicy`, `iam:PutUserPolicy`, `iam:PutRolePolicy` |\n| IAM Roles | `iam:CreateRole`, `iam:DeleteRole`, `iam:AttachRolePolicy` |\n| Account | `organizations:*`, `account:*`, `billing:*` |\n| Role Assumption | `sts:AssumeRole` (prevents lateral movement) |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🌍 Runtime Subdomain Routing\u003c/strong\u003e\u003c/summary\u003e\n\nWhen AI agents run applications (e.g., Flask, Node.js) inside the sandbox, they are accessible via dedicated runtime subdomains:\n\n```\nhttps://{port}-{convId}.runtime.{subdomain}.{domain}/\n```\n\n**Example**: `https://5000-abc123def456.runtime.openhands.example.com/`\n\n| Feature | Benefit |\n|---------|---------|\n| Domain Root | Apps run at `/` — internal routes work correctly |\n| Cookie Isolation | Each runtime has isolated cookies |\n| Security Headers | X-Frame-Options, CSP, X-XSS-Protection applied automatically |\n| No Authentication | Runtime subdomains bypass Cognito (public within conversation) |\n\n### Architecture\n\n```\nUser Browser\n    ↓\nhttps://5000-{convId}.runtime.openhands.example.com/\n    ↓\nCloudFront (matches *.runtime.* wildcard certificate)\n    ↓\nLambda@Edge (viewer-request: parse subdomain, rewrite URI)\n    ↓\nALB → OpenResty → Sandbox Discovery (DynamoDB) → User App\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e💾 Data Persistence\u003c/strong\u003e\u003c/summary\u003e\n\n| Data Type | Storage | Persistence |\n|-----------|---------|-------------|\n| Conversation Metadata | Aurora PostgreSQL | Permanent (via RDS Proxy) |\n| Conversation Events | S3 | Permanent (survives task replacement) |\n| User Settings / Secrets | S3 | Permanent (KMS envelope encryption) |\n| Workspace Files | EFS | Persistent (per-conversation access points) |\n| SDK Conversation Cache | EFS | Persistent (enables LLM context restoration) |\n| Sandbox Registry | DynamoDB | Permanent (task state, user ownership) |\n\n**Aurora Serverless v2**: PostgreSQL 15.8, RDS Proxy connection pooling, 0.5-4 ACU auto-scaling, 35-day backups.\n\n**S3 Bucket**: SSE-S3 encryption, versioning (30-day retention), RETAIN removal policy.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔒 Security\u003c/strong\u003e\u003c/summary\u003e\n\n- Fargate tasks in private subnets only\n- Per-conversation EFS isolation via access points\n- All AWS service access via VPC Endpoints\n- IAM Roles with least privilege per service\n- Database credentials in Secrets Manager\n- RDS Proxy with TLS-encrypted connections\n- User secrets protected by KMS envelope encryption\n- Cognito authentication (30-day sessions)\n- Lambda@Edge header spoofing prevention\n- WAF protection with rate limiting\n- S3 and Aurora storage encryption\n\n**Session Management:**\n\n| Token Type | Validity | Description |\n|------------|----------|-------------|\n| Access Token | 1 hour | API access token |\n| ID Token | 1 day | Identity token (stored in cookie) |\n| Refresh Token | 30 days | Used to obtain new tokens |\n\n\u003c/details\u003e\n\n## VPC Requirements\n\nYour existing VPC must have:\n- At least 2 private subnets in different AZs\n- NAT Gateway for outbound internet access\n- DNS hostnames enabled\n\n## CI/CD\n\n| Workflow | Trigger | Description |\n|----------|---------|-------------|\n| **CI** | Push/PR to main, develop | Build TypeScript, run all tests (Jest + pytest) |\n| **Security Scan** | Push/PR to main, daily | npm audit, Checkov, git-secrets, Semgrep SAST, cfn-lint |\n\n```bash\nnpm run test        # Run all tests\nnpm run test:ts     # TypeScript tests only\nnpm run test:py     # Python tests only\nnpm run test:ts -- -u  # Update snapshots\n```\n\n## Useful Commands\n\n```bash\nnpm run build       # Build TypeScript\nnpm run watch       # Watch for changes\nnpx cdk diff --all  # Show diff before deploy\nnpx cdk synth --all # Synthesize CloudFormation\nnpx cdk destroy --all  # Destroy all stacks\n```\n\n## Troubleshooting\n\n\u003cdetails\u003e\n\u003csummary\u003eCommon issues\u003c/summary\u003e\n\n**VPC Lookup Fails** — Ensure the VPC exists and your AWS credentials have `ec2:DescribeVpcs` permission.\n\n**Certificate Validation Pending** — ACM certificates use DNS validation. Ensure the Hosted Zone is correctly configured.\n\n**Fargate Task Not Starting** — Check CloudWatch Logs at `/openhands/application` for container startup errors. Check ECS service events for Fargate capacity issues.\n\n\u003c/details\u003e\n\n## Contributing\n\nContributions are welcome! Please feel free to submit issues and pull requests.\n\n### AI Agent Skills\n\nThis project uses [autonomous-dev-team](https://github.com/zxkane/autonomous-dev-team) skills for AI-assisted development with Claude Code, Kiro CLI, and Codex. Install after cloning:\n\n```bash\nnpx skills add zxkane/autonomous-dev-team -s '*' -a claude-code -a kiro-cli -a codex -y\n```\n\nOr restore from the lock file:\n\n```bash\nnpx skills experimental_install\n```\n\nThese skills enforce TDD, git worktree isolation, PR workflows, and E2E testing. See `CLAUDE.md` for the full workflow.\n\n## License\n\nThis project is licensed under the Apache License 2.0 — see the [LICENSE](LICENSE) file for details.\n\nThis infrastructure project deploys [OpenHands](https://github.com/All-Hands-AI/OpenHands). See the [OpenHands License](https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE) for the main application.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**If this project helps you deploy OpenHands, consider giving it a ⭐**\n\nBuilt with ❤️ using [AWS CDK](https://aws.amazon.com/cdk/) and [OpenHands](https://github.com/All-Hands-AI/OpenHands)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzxkane%2Fopenhands-infra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzxkane%2Fopenhands-infra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzxkane%2Fopenhands-infra/lists"}