How to Host a NestJS Application in Production

Glowing server nodes surround a protected core in a dark cloud setting.

A NestJS app can work perfectly on your laptop and still fail after deployment. The usual causes are simple: the wrong Node version, a missing environment variable, a blocked port, or no health check.

To host NestJS application code in production, build TypeScript into JavaScript, run the compiled files, pass secrets through the host, and give the platform a reliable way to test the service. Start with a repeatable deployment process, not a terminal session that happens to stay open.

HOW TO HOST NESTJS APPLICATION CODE FOR PRODUCTION

Production starts with a clean build. Your host should run JavaScript from dist/, not TypeScript through a development watcher.

Nest’s official deployment guidance recommends an LTS Node.js release and a compiled production build. Node 24 is the current LTS line listed on the Node.js release page, while Node 26 is still Current as of September 2026. Use an LTS release unless your team has tested a newer line.

Check your package scripts

Your package.json needs scripts that separate development from production. A standard setup uses nest start --watch locally and node dist/main in production.

Use these commands before the first deployment:

  1. Install locked dependencies with npm ci.
  2. Compile the application with npm run build.
  3. Start the compiled server with npm run start:prod.

Set your production script to node dist/main if it doesn’t already point there. Do not use npm run start:dev on a production server. Watch mode consumes resources and reloads processes after file changes.

Test the same artifact locally

Run npm run build, then run node dist/main from your machine. Call a real API route and confirm the app starts without development-only tools.

This catches common problems before they reach the host:

  • A module exists in devDependencies but is required at runtime.
  • A required environment variable is missing.
  • The production database connection fails.
  • The app only accepts a hard-coded local port.

A green build proves the code compiles. It does not prove the app can connect to its database, bind its port, or receive traffic.

SET THE PORT, SECRETS, AND RUNTIME VARIABLES

A hosting platform assigns a port through an environment variable. Your application must read that value. Do not force every deployment to use port 3000.

In main.ts, use a port setup such as const port = Number(process.env.PORT ?? 3000); followed by await app.listen(port, '0.0.0.0');. The 0.0.0.0 binding allows traffic from Docker, a platform router, or a reverse proxy.

Keep configuration outside Git

Create a .env.example file with variable names only. It can include DATABASE_URL=, JWT_SECRET=, REDIS_URL=, and CORS_ORIGIN=. Do not place working secrets in that file.

Add the real values in your provider’s environment-variable settings, CI secret store, or server secret manager. Production and staging need separate credentials. A staging app pointed at the production database is an avoidable incident.

Set NODE_ENV=production on the host. This lets libraries use production behavior and helps your team identify the running environment.

Restrict the public surface

Set CORS to known front-end origins. Don’t use * if your API accepts cookies, authorization flows, or private user data.

Disable verbose error output in public responses. Log the underlying exception on the server, but don’t return database names, stack traces, or secret values to the browser.

If you accept uploaded files, store them outside the container filesystem. Container storage can disappear during a rebuild or replica restart.

DEPLOY WITH A DOCKER IMAGE WHEN YOU NEED CONSISTENCY

Docker gives the same runtime to local development, CI, staging, and production. It also makes moving hosts easier because the application package stays consistent.

Use a multi-stage build. The first stage installs build tools and compiles NestJS. The final stage contains only the production dependencies and compiled output. The official Node Docker image guidance recommends this pattern to keep runtime images smaller.

Build a small NestJS image

Create a Dockerfile with these instructions:

  1. Start the build stage with FROM node:24-alpine AS build.
  2. Set a work directory with WORKDIR /app.
  3. Copy package.json and package-lock.json.
  4. Install dependencies with RUN npm ci.
  5. Copy the remaining source files with COPY . ..
  6. Compile with RUN npm run build.
  7. Start a second stage with FROM node:24-alpine.
  8. Set WORKDIR /app again.
  9. Copy package files, then run RUN npm ci --omit=dev.
  10. Copy dist from the build stage with COPY --from=build /app/dist ./dist.
  11. Run the app with CMD ["node", "dist/main"].

Add a .dockerignore file. Include node_modules, dist, .git, .env, coverage files, and local logs. You want the image build to use its own dependencies, not files left on your laptop.

Test the container before release

Build the image with docker build -t nest-api .. Run it with docker run --rm -p 3000:3000 --env-file .env nest-api.

Then call the same routes your host will call. Check startup logs. Stop the container and confirm it shuts down cleanly.

A container image does not replace environment management. It packages the app. Your host still needs database access, secrets, DNS settings, TLS, and health-check configuration.

CHOOSE A HOSTING MODEL THAT MATCHES THE APP

Most small NestJS APIs fit one of three hosting models: a managed application platform, a container service, or a virtual private server.

Managed platforms reduce server work

Render, Railway, and similar platforms connect to a Git repository, run a build command, start the process, and provide HTTPS. This works well for small teams that want deploy logs and automatic builds without managing an operating system.

For Render, create a web service from the repository. Use npm ci && npm run build as the build command and npm run start:prod as the start command. Render’s Node deployment guide shows the same build-and-start model used by most Node services.

Set all required environment variables in the service dashboard. Add the health-check path after you create it. Check the provider’s current documentation before launch because plan limits, build behavior, private networking, and health-check fields can change.

Containers and VPS servers give more control

Use AWS App Runner, Amazon ECS, Fly.io, Google Cloud Run, or DigitalOcean App Platform when you want a container-first service. Each platform has different settings for ports, image registries, regions, scaling, and health checks.

A VPS is useful when you need full control or run several internal services. Use Docker Compose or systemd to keep the process alive. Do not start node dist/main in an SSH shell and treat that as hosting.

On a VPS, place Nginx or Caddy in front of NestJS for TLS termination and domain routing. Keep the NestJS process on a private port. Allow public traffic only to ports 80 and 443 unless your architecture requires more.

ADD HEALTH CHECKS AND GRACEFUL SHUTDOWN

A host needs more than a process that listens on a port. It needs to know when the API is ready for traffic and when it should remove a failing instance.

Nest supports health checks through @nestjs/terminus. Install it with npm install @nestjs/terminus. The Terminus health-check documentation covers database, HTTP, memory, and custom indicators.

Separate liveness from readiness

Use a liveness endpoint such as /health/live to confirm that the Node process is running. Keep it lightweight. It should not fail because an optional third-party API is slow.

Use a readiness endpoint such as /health/ready to confirm that the app can accept real traffic. Check dependencies that are required for requests, such as PostgreSQL, Redis, or a queue connection.

Point your platform’s health check at the readiness endpoint. Set a reasonable start-up grace period if migrations, cache warming, or connection setup takes time.

Handle termination signals

Containers and managed services send SIGTERM before they stop or replace an instance. NestJS needs to close database pools and finish its shutdown sequence.

Call app.enableShutdownHooks() during bootstrap. Configure Terminus with a short graceful shutdown delay when it fits your platform’s readiness process. The instance should first stop receiving new requests, then close active resources.

Don’t set an arbitrary long timeout. Match it to your load balancer’s drain period and your provider’s termination deadline.

BUILD A RELEASE PROCESS THAT CAN RECOVER

Every deployment needs a record. Keep the Git commit, build time, environment, image tag, migration result, and release owner. When a defect appears, this tells you what changed.

Run migrations as a controlled step

Database migrations can break an otherwise healthy release. Run them once per release, not once for every replica.

For Prisma, use a production command such as npx prisma migrate deploy. For TypeORM, use the migration command defined by your project. Test migrations against a staging database first, then back up production data before a risky schema change.

Avoid destructive schema changes in the same release as the code that depends on them. Add a nullable column first. Deploy code that writes to it. Backfill data. Remove old fields in a later release.

Watch the signals that matter

Track HTTP error rate, response time, restart count, memory use, CPU use, database connection failures, and health-check status. Add structured logs with request IDs so one failed request can be traced across the API and supporting services.

Set alerts for sustained failures, not every isolated 404. A useful alert tells the on-call person what failed, where it failed, and which release is running.

Keep the previous image or release available. A rollback should take minutes. If rollback requires rebuilding an old branch under pressure, the process is incomplete.

FINAL DEPLOYMENT CHECK

To host NestJS application code reliably, use an LTS Node release, run the compiled dist output, and configure the host through environment variables. Then add a readiness check, graceful shutdown hooks, deployment logs, and a tested rollback path.

The server is only one part of production. A repeatable release process is what keeps a working NestJS app working after the next code push.

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights