Deploy your Hono applications to AWS using Thunder. Choose the pattern that fits your app’s needs.
Available Patterns
Prerequisites
Getting Started
Create Project
Scaffold a new Hono project using your preferred package manager. This sets up the project structure, installs dependencies, and prepares you for development.
bun create hono my-hono-appcd my-hono-appnpm create hono@latest my-hono-appcd my-hono-apppnpm create hono my-hono-appcd my-hono-appInstall Thunder
Add Thunder as a development dependency. It provides the CDK constructs you’ll use to define your AWS infrastructure.
bun add @thunder-so/thunder --developmentnpm install @thunder-so/thunder --save-devpnpm add -D @thunder-so/thunderHono Lambda Deployment
Deploy your Hono API to AWS Lambda with API Gateway as the public HTTP endpoint. Hono has first-class support for Lambda via its hono/aws-lambda adapter — no additional packages needed.
Configure Hono for AWS Lambda
Hono’s handle() adapter wraps your app in the Lambda handler signature expected by API Gateway. Export it as handler and your function is ready to deploy.
import { Hono } from 'hono'import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.json({ message: 'Hello from Hono!' }))
export const handler = handle(app)Hono on Lambda works best bundled to a single file with esbuild. Add a build script to package.json:
{ "scripts": { "build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node22 ./src/index.ts" }}Running the build produces dist/index.js — the file Lambda will execute.
Stack (Zip mode)
The Lambda construct provisions a Lambda function and an API Gateway HTTP API. The Zip mode packages dist/ directly — no Docker required.
import { Cdk, Lambda, type LambdaProps } from '@thunder-so/thunder';
const config: LambdaProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', functionProps: { runtime: Cdk.aws_lambda.Runtime.NODEJS_22_X, architecture: Cdk.aws_lambda.Architecture.ARM_64, codeDir: 'dist', handler: 'index.handler', memorySize: 512, timeout: 10, keepWarm: true, },};
new Lambda(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);Container Mode
Zip deployments have a 250 MB unzipped size limit. If your app has large dependencies, switch to container mode. Thunder builds a Docker image, pushes it to ECR, and deploys it as a container Lambda, which supports up to 10 GB.
Stack (Container mode)
Add dockerFile to functionProps to enable container mode.
const config: LambdaProps = { // ... functionProps: { dockerFile: 'Dockerfile', memorySize: 1792, timeout: 10, keepWarm: true, },};Dockerfile
FROM public.ecr.aws/lambda/nodejs:22 AS builderWORKDIR ${LAMBDA_TASK_ROOT}COPY . .RUN npm installRUN npm run build
FROM public.ecr.aws/lambda/nodejs:22WORKDIR ${LAMBDA_TASK_ROOT}COPY --from=builder /var/task/dist/* ./COPY --from=builder /var/task/node_modules ./node_modulesCMD ["index.handler"]Environment Variables and Secrets
Runtime environment variables are injected into the Lambda function at deploy time. For sensitive values, store them in AWS Secrets Manager and reference them by ARN — Thunder fetches and injects them automatically.
const config: LambdaProps = { // ... functionProps: { variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], },};Deploy
Build the handler first, then deploy with CDK. CDK outputs the API Gateway URL.
bun run buildnpx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultnpm run buildnpx cdk deploy --app "npx tsx stack/prod.ts" --profile defaultpnpm run buildpnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile defaultAfter deployment, CDK outputs the API Gateway URL for your function.
Hono Containerized Deployment with Fargate
Run your Hono API as a Node.js server inside a Docker container on ECS Fargate. Traffic is routed through an Application Load Balancer. This pattern is ideal for long-running services, persistent connections, and workloads that exceed Lambda’s limits.
Configure for Node Server
Install @hono/node-server to run Hono as a standard HTTP server — the same code works locally and inside the container.
bun add @hono/node-servernpm install @hono/node-serverpnpm add @hono/node-serverimport { Hono } from 'hono'import { serve } from '@hono/node-server'
const app = new Hono()
app.get('/', (c) => c.json({ message: 'Hello from Hono!' }))
serve({ fetch: app.fetch, port: Number(process.env.PORT) || 3000,})Stack
The Fargate construct creates an ECS cluster, a Fargate task definition, and an Application Load Balancer.
import { Cdk, Fargate, type FargateProps } from '@thunder-so/thunder';
const config: FargateProps = { env: { account: 'YOUR_ACCOUNT_ID', region: 'us-east-1' }, application: 'myapp', service: 'api', environment: 'prod', rootDir: '.', serviceProps: { dockerFile: 'Dockerfile', architecture: Cdk.aws_ecs.CpuArchitecture.ARM64, cpu: 512, memorySize: 1024, port: 3000, desiredCount: 1, healthCheckPath: '/', },};
new Fargate(new Cdk.App(), `${config.application}-${config.service}-${config.environment}-stack`, config);Dockerfile
Create a Dockerfile in your project root. The multi-stage build keeps the final image lean by separating the build environment from the runtime.
FROM public.ecr.aws/docker/library/node:22-alpine AS builderWORKDIR /appCOPY package.json bun.lockb tsconfig.json ./RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH"RUN bun install --frozen-lockfileCOPY src ./srcRUN bun run build
FROM public.ecr.aws/docker/library/node:22-alpine AS runnerWORKDIR /appENV NODE_ENV=productionENV PORT=3000COPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY package.json ./EXPOSE 3000CMD ["node", "dist/index.js"]Environment Variables and Secrets
Runtime environment variables are injected into the Fargate task at deploy time. For sensitive values, store them in AWS Secrets Manager and reference them by ARN — Thunder fetches and injects them automatically.
const config: FargateProps = { // ... serviceProps: { // ... variables: [ { NODE_ENV: 'production' }, ], secrets: [ { key: 'DATABASE_URL', resource: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:/myapp/DATABASE_URL-abc123', }, ], },};Deploy
CDK builds the Docker image, pushes it to ECR, and deploys it to Fargate. No manual Docker commands needed.
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile defaultnpx cdk deploy --app "npx tsx stack/prod.ts" --profile defaultpnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile defaultAfter deployment, CDK outputs the Load Balancer DNS for your application.