Dockerize a Next.js Application

Khimananda Oli 8 min read Programming and Languages
Dockerize a Next.js Application

By Khimananda Oli | Last reviewed: August 2026

Shipping frontend applications reliably requires consistent environments, and the most effective way to achieve this is to Dockerize a Next.js application using modern build optimizations. Many teams still ship bloated images containing development dependencies, source maps, and unnecessary tooling, leading to slow deployments and expanded attack surfaces. This guide walks you through creating a secure, optimized production container that leverages Next.js standalone output and Docker multi-stage builds.

Why should you Dockerize a Next.js application instead of using Node directly?

Running Next.js directly on a host via Node.js works for development but introduces significant operational risk in production. When you containerize applications consistently, you eliminate environment drift between staging and production servers. A common mistake I see in audits is teams running npm start inside containers with full source code and dev dependencies exposed. This creates three specific problems: larger attack surface due to included packages like webpack and babel, slower cold starts because the runtime must parse unnecessary files, and compliance failures when auditors find development tools in production artifacts.

Source Codepackage.jsonnext.config.tssrc/ pages/Build Stagenode:22-alpinenpm ci --production=falsenpm run buildGenerates .next/standalone~800MB (discarded)Runtime Stagenode:22-alpineCOPY standalone + staticUSER nextjs (non-root)~150MB Final ImageProductionK8s / ECSPort 3000
Multi-stage build architecture for Dockerize a Next.js Application workflow showing build isolation and final artifact optimization

The standalone output mode solves these issues by tracing only the actual dependencies your server needs at runtime. Instead of shipping node_modules wholesale, Next.js analyzes your import graph and copies just the required files into .next/standalone. Combined with Alpine Linux as the base image, this reduces typical image sizes from 800MB+ down to 120–180MB. For teams managing infrastructure across Nepal and global regions, smaller images mean faster pulls over constrained networks and lower storage costs in registries like ECR or GitLab Container Registry.

How do you configure Next.js standalone output for Docker?

Before writing any Dockerfile, you must enable standalone output in your Next.js configuration. Without this flag, the framework expects the full node_modules directory at runtime, defeating the purpose of multi-stage builds. In your next.config.ts (or next.config.mjs), add the output property:

<!-- next.config.ts -->
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
  // Optional: compress static assets for faster transfers
  compress: true,
  // Security headers applied at the framework level
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'X-Frame-Options', value: 'DENY' },
        ],
      },
    ];
  },
};

export default nextConfig;

After enabling this, run npm run build locally to verify the structure. You should see .next/standalone containing a minimal server.js, a trimmed node_modules folder with only production dependencies, and symbolic links where appropriate. The .next/static directory remains separate and must be copied explicitly during the Docker build. If you're using TypeScript, ensure your tsconfig.json doesn't reference paths outside the build context, as the tracer cannot follow absolute imports outside the project root.

Handling environment variables correctly

A frequent pitfall when you Dockerize a Next.js application is misunderstanding which environment variables are baked in at build time versus injected at runtime. Variables prefixed with NEXT_PUBLIC_ are embedded into the JavaScript bundle during next build and cannot be changed without rebuilding. Server-only variables like DATABASE_URL or API keys are read at runtime from the container environment. Never put secrets in your Dockerfile or build arguments. Instead, pass them via Kubernetes Secrets, AWS Systems Manager Parameter Store, or Docker Compose environment blocks at deploy time. For deeper guidance on handling credentials safely, review Kubernetes secrets management patterns.

What does a production-ready Next.js Dockerfile look like?

The following Dockerfile implements all current best practices for 2026. It uses explicit version pinning, separates concerns across stages, sets proper permissions, and avoids common security misconfigurations. Save this as Dockerfile in your project root:

# Stage 1: Install dependencies
FROM node:22.14.0-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts && npm cache clean --force

# Stage 2: Build the application
FROM node:22.14.0-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# Stage 3: Production runtime
FROM node:22.14.0-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# Create non-root user with explicit UID/GID for predictable permissions
RUN addgroup --system --gid 1001 nodejs \
    && adduser --system --uid 1001 nextjs

# Copy built assets
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]
deps StageCOPY package*.jsonnpm ciClean cacheOutput:/app/node_modulesbuilder StageCOPY node_modulesCOPY source codenpm run buildOutput:.next/standalone.next/staticrunner StageCreate nextjs userCOPY publicCOPY standaloneCOPY staticFinal: ~150MBSecurity Checks✓ Non-root USER✓ No devDependencies✓ Pinned base image✓ Telemetry disabled✓ Minimal APK packages✓ Read-only filesystem ready
Three-stage Docker build sequence for Next.js showing artifact propagation and security validation checkpoints

Several details here matter for production reliability. The --ignore-scripts flag during npm ci prevents arbitrary post-install scripts from executing, mitigating supply chain attacks. Setting NEXT_TELEMETRY_DISABLED=1 stops anonymous usage data collection during builds, which matters for air-gapped environments and GDPR compliance. The HOSTNAME="0.0.0.0" variable is critical: without it, Next.js 14+ binds to localhost only and refuses external connections inside containers. Always pin exact Node versions rather than using lts tags to ensure reproducible builds across CI runners and developer machines.

How does standalone Docker compare to traditional Next.js deployment approaches?

Understanding the trade-offs helps you justify architectural decisions to stakeholders and choose the right approach for your team's maturity level. Here's a direct comparison based on real deployments I've managed:

CriteriaStandalone DockerFull node_modules DockerVercel / Managed Platform
Image Size120–180 MB600–900 MBN/A (managed)
Cold Start Time~200ms~800msVariable (edge-dependent)
Build ComplexityModerate (multi-stage)Simple (single stage)Minimal (git push)
Vendor Lock-inNoneNoneHigh
Custom Server MiddlewareFully supportedFully supportedLimited
Self-hosted ViabilityExcellentPoor (resource-heavy)Not applicable
Compliance Audit ReadinessHigh (minimal surface)Medium (excess packages)Depends on vendor SOC 2

For teams operating in regulated industries or managing infrastructure across multiple cloud providers, standalone Docker offers the best balance of control, security, and performance. Managed platforms excel for rapid prototyping but become expensive at scale and limit your ability to implement custom networking, WAF rules, or data residency requirements. If you're evaluating hosting options for Nepali businesses with local compliance needs, check hosting considerations for regional deployments.

What are common pitfalls when running Next.js containers in production?

Even with a perfect Dockerfile, runtime misconfigurations cause outages. After debugging dozens of Next.js container incidents, these are the issues I encounter most frequently:

  • Missing static assets: The standalone output does not include .next/static. If you forget the explicit COPY instruction, pages load without CSS or client-side JavaScript. Always verify both directories exist in the final image.
  • Health check failures: Next.js doesn't expose /health by default. Add an API route at app/api/health/route.ts returning a 200 status, then configure your orchestrator's liveness probe accordingly. Without this, Kubernetes will restart healthy pods unnecessarily.
  • Memory limits too low: Next.js server-side rendering can spike memory during initial page generation. Set container memory requests to at least 256Mi and limits to 512Mi for medium-traffic apps. Monitor actual usage with Prometheus before tuning down.
  • Ignoring SIGTERM: Node.js doesn't handle graceful shutdown by default. Wrap your entrypoint with tini or use STOPSIGNAL SIGINT in the Dockerfile to allow in-flight requests to complete during deployments.
  • Public directory missing: Unlike static assets, the public/ folder isn't traced automatically. If you serve robots.txt, favicons, or manifest files, you must copy this directory explicitly.

For comprehensive observability once your containers are running, integrate OpenTelemetry early. The article on instrumenting applications with OpenTelemetry covers adding traces to Next.js API routes and server components, which proves invaluable when debugging latency in containerized environments.

Deployment Strategy Comparison02505007501000Size (MB) / Time (ms)150 MBStandalone200ms start800 MBFull Modules800ms start400 MB*Custom Server400ms start600 MB*Dev Container1200ms start* Estimated
Visual comparison of image sizes and cold start times across different Next.js containerization strategies

Next steps for production-ready Next.js containers

When you properly Dockerize a Next.js application using standalone output and multi-stage builds, you gain reproducible deployments, reduced attack surface, and faster scaling characteristics that directly impact user experience and operational costs. Start by implementing the Dockerfile above in a staging environment, validate health checks and static asset delivery, then gradually roll out to production with resource monitoring enabled. If you need help auditing your existing container setup or designing a compliant deployment pipeline, reach out to discuss your infrastructure.

Frequently Asked Questions

Use node:22-alpine as your final stage base image. It reduces attack surface and size significantly compared to standard Debian images while maintaining full compatibility with current Next.js standalone output modes and native module compilation requirements for production deployments.

Set output: 'standalone' in next.config.js. This bundles only required node_modules into .next/standalone, reducing Docker image size from gigabytes to under 200MB by excluding dev dependencies entirely during the final production build stage.

Missing public or .next/static folders causes runtime crashes. Copy these directories explicitly in your Dockerfile after the standalone build step, as Next.js does not include them automatically in standalone output despite being required for serving assets correctly.

Yes. Multi-stage builds separate dependency installation, building, and runtime into distinct stages. This prevents leaking source code, environment variables, and development tools into the final production image while keeping layer caching efficient for faster CI rebuilds.

Pass runtime variables via ENV directives or docker run -e flags. Build-time NEXT_PUBLIC_ vars bake into JavaScript bundles, but server-side secrets must be injected at container start to avoid embedding sensitive credentials in immutable image layers permanently.

Expose port 3000 by default. Configure HOSTNAME=0.0.0.0 and PORT=3000 as environment variables since Next.js binds to localhost only by default, making the container unreachable from outside without explicit host binding configuration in production environments.

No. Turbopack remains development-only in 2026. Production Docker builds must use webpack-based next build commands. Attempting turbopack in CI fails silently or produces incomplete bundles unsuitable for standalone deployment artifacts.

Enable standalone output, use Alpine base images, remove dev dependencies, and copy only .next/standalone, public, and static folders. These steps typically shrink production images below 150MB while preserving full server-side rendering and API route functionality.

No. Standalone mode includes pruned node_modules. Running npm install in the final stage adds unnecessary bloat and potential security vulnerabilities. Only copy the pre-built standalone directory containing exactly what the runtime requires.

Copy package.json and lock files first, run npm ci, then copy source code. This ordering ensures dependency layers cache independently of application changes, preventing redundant reinstalls on every commit and cutting CI build times by sixty percent typically.

No. Create a non-root node user and switch before CMD. Running as root violates container security best practices and increases blast radius if compromised. Most official Node images include this user pre-configured for immediate use.

Add HEALTHCHECK CMD wget --spider http://localhost:3000/ || exit 1 to your Dockerfile. This enables orchestrators like Kubernetes or Docker Swarm to detect unresponsive containers automatically and restart them without relying solely on process-level supervision.

No. Incremental Static Regeneration works identically inside containers provided filesystem write permissions exist for .next/cache. Ensure volumes or ephemeral storage allow cache writes, otherwise regenerated pages fail silently and fall back to stale content indefinitely.

Yes. Set ignoreBuildErrors: true in next.config.js typescript section. This speeds up CI builds significantly when type safety is already enforced via pre-commit hooks or separate lint jobs, avoiding duplicate validation overhead in container pipelines.

Run intermediate stages interactively using docker build --target deps -it . Then inspect installed packages, verify lock file integrity, and test build commands manually. This isolates whether failures stem from dependencies, configuration, or source code issues efficiently.