wip: contenido inicial + test de reusable workflow / composite action

This commit is contained in:
devops committed 2026-08-17 00:12:10 -05:00
1 parent e9fe7d0567
commit 262c0f271a
22 files changed
+1480

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
# Basado en el Dockerfile real de ecommerce-frontend (Next.js, output
# "standalone"). Tu next.config.* necesita "output: 'standalone'" para
# que este Dockerfile funcione -- sin eso, .next/standalone no existe
# y el build de la imagen falla en el "test -f" del stage builder.
#
# ARGs a completar por tu app:
# NODE_IMAGE -- versión exacta de Node, fijá una concreta.
# APP_PORT -- puerto de escucha (Next.js con HOSTNAME/PORT del
# runtime standalone). Default 3000; el frontend real
# del lab lo corre en 80 vía este mismo ARG.
# syntax=docker/dockerfile:1.7
ARG NODE_IMAGE=node:24.18.0-bookworm-slim
ARG APP_PORT=3000
# ============================================================
# 1. Dependencias
# ============================================================
FROM ${NODE_IMAGE} AS deps
WORKDIR /app
ENV NODE_ENV=development \
NEXT_TELEMETRY_DISABLED=1 \
NPM_CONFIG_AUDIT=false \
NPM_CONFIG_FUND=false
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=private \
npm ci \
--include=prod \
--include=dev \
--no-audit \
--no-fund \
--foreground-scripts
# ============================================================
# 2. Compilación Next.js
# ============================================================
FROM ${NODE_IMAGE} AS builder
WORKDIR /app
ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1 \
NODE_OPTIONS=--max-old-space-size=2048
COPY --from=deps /app/node_modules ./node_modules
COPY package.json package-lock.json ./
COPY . .
RUN --mount=type=cache,target=/app/.next/cache,sharing=private \
npm run build
# Falla temprano y con un mensaje claro si next.config.* no tiene
# "output: 'standalone'" -- mejor que un COPY silencioso más abajo
# fallando con "no existe el archivo".
RUN test -f /app/.next/standalone/server.js \
&& test -d /app/.next/static \
|| { echo "ERROR: falta 'output: standalone' en next.config.*"; exit 1; }
# ============================================================
# 3. Runtime de producción
# ============================================================
FROM ${NODE_IMAGE} AS runner
ARG APP_PORT
WORKDIR /app
ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1 \
HOSTNAME=0.0.0.0 \
PORT=${APP_PORT}
# setcap: solo hace falta si APP_PORT < 1024 (un puerto "privilegiado"
# que normalmente requiere root para bindear). Si tu APP_PORT es 3000
# o cualquier valor >= 1024, podés borrar el setcap y libcap2-bin.
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates libcap2-bin \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1001 nodejs \
&& useradd --uid 1001 --gid 1001 --create-home --shell /usr/sbin/nologin nextjs \
&& setcap 'cap_net_bind_service=+ep' /usr/local/bin/node
# El runtime solo ejecuta "node server.js" -- nunca npm/npx/corepack.
# Sacarlos reduce lo que Trivy escanea al set real de producción.
RUN rm -rf \
/usr/local/lib/node_modules/npm \
/usr/local/lib/node_modules/corepack \
/usr/local/bin/npm \
/usr/local/bin/npx \
/usr/local/bin/corepack
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 ${APP_PORT}
# CAMBIAR: /api/health tiene que existir en tu app (una route handler
# de Next.js que devuelva 200). Si no la tenés todavía, es la primera
# ruta a agregar -- este healthcheck y el readinessProbe/livenessProbe
# de k8s-manifests/base/deployment.yaml asumen que existe.
HEALTHCHECK --interval=30s --timeout=8s --start-period=30s --retries=3 \
CMD ["node", "-e", "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + '/api/health').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
CMD ["node", "server.js"]
+94
View File
@@ -0,0 +1,94 @@
# Basado en el Dockerfile real de commerce-backend (Medusa v2 / Express).
# Multi-stage: build con devDependencies, runtime solo con lo necesario
# para correr -- sin compilador, sin devDependencies, sin npm/npx.
#
# ARGs a completar por tu app (todos tienen un default razonable):
# NODE_IMAGE -- versión exacta de Node. Fijala a una versión concreta
# (no "node:22-slim"), así un rebuild no te cambia la
# versión de Node por sorpresa un día cualquiera.
# APP_PORT -- puerto en el que tu app escucha.
# START_CMD -- cómo arrancar tu app en runtime. Por default invoca
# "node dist/index.js" -- CAMBIALO al entrypoint real de
# tu build. No uses "npm start"/"npx algo": el runtime
# de esta imagen NO tiene npm (ver más abajo, por qué).
# syntax=docker/dockerfile:1.7
ARG NODE_IMAGE=node:22.18.0-bookworm-slim
ARG APP_PORT=3000
#############################################################
# Builder
#############################################################
FROM ${NODE_IMAGE} AS builder
WORKDIR /app
ENV CI=true \
NODE_ENV=development \
NPM_CONFIG_UPDATE_NOTIFIER=false
COPY package.json package-lock.json* .npmrc* ./
# --legacy-peer-deps: sacalo si tu proyecto no lo necesita. Medusa v2 sí
# lo necesita hoy (conflictos de peer deps conocidos del ecosistema).
RUN --mount=type=cache,id=node-api-npm-cache,target=/root/.npm \
npm ci \
--legacy-peer-deps \
--prefer-offline \
--no-audit \
--no-fund
# CAMBIAR: copiá acá los archivos/carpetas fuente reales de tu app
# (tsconfig, src/, config específico). Estos son solo un ejemplo.
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
RUN npm prune --omit=dev \
&& npm cache clean --force
#############################################################
# Runtime
#############################################################
FROM ${NODE_IMAGE}
ARG APP_PORT
ENV NODE_ENV=production \
PORT=${APP_PORT} \
NPM_CONFIG_UPDATE_NOTIFIER=false
# El CLI global de npm que trae la imagen base node:*-slim arrastra su
# propia copia de "tar" con historial de CVEs — no hace falta en
# runtime (nunca corrés npm/npx acá, solo "node algo.js"). Borrarlo
# reduce lo que Trivy tiene que escanear al set real que corre en
# producción.
#
# OJO si tu app usa un init container que corre "npx <algo>" contra
# esta misma imagen (ej. migraciones): eso se rompe si borrás npm acá.
# Cambiá ese comando a invocar el binario directo con "node", como el
# CMD de abajo — no vuelvas a agregar npm solo para eso.
RUN rm -rf /usr/local/lib/node_modules/npm
WORKDIR /app
# CAMBIAR: qué carpetas copiar del builder depende de tu build real
# (dist/, .output/, node_modules). Este ejemplo asume un build típico
# de tsc/esbuild a dist/ + node_modules de producción.
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
COPY --from=builder --chown=node:node /app/dist ./dist
# La imagen base node:*-bookworm-slim ya trae un usuario "node" (uid
# 1000) sin privilegios -- no hace falta crear uno nuevo, a diferencia
# de nextjs.Dockerfile.
USER node
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=8s --start-period=20s --retries=3 \
CMD ["node", "-e", "fetch('http://127.0.0.1:' + (process.env.PORT || 3000) + '/health').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
# CAMBIAR: el entrypoint real. Nunca "npm start"/"npx algo" -- ver nota
# de arriba sobre por qué npm no existe en esta imagen.
CMD ["node", "dist/index.js"]
+66
View File
@@ -0,0 +1,66 @@
# Basado en el Dockerfile real de docs-portal (sitio MkDocs Material).
# Mejora respecto al original: nginx corre como usuario NO-root acá
# (nginxinc/nginx-unprivileged) -- el docs-portal real del lab todavía
# corre nginx:1.27-alpine estándar (como root), un gap real encontrado
# al armar este template, pendiente de aplicar al repo real por
# separado.
#
# Este template asume MkDocs -- es el único generador de sitio estático
# que existe hoy en el lab. Si tu app usa otro generador (Hugo, Astro,
# un build de Vite sin backend), el STAGE 1 es lo único que cambia:
# reemplazalo por el build real de tu herramienta, el STAGE 2
# (nginx no-root) sirve igual para cualquier sitio 100% estático.
#
# ARGs:
# PYTHON_IMAGE -- versión del builder (solo aplica al ejemplo MkDocs).
# NGINX_IMAGE -- versión de nginx no-root.
# APP_PORT -- OJO: esto solo cambia el EXPOSE/healthcheck de este
# Dockerfile, NO el puerto real en el que nginx
# escucha -- eso lo define el nginx.conf que trae la
# propia imagen nginx-unprivileged, fijo en 8080. Si
# cambiás este ARG a otro valor, tenés que agregar un
# nginx.conf propio (COPY) que escuche en ese mismo
# puerto, o quedan desalineados. Dejalo en 8080
# mientras no necesites tocar nginx.conf.
# syntax=docker/dockerfile:1.7
ARG PYTHON_IMAGE=python:3.12-slim
ARG NGINX_IMAGE=nginxinc/nginx-unprivileged:1.27-alpine
ARG APP_PORT=8080
# --- Stage 1: build del sitio estático (ejemplo: MkDocs Material) ---
FROM ${PYTHON_IMAGE} AS build
WORKDIR /site
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY mkdocs.yml .
COPY docs/ docs/
# --strict: cualquier link roto o warning de nav rompe el build, para
# no publicar nunca un sitio con referencias muertas. Si tu generador
# no tiene un equivalente a --strict, considerá agregar un paso de
# validación de links aparte (ej. linkinator) antes de este build.
RUN mkdocs build --strict
# --- Stage 2: sirve el sitio estático con nginx, sin root ---
FROM ${NGINX_IMAGE}
ARG APP_PORT
# ARG no alcanza para el HEALTHCHECK -- ese CMD corre en runtime, no en
# build time, y ahí un ARG ya no existe. Hace falta ENV.
ENV APP_PORT=${APP_PORT}
# CAMBIAR "/site/site" al directorio real de salida de tu build si no
# usás MkDocs (ej. "dist/", "public/", "build/", ".output/public/").
COPY --from=build /site/site /usr/share/nginx/html
EXPOSE ${APP_PORT}
# nginx-unprivileged trae wget (busybox) -- alcanza para un healthcheck
# simple sin instalar nada más.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -q -O /dev/null "http://127.0.0.1:${APP_PORT}/" || exit 1