guias·12 min de lectura

Docker para Next.js: De Desarrollo a Produccion con Multi-Stage Build

Guia paso a paso para dockerizar tu proyecto Next.js. Multi-stage build, standalone output, Docker Compose para dev, y optimizacion de imagen de 2GB a menos de 200MB.

Docker para Next.js: De Desarrollo a Produccion con Multi-Stage Build

Docker para tu proyecto Next.js es de esas herramientas que una vez que configuras, no entiendes como deployabas antes. En vez de depender de la configuracion de cada servidor, empaquetas tu app en un contenedor que corre igual en tu maquina, en CI, y en produccion.

El problema es que un Dockerfile mal configurado genera imagenes de 2GB o mas. Con la configuracion correcta -- multi-stage build y standalone output -- esa misma imagen baja a menos de 200MB. Esta guia te lleva paso a paso desde cero.

Por que Docker para tu proyecto Next.js

Si deployeas en Vercel, probablemente no necesitas Docker. Vercel maneja todo por ti. Pero Docker es la respuesta cuando:

  • Deployeas en tu propio servidor o VPS (DigitalOcean, Railway, AWS EC2)
  • Usas Kubernetes para orquestar multiples servicios
  • Tu proyecto tiene dependencias de sistema que necesitas controlar (como sharp para optimizar imagenes)
  • Necesitas el mismo entorno en desarrollo, CI/CD y produccion
  • Tu empresa tiene infraestructura propia y no usa servicios managed

Segun el Stack Overflow Developer Survey 2025, Docker tuvo el mayor crecimiento de adopcion de cualquier tecnologia: +17 puntos en un solo ano. Ya no es opcional -- es parte del stack moderno.

Requisitos previos

  • Node.js 18+ instalado
  • Docker Desktop instalado (docker.com)
  • Un proyecto Next.js funcionando localmente
  • Conocimiento basico de terminal

Si no tienes un proyecto, puedes crear uno rapido:

bash
npx create-next-app@latest mi-app --typescript --tailwind --app
cd mi-app

Configurar Next.js para standalone output

El primer paso es decirle a Next.js que genere una version standalone de tu app. Esto es lo que hace posible que la imagen Docker sea liviana.

Abre tu next.config.ts y agrega la opcion output:

typescript
// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  output: "standalone",
};
 
export default nextConfig;

Que hace output: "standalone"? Cuando corres next build, Next.js analiza las dependencias que tu app realmente usa y genera una carpeta .next/standalone/ con solo esas dependencias. En vez de copiar 500MB+ de node_modules, standalone incluye solo lo necesario (~30MB).

Esto es critico para Docker. Sin standalone, tu imagen copia TODOS los node_modules al contenedor. Con standalone, copia una fraccion.

El Dockerfile optimizado (multi-stage build)

Este es el Dockerfile completo. Despues explico cada linea:

dockerfile
# Etapa 1: Instalar dependencias
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
 
# Etapa 2: Build de la aplicacion
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
# Etapa 3: Runtime de produccion
FROM node:22-alpine AS runner
WORKDIR /app
 
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
 
# Usuario no-root por seguridad
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
# Copiar solo lo necesario del build
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"]

Que hace cada linea del Dockerfile

Etapa 1: Dependencias

dockerfile
FROM node:22-alpine AS deps

Usa Node.js 22 sobre Alpine Linux. Alpine pesa ~5MB en vez de los ~900MB de la imagen completa de Node. El AS deps le da nombre a esta etapa para referenciarla despues.

dockerfile
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

Copia SOLO los archivos de dependencias primero. Esto aprovecha el layer caching de Docker: si tus dependencias no cambian, Docker reutiliza esta capa sin reinstalar nada. npm ci es mas rapido y determinista que npm install porque usa el lockfile exacto.

Etapa 2: Build

dockerfile
FROM node:22-alpine AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

Empieza una imagen limpia, copia los node_modules de la etapa anterior, luego copia todo el codigo fuente y corre el build. El resultado es la carpeta .next/ con tu app compilada.

Etapa 3: Runtime (la imagen final)

Esta es la imagen que realmente se deploya. No tiene node_modules completos, no tiene codigo fuente, no tiene dependencias de desarrollo:

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

Crea un usuario dedicado. Nunca corras contenedores como root en produccion. Si alguien logra explotar una vulnerabilidad en tu app, el dano esta limitado a los permisos de este usuario.

dockerfile
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

Copia solo tres cosas del build:

  1. /public -- tus archivos estaticos (imagenes, favicon, etc.)
  2. .next/standalone -- el servidor minimo con solo las dependencias necesarias
  3. .next/static -- los assets generados (JS/CSS bundles)

El --chown=nextjs:nodejs asigna los archivos al usuario no-root.

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

El standalone output genera un server.js en la raiz. Este archivo ES tu servidor de produccion. No necesitas next start ni ningun otro comando.

El .dockerignore que todos olvidan

Sin .dockerignore, Docker copia TODO al contexto del build. Incluyendo node_modules, .next, .git y cualquier otra cosa en tu proyecto. Esto hace el build mas lento y la imagen mas pesada.

Crea un .dockerignore en la raiz:

text
node_modules
.next
.git
.gitignore
*.md
docker-compose*.yml
.env
.env.*

Importante: nunca incluyas archivos .env en tu imagen Docker. Las variables de entorno se pasan al contenedor en runtime, no en build time. Si necesitas variables durante el build (como NEXT_PUBLIC_*), pasalas como build args.

Si manejas API keys o secrets en tu proyecto, asegurate de que no esten expuestos en tu repo. Herramientas como datahogo escanean tu repositorio y detectan credenciales filtradas automaticamente.

Docker Compose para desarrollo local

Para desarrollo, no quieres multi-stage build ni standalone. Quieres hot reload y acceso rapido. Este docker-compose.yml levanta tu app con todo lo que necesitas:

yaml
# docker-compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
 
  db:
    image: postgres:17-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: miapp
    volumes:
      - postgres_data:/var/lib/postgresql/data
 
volumes:
  postgres_data:

Y el Dockerfile.dev para desarrollo:

dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

Con docker compose up levantas tu app Next.js y una base de datos PostgreSQL en segundos. Los volumes montan tu codigo local en el contenedor, asi que los cambios se reflejan al instante con hot reload.

Build y run: de imagen a contenedor

Build de la imagen

bash
docker build -t mi-app-nextjs .

La primera vez toma unos minutos. Las siguientes son mucho mas rapidas gracias al layer caching.

Verificar el tamano

bash
docker images mi-app-nextjs

Con la configuracion de esta guia, tu imagen deberia estar entre 150MB y 200MB. Si ves mas de 500MB, algo salio mal -- probablemente falta el output: "standalone" en next.config.ts.

Correr el contenedor

bash
docker run -p 3000:3000 --env-file .env.production mi-app-nextjs

Tu app esta corriendo en http://localhost:3000. El --env-file inyecta las variables de entorno en runtime, sin que queden en la imagen.

Comparativa de tamano

ConfiguracionTamano de imagen
Sin optimizar (COPY node_modules completo)~2.1 GB
Con Alpine (sin standalone)~800 MB
Con Alpine + standalone~180 MB
Con Alpine + standalone + .dockerignore correcto~150 MB

La diferencia no es solo de disco. Una imagen mas liviana significa deploys mas rapidos, cold starts mas cortos, y menos costos de almacenamiento en tu registry.

Errores comunes y como resolverlos

"Module not found" despues del build

Standalone a veces no detecta dependencias que se importan de forma dinamica. Agrega esas dependencias a serverExternalPackages en tu config:

typescript
// next.config.ts
const nextConfig: NextConfig = {
  output: "standalone",
  serverExternalPackages: ["sharp", "bcrypt"],
};

La carpeta public no se copia

Standalone no incluye /public ni .next/static automaticamente. Por eso los copiamos explicitamente en el Dockerfile. Si te faltan assets, verifica que esas lineas COPY esten presentes.

Variables de entorno no disponibles

Las variables NEXT_PUBLIC_* se inyectan en build time, no en runtime. Si necesitas que esten disponibles en la imagen, pasalas como build args:

dockerfile
# En el Dockerfile, etapa builder
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
bash
docker build --build-arg NEXT_PUBLIC_API_URL=https://api.ejemplo.com -t mi-app .

Las variables del servidor (sin NEXT_PUBLIC_) si funcionan en runtime con --env-file.

El contenedor corre como root

Si olvidaste agregar el usuario no-root, tu contenedor corre como root. Esto es un riesgo de seguridad. Verifica con:

bash
docker exec -it <container-id> whoami

Deberia mostrar nextjs, no root.

Siguiente paso: automatizar con CI/CD

Ya tienes tu app dockerizada. El siguiente paso logico es automatizar el build y deploy con cada push. Si usas GitHub Actions, tengo una guia completa de CI/CD con GitHub Actions para Next.js que cubre exactamente eso.

Tambien te puede servir la guia de variables de entorno en Next.js y Vercel para manejar correctamente los secrets entre ambientes.

Si estas evaluando donde deployar tu contenedor, la guia de deploy en Vercel te ayuda a comparar opciones.

Preguntas frecuentes

Como dockerizar una aplicacion Next.js?

Necesitas tres cosas: configurar output: "standalone" en next.config.ts, crear un Dockerfile con multi-stage build (dependencias, build, runtime), y un .dockerignore. Con esto generas una imagen optimizada lista para produccion.

Que es output standalone en Next.js?

Es una configuracion que genera una version minima de tu app con solo las dependencias necesarias. En vez de copiar todos los node_modules (500MB+), standalone incluye solo lo que tu app realmente importa (~30MB). Lo activas con output: "standalone" en next.config.ts.

Cuanto pesa una imagen Docker de Next.js optimizada?

Con multi-stage build y standalone output, una imagen tipica queda entre 150MB y 200MB. Sin optimizar, la misma app puede pesar mas de 2GB. La diferencia impacta directamente en tiempos de deploy y costos de hosting.

Docker o Vercel para deployar Next.js?

Depende de tu caso. Vercel es mas simple si tu proyecto es estandar y no necesitas control total de la infraestructura. Docker tiene sentido cuando corres en tu propio servidor, usas Kubernetes, tienes requisitos especificos de red o seguridad, o necesitas el mismo entorno en todos tus ambientes.

Como uso Docker Compose con Next.js y una base de datos?

Creas un docker-compose.yml con dos servicios: tu app Next.js y tu base de datos. Docker Compose levanta ambos contenedores en la misma red, y tu app se conecta a la base de datos usando el nombre del servicio como hostname. En esta guia hay un ejemplo completo con PostgreSQL.

#docker#nextjs#devops#deploy#produccion#contenedores

Preguntas frecuentes

Como dockerizar una aplicacion Next.js?

Necesitas tres cosas: configurar output standalone en next.config.ts, crear un Dockerfile con multi-stage build (dependencias, build, runtime), y un .dockerignore. Con esto generas una imagen optimizada lista para produccion.

Que es output standalone en Next.js?

Es una configuracion de Next.js que genera una version minima de tu aplicacion con solo las dependencias necesarias para correr. En vez de copiar todos los node_modules (500MB+), standalone incluye solo lo que tu app realmente importa (~30MB).

Cuanto pesa una imagen Docker de Next.js optimizada?

Con multi-stage build y standalone output, una imagen tipica de Next.js queda entre 150MB y 200MB. Sin optimizar, la misma app puede pesar mas de 2GB. La diferencia es enorme en tiempos de deploy y costos de hosting.

Docker o Vercel para deployar Next.js?

Si tu proyecto es estandar y no necesitas control total, Vercel es mas simple. Docker tiene sentido cuando necesitas control sobre la infraestructura, corres en tu propio servidor, usas Kubernetes, o tienes requisitos especificos de red o seguridad.

Como uso Docker Compose con Next.js y una base de datos?

Creas un docker-compose.yml con dos servicios: tu app Next.js y tu base de datos (PostgreSQL, por ejemplo). Docker Compose levanta ambos contenedores en la misma red, y tu app se conecta a la base de datos por el nombre del servicio.