Saltar a contenido

ADR 0011 — Ambiente de Staging para el storefront

Estado: aceptada — desplegado y verificado en vivo Fecha: 2026-09-14

Contexto

Hasta este punto, todo cambio de frontend se probaba directamente contra shop.cruzcloud.net (producción): el mismo push a main que disparaba el build de imagen también actualizaba el manifiesto real que sirve tráfico. Cada iteración de UI (rediseño de la página de producto, zoom estilo Amazon, ajustes mobile) implicaba validar en vivo sobre el sitio real, sin una red de contención — un cambio roto llegaba directo al usuario final antes de poder revertirlo.

El usuario pidió explícitamente un ambiente de Staging para dejar de probar en producción ("Me interesa crear el ambiente Staging para no estar probando en Prod").

Opciones consideradas

Opción A favor En contra
Namespace + Ingress propios, backend compartido con Prod — elegida Cero costo adicional de Postgres/Medusa/Meilisearch en un NAS ya ajustado de recursos; rápido de armar reusando el mismo patrón workload+governance de ecommerce Staging y Prod comparten datos reales (catálogo, inventario, órdenes) — no aísla pruebas que muten datos de negocio
Backend propio para Staging (Postgres/Medusa/Meilisearch duplicados) Aislamiento completo, se podrían probar migraciones de datos sin riesgo Duplica el consumo de recursos de la NAS sin necesidad todavía (decisión explícita del usuario de descartar esto por ahora)
Feature flags / preview deploys por PR en vez de un ambiente fijo Sin infraestructura nueva que mantener Gitea Actions en este lab no tiene un mecanismo de preview-per-branch ya armado; más esfuerzo de tooling que un namespace nuevo

Decisión

Namespace ecommerce-staging nuevo, con su propio Ingress (staging.cruzcloud.net) y su propio Deployment de frontend (misma imagen Docker que Prod), pero apuntando al mismo backend Medusa que producción (medusa-svc.ecommerce.svc.cluster.local:9000 — FQDN cross-namespace, un nombre corto solo resuelve dentro del mismo namespace). Flujo de promoción: todo push a main despliega a Staging automáticamente; promover a Producción es una acción manual separada (scripts/promote-to-production.sh).

Qué se armó

  • apps-registry/workloads/ecommerce-staging/ — overlay Kustomize: 1 réplica (no 2 como Prod — ambiente de verificación, no de tráfico real), requests/limits más chicos, mismo securityContext que Prod (mismas restricciones del Dockerfile: runAsUser numérico, NET_BIND_SERVICE, readOnlyRootFilesystem).
  • apps-registry/apps/ecommerce-staging-app.yaml + ecommerce-staging-governance-app.yaml — Applications de Argo CD (workload + governance separadas, misma convención que el resto del lab).
  • platform-infra/ecommerce-staging-governance/ — ResourceQuota, LimitRange y NetworkPolicy propios del namespace nuevo.
  • .gitea/workflows/build.yaml — el paso de CD (Actualizar Manifiesto GitOps) ahora apunta a workloads/ecommerce-staging/frontend/patches/deployment.yaml en vez del manifiesto de Prod: cada push a main bumpea el tag de imagen (y, desde el mismo día, la variable APP_VERSION — ver nota de implementación) de Staging automáticamente.
  • scripts/promote-to-production.sh — lee el tag actualmente desplegado en Staging, pide confirmación explícita, y lo aplica al manifiesto real de Producción (workloads/ecommerce/frontend/patches/deployment.yaml). Nunca corre automáticamente: es la única vía para que un cambio llegue a shop.cruzcloud.net.
  • Secrets commerce-storefront y gitea-registry-secret copiados (no regenerados) desde el namespace ecommerce hacia ecommerce-staging, para que el frontend de Staging tenga las mismas credenciales reales contra el backend compartido.

Descubrimiento de red real (no solo Kubernetes)

Confirmado en vivo el mismo día: aunque Cloudflare tiene un wildcard *.cruzcloud.net, eso no alcanza para exponer un Ingress nuevo. La cadena real es Cloudflare → túnel cloudflared → Nginx Proxy Manager → k3d-lab-cluster-serverlb (puerto 90) → Traefik → Service. Cada subdominio que ya funciona (shop, commerce, media, etc.) tiene su propia fila de Proxy Host en NPM apuntando a 192.168.68.61:90. Un Ingress nuevo sin esa fila devuelve la página por defecto de NPM aunque Traefik ya esté enrutando bien internamente — verificado con curl -H "Host: staging.cruzcloud.net" http://192.168.68.61:90/ (funcionaba) contra el dominio público (seguía devolviendo la página por defecto hasta que se agregó la fila en NPM). Ver Red y exposición. Esta fila de NPM la agregó el usuario manualmente — no hay credenciales de ese panel disponibles para automatizarlo.

El footer mostraba un string fijo (v4.1), inútil para distinguir a simple vista si Staging y Producción están en la misma versión. Se agregó una variable de entorno APP_VERSION (leída en footer.tsx vía process.env.APP_VERSION, sin necesidad de prefijo NEXT_PUBLIC_ por ser un Server Component) a ambos manifiestos de Deployment; el mismo paso del CD que bumpea el tag de imagen ahora también bumpea APP_VERSION (vía perl -0777 para una sustitución multilínea, ya que el nombre y el valor de la variable viven en líneas distintas del YAML — un sed de una sola línea no alcanza). promote-to-production.sh hace el mismo bump al promover. Verificado en vivo con Playwright: tras el primer push, Staging mostró v1.0.263 mientras Producción seguía en v4.1 (la imagen vieja no leía la variable); tras promover, ambos ambientes quedaron visualmente distinguibles por versión real en su footer.

Consecuencias

  • Ganado: ningún cambio de frontend vuelve a validarse por primera vez contra tráfico real; Staging y Producción ahora se distinguen a simple vista por la versión mostrada en el footer.
  • Deuda aceptada: Staging comparte datos reales de negocio con Producción (mismo Medusa/Postgres) — una prueba que cree o modifique órdenes/inventario en Staging afecta datos reales. Si esto se vuelve un problema, la opción descartada de backend propio para Staging queda como camino de escalamiento.
  • Para revertir: apuntar de nuevo MANIFEST_FILE/KUSTOMIZE_DIR en build.yaml hacia workloads/ecommerce/... y eliminar el namespace ecommerce-staging — el namespace de Producción no cambia de forma en ningún momento de este ADR.