---
title: 'Deploy Hono on AWS'
description: 'Deploy Hono applications on AWS Lambda and API Gateway, or as containerized services with ECS Fargate and Application Load Balancer.'
---
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
import PatternList from '../../../components/docs/PatternList.astro';
import FrameworkHero from '../../../components/docs/FrameworkHero.astro';
Deploy your [Hono](https://hono.dev/) 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.
```sh
bun create hono my-hono-app
cd my-hono-app
```
```sh
npm create hono@latest my-hono-app
cd my-hono-app
```
```sh
pnpm create hono my-hono-app
cd my-hono-app
```
### Install Thunder
Add Thunder as a development dependency. It provides the CDK constructs you'll use to define your AWS infrastructure.
```sh
bun add @thunder-so/thunder --development
```
```sh
npm install @thunder-so/thunder --save-dev
```
```sh
pnpm add -D @thunder-so/thunder
```
---
## Hono Lambda Deployment
Deploy your Hono API to [AWS Lambda](https://aws.amazon.com/lambda/) with [API Gateway](https://aws.amazon.com/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](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) expected by API Gateway. Export it as `handler` and your function is ready to deploy.
```ts title="src/index.ts"
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`:
```json title="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.
```ts title="stack/prod.ts"
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](https://aws.amazon.com/ecr/), and deploys it as a [container Lambda](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html), which supports up to 10 GB.
#### Stack (Container mode)
Add `dockerFile` to `functionProps` to enable container mode.
```ts title="stack/prod.ts"
const config: LambdaProps = {
// ...
functionProps: {
dockerFile: 'Dockerfile',
memorySize: 1792,
timeout: 10,
keepWarm: true,
},
};
```
#### Dockerfile
```dockerfile title="Dockerfile"
FROM public.ecr.aws/lambda/nodejs:22 AS builder
WORKDIR ${LAMBDA_TASK_ROOT}
COPY . .
RUN npm install
RUN npm run build
FROM public.ecr.aws/lambda/nodejs:22
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /var/task/dist/* ./
COPY --from=builder /var/task/node_modules ./node_modules
CMD ["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](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically.
```ts title="stack/prod.ts"
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.
```sh
bun run build
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default
```
```sh
npm run build
npx cdk deploy --app "npx tsx stack/prod.ts" --profile default
```
```sh
pnpm run build
pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default
```
After 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](https://aws.amazon.com/fargate/). Traffic is routed through an [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/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.
```sh
bun add @hono/node-server
```
```sh
npm install @hono/node-server
```
```sh
pnpm add @hono/node-server
```
```ts title="src/index.ts"
import { 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.
```ts title="stack/prod.ts"
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.
```dockerfile title="Dockerfile"
FROM public.ecr.aws/docker/library/node:22-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb tsconfig.json ./
RUN curl -fsSL https://bun.sh/install | bash && export PATH="$HOME/.bun/bin:$PATH"
RUN bun install --frozen-lockfile
COPY src ./src
RUN bun run build
FROM public.ecr.aws/docker/library/node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["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](https://aws.amazon.com/secrets-manager/) and reference them by ARN — Thunder fetches and injects them automatically.
```ts title="stack/prod.ts"
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](https://aws.amazon.com/ecr/), and deploys it to Fargate. No manual Docker commands needed.
```sh
npx cdk deploy --app "bunx tsx stack/prod.ts" --profile default
```
```sh
npx cdk deploy --app "npx tsx stack/prod.ts" --profile default
```
```sh
pnpm exec cdk deploy --app "pnpm exec tsx stack/prod.ts" --profile default
```
After deployment, CDK outputs the **Load Balancer DNS** for your application.