---
title: 'Deploy NestJS on AWS'
description: 'Deploy NestJS 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 [NestJS](https://nestjs.com/) applications to AWS using Thunder. Choose the pattern that fits your app's needs.
## Available Patterns
## Prerequisites
## Getting Started
### Create Project
Install the NestJS CLI and scaffold a new project. This sets up the project structure, installs dependencies, and prepares you for development.
```sh
npm install -g @nestjs/cli
nest new my-nestjs-app
cd my-nestjs-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
```
---
## NestJS Lambda Deployment
Deploy your NestJS API to [AWS Lambda](https://aws.amazon.com/lambda/) with [API Gateway](https://aws.amazon.com/api-gateway/) as the public HTTP endpoint. NestJS adapts to Lambda using `@vendia/serverless-express`, which wraps the Express HTTP adapter in a Lambda-compatible handler.
### Install Adapter for Lambda
`@vendia/serverless-express` bridges NestJS's Express adapter to the [Lambda handler format](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html) expected by API Gateway.
```sh
bun add @vendia/serverless-express
bun add -D @types/aws-lambda
```
```sh
npm install @vendia/serverless-express
npm install -D @types/aws-lambda
```
```sh
pnpm add @vendia/serverless-express
pnpm add -D @types/aws-lambda
```
### Configure NestJS for AWS Lambda
Create a separate entry point for Lambda. This bootstraps the NestJS app once on cold start and reuses the cached handler across subsequent invocations — avoiding the cost of re-initializing the app on every request.
```ts title="src/lambda.ts"
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { configure as serverlessExpress } from '@vendia/serverless-express';
import express from 'express';
import { AppModule } from './app.module';
let cachedHandler: any;
async function bootstrap() {
const expressApp = express();
const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));
await app.init();
return serverlessExpress({ app: expressApp });
}
export const handler = async (event: any, context: any) => {
if (!cachedHandler) {
cachedHandler = await bootstrap();
}
return cachedHandler(event, context);
};
```
Reference: [NestJS Serverless docs](https://docs.nestjs.com/faq/serverless)
### Stack (Zip mode)
The `Lambda` construct provisions a Lambda function and an API Gateway HTTP API. Point `codeDir` at the NestJS build output (`dist/`) and set the handler to the Lambda entry point.
```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: 'lambda.handler',
memorySize: 1792,
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. NestJS apps with many dependencies can exceed this. 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 ci
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 ["lambda.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 NestJS app first — `nest build` compiles TypeScript to `dist/`. Then deploy with CDK.
```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.
---
## NestJS Containerized Deployment with Fargate
Run your NestJS 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/). No Lambda adapter needed — NestJS runs as a standard HTTP server. This pattern is ideal for long-running services, WebSocket support, and workloads that exceed Lambda's limits.
### Configure for Node Server
NestJS listens on port 3000 by default. Make it configurable via environment variable so the container runtime can override it if needed.
```ts title="src/main.ts"
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
```
### 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 compiles TypeScript in the builder stage, then copies only the compiled output and production dependencies into the final image.
```dockerfile title="Dockerfile"
FROM public.ecr.aws/docker/library/node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm 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 --from=builder /app/package.json ./
EXPOSE 3000
CMD ["node", "dist/main"]
```
### 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.