Single Docker Image

Run Next.js and NestJS together in one container

Package both apps into a single Docker image and start them with a process manager. This simplifies ops (one image to build, push, and deploy) at the cost of independent scaling.

Next.js still runs on port 3000, NestJS on port 3001. The existing rewrite proxy in next.config.js forwards /api/* internally. Only port 3000 is exposed externally.

Docker imageNext.js:3000NestJS:3001/api/*

Dockerfile

Dockerfile
FROM node:22-alpine AS base
 
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/web/package.json ./apps/web/package.json
COPY apps/api/package.json ./apps/api/package.json
COPY packages/ui/package.json ./packages/ui/package.json
COPY packages/auth/package.json ./packages/auth/package.json
COPY packages/eslint-config/package.json ./packages/eslint-config/package.json
COPY packages/typescript-config/package.json ./packages/typescript-config/package.json
RUN corepack enable && pnpm install --frozen-lockfile
 
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build --filter=web --filter=api
 
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
 
# Next.js standalone output
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
 
# NestJS compiled output
COPY --from=builder /app/apps/api/dist ./apps/api/dist
COPY --from=builder /app/node_modules ./node_modules
 
RUN npm install -g concurrently
 
EXPOSE 3000
 
CMD ["concurrently", \
  "node apps/web/server.js", \
  "node apps/api/dist/main.js"]

next.config.js

The rewrite destination stays as localhost since both processes share the same container:

apps/web/next.config.js
async rewrites() {
  return [
    {
      source: '/api/:path*',
      destination: 'http://localhost:3001/api/:path*',
    },
  ];
},

Tradeoffs

Simpler ops: one image, one deploy, one place to check logs (if you aggregate stdout).

No independent scaling: you can't scale the API without also scaling the frontend, and vice versa. If NestJS is the bottleneck, you're scaling the whole image.

Startup order: Next.js may start serving before NestJS is ready. The first few /api/* requests during startup may fail. For production, consider adding a readiness check or a short startup delay in the web process.

Crash isolation: if NestJS crashes, concurrently exits the container and your platform's restart policy will bring it back up. Configure your orchestrator (Docker restart policy, Railway, Fly.io, etc.) to restart on exit.