Deploying OpenHands AI Platform on AWS with CDK

Overview

OpenHands is an open-source AI-driven development platform that enables AI agents to write code, fix bugs, and execute complex development tasks autonomously. The default setup works well for local development, but what if you want to run it for a team or make it accessible from anywhere?

This post introduces an AWS CDK project that extends OpenHands beyond single-user local usage. It adds multi-user authentication, persistent storage, and automatic recovery capabilities—transforming OpenHands into a shared service that your team can access from anywhere.

Why AWS CDK?

AWS CDK (Cloud Development Kit) is an Infrastructure as Code (IaC) tool that lets you define cloud infrastructure using familiar programming languages like TypeScript, Python, or Java. Instead of manually clicking through the AWS Console or writing verbose CloudFormation YAML, you write code that CDK compiles into CloudFormation templates.

Key benefits for this project:

  • Reproducible deployments: Deploy the same infrastructure to dev, staging, and production accounts with a single codebase
  • Multi-account management: Use CDK's cross-account deployment capabilities to manage OpenHands instances across different AWS accounts
  • Version controlled infrastructure: Track infrastructure changes in Git, review in pull requests, and roll back if needed
  • Type safety: TypeScript catches configuration errors at compile time rather than deployment time
  • Reusable constructs: The six stacks in this project can be customized via context parameters for different environments

For teams running OpenHands across multiple AWS accounts (e.g., separate accounts for each team or environment), CDK makes it straightforward to maintain consistency while allowing per-account customization.

OpenHands OSS vs This Project

Before diving into the architecture, let's understand what the default OpenHands provides and what this project adds.

Default OpenHands (Local Setup)

Out of the box, OpenHands runs as a single Docker container:

AspectDefault Behavior
DatabaseSQLite (single file)
StorageLocal filesystem
UsersSingle user (localhost only)
AuthenticationNone
LLMMultiple providers (API keys required)
PersistenceLost when container removed
ScalingSingle container

This works well for individual developers experimenting locally, but has limitations for team usage or always-on deployments.

What This Project Adds

The openhands-infra project addresses these limitations:

AspectThis Project
DatabaseAurora Serverless v2 PostgreSQL
StorageS3 (events) + EFS (workspaces)
UsersMulti-user with Cognito
AuthenticationOAuth 2.0 via Lambda@Edge
LLMAWS Bedrock (no API keys needed)
PersistenceSelf-healing (survives instance replacement)
ScalingAuto Scaling Group

Architecture Overview

The infrastructure uses six CDK stacks across two AWS regions:

flowchart LR
    User([User]) --> CF

    subgraph us-east-1
        CF[CloudFront]
        WAF[WAF]
        LE[Lambda Edge]
        Cognito[Cognito]
    end

    subgraph Main Region
        ALB[ALB]
        EC2[EC2 Graviton]
        Aurora[(Aurora)]
        S3[(S3)]
        EFS[(EFS)]
        Bedrock[Bedrock]
    end

    CF --> WAF
    CF --> LE
    LE --> Cognito
    CF --> ALB
    ALB --> EC2
    EC2 --> Aurora
    EC2 --> S3
    EC2 --> EFS
    EC2 --> Bedrock

Edge Layer (us-east-1): CloudFront for global access, Lambda@Edge for authentication, Cognito for user management, WAF for protection.

Main Region: EC2 with Docker Compose running OpenHands, Aurora for conversation metadata, S3 for events, EFS for workspace files, Bedrock for LLM access.

Key Differences Explained

1. Multi-User Authentication

OSS: No authentication—anyone with access to the URL can use it.

This Project: Cognito User Pool with OAuth 2.0 flow. Lambda@Edge validates JWT tokens at the CloudFront edge before requests reach the backend.

sequenceDiagram
    User->>CloudFront: Request
    CloudFront->>LambdaEdge: Check auth
    alt No valid token
        LambdaEdge->>User: Redirect to Cognito
        User->>Cognito: Login
        Cognito->>User: JWT token
    end
    LambdaEdge->>ALB: Forward with user ID
    ALB->>OpenHands: Process request

Each user's conversations are isolated—stored in per-user S3 paths and labeled containers.

2. Persistent Storage

OSS: SQLite file and local storage. Data is lost when the container is removed.

This Project: Three-tier persistent storage:

Data TypeStorageWhat Happens on Instance Replacement
Conversation metadataAurora PostgreSQLPreserved
Conversation eventsS3 (versioned)Preserved
Workspace filesEFSPreserved
Instance stateEBSCleared

When an EC2 instance is replaced (due to scaling, updates, or failures), users can resume their conversations. The new instance mounts the same EFS workspace and reconnects to Aurora.

3. LLM Integration

OSS: Supports many LLM providers (OpenAI, Anthropic, Google, local models, etc.) but requires users to configure and manage their own API keys.

This Project: Uses AWS Bedrock with IAM role-based access. No API keys needed—the EC2 instance's IAM role grants access to Claude models. This simplifies credential management and enables usage tracking via AWS billing.

4. Container Discovery

OSS: Single container with direct port access.

This Project: OpenHands creates sandbox containers dynamically for each conversation. An OpenResty proxy discovers these containers via Docker API and routes requests using wildcard subdomains:

1https://5000-abc123.runtime.openhands.example.com/
23OpenResty queries Docker API → finds container → proxies to container IP:5000

This allows multiple concurrent conversations with isolated runtime environments.

Deployment

Prerequisites

  • VPC with private subnets (2+ AZs)
  • NAT Gateway
  • Route 53 hosted zone
  • Node.js 20+

Quick Start

 1git clone https://github.com/zxkane/openhands-infra.git
 2cd openhands-infra
 3npm install
 4
 5# Bootstrap CDK
 6npx cdk bootstrap --region us-west-2
 7npx cdk bootstrap --region us-east-1
 8
 9# Deploy
10npx cdk deploy --all \
11  --context vpcId=vpc-xxxxx \
12  --context hostedZoneId=Z0xxxxx \
13  --context domainName=example.com \
14  --context subDomain=openhands

After deployment, access https://openhands.example.com and log in with Cognito credentials.

Cost Considerations

Running this infrastructure costs approximately $375-420/month for the base setup:

ComponentMonthly Cost
EC2 m7g.xlarge (Graviton)~$112
Aurora Serverless v2~$43-80
CloudFront + ALB~$110
VPC Endpoints~$50
Other (EBS, S3, NAT, etc.)~$60-70

Bedrock usage is additional and varies based on Claude model and token consumption.

The cost is higher than running locally, but provides:

  • Always-on availability
  • Multi-user support
  • Automatic backups
  • Self-healing on failures

When to Use This Project

Good fit for:

  • Teams wanting shared access to OpenHands
  • Organizations preferring AWS-managed services
  • Deployments requiring authentication and audit trails
  • Scenarios needing persistent conversations across sessions

May be overkill for:

  • Individual developers working locally
  • Quick experimentation or demos
  • Cost-sensitive use cases with occasional usage

Limitations and Trade-offs

  • WebSocket requirement: CloudFront VPC Origin doesn't support WebSocket, so the ALB is internet-facing with origin verification headers
  • Single region compute: The EC2 instances run in one region (though CloudFront provides global edge access)
  • Admin-managed users: Cognito is configured without self-signup; an admin must create user accounts

Resources