nextjs-deployment

安装量: 185
排名: #4634

安装

npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill 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)

返回排行榜