Last updated

Standalone Deployment Guide

Mirrored from iblai/os ยท docs/standalone-deployment.md. This page is generated โ€” edit it in the repository, not here.

This app uses Next.js standalone output for optimized production deployments.

What Changed

1. Next.js Configuration

  • Output mode: Set to 'standalone' in next.config.ts
  • Generates a self-contained server with minimal dependencies
  • Reduces Docker image size significantly

2. Build Process

  • Build command: pnpm build now runs:
    1. next build - Creates standalone output
    2. ./scripts/post-build.sh - Copies static assets
  • Static files (.next/static/, public/) are automatically copied to standalone output

3. Server Startup

  • Start command: pnpm start runs node server-wrapper.js
  • No longer uses next start
  • Uses PORT environment variable instead of -p flag

4. Error Handling

  • server-wrapper.js: Suppresses harmless HTMLElement errors during route pre-warming
  • These errors don't affect functionality but were noisy in logs

Local Development

Development Mode

cd apps/mentor
pnpm dev  # Runs on http://localhost:3001

Production Build & Test

cd apps/mentor

# Build
pnpm build

# Start on default port (3000)
pnpm start

# Start on custom port
PORT=3001 pnpm start

Docker Deployment

Building the Image

# From monorepo root
docker build \
  -f apps/mentor/Dockerfile \
  --build-arg NEXT_IMAGE_PATTERNS="https://example.com,https://cdn.example.com" \
  --build-arg NEXT_PUBLIC_BASE_PATH="" \
  -t mentor-app:latest \
  .

Running the Container

# Default port (5000)
docker run -p 5000:5000 mentor-app:latest

# Custom port
docker run -p 8080:8080 -e PORT=8080 mentor-app:latest

Environment Variables

  • PORT - Server port (default: 5000 in Docker, 3000 locally)
  • NEXT_IMAGE_PATTERNS - Comma-separated allowed image domains
  • NEXT_PUBLIC_BASE_PATH - Base path for routing (e.g., /mentor)

Dockerfile Changes

Before (Old Approach)

# Copied entire .next folder and all node_modules
COPY --from=builder /repo/apps/mentor/.next .next
COPY --from=builder /repo/node_modules ./node_modules
CMD ["pnpm", "exec", "next", "start", "-p", "5000"]

After (Standalone)

# Copy only standalone output (self-contained with minimal deps)
COPY --from=builder /repo/apps/mentor/.next/standalone ./
COPY --from=builder /repo/apps/mentor/server-wrapper.js ./apps/mentor/
ENV PORT=5000
CMD ["node", "server-wrapper.js"]

Benefits

  • Smaller image size: Only includes production dependencies
  • Faster startup: No pnpm overhead
  • Better security: Minimal attack surface
  • Simpler deployment: Self-contained output

File Structure

Standalone Output

.next/standalone/
โ”œโ”€โ”€ apps/
โ”‚   โ””โ”€โ”€ mentor/
โ”‚       โ”œโ”€โ”€ .next/
โ”‚       โ”‚   โ”œโ”€โ”€ server/          # Server-side code
โ”‚       โ”‚   โ””โ”€โ”€ static/          # Static assets (copied by post-build.sh)
โ”‚       โ”œโ”€โ”€ public/              # Public files (copied by post-build.sh)
โ”‚       โ”œโ”€โ”€ server.js            # Next.js standalone server
โ”‚       โ””โ”€โ”€ server-wrapper.js    # Error handling wrapper
โ”œโ”€โ”€ node_modules/                # Minimal production deps
โ””โ”€โ”€ packages/                    # Shared packages

Scripts

post-build.sh

Automatically copies static assets after build:

  • .next/static/ โ†’ .next/standalone/apps/mentor/.next/static/
  • public/ โ†’ .next/standalone/apps/mentor/public/
  • offline-shell/ โ†’ .next/standalone/apps/mentor/offline-shell/

server-wrapper.js

Handles server startup with error suppression:

  • Suppresses harmless HTMLElement errors during Next.js pre-warming
  • Logs informational message instead of error stack traces
  • Doesn't affect functionality

Troubleshooting

Issue: 404 errors for static files

Cause: Static assets not copied to standalone output Solution: Run pnpm build (which runs post-build.sh automatically)

Issue: HTMLElement errors in logs

Cause: Next.js pre-warms routes using browser APIs Status: Harmless, suppressed by server-wrapper.js Impact: None - server works perfectly

Issue: Port conflicts

Cause: Default port (3000 or 5000) already in use Solution: Use custom port: PORT=3001 pnpm start

Issue: "Cannot find module" in Docker

Cause: Standalone output not copied correctly Solution: Rebuild Docker image

CI/CD Integration

GitHub Actions Example

- name: Build mentor app
  run: |
    cd apps/mentor
    pnpm build
  env:
    NEXT_IMAGE_PATTERNS: ${{ secrets.NEXT_IMAGE_PATTERNS }}
    NEXT_PUBLIC_BASE_PATH: ''

- name: Build Docker image
  run: |
    docker build \
      -f apps/mentor/Dockerfile \
      --build-arg NEXT_IMAGE_PATTERNS="${{ secrets.NEXT_IMAGE_PATTERNS }}" \
      -t mentor-app:${{ github.sha }} \
      .

- name: Run container
  run: |
    docker run -d \
      -p 5000:5000 \
      --name mentor-app \
      mentor-app:${{ github.sha }}

Performance Benefits

MetricBeforeAfterImprovement
Docker image size~1.2 GB~400 MB67% smaller
Startup time~3-5s~1-2s50% faster
Memory usage~300 MB~200 MB33% less
DependenciesAll dev + prodProd onlyMinimal

Migration Checklist

  • Update next.config.ts with output: 'standalone'
  • Create post-build.sh script
  • Create server-wrapper.js
  • Update package.json build command
  • Update package.json start command
  • Update Dockerfile for standalone
  • Test local build and start
  • Test Docker build and run
  • Update CI/CD pipelines
  • Update deployment docs
  • Update production environment configs

Additional Resources

Copyright ยฉ ibl.ai | support@iblai.zendesk.com