Skip to main content

Overview

This portfolio includes a multi-stage Dockerfile optimized for production builds using Next.js standalone output mode. Docker provides consistent deployment environments and efficient resource usage.

Production Deployment

1

Build the Docker Image

The Dockerfile uses a three-stage build process for optimal image size:
docker build -t luan/portfolio .
Build Stages:
  • deps: Installs dependencies using frozen lockfile (supports npm, pnpm, or yarn)
  • builder: Creates Next.js standalone build with output: "standalone"
  • runner: Lean runtime image containing only production assets
2

Run the Container

docker run --rm -p 3000:3000 luan/portfolio
The application will be available at http://localhost:3000
3

Run with Environment Variables

For production deployments requiring environment variables:
docker run --rm -p 3000:3000 \
  -e NODE_ENV=production \
  -e NEXT_PUBLIC_API_URL=https://api.example.com \
  luan/portfolio

Dockerfile Configuration

The production Dockerfile implements Next.js best practices:
# Base deps stage - install dependencies exactly from lockfile
FROM node:18-alpine AS deps
WORKDIR /app
RUN apk add --no-cache libc6-compat
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./
RUN if [ -f pnpm-lock.yaml ]; then \
        corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile; \
    elif [ -f yarn.lock ]; then yarn --frozen-lockfile; \
    else npm ci; fi

# Builder stage - create standalone build
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
RUN npm run build

# Runner stage - lean production runtime
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000

COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]
The output: "standalone" configuration in next.config.mjs is required for Docker builds. This creates a minimal production server with automatic code tracing.

Development Setup with Hot Reload

For local development with live code updates, use Docker Compose:
1

Start Development Server

docker compose -f docker-compose.dev.yml up
This mounts your source code as a volume, enabling hot module replacement.
2

Access the Application

Open http://localhost:3000 in your browser. Changes to source files will automatically reload.
3

Stop the Server

docker compose -f docker-compose.dev.yml down

Development Docker Compose Configuration

version: "3.9"
services:
  web:
    image: node:18-alpine
    working_dir: /app
    command: sh -c "npm i && npm run dev"
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - PORT=3000
The volume mount - /app/node_modules prevents the host’s node_modules from overwriting the container’s dependencies.

Docker Ignore Configuration

The .dockerignore file excludes unnecessary files from the build context:
# Build artifacts
node_modules
.next
out

# VCS & config files
.git
.gitignore
Dockerfile*
docker-compose*.yml
README.md

# OS/editor files
.DS_Store
*.log
Always include node_modules and .next in .dockerignore to prevent copying unnecessary files and ensure clean builds.

Environment Variables

docker run --rm -p 3000:3000 \
  -e NODE_ENV=production \
  -e PORT=3000 \
  -e NEXT_TELEMETRY_DISABLED=1 \
  luan/portfolio

Build Optimization

Multi-stage Benefits:
  • Smaller image size: Only production dependencies and built assets in final image
  • Security: No source code or build tools in runtime image
  • Performance: Node.js standalone output includes only required dependencies
Image Size Comparison:
  • Full build with dev dependencies: ~1.2GB
  • Standalone multi-stage build: ~150MB
For further optimization, consider using node:18-alpine base images and enabling Next.js output file tracing with outputFileTracingRoot.

Deployment to Cloud Platforms

# Tag and push to Docker Hub
docker tag luan/portfolio username/portfolio:latest
docker push username/portfolio:latest

Troubleshooting

Build fails at dependencies stage:
  • Ensure your lockfile (package-lock.json, pnpm-lock.yaml, or yarn.lock) is committed
  • Check for platform-specific dependencies requiring libc6-compat
Container exits immediately:
  • Verify output: "standalone" is set in next.config.mjs
  • Check that server.js exists in .next/standalone/ after build
Port conflicts:
  • Change the host port mapping: docker run -p 8080:3000 luan/portfolio

Build docs developers (and LLMs) love