Oct 26, 202611 min read

How to Deploy a NestJS App to AWS

Run a NestJS API with Postgres on AWS, from ECS Fargate, Lambda and Elastic Beanstalk by hand to provisioning the infrastructure from your TypeScript code.

NestJS gives a Node.js backend the structure that plain Express leaves to you, with modules, providers and dependency injection. Running it on a laptop is npm run start:dev, and most of the effort comes when the same app has to run on AWS with a database next to it.

A production setup needs an image registry, compute, a load balancer, RDS Postgres in private subnets, credentials in Secrets Manager and IAM roles to tie them together. This guide starts with provisioning that from your TypeScript code and then walks through ECS Fargate, Lambda and Elastic Beanstalk by hand.

What a NestJS app on AWS usually needs:

  • A container image in Amazon ECR and something that runs it, such as ECS on Fargate, or a Lambda function or Beanstalk environment instead.
  • A VPC with public and private subnets, an Application Load Balancer and security groups between the load balancer, the tasks and the database.
  • Postgres on Amazon RDS, with its credentials stored in Secrets Manager and injected at startup.
  • IAM roles that let each task pull its image, write logs and read only the resources it uses.
  • A description of all of that in CDK or Terraform, plus a separate local setup that resembles it.

NestJS on AWS at a glance

OptionHow it worksBest fit
EncoreNest modules behind Encore APIs, with the database, Pub/Sub and IAM provisioned in your AWS account from the codeNest apps that need Postgres, queues and a deploy to their own AWS account from the same codebase
ECS on FargateYou build a non-root image, push it to ECR and run it as an ECS service behind an ALBOne containerized Nest API, with a team that owns the surrounding AWS resources in CDK or Terraform
AWS Lambda with serverless-expressThe Express app inside Nest runs as one Lambda function behind API Gateway or a function URLSmall APIs with low or spiky traffic and short requests
Elastic BeanstalkAWS runs npm start or a Procfile on EC2 instances behind a load balancerA single Nest app that fits one Beanstalk environment
Compare what each option covers
CapabilityEncoreECS on FargateLambda with serverless-expressElastic Beanstalk
ComputeECS Fargate or EKS, chosen per environmentECS FargateLambdaEC2 instances
PostgresDeclared in code, RDS in productionCreated separately in RDSCreated separately in RDS, usually with RDS ProxyCreated separately, or attached to the environment
Secretssecret() in code, stored in Secrets ManagerSecrets Manager ARNs in the task definitionEnvironment variables or Secrets Manager callsEnvironment properties or Secrets Manager
Networking and IAMVPC, load balancer and an IAM role per service set up for youYou configure VPC, ALB, security groups and rolesAPI Gateway, execution role and VPC access configured by youMostly managed, extra resources configured by you
Local developmentencore run starts Postgres, Pub/Sub and your servicesYour own Compose setup and a local PostgresEmulators, or deploy to testYour own local setup
Infrastructure codeNone to write for the declared resourcesCDK, Terraform or CLI scriptsSAM, CDK or Terraform.ebextensions and platform hooks

Encore: keep your Nest modules, provision AWS from the code

Encore

Encore combines the NestJS code you already have with the AWS infrastructure it needs, because the Postgres database and Pub/Sub topics are declared in TypeScript next to the Nest providers that use them. encore run starts Postgres and the topics locally with your services, and a deploy creates the same resources in your own AWS account.

Application code passes through Encore and infrastructure guardrails to resources provisioned in your own AWS or GCP account.
Encore provisions the infrastructure your application needs in your own AWS or GCP account.

The approach in the NestJS guide in the Encore docs keeps Nest for modules, providers and dependency injection and lets Encore handle the HTTP layer and the infrastructure. You add encore.dev to the project, and an encore.service.ts file at the root marks the directory as one service:

import { Service } from 'encore.dev/service'; // Encore will consider this directory and all its subdirectories as part of the nest service. export default new Service('nest');

The database is declared with SQLDatabase, and Knex connects to it through the connection string Encore provides, so the existing Nest providers that inject the Knex instance stay the same:

import { SQLDatabase } from 'encore.dev/storage/sqldb'; import knex from 'knex'; // Define a database named 'appDB', using the database migrations // in the "./migrations" folder. Encore automatically provisions, // migrates, and connects to the database. const DB = new SQLDatabase('appDB', { migrations: './migrations', }); // Use Knex.js to connect to the database const ORM = knex({ client: 'pg', connection: DB.connectionString, }); export const databaseProviders = [ { provide: 'DATABASE_CONNECTION', useFactory: async () => ORM, }, ];
The database becomes RDS Postgres on AWS, with migrations applied on deploy.

Nest then starts as a standalone application context instead of an HTTP server, which gives the Encore endpoints access to the same services that a Nest controller would have injected:

import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { CatsService } from './cats/cats.service'; import { CatsModule } from './cats/cats.module'; const applicationContext: Promise<{ catsService: CatsService }> = NestFactory.createApplicationContext(AppModule).then((app) => { return { catsService: app.select(CatsModule).get(CatsService, { strict: true }), }; }); export default applicationContext;

The controller is where the change happens. Each route becomes an Encore api endpoint that calls the Nest service, and a Pub/Sub topic declared in the same file lets work such as search indexing happen after the request has returned:

import { api } from 'encore.dev/api'; import { Subscription, Topic } from 'encore.dev/pubsub'; import applicationContext from '../applicationContext'; import { CreateCatDto } from './dto/create-cat.dto'; import { Cat } from './interfaces/cat.interface'; // SNS and SQS on AWS, a local broker under encore run. export const catCreated = new Topic<{ id: number; name: string }>('cat-created', { deliveryGuarantee: 'at-least-once', }); // Encore APIs replace the Nest controller, and the Nest service stays as it is. export const create = api( { expose: true, method: 'POST', path: '/cats' }, async (dto: CreateCatDto): Promise<{ cat: Cat }> => { const { catsService } = await applicationContext; const cat = await catsService.create(dto); await catCreated.publish({ id: cat.id, name: cat.name }); return { cat }; }, ); export const list = api( { expose: true, method: 'GET', path: '/cats' }, async (): Promise<{ cats: Cat[] }> => { const { catsService } = await applicationContext; return { cats: await catsService.findAll() }; }, ); // Delivery is at-least-once, so the handler is keyed on the cat id. const _ = new Subscription(catCreated, 'index-cat', { handler: async (event) => { console.log('indexing cat', event.id, event.name); }, });
Two endpoints and a subscriber, running the unchanged Nest CatsService.

Running encore run starts Postgres locally, applies the migrations, boots the Nest context and serves the endpoints, so the topic and subscriber run on your machine as well:

~/nest-app
$ encore run$ curl -X POST localhost:4000/cats -d '{"name":"Mochi","age":3,"breed":"Siamese"}'{"cat":{"age":3,"breed":"Siamese","id":1,"name":"Mochi"}}$ curl localhost:4000/cats{"cats":[{"age":3,"breed":"Siamese","id":1,"name":"Mochi"}]}

Connecting an AWS account in the Encore dashboard and pushing the code lets the next deploy create the VPC, load balancer, ECS service, RDS instance and SNS/SQS in that account, as described in the AWS infrastructure docs. Because the database and topic live in the application code, an AI coding agent that adds a new Nest module can declare the table migration and the topic in the same change and run the result under encore run before it reaches AWS.

Key features

  • Each service runs on ECS Fargate or EKS in your account and gets its own IAM role.
  • Postgres becomes RDS, Pub/Sub becomes SNS/SQS with dead-letter queues, and secrets are stored in Secrets Manager.
  • Existing RDS databases can be imported instead of creating new ones.
  • With GitHub connected, each pull request gets its own preview environment running the changed code.
  • encore build docker produces a standard Docker image if you want to run the containers yourself.

Good to know

Nest controllers, guards and interceptors are replaced by Encore endpoints and Encore's own auth handler, so the HTTP layer is a real code change, while modules, providers and services carry over. The NestJS vs Encore comparison covers where the two overlap, and the NestJS microservices guide shows how a larger app splits into services.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

ECS on Fargate with a non-root Dockerfile

Amazon ECS

ECS on Fargate runs the Nest app as a container on capacity AWS manages. A multi-stage Dockerfile builds the app with dev dependencies and ships only dist and the production node_modules, running as the node user that the official image already includes:

FROM node:24-slim AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build && npm prune --omit=dev FROM node:24-slim WORKDIR /app ENV NODE_ENV=production COPY --from=build --chown=node:node /app/node_modules ./node_modules COPY --from=build --chown=node:node /app/dist ./dist USER node EXPOSE 3000 CMD ["node", "dist/main.js"]

The image is pushed to ECR, and the task definition references the database URL in Secrets Manager rather than putting it in a plain environment variable, which ECS injects when the task starts:

"secrets": [ { "name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:nest/database-url" } ]

Key features

  • No servers or cluster nodes to patch, and any Node.js library works because it is just a container.
  • ECS Express Mode creates the Fargate service, load balancer and scaling policy from an image and two IAM roles.
  • Task roles give the container scoped access to other AWS services without long-lived keys.

Good to know

Beyond the Dockerfile, the RDS instance, its subnet group and security group, the secret, the task execution role and its permission to read that secret, the log group and the target group are separate resources, and most teams describe them in CDK or Terraform and maintain a Compose file locally that approximates the same setup. The Node.js on AWS guide goes through those pieces in more detail.

AWS Lambda with serverless-express

AWS Lambda

@codegenie/serverless-express adapts the Express app that Nest runs on to the event format Lambda passes in. Version 5.0.0 is the current release, it targets the Node.js 24 Lambda runtime and it only supports promise-based handlers:

import 'reflect-metadata'; import { NestFactory } from '@nestjs/core'; import { ExpressAdapter } from '@nestjs/platform-express'; import serverlessExpress from '@codegenie/serverless-express'; import type { Context, Handler } from 'aws-lambda'; import express from 'express'; import { AppModule } from './app.module'; let server: Handler | undefined; async function bootstrap(): Promise<Handler> { const expressApp = express(); const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp)); await app.init(); return serverlessExpress({ app: expressApp }); } // Reuse the Nest app across invocations of a warm Lambda container. export const handler = async (event: unknown, context: Context) => { server ??= await bootstrap(); return server(event, context, () => {}); };
The whole Nest app becomes one function behind API Gateway or a function URL.

Key features

  • You pay per request and duration, which suits low or uneven traffic.
  • Scaling to zero and back is handled by Lambda.
  • The same Nest modules still run as a normal server locally with npm run start:dev.

Good to know

Nest builds its whole module graph on a cold start, so a larger app pays for that on the first request to each new container. A function can run for up to 15 minutes, but API Gateway has its own shorter integration timeout, and many concurrent functions opening Postgres connections usually calls for RDS Proxy, which is one more resource to define in SAM, CDK or Terraform.

Elastic Beanstalk

AWS Elastic Beanstalk

Elastic Beanstalk has a managed Node.js platform that runs the app on EC2 instances behind a load balancer it creates. It uses a Procfile at the root if there is one and otherwise runs npm start, so a Nest app only needs a start script that runs the built dist/main.js.

Key features

  • The Node.js 24 platform on Amazon Linux 2023 is current, with a Docker platform as the alternative.
  • Rolling deployments, health checks and auto scaling are built in.
  • An RDS instance can be attached to the environment, though a separately created one is easier to keep when the environment changes.

Good to know

Each environment is one application, so a queue worker or a second Nest service means another environment, and SQS, S3 and IAM around it are still set up separately. Custom configuration lives in .ebextensions and platform hooks, which are hard to test before a deploy. The Elastic Beanstalk alternatives guide covers where teams usually move from there.

How to choose

  • Encore if the Nest app has a database, queues or several services and you want them running locally and in your AWS account from the same TypeScript code, without a separate CDK or Terraform project.
  • ECS on Fargate for a single containerized Nest API when the team is comfortable owning the RDS, IAM and networking around it.
  • Lambda with serverless-express for small APIs with spiky traffic and short requests.
  • Elastic Beanstalk for one Nest app that fits its environment model.

The manual routes all leave RDS, Secrets Manager, IAM and networking to be described somewhere, which is usually where CDK or Terraform comes in, while Encore derives those resources from the code that uses them. A well-organized codebase moves more easily either way, and the NestJS project structure guide covers how to lay out modules. The quickstart walks through a first Encore app.

Frequently asked questions

What is the best way to deploy NestJS to AWS?

For one container, ECS on Fargate is the usual choice, since you push an image to ECR and run it as a service behind a load balancer without managing servers. When the app also needs Postgres, queues and secrets, Encore keeps your Nest modules and provisions the database, Pub/Sub and IAM in your own AWS account from the TypeScript code, so there is no Terraform or CDK to write for those resources.

Can I run NestJS on AWS Lambda?

Yes. The @codegenie/serverless-express package wraps the Express app that Nest runs on as a Lambda handler behind API Gateway or a function URL. Version 5 targets Node.js 24 and only supports promise-based handlers. Cold starts grow with the size of the Nest module graph, and many concurrent functions talking to Postgres usually need RDS Proxy.

Should I use ECS Fargate or Elastic Beanstalk for NestJS?

Elastic Beanstalk has a managed Node.js platform that runs npm start or a Procfile on EC2 instances behind a load balancer, which suits a single app. ECS on Fargate runs a container image and gives you more control over services, task roles and scaling, and it is the better base once there is more than one service or a worker.

How do I pass database credentials to NestJS on ECS?

Store the connection string in AWS Secrets Manager and reference its ARN in the secrets section of the ECS task definition, which injects it as an environment variable when the task starts. The task execution role needs permission to read that secret. With Encore the RDS credentials are created and passed to the service for you.

How do I deploy a NestJS app to AWS with Encore?

Add encore.dev to the Nest project, put an encore.service.ts file at the root, start Nest as a standalone application context and expose your Nest services through Encore api endpoints. Declare the database with new SQLDatabase and connect Knex to its connectionString. encore run starts it all locally, and a deploy to a connected AWS account provisions ECS, RDS and SNS/SQS.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders