nextjs-deployment

Deploy Next.js applications to production with Docker, CI/CD pipelines, and comprehensive monitoring.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "nextjs-deployment" with this command: npx skills add giuseppe-trisciuoglio/developer-kit/giuseppe-trisciuoglio-developer-kit-nextjs-deployment

Next.js Deployment

Deploy Next.js applications to production with Docker, CI/CD pipelines, and comprehensive monitoring.

Overview

This skill provides patterns for:

  • Docker configuration with multi-stage builds

  • GitHub Actions CI/CD pipelines

  • Environment variables management (build-time and runtime)

  • Preview deployments

  • Monitoring with OpenTelemetry

  • Logging and health checks

  • Production optimization

When to Use

Activate when user requests involve:

  • "Deploy Next.js", "Dockerize Next.js", "containerize"

  • "GitHub Actions", "CI/CD pipeline", "automated deployment"

  • "Environment variables", "runtime config", "NEXT_PUBLIC"

  • "Preview deployment", "staging environment"

  • "Monitoring", "OpenTelemetry", "tracing", "logging"

  • "Health checks", "readiness", "liveness"

  • "Production build", "standalone output"

  • "Server Actions encryption key", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY"

Quick Reference

Output Modes

Mode Use Case Command

standalone

Docker/container deployment output: 'standalone'

export

Static site (no server) output: 'export'

(default) Node.js server deployment next start

Environment Variable Types

Prefix Availability Use Case

NEXT_PUBLIC_

Build-time + Browser Public API keys, feature flags

(no prefix) Server-only Database URLs, secrets

Runtime Server-only Different values per environment

Key Files

File Purpose

Dockerfile

Multi-stage container build

.github/workflows/deploy.yml

CI/CD pipeline

next.config.ts

Build configuration

instrumentation.ts

OpenTelemetry setup

src/app/api/health/route.ts

Health check endpoint

Instructions

Configure Standalone Output

Enable standalone output for optimized Docker deployments:

// next.config.ts import type { NextConfig } from 'next'

const nextConfig: NextConfig = { output: 'standalone', poweredByHeader: false, generateBuildId: async () => { // Use git hash for consistent builds across servers return process.env.GIT_HASH || process.env.GITHUB_SHA || 'build' }, }

export default nextConfig

Create Multi-Stage Dockerfile

Build optimized Docker image with minimal footprint:

syntax=docker/dockerfile:1

FROM node:20-alpine AS base

Install dependencies only when needed

FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app

Install dependencies

COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./ RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile;
elif [ -f yarn.lock ]; then yarn --frozen-lockfile;
elif [ -f package-lock.json ]; then npm ci;
else echo "Lockfile not found." && exit 1;
fi

Rebuild the source code only when needed

FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . .

Set build-time environment variables

ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production

Generate build ID from git (set during build)

ARG GIT_HASH ENV GIT_HASH=${GIT_HASH}

Server Actions encryption key (CRITICAL for multi-server deployments)

ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}

RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build;
elif [ -f yarn.lock ]; then yarn build;
elif [ -f package-lock.json ]; then npm run build;
else npm run build;
fi

Production image, copy all the files and run next

FROM base AS runner WORKDIR /app

ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0"

RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs

Copy standalone output

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

Copy public files if they exist

COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs

EXPOSE 3000

Health check

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"

CMD ["node", "server.js"]

Set Up GitHub Actions CI/CD

Create automated build and deployment pipeline:

.github/workflows/deploy.yml

name: Build and Deploy

on: push: branches: [main, develop] pull_request: branches: [main]

env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}

jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write

steps:
  - name: Checkout
    uses: actions/checkout@v4

  - name: Set up Docker Buildx
    uses: docker/setup-buildx-action@v3

  - name: Log in to Container Registry
    uses: docker/login-action@v3
    with:
      registry: ${{ env.REGISTRY }}
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}

  - name: Extract metadata
    id: meta
    uses: docker/metadata-action@v5
    with:
      images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
      tags: |
        type=ref,event=branch
        type=ref,event=pr
        type=sha,prefix={{branch}}-
        type=raw,value=latest,enable={{is_default_branch}}

  - name: Generate Server Actions Key
    id: generate-key
    run: |
      KEY=$(openssl rand -base64 32)
      echo "key=$KEY" >> $GITHUB_OUTPUT

  - name: Build and push Docker image
    uses: docker/build-push-action@v5
    with:
      context: .
      push: true
      tags: ${{ steps.meta.outputs.tags }}
      labels: ${{ steps.meta.outputs.labels }}
      cache-from: type=gha
      cache-to: type=gha,mode=max
      build-args: |
        GIT_HASH=${{ github.sha }}
        NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.generate-key.outputs.key }}

deploy-staging: needs: build if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com

steps:
  - name: Deploy to staging
    run: |
      echo "Deploying to staging..."
      # Add your deployment commands here
      # e.g., kubectl, helm, or platform-specific CLI

deploy-production: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: name: production url: https://example.com

steps:
  - name: Deploy to production
    run: |
      echo "Deploying to production..."
      # Add your deployment commands here

Manage Environment Variables

Build-Time Variables (next.config.ts)

// next.config.ts import type { NextConfig } from 'next'

const nextConfig: NextConfig = { env: { // These are inlined at build time APP_VERSION: process.env.npm_package_version || '1.0.0', BUILD_DATE: new Date().toISOString(), }, // Public runtime config (available on server and client) publicRuntimeConfig: { apiUrl: process.env.NEXT_PUBLIC_API_URL, featureFlags: { newDashboard: process.env.NEXT_PUBLIC_FF_NEW_DASHBOARD === 'true', }, }, }

export default nextConfig

Runtime Environment Variables

For runtime variables with Docker, use a single image across environments:

// src/lib/env.ts export function getEnv() { return { // Server-only (read at request time) databaseUrl: process.env.DATABASE_URL!, apiKey: process.env.API_KEY!,

// Public (must be prefixed with NEXT_PUBLIC_ at build time)
publicApiUrl: process.env.NEXT_PUBLIC_API_URL!,

} }

// Validate required environment variables export function validateEnv() { const required = ['DATABASE_URL', 'API_KEY', 'NEXT_PUBLIC_API_URL'] const missing = required.filter((key) => !process.env[key])

if (missing.length > 0) { throw new Error(Missing required environment variables: ${missing.join(', ')}) } }

Environment Variable Files

.env.local (development - never commit)

DATABASE_URL=postgresql://localhost:5432/mydb API_KEY=dev-key NEXT_PUBLIC_API_URL=http://localhost:3000/api

.env.production (production defaults)

NEXT_PUBLIC_API_URL=https://api.example.com

.env.example (template for developers)

DATABASE_URL= API_KEY= NEXT_PUBLIC_API_URL=

Implement Health Checks

Create a health check endpoint for load balancers and orchestrators:

// src/app/api/health/route.ts import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET() { const checks = { status: 'healthy', timestamp: new Date().toISOString(), version: process.env.npm_package_version || 'unknown', buildId: process.env.GIT_HASH || 'unknown', uptime: process.uptime(), checks: { memory: checkMemory(), // Add database, cache, etc. checks here }, }

const isHealthy = Object.values(checks.checks).every((check) => check.status === 'ok')

return NextResponse.json(checks, { status: isHealthy ? 200 : 503 }) }

function checkMemory() { const used = process.memoryUsage() const threshold = 1024 * 1024 * 1024 // 1GB

return { status: used.heapUsed < threshold ? 'ok' : 'warning', heapUsed: ${Math.round(used.heapUsed / 1024 / 1024)}MB, heapTotal: ${Math.round(used.heapTotal / 1024 / 1024)}MB, } }

Set Up OpenTelemetry Monitoring

Add observability with OpenTelemetry:

// instrumentation.ts import { registerOTel } from '@vercel/otel'

export function register() { registerOTel({ serviceName: process.env.OTEL_SERVICE_NAME || 'next-app', serviceVersion: process.env.npm_package_version, }) }

// instrumentation.node.ts import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http' import { NodeSDK } from '@opentelemetry/sdk-node' import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node' import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics' import { resourceFromAttributes } from '@opentelemetry/resources' import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app', [ATTR_SERVICE_VERSION]: process.env.npm_package_version || '1.0.0', }), spanProcessor: new SimpleSpanProcessor( new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, }) ), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, }), }), })

sdk.start()

// Graceful shutdown process.on('SIGTERM', () => { sdk.shutdown() .then(() => console.log('OpenTelemetry terminated')) .catch((err) => console.error('OpenTelemetry termination error', err)) .finally(() => process.exit(0)) })

// src/lib/logger.ts interface LogEntry { level: string message: string timestamp: string requestId?: string [key: string]: unknown }

export function createLogger(requestId?: string) { const base = { timestamp: new Date().toISOString(), ...(requestId && { requestId }), }

return { info: (message: string, meta?: Record<string, unknown>) => { log({ level: 'info', message, ...base, ...meta }) }, warn: (message: string, meta?: Record<string, unknown>) => { log({ level: 'warn', message, ...base, ...meta }) }, error: (message: string, error?: Error, meta?: Record<string, unknown>) => { log({ level: 'error', message, error: error?.message, stack: error?.stack, ...base, ...meta }) }, } }

function log(entry: LogEntry) { // In production, send to structured logging service // In development, pretty print if (process.env.NODE_ENV === 'production') { console.log(JSON.stringify(entry)) } else { console.log([${entry.level.toUpperCase()}] ${entry.message}, entry) } }

Configure Preview Deployments

Set up preview environments for pull requests:

.github/workflows/preview.yml

name: Preview Deployment

on: pull_request: types: [opened, synchronize, closed]

jobs: deploy-preview: if: github.event.action != 'closed' runs-on: ubuntu-latest

steps:
  - name: Checkout
    uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '20'
      cache: 'npm'

  - name: Install dependencies
    run: npm ci

  - name: Build
    run: npm run build
    env:
      NEXT_PUBLIC_API_URL: https://staging-api.example.com
      NEXT_PUBLIC_PREVIEW: 'true'

  - name: Deploy to Preview
    run: |
      # Example: Deploy to Vercel, Netlify, or your platform
      # npx vercel --token=${{ secrets.VERCEL_TOKEN }} --prebuilt
      echo "Deploying preview for PR #${{ github.event.number }}"

cleanup-preview: if: github.event.action == 'closed' runs-on: ubuntu-latest

steps:
  - name: Cleanup Preview
    run: |
      echo "Cleaning up preview for PR #${{ github.event.number }}"

Handle Server Actions Encryption

CRITICAL: For multi-server deployments, set a consistent encryption key:

Generate a key locally

openssl rand -base64 32

Set in GitHub Actions (Secret)

NEXT_SERVER_ACTIONS_ENCRYPTION_KEY

Or generate in workflow (see GitHub Actions example above)

In Dockerfile - pass as build arg

ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}

Without this key, Server Actions will fail with "Failed to find Server Action" errors in multi-server deployments.

Best Practices

Docker Best Practices

  • Use multi-stage builds to minimize final image size

  • Enable standalone output for production deployments

  • Set proper permissions with non-root user

  • Include health checks for orchestration platforms

  • Cache dependencies in CI/CD for faster builds

Security Checklist

  • Never commit .env.local or secrets

  • Use NEXT_PUBLIC_ prefix only for truly public values

  • Set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for multi-server deployments

  • Disable telemetry in production with NEXT_TELEMETRY_DISABLED=1

  • Remove poweredByHeader in production

Performance Optimization

  • Use output: 'standalone' for minimal Docker images

  • Enable compression at the reverse proxy level

  • Configure CDN for static assets

  • Use next/image for optimized images

Environment Management

  • Use same Docker image across all environments

  • Inject runtime configuration via environment variables

  • Use different tags for different environments (latest , staging )

  • Implement feature flags for gradual rollouts

Examples

Example 1: Complete Production Setup

Input: Set up production deployment with Docker and GitHub Actions

Output:

// next.config.ts import type { NextConfig } from 'next'

const nextConfig: NextConfig = { output: 'standalone', poweredByHeader: false, compress: true, generateBuildId: async () => process.env.GIT_HASH || 'build', env: { APP_VERSION: process.env.npm_package_version, }, }

export default nextConfig

Dockerfile

FROM node:20-alpine AS base

FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci

FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production ARG GIT_HASH ENV GIT_HASH=${GIT_HASH} ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY} RUN npm run build

FROM base AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/public ./public USER nextjs EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" CMD ["node", "server.js"]

.github/workflows/deploy.yml

name: Deploy on: push: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - id: key run: echo "key=$(openssl rand -base64 32)" >> $GITHUB_OUTPUT - uses: docker/build-push-action@v5 with: push: true tags: ${{ steps.meta.outputs.tags }} build-args: | GIT_HASH=${{ github.sha }} NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.key.outputs.key }}

Example 2: Runtime Environment Variables

Input: Configure different API URLs for staging and production

Output:

// src/lib/env.ts const envSchema = { server: { DATABASE_URL: process.env.DATABASE_URL!, API_SECRET: process.env.API_SECRET!, }, public: { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!, NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'MyApp', }, }

export function getServerEnv() { return envSchema.server }

export function getPublicEnv() { return envSchema.public }

// Use in Server Components import { getServerEnv } from '@/lib/env'

async function fetchData() { const env = getServerEnv() // Use env.DATABASE_URL }

// Use in Client Components import { getPublicEnv } from '@/lib/env'

function ApiClient() { const env = getPublicEnv() // Use env.NEXT_PUBLIC_API_URL }

docker-compose.yml for local development

version: '3.8' services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgresql://db:5432/myapp - NEXT_PUBLIC_API_URL=http://localhost:3000/api

Example 3: OpenTelemetry Integration

Input: Add distributed tracing to Next.js application

Output:

// instrumentation.ts export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { await import('./instrumentation.node') } }

// instrumentation.node.ts import { NodeSDK } from '@opentelemetry/sdk-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' import { resourceFromAttributes } from '@opentelemetry/resources' import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app', }), spanProcessor: new SimpleSpanProcessor( new OTLPTraceExporter({ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, }) ), })

sdk.start()

// src/app/api/users/route.ts import { trace } from '@opentelemetry/api'

export async function GET() { const tracer = trace.getTracer('next-app')

return tracer.startActiveSpan('fetch-users', async (span) => { try { const users = await db.user.findMany() span.setAttribute('user.count', users.length) return NextResponse.json(users) } catch (error) { span.recordException(error as Error) throw error } finally { span.end() } }) }

Constraints and Warnings

Constraints

  • Standalone output requires Node.js 18+

  • Server Actions encryption key must be consistent across all instances

  • Runtime environment variables only work with output: 'standalone'

  • Health checks need explicit route handler

  • OpenTelemetry requires instrumentation.ts at project root

Warnings

  • Never use NEXT_PUBLIC_ prefix for sensitive values

  • Always set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY for multi-server deployments

  • Without health checks, orchestrators may send traffic to unhealthy instances

  • Runtime env vars don't work with static export (output: 'export' )

  • Cache build artifacts in CI/CD to speed up builds

References

Consult these files for detailed patterns:

  • references/docker-patterns.md - Advanced Docker configurations, multi-arch builds, optimization

  • references/github-actions.md - Complete CI/CD workflows, testing, security scanning

  • references/monitoring.md - OpenTelemetry, logging, alerting, dashboards

  • references/deployment-platforms.md - Platform-specific guides (Vercel, AWS, GCP, Azure)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tailwind-css-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-test-bean-validation

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-patterns

No summary provided by upstream source.

Repository SourceNeeds Review