Files
apps-registry/workloads/docs-portal/docs/playbooks/incidente-imagenes-npm.md
devops fb5cf8bc98 feat(docs-portal): pipeline GitOps y manifiestos K8s para el portal MkDocs
Agrega lo que faltaba para desplegar workloads/docs-portal/ (ya existente
sin commitear): deployment/service/ingress + kustomization siguiendo el
patrón de workloads/nginx, Applications de workload y gobernanza
separadas, y el workflow de Gitea Actions (build+push a Gitea Registry,
bump de versión en el manifiesto) siguiendo el mismo patrón que
build.yaml/build-medusa.yaml.
2026-08-13 21:08:04 -05:00

5.1 KiB

Incidente: imágenes de producto en blanco (proxy host de media.cruzcloud.net mal configurado en NPM) (2026-08)

!!! info "Origen" Migrado desde apps-registry/docs/playbooks/incidente-medusa-imagenes-proxy-npm-2026-08.md. Contenido técnico preservado sin cambios de fondo — solo formato adaptado a MkDocs Material.

Ventana del incidente 2026-08-13, resuelto el mismo día
Servicio afectado media.cruzcloud.net (Nginx Proxy Manager → MinIO, minio-svc en namespace ecommerce)
Impacto Las imágenes subidas a productos desde el Admin de Medusa no se visualizaban en el detalle de producto del storefront (shop.cruzcloud.net) — el resto del producto (título, precio, subtítulo, botón añadir al carrito) se renderizaba bien

Síntoma

Al agregar imágenes a un producto desde el Admin de Medusa (sección Media), estas se veían correctamente cargadas en el Admin, pero en la página de detalle de producto del frontend los thumbnails aparecían vacíos/en blanco.

Diagnóstico

  1. Storage: confirmado que Medusa usa el provider @medusajs/medusa/file-s3 apuntando a MinIO interno (S3_ENDPOINT=http://minio-svc:9000, bucket ari-products), con URLs públicas construidas sobre S3_FILE_URL=https://media.cruzcloud.net/ari-products (env en configmap commerce-config, namespace ecommerce).
  2. API: el producto traía URLs absolutas válidas, ej. https://media.cruzcloud.net/ari-products/<archivo>.jpg.
  3. Prueba directa de la URL:
    • Contra el ingress interno del cluster (bypaseando el proxy externo, Host: media.cruzcloud.net directo a la IP del k3d-proxy) → 200 OK, MinIO sirve el JPEG correctamente.
    • Contra https://media.cruzcloud.net/... (la ruta real que usa el navegador del cliente, vía Cloudflare) → 500 Internal Server Error, Server: openresty.

!!! tip "Patrón de diagnóstico clave" Ese contraste (funciona interno, falla público, con Server: openresty — el motor de Nginx Proxy Manager) aisló el problema al proxy externo, no a Medusa/MinIO/frontend.

Causa raíz

En Nginx Proxy Manager (/DATA/AppData/nginxproxymanager/, contenedor nginxproxymanager), el proxy host de media.cruzcloud.net (id=13 en la tabla proxy_host de database.sqlite) estaba mal configurado:

forward_host = "http://192.168.68.61/"   -- esquema y barra final metidos dentro del campo (formato inválido)
forward_port = 5005                       -- puerto donde no escucha ningún proceso en el host

Los proxy hosts que sí funcionan (shop.cruzcloud.net id 23, commerce.cruzcloud.net id 28) apuntan correctamente a:

forward_host = "192.168.68.61"
forward_port = 90   -- puerto publicado por k3d-lab-cluster-serverlb hacia Traefik

Se confirmó con ss/docker ps en el host que nada escuchaba en el puerto 5005 — este proxy host nunca estuvo apuntando correctamente al cluster, muy probablemente un typo desde que se creó.

Fix aplicado

  1. Backups previos: database.sqlite.bak-pre-media-fix y 13.conf.bak-pre-media-fix.
  2. Corrección directa en la base SQLite de NPM:
    UPDATE proxy_host SET forward_host='192.168.68.61', forward_port=90 WHERE id=13;
    
  3. NPM regeneró automáticamente nginx/proxy_host/13.conf con los valores correctos (la app Node de NPM detecta el cambio en la base y reescribe el .conf; no hace falta editarlo a mano).
  4. Recarga de nginx dentro del contenedor:
    docker exec nginxproxymanager nginx -t
    docker exec nginxproxymanager nginx -s reload
    

Adicionalmente, se seteó el campo thumbnail del producto de prueba desde el Admin (estaba en null) — Medusa no lo autocompleta a partir del array images, y el frontend lo usa como imagen hero en listados/ tarjetas de catálogo.

Validación

Confirmado visualmente en shop.cruzcloud.net/product/cybertron-sentinel-figura-de-coleccion-test-revalidate que tanto la galería del detalle como el thumbnail en listados/tarjetas se renderizan correctamente. También se validó que ediciones de descripción/título/precio desde el Admin se reflejan automáticamente en el storefront sin necesidad de restart ni intervención manual, vía el subscriber revalidate-storefront.ts (workloads/commerce-backend/src/subscribers/) que procesa el evento product.updated de Medusa.

Lecciones aprendidas

Aislar interno vs. público antes de sospechar de la app. Medusa, MinIO y el frontend estaban sanos todo el tiempo — la falla estaba en una capa de infraestructura fuera de los repos de GitOps (Nginx Proxy Manager, gestionado por fuera de apps-registry/platform-infra, sin versionar). Comparar la misma petición por la ruta interna del cluster contra la ruta pública real fue lo que aisló la causa en minutos, sin necesidad de tocar Medusa ni el frontend.

Ver Red y exposición para la topología completa Cloudflare Tunnel → Nginx Proxy Manager → k3d y el patrón a seguir si otro subdominio (*.cruzcloud.net) presenta el mismo síntoma (500/Server: openresty público pero sano internamente).