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:
npx create-next-app@latest mi-app --typescript --tailwind --app
cd mi-appConfigurar 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:
// 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:
# 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
FROM node:22-alpine AS depsUsa 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.
COPY package.json package-lock.json ./
RUN npm ci --ignore-scriptsCopia 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
FROM node:22-alpine AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run buildEmpieza 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:
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjsCrea 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.
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/staticCopia solo tres cosas del build:
/public-- tus archivos estaticos (imagenes, favicon, etc.).next/standalone-- el servidor minimo con solo las dependencias necesarias.next/static-- los assets generados (JS/CSS bundles)
El --chown=nextjs:nodejs asigna los archivos al usuario no-root.
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:
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:
# 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:
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
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
docker images mi-app-nextjsCon 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
docker run -p 3000:3000 --env-file .env.production mi-app-nextjsTu 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
| Configuracion | Tamano 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:
// 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:
# En el Dockerfile, etapa builder
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URLdocker 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:
docker exec -it <container-id> whoamiDeberia 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.
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.
Articulos relacionados
Next.js 16: Guia de Migracion y Novedades
Migra tu proyecto de Next.js 15 a 16. Novedades principales, breaking changes, y pasos para actualizar sin romper tu app.
Testing en Next.js con Vitest y Playwright
Configura testing en tu proyecto Next.js. Unit tests con Vitest, E2E con Playwright, y como integrarlos en tu pipeline de CI/CD.
Tailwind CSS 4: Migracion desde v3
Migra tu proyecto de Tailwind CSS 3 a 4. Cambios principales, nuevo sistema de configuracion, CSS-first config y como actualizar sin romper tu app.