How to Deploy a Turborepo with Next.js and NestJs
Hosting on Vercel? This is not your blog. This guide is for those deploying Next.js frontend and NestJs backend from a Turborepo into custom environments like VPS, bare metal, Docker or Kubernetes.

Repo Structure
apps/
web/ ← Next.js 15.2.1 (Frontend)
api/ ← NestJS 11.1.2 (Backend)
packages/ ← Shared libraries
Prerequisite: Docker Installed
Ensure you have Docker installed on your system where you are going to deploy the Turborepo. If not, refer to Docker official docs
Step 1: Configure Next.js (apps/web)
Next.js needs to be run in standalone mode for optimal Docker performance
Edit apps/web/next.config.ts
or .js
module.exports = {
output: 'standalone',
}
Step 2: Create Dockerfile for Next.js
Create a Dockerfile in Next.js folder, in our case it is apps/web/Dockerfile
# Stage 1: Install dependencies
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lock turbo.json ./
COPY apps/web/package.json ./apps/web/
COPY packages/ ./packages/
RUN bun install
# Stage 2: Build the Next.js application
FROM oven/bun:1-alpine AS builder
WORKDIR /app
ENV NODE_OPTIONS="--max-old-space-size=4096"
COPY . .
RUN bun install
RUN bun run build --filter=web --concurrency=1
# Stage 3: Prepare the runtime
FROM oven/bun:1-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/web/.next/standalone ./
COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder /app/apps/web/public ./apps/web/public
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
CMD ["node", "apps/web/server.js"]
Tested and working - feel free to copy paste, but always strive to understand the concept.
Step 3: Dockerfile for NestJS (apps/api)
Let's create a Dockerfile for our NestJS backend. In our case it's apps/api/Dockerfile
FROM oven/bun:1-alpine AS deps
WORKDIR /app
COPY package.json bun.lock turbo.json ./
COPY apps/api/package.json ./apps/api/
COPY packages/ ./packages/
RUN bun install
FROM oven/bun:1-alpine AS builder
WORKDIR /app
COPY . .
RUN bun install
RUN bun run build --filter=api
FROM oven/bun:1-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nestjs
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/apps/api/package.json ./package.json
RUN chown -R nestjs:nodejs /app
USER nestjs
EXPOSE 3001
ENV NODE_ENV=production PORT=3001
CMD ["bun", "run", "start:prod"]
Tested and working - feel free to copy paste, but always strive to understand the concept.
Step 4: Create a Docker compose yaml
At the root of your folder (not at apps/, it must be located where apps, turbo.json is located), create a docker-compose.yml:
version: '3.8'
services:
web:
build:
context: .
dockerfile: apps/web/Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
env_file:
- apps/web/.env.production
depends_on:
- api
networks:
- app-network
api:
build:
context: .
dockerfile: apps/api/Dockerfile
ports:
- "3001:3001"
environment:
- NODE_ENV=production
- PORT=3001
networks:
- app-network
networks:
app-network:
driver: bridge
Step 5: Build & Run
docker compose -f docker-compose.yml up -d --build
That's it's done, pull the image and run the container on any system of your preference.
Here are the common pitfall's and misconfigurations while deploying a Turborepo:
1. Turbo.json Misconfiguration:
Here's a Tested & Working turbo.json
config for this setup:
{
"$schema": "https://turborepo.com/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**", "dist/**", "build/**"]
},
"web#build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**"]
},
"api#build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"check-types": {
"dependsOn": ["^check-types"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
2. Frontend ↔ Backend Communication:
When communicating between frontend and backend inside Docker never use localhost
.
✅ Use: http://api:3001
❌ Don’t use: http://localhost:3001
This works because api
is the Docker service name, and both containers are on the same bridge
network. Check your Dockerfile created in the above steps to determine the name usage. In our case it is api
.
Deployment Tip
Once your image is build, you can export it using:docker save -o myapp.tar <image_name>
Then transfer to another server and run:
docker load -i myapp.tar
docker compose up -d
☸️ Kubernetes Deployment?
🧭 If you're looking to host this into a Kubernetes (k8s/k3s). check out our guide: How to Deploy a Turborepo into Kubernetes.
Conclusion
You now have a production ready, scalable deployment setup for your Turborepo app with Next.js and NestJS - fully containerized and platform agnostic. Whether you are deploying it to a VPS, cloud VM or in a Kubernetes Cluster, the foundation is solid.
Tags
#Deploying a Turborepo (Next.js + NestJs) on any platform
#how to deploy a Turborepo
#How to deploy a Nextjs and Nestjs turborepo
#fullstack turborepo deployment
#nextjs nestjs project deployment tutorial
#monorepo deployment platforms