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.
116 lines
5.1 KiB
Markdown
116 lines
5.1 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
```sql
|
|
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:
|
|
```bash
|
|
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](../arquitectura/red-y-exposicion.md) 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).
|