Post-deploy: rollbacks y observabilidad
Desplegar no es la meta sino el momento en que el riesgo se vuelve real, y la disciplina de post-despliegue es un bucle de cuatro pasos: verificar el despliegue con healthchecks y smoke tests antes de dirigirle tráfico; poder revertir en segundos reapuntando el alias a un despliegue inmutable anterior sin reconstruir nada; observar qué ocurre con telemetría de errores con source maps y de rendimiento real de usuario con Web Vitals; y cerrar el bucle convirtiendo esas señales de producción en guardas del propio pipeline. Un despliegue sin estos cuatro pasos es un acto de fe.
Desplegar no es cruzar la meta, sino el instante en que el riesgo deja de ser hipotético y se vuelve real: el código que hasta entonces solo se había ejecutado en entornos controlados empieza a servir tráfico de verdad. La madurez en producción no consiste en desplegar sin miedo, sino en montar el bucle que hace el miedo innecesario, y ese bucle tiene cuatro movimientos. Verificar que el despliegue está sano antes de confiarle el tráfico, con healthchecks y smoke tests. Poder deshacer en segundos si algo va mal, con un rollback que reapunta a un despliegue inmutable anterior sin reconstruir nada. Observar qué ocurre de verdad, con telemetría de errores y de rendimiento real de usuario. Y cerrar el bucle, devolviendo esas señales de producción al pipeline para que la próxima vez fallen antes. Un despliegue sin estos cuatro pasos es un acto de fe; con ellos, es una operación reversible y medida.
- Verificar un despliegue con healthchecks y smoke tests antes de dirigirle todo el tráfico.
- Revertir en segundos reapuntando el alias a un despliegue inmutable anterior, sin reconstruir.
- Instrumentar telemetría de errores con source maps y de rendimiento real con
Web Vitals. - Cerrar el bucle convirtiendo las señales de producción en guardas del propio pipeline.
Verificar
Healthchecks y smoke tests confirman que el despliegue está sano antes de dirigirle tráfico. Si algo falla, el alias no se mueve.
Revertir
El rollback reapunta el alias a un despliegue inmutable anterior. Deshacer cuesta segundos y ningún build nuevo.
Observar
Telemetría de errores con source maps y Web Vitals de campo dicen qué ocurre de verdad en producción.
Realimentar
Cada señal de producción vuelve al pipeline como guarda, para que el próximo fallo se atrape antes de desplegar.
Verificar antes de confiar
Un despliegue que termina sin error no es un despliegue sano: solo significa que el proceso de subida no falló, no que la app responda bien. Entre ambas cosas hay un abismo que se cruza con healthchecks: un endpoint de salud —/health o /healthz— que la app expone y que responde con éxito solo cuando de verdad puede atender tráfico. Conviene distinguir dos preguntas que suelen mezclarse. La liveness pregunta si el proceso está vivo y hay que reiniciarlo si no; la readiness pregunta si está listo para recibir peticiones ahora mismo, lo cual puede ser falso un rato tras arrancar aunque el proceso viva, mientras calienta cachés o abre conexiones. Dirigir tráfico a algo vivo pero no listo es servir errores con toda la confianza del mundo.
Aquí ayuda separar dos palabras que el habla cotidiana funde: desplegar y liberar. Desplegar es publicar el artefacto y dejarlo accesible en su URL propia; liberar es dirigirle el tráfico de usuarios reales moviendo el alias. La verificación vive justo en la rendija entre ambos: el despliegue ya existe y se puede sondear, pero todavía no es producción, así que puedes ejercitarlo sin arriesgar a nadie. Todo el post-despliegue seguro se apoya en mantener esa rendija abierta el tiempo suficiente para mirar antes de liberar.
Sobre esa base se monta el smoke test post-despliegue: un puñado de peticiones contra la URL recién desplegada —que, por ser inmutable, tiene dirección propia antes de ser producción— que comprueban las rutas críticas antes de reapuntar el alias. Si el smoke test falla, el alias no se mueve y los usuarios siguen viendo la versión anterior, intacta. La forma madura de este patrón es el despliegue gradual o canary: se dirige una fracción del tráfico al despliegue nuevo, se vigila su tasa de error y sus latencias unos minutos, y solo si se mantiene sano se promueve al cien por cien.
El despliegue gradual no es solo prudencia: acota el radio de impacto. Si el despliegue nuevo resulta defectuoso, solo la fracción de tráfico que le dirigiste sufre el fallo, no toda tu base de usuarios, y esa fracción es una palanca que ajustas según lo arriesgado que sea el cambio. Cada etapa del canary es una apuesta pequeña y reversible en lugar de una grande e irreversible.
Un detalle que separa un healthcheck útil de uno decorativo es su profundidad. Un check superficial que solo responde que el proceso atiende peticiones puede dar verde mientras la base de datos está caída; un check profundo comprueba de verdad las dependencias críticas —la conexión a datos, un servicio del que dependes— antes de declararse sano. El equilibrio es fino: demasiado profundo y una dependencia lenta tumba una instancia por lo demás sana; demasiado superficial y mientes sobre tu propia salud.
# smoke test que bloquea la promocion si algo falla
DEPLOY_URL="$(pnpm run deploy --json | jq -r .url)"
curl -fsS "$DEPLOY_URL/healthz" || exit 1
curl -fsS "$DEPLOY_URL/" > /dev/null || exit 1
echo "sano: promoviendo el alias a produccion"
Colapsar las dos comprobaciones en una sola causa dos fallos opuestos. Si tratas la readiness como liveness, provocas un reinicio innecesario cada vez que la app está ocupada un segundo; si tratas la liveness como readiness, mandas tráfico a un proceso que arrancó pero aún no cargó su configuración y sirve errores. La regla es nítida: la liveness solo debe fallar cuando reiniciar ayudaría, y la readiness debe fallar siempre que la instancia no pueda atender bien una petición ahora, aunque esté perfectamente viva. Son dos señales, no una con dos nombres.
Rollback instantáneo: reapuntar, no reconstruir
Aquí es donde el modelo de despliegues inmutables paga su mayor dividendo. Si cada build es un despliegue congelado con su propia URL y producción es solo un alias que apunta a uno de ellos, revertir no es reconstruir la versión anterior ni redesplegarla: es reapuntar el alias al despliegue inmutable previo, que sigue existiendo intacto porque nada lo borró. La operación dura lo que tarda en propagarse un cambio de puntero —segundos— y es segura por construcción, porque el despliegue al que vuelves es exactamente el que ya estuvo sirviendo tráfico sin problemas, bit por bit, no una reconstrucción que podría diferir.
Esto reordena la respuesta a incidentes. La tentación bajo presión es diagnosticar y arreglar hacia adelante, pero el forward-fix es lento e incierto justo cuando no puedes permitirte ninguna de las dos cosas. La disciplina correcta es revertir primero y diagnosticar después: el rollback devuelve el sistema a un estado bueno conocido en segundos, y solo entonces, sin usuarios afectados y sin el reloj corriendo, investigas la causa. El tiempo medio de recuperación deja de depender de lo rápido que encuentres el bug y pasa a depender solo de lo rápido que muevas el puntero.
Conviene no confundir este rollback con un git revert. Revertir el commit en el repositorio es una corrección hacia adelante disfrazada: genera un cambio nuevo que hay que construir y desplegar como cualquier otro, con su propio riesgo y su propia espera. El rollback del que hablamos no toca el repositorio en el momento del incidente; solo reapunta el alias a un artefacto que ya existe y ya funcionaba. El git revert viene después, con calma, para que el historial refleje la decisión.
Y hay una consecuencia cultural en que revertir sea tan barato: habilita desplegar seguido. Cuando deshacer un despliegue cuesta segundos y ningún riesgo, el coste esperado de un despliegue fallido se desploma, y con él la aversión a desplegar. Los equipos que publican muchas veces al día no son más valientes, sino que han hecho el error tan reversible que desplegar deja de dar miedo. La frecuencia de despliegue y la facilidad de rollback son, en el fondo, la misma propiedad vista desde dos lados.
Esto descansa en una condición silenciosa: que los despliegues antiguos sigan existiendo. Un rollback solo alcanza tan atrás como llegue la retención de artefactos inmutables que guarda tu plataforma; si se podan demasiado pronto, el puntero no tiene a dónde volver. Por eso conservar un historial razonable de despliegues no es acumular basura, sino mantener disponible el conjunto de estados buenos conocidos a los que podrías necesitar regresar en segundos.
flowchart TD N[despliegue N nuevo] --> HC[healthcheck y smoke] HC -->|verde| AL[alias de produccion apunta a N] HC -->|rojo| STOP[el alias no se mueve] AL --> ERR[telemetria detecta un pico de errores] ERR --> RB[reapunta el alias a N-1] RB --> OK[N-1 intacto vuelve a servir en segundos] style AL fill:#89b4fa,color:#11111b style RB fill:#f38ba8,color:#11111b style OK fill:#a6e3a1,color:#11111b
El rollback instantáneo es cierto para el código y falso para el estado. Si el despliegue nuevo aplicó una migración que borró una columna, volver el puntero al despliegue anterior no la resucita, y ahora tienes código viejo contra un esquema nuevo. La disciplina que lo evita es expand and contract: primero despliegas migraciones solo aditivas y compatibles hacia atrás, de modo que el código viejo y el nuevo funcionen ambos contra el esquema; solo después de que el código nuevo lleve tiempo estable retiras lo viejo, en un despliegue aparte. Así el rollback del código nunca se topa con un esquema que ya no entiende.
Observabilidad: errores y rendimiento
Un despliegue reversible y verificado sigue siendo ciego si no ves qué le pasa en producción. La observabilidad de frontend descansa en dos pilares que responden preguntas distintas. El primero es la telemetría de errores: capturar las excepciones que ocurren en el navegador y en el servidor y agregarlas por versión. El detalle que la hace útil de verdad es subir los source maps en el pipeline; sin ellos, un error llega como una traza sobre código minificado ilegible; con ellos, la plataforma reescribe la traza a tu código fuente original, con archivo y línea reales. Etiquetar cada despliegue con una versión —un release— es lo que permite decir “los errores empezaron con este despliegue” en lugar de mirar una gráfica plana sin fronteras.
El segundo pilar es el rendimiento real de usuario, o RUM, y no hay que confundirlo con las mediciones de laboratorio. Un Lighthouse en CI mide una carga sintética en una máquina controlada; el RUM mide las Core Web Vitals —LCP, INP, CLS— tal como las viven usuarios reales, con sus redes lentas y sus dispositivos modestos. Solo el campo te dice la verdad sobre la experiencia; el laboratorio te da una señal temprana y repetible, pero no sustituye al campo. En el servidor, el equivalente es el tracing distribuido con OpenTelemetry y los logs estructurados, que convierten un “va lento” en una traza donde se ve exactamente qué tramo consumió el tiempo.
La distinción entre laboratorio y campo no es académica: gobierna dónde pones cada control. El laboratorio, barato y determinista, va en el pipeline como guarda temprana que rechaza una regresión antes de que llegue a nadie; el campo, que solo existe tras desplegar, va en producción como el juez final capaz de disparar una alerta o un rollback. Ninguno sustituye al otro, y confundir sus papeles deja un agujero por el que se cuelan justo los problemas que más importan.
| Medición | Cuándo | Dónde corre | Qué captura |
|---|---|---|---|
| Laboratorio con Lighthouse | en CI, antes de desplegar | máquina controlada | señal temprana y repetible |
| Campo con RUM | en producción, tras desplegar | dispositivos reales | la verdad sobre la experiencia |
En el lado del servidor esa observabilidad se apoya en tres señales que se complementan: las métricas agregan el comportamiento en números baratos de almacenar y graficar, las trazas siguen una petición concreta a través de cada servicio que toca y revelan dónde se fue el tiempo, y los logs estructurados guardan el detalle de cada evento como datos consultables en lugar de texto suelto. Emitirlas con un estándar común es lo que te deja cambiar de proveedor sin reinstrumentar la app.
Con las señales en su sitio, el último cuidado es alertar sobre síntomas, no sobre causas: una alerta debe dispararse cuando el usuario sufre —la tasa de error sube, la latencia se degrada, un Web Vital se sale de rango— y no por cada fluctuación interna sin efecto visible. Una alerta que no corresponde a un dolor real erosiona la confianza en todas las demás, y una guardia sepultada bajo ruido termina ignorando justo la que importaba.
# subir source maps al publicar, para trazas legibles en produccion
- run: pnpm build
- run: pnpm dlx @sentry/cli sourcemaps upload --release "$GITHUB_SHA" ./dist
// reportar Web Vitals de campo al endpoint de telemetria
import { onLCP, onINP, onCLS } from 'web-vitals';
const send = (m) => navigator.sendBeacon('/rum', JSON.stringify(m));
onLCP(send); onINP(send); onCLS(send);
Capturar la excepción es solo la mitad del trabajo; reproducirla es la otra. Las plataformas modernas adjuntan a cada error el contexto que lo hace accionable: la versión desplegada, la ruta, el navegador, los pasos previos del usuario e incluso una repetición de la sesión que lo provocó. Ese contexto es la diferencia entre un ticket que dice que algo falló y uno que te lleva directo a la línea y al estado que rompió. Invierte en el contexto tanto como en la captura, porque un error que no puedes reproducir consume más tiempo que diez que sí.
Una gráfica de errores sin fronteras de release es casi inservible: ves que algo subió, pero no contra qué cambio correlacionarlo. Si cada despliegue emite su identificador —el SHA del commit sirve— tanto a la telemetría de errores como al RUM, cada métrica queda anclada a una versión concreta, y un pico se lee al instante como “empezó aquí”. Esa correlación entre una regresión y el despliegue que la introdujo es lo que convierte la observabilidad en accionable, y es también el gancho por el que un rollback automático puede dispararse solo cuando una métrica cruza su umbral.
Cerrar el bucle
Los tres pasos anteriores todavía dejan a producción como un destino: el sitio donde el código termina su viaje. El paso que falta convierte producción en una fuente, la que alimenta de vuelta al pipeline. Cerrar el bucle es tomar las señales que produce la observabilidad y volverlas guardas que actúan antes, más cerca del origen: un presupuesto de tamaño de bundle que rompe el CI cuando una dependencia lo infla; una regresión de INP medida en campo que bloquea la promoción del siguiente despliegue; un pico de tasa de error que, cruzado el umbral, dispara un rollback automático sin esperar a que un humano despierte.
Esto es lo que une la entrega y la operación en un solo sistema con realimentación. Los SLO y su presupuesto de error dan el lenguaje: defines qué nivel de fallo es tolerable, mides cuánto de ese presupuesto consumes, y ese consumo gobierna el ritmo —si te lo estás gastando, frenas los despliegues arriesgados; si te sobra, aceleras—. El bucle completo es dev, deploy, observe y de vuelta a dev, y su virtud es que cada vuelta hace la siguiente más segura, porque cada incidente que observas en producción se traduce en una guarda que lo atrapará antes la próxima vez. Producción deja de ser el final de la tubería y pasa a ser el sensor que la calibra.
Fíjate en que este bucle es lo que da sentido operativo a los SLO: un objetivo de nivel de servicio no es un número para un informe, sino el umbral que gobierna el ritmo. Cuando la tasa a la que consumes tu presupuesto de error —tu burn rate— se dispara, el bucle reacciona frenando los despliegues arriesgados y volcando el esfuerzo en estabilizar; cuando el presupuesto sobra, el mismo bucle autoriza a arriesgar más y a moverse rápido. El presupuesto de error convierte la fiabilidad de una aspiración vaga en un recurso medible que se gasta y se repone.
Un incidente que se resuelve y se olvida está condenado a repetirse; el que cierra el bucle deja tras de sí una guarda nueva —un test, un presupuesto, una alerta, una comprobación en el smoke test— que lo volvería imposible o lo atraparía antes. Esa es la forma operativa de que el sistema aprenda: la salida de cada postmortem no es solo la causa raíz, sino la modificación concreta del pipeline que impide su reaparición. Con el tiempo, el conjunto de guardas acumuladas es la memoria endurecida de todo lo que alguna vez salió mal.
El cambio de mentalidad que ordena todo lo demás es dejar de ver el despliegue como un evento —un instante en el que el código “sale”, con un antes y un después— y empezar a verlo como la entrada a un bucle de control, un ciclo que no se cierra hasta que producción te ha dicho si el cambio fue bueno. Bajo la vista de evento, todo el esfuerzo se concentra en el momento de publicar y lo que viene después es celebración o pánico; bajo la vista de bucle, publicar es apenas el punto donde empiezas a medir, y el trabajo real es el ciclo que verifica, observa y realimenta. Las cuatro piezas de esta lección no son una lista de buenas prácticas sueltas, sino los cuatro tramos de ese único bucle, y cada una habilita a la siguiente. La verificación —healthchecks y smoke tests— es la guarda de entrada que impide que un despliegue enfermo llegue a los usuarios. La reversibilidad —el rollback instantáneo— es lo que hace que equivocarse sea barato, y ese abaratamiento no es un lujo operativo sino la condición que permite desplegar seguido sin temeridad, porque un error que se deshace en segundos deja de ser una catástrofe y pasa a ser un dato. La observabilidad es el sentido que cierra el arco perceptivo: sin ella el bucle queda abierto, disparas cambios a un sistema del que no recibes lectura, que es la definición misma de volar a ciegas. Y la realimentación al pipeline es lo que hace del bucle un mecanismo que aprende en vez de uno que solo repite, porque convierte cada dolor de producción en una guarda que lo previene la próxima vez. Fíjate en que la inmutabilidad que apareció con las previews es la misma que hace trivial el rollback aquí: cuando los despliegues son objetos congelados y producción es un puntero, deshacer es mover el puntero a un objeto que nunca dejó de existir, y esa única propiedad sostiene la mitad del bucle. La conclusión que reordena tu forma de operar es que la fiabilidad no se consigue desplegando con más cuidado, sino construyendo el bucle que hace que el cuidado sobre: la meta no es no equivocarse nunca, meta imposible, sino que equivocarse sea detectable en segundos y reversible en segundos. Y hay una última inversión que interiorizar: producción no es el vertedero donde el código acaba, es la única fuente de verdad sobre si tu cambio sirvió. El laboratorio predice, el pipeline verifica, pero solo el campo sentencia; cerrar el bucle es tomar esa sentencia en serio y dejar que gobierne la próxima vuelta.
- Expón un endpoint
/healthzque distinga readiness de liveness y añade un smoke test en CI que bloquee la promoción del alias si una ruta crítica falla. - Provoca un despliegue malo a propósito y revierte reapuntando el alias al despliegue anterior; cronometra el tiempo de recuperación y compáralo con reconstruir desde cero.
- Sube los source maps en el pipeline, lanza un error de prueba en producción y confirma que la traza aparece sobre tu código fuente y no sobre el minificado.
- Instrumenta
Web Vitalsde campo enviandoLCP,INPyCLSa un endpoint, y compara los percentiles reales con lo que decía tuLighthousede laboratorio. - Define un presupuesto de bundle o una alerta de tasa de error que actúe como guarda: que rompa el CI o dispare un rollback, cerrando el bucle de vuelta al pipeline.