Bandera: Русский Русский Bandera: English English

Despliegue moderno de Next.js: GitHub Actions, Docker y cero tiempo de inactividad

Publicado el 02.03.2026

Si todavía haces next build directamente en el servidor de producción — tu servidor realmente sufre. CPU al máximo, OOM-kill, errores 502 y largos tiempos de inactividad — es un clásico que ya es hora de terminar.

En 2026, el estándar de la industria es la construcción separada:

  1. Construimos una imagen standalone mínima en la nube con GitHub Actions.
  2. La empujamos a GHCR (GitHub Container Registry).
  3. En el servidor solo hacemos pull + reinicio atómico.

Capítulo 1. Dockerfile ideal (multietapa + standalone)

Todo el secreto de una imagen pequeña y rápida está en el modo standalone. Next.js calcula por sí mismo qué archivos y partes de node_modules son realmente necesarios para que funcione el servidor, y copia solo esos.

# syntax=docker/dockerfile:1
# ETAPA 1 — Dependencias
FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json yarn.lock* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
  else npm ci; \
  fi

# ETAPA 2 — Compilación
FROM node:24-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# ETAPA 3 — Imagen de producción (Runner)
FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# Seguridad ante todo: no ejecutar como root
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs

# Copiamos solo los artefactos de la compilación standalone
COPY --from=builder --chown=nextjs:nodejs /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"

# Comprobación de salud del contenedor
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1

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

¿Por qué es genial?

  • Tamaño: La imagen pesa ~200 MB frente a 1.5 GB de la habitual.
  • Seguridad: Usar un usuario non-root (nextjs) protege el sistema anfitrión en caso de compromiso del contenedor.
  • Healthcheck: Docker detectará si la aplicación se ‘colgó’ al iniciar y no enviará tráfico a ella.

Capítulo 2. Análisis del workflow de GitHub Actions

Tu pipeline está dividido en dos etapas (jobs): la construcción en la nube y el despliegue en tu “hardware”.

1. Preparación y build

- name: Docker meta & tags
  id: prepare
  run: |
    IMAGE_REPO="ghcr.io/${GITHUB_REPOSITORY,,}"
    SHORT_SHA="${GITHUB_SHA::8}"
    echo "image_repo=$IMAGE_REPO" >> $GITHUB_OUTPUT
    echo "image_tag=sha-$SHORT_SHA" >> $GITHUB_OUTPUT

Importante: La sintaxis ${GITHUB_REPOSITORY,,} convierte el nombre del repositorio a minúsculas. Los registros de Docker no aceptan mayúsculas, y en GitHub a menudo las hay.


2. Caché de la compilación

cache-from: type=gha
cache-to: type=gha,mode=max

Usamos el caché nativo de GitHub Actions. Si no has cambiado package.json, la etapa de instalación de dependencias se omitirá y el build durará 1–2 minutos en lugar de 10.


3. Despliegue inteligente (bucle de healthcheck)

El punto más importante — no solo decimos al servidor “actualízate”, comprobamos si la aplicación sobrevivió.

for i in {1..60}; do
  STATUS=$(docker inspect --format='{{json .State.Health.Status}}' nextjs 2>/dev/null || echo '"not-found"')
  if [[ $STATUS == '"healthy"' || $STATUS == '"no-healthcheck"' ]]; then
    HEALTHY=1
    break
  fi
  sleep 2
done

Si Next.js se cae por un error en las variables de entorno, el script lo detectará, no actualizará el proxy (Caddy/Nginx) y terminará la acción con error. Tu sitio antiguo seguirá funcionando y recibirás una notificación del problema.


Capítulo 3. Variables en tiempo de ejecución vs en tiempo de compilación

Estas son las trampas en las que casi todos caen.

  1. NEXT_PUBLIC_ (Build-time): Estas variables se “incrustan” en el bundle JS durante el comando next build. Si las cambias en el servidor en .env, no cambiará nada. Debes pasarlas en GitHub Actions como build-args.

  2. Secretos (Runtime): DATABASE_URL, JWT_SECRET. No se deben meter en la imagen Docker. Se inyectan en el momento de arrancar el contenedor mediante docker-compose.

Consejo: Si es posible, haz que la URL de la API también sea una variable en tiempo de ejecución mediante proxy o scripts de configuración especiales, para que la misma imagen pueda desplegarse tanto en staging como en producción sin reconstrucción.

Reseñas relacionadas

Hubo varios problemas, tanto en la parte técnica como en la comprensión general. Mijaíl respondió rápido a la solicitud, ayudó a aclarar las cosas y resolvió los problemas técnicos; por ello, muchas gracias. Estoy satisfecho con el resultado.

abazawolf · Configuración de VPS, configuración del servidor

18.02.2026 · ⭐ 5/5

Hubo varios problemas relacionados tanto con la parte técnica como con la comprensión en general. Mijaíl respondió rápidamente a la solicitud, ayudó a aclarar las cosas y resolvió los problemas técnicos, por lo que le doy las gracias por ello. Estoy satisfecho con el resultado.

¿Necesitas ayuda?

Escríbeme y te ayudaré a resolver el problema

Publicaciones relacionadas