Estado: Completado

Publicar una web estática con rollback antes de tocar producción

Cómo preparar, validar y sustituir una release estática manteniendo la versión anterior disponible para volver atrás si el deployment falla.

Etiquetas: deployment · producción · arquitectura · rollback

¿Podemos sustituir una web en producción haciendo que el cambio sea pequeño, verificable y fácil de deshacer?

STATUS       COMPLETED
AREA         DEPLOYMENT / OPERACIÓN
ARTEFACTO    SITIO ESTÁTICO
SERVIDOR     APACHE
ESTRATEGIA   STAGING + REEMPLAZO + ROLLBACK
RESULTADO    DEPLOYMENT VALIDADO

La pregunta

Publicar una web estática puede parecer una operación casi trivial. Construyes el proyecto, copias los archivos al servidor y compruebas que la página carga.

Precisamente por ser sencilla, resulta tentador hacerla así.

El problema es que “copiar los archivos nuevos encima de los anteriores” mezcla demasiadas operaciones en un único momento. Mientras la copia está en curso, producción puede contener una combinación de versiones. Si después descubrimos un problema, tenemos que reconstruir qué había antes. Y si además hacemos el build directamente en el servidor, introducimos dependencias y pasos que no son necesarios para servir el resultado final.

Para la primera publicación de esta web quería una propiedad por encima de todas las demás: que el cambio pudiera deshacerse de manera sencilla.

Eso llevó a separar el deployment en varias fases.

CONSTRUIR
↓
VALIDAR
↓
EMPAQUETAR
↓
PREPARAR NUEVA VERSIÓN
↓
VALIDAR SERVIDOR
↓
PRESERVAR VERSIÓN ACTUAL
↓
CAMBIAR
↓
COMPROBAR DESDE FUERA

El objetivo no era diseñar un sistema sofisticado de despliegue. Era conseguir que cada paso tuviera una función clara y que el momento realmente irreversible fuese lo más pequeño posible.

Producción no construye la web

El proyecto utiliza Astro para generar el sitio.

Durante el desarrollo necesito Node, npm, Astro y las dependencias del proyecto. Todo eso sirve para convertir el código fuente y los contenidos en el resultado que finalmente verá el navegador.

Después del build, sin embargo, el producto es mucho más sencillo:

HTML
CSS
JavaScript
imágenes
otros archivos estáticos

El servidor web no necesita saber cómo se generaron.

Esta separación condicionó todo el deployment. El build se realiza en el entorno de desarrollo y producción recibe únicamente el artefacto terminado.

DESARROLLO

código fuente
+
dependencias
+
Astro
+
herramientas
        ↓
      BUILD
        ↓
ARTEFACTO ESTÁTICO
        ↓
    PRODUCCIÓN
        ↓
      Apache

Eso evita instalar en producción herramientas que solo son necesarias para construir el proyecto y, al mismo tiempo, permite validar exactamente el mismo conjunto de archivos que después se publicará.

Antes de desplegar, validar el artefacto

Que el comando de build termine correctamente no significa automáticamente que el artefacto contenga todo lo necesario para producción.

En este caso había unos mínimos especialmente fáciles de comprobar: debía existir la página principal, tenía que haberse generado la página de error y debía viajar también la configuración específica que necesitaba el servidor web.

La idea era sencilla:

build terminado
≠
artefacto validado

Antes de empaquetar la release comprobé explícitamente que esos elementos existían.

Esto no sustituye a las pruebas de la aplicación ni demuestra que cada página esté perfecta. Simplemente añade una barrera muy barata contra una clase de errores igualmente sencilla: publicar un artefacto incompleto.

Si sabemos que determinados archivos son imprescindibles, comprobar que existen cuesta muy poco.

El servidor recibe una release, no una sucesión de archivos

Una vez validado el resultado, lo empaqueté como una unidad.

Podría haber transferido directamente el contenido generado al directorio servido por Apache, pero eso habría hecho que producción cambiara mientras los archivos seguían llegando.

Preferí separar ambas cosas.

La nueva versión se transfería y extraía en un directorio distinto al que estaba sirviendo la web:

SITIO ACTUAL
→ continúa atendiendo peticiones

NUEVA RELEASE
→ se transfiere
→ se extrae
→ se prepara
→ todavía no recibe tráfico

Esto crea una pequeña zona de staging dentro del propio servidor.

El término puede sonar más sofisticado de lo que realmente es. En este caso significa simplemente que los archivos nuevos pueden prepararse completamente antes de sustituir a los anteriores.

Durante toda esa fase la web existente continúa intacta.

Validar antes del corte

Con la nueva versión ya preparada, todavía no había ningún motivo para publicarla inmediatamente.

Antes podía comprobar que los permisos de archivos y directorios eran adecuados y que la configuración del servidor seguía siendo válida.

Apache dispone de una comprobación de configuración que permite detectar errores antes de que tengan consecuencias sobre el servicio.

El orden importa:

preparar nueva versión
↓
validar
↓
cambiar producción

en lugar de:

cambiar producción
↓
descubrir si era válida

Parece una diferencia obvia expresada así, pero muchos despliegues manuales mezclan ambos momentos.

Siempre que una comprobación pueda hacerse antes del corte, prefiero hacerla antes.

El rollback se prepara antes del deployment

Este fue probablemente el criterio más importante de todo el proceso.

No quería publicar primero y pensar después qué hacer si algo salía mal.

Antes de sustituir la web conservé la versión que estaba funcionando. Además mantuve una segunda copia empaquetada como protección adicional.

Eso dejaba dos niveles sencillos de recuperación:

VERSIÓN ACTUAL
↓
directorio anterior conservado
+
copia empaquetada adicional

El primer mecanismo estaba pensado para un rollback rápido.

El segundo proporcionaba una copia independiente por si durante las operaciones posteriores se modificaba accidentalmente el directorio anterior.

No era una estrategia de backup completa del servidor ni pretendía serlo. Era una protección específica para el cambio que estaba a punto de realizar.

Y, sobre todo, existía antes de necesitarla.

El momento del cambio debe ser pequeño

Después de preparar la nueva release, validarla y conservar la anterior, llegaba el único paso que realmente modificaba lo que estaba sirviendo Apache.

En lugar de copiar archivos individualmente sobre la web activa, el cambio consistía esencialmente en sustituir un directorio por otro.

Conceptualmente:

web actual
→ conservar como versión anterior

nueva release
→ pasa a ocupar su lugar

El DocumentRoot del servidor no tenía que cambiar. Apache seguía sirviendo el mismo lugar lógico; lo que cambiaba era el contenido que había detrás.

Eso reducía mucho el número de operaciones realizadas durante el corte.

La mayor parte del trabajo ya había ocurrido antes.

Por qué no copiar encima

Comparar ambos enfoques ayuda a entender la diferencia.

Una copia directa sobre producción puede producir algo parecido a esto:

archivo 1 nuevo
archivo 2 nuevo
archivo 3 antiguo
archivo 4 antiguo
archivo 5 nuevo
...

Durante un intervalo, aunque sea corto, el sitio puede encontrarse en un estado que nunca existió realmente como release.

Además, si después queremos volver atrás, necesitamos saber exactamente qué archivos fueron sustituidos, cuáles se añadieron y cuáles deberían eliminarse.

Con releases separadas, la unidad de cambio deja de ser el archivo.

Pasa a ser la versión completa.

RELEASE A
→ conjunto coherente

RELEASE B
→ conjunto coherente

Cambiar entre ellas resulta bastante más fácil de razonar.

El rollback también debe ser pequeño

La misma idea funciona en sentido contrario.

Si después del deployment aparecía un problema grave, no necesitaba reconstruir la versión anterior, recuperar archivos uno por uno ni volver a ejecutar el build.

La versión previa seguía disponible.

El rollback conceptual era simplemente:

release nueva
→ retirar

release anterior
→ recuperar su lugar

Eso no convierte el deployment en infalible.

Lo convierte en reversible.

Y esa diferencia cambia bastante la forma de afrontar un cambio en producción. Un fallo sigue siendo un problema, pero deja de obligarnos a improvisar el mecanismo de recuperación en el peor momento posible.

Después del cambio todavía falta verificar

Una sustitución correcta de archivos tampoco demuestra por sí sola que el deployment haya terminado bien.

El último paso fue comprobar la web desde fuera del servidor.

Ahí ya no interesa únicamente saber que los archivos existen o que Apache acepta su configuración. Interesa observar el sistema de la forma más parecida posible a como lo hará un usuario.

La secuencia completa queda entonces:

BUILD
↓
validación del artefacto
↓
empaquetado
↓
transferencia
↓
staging
↓
validación del servidor
↓
protección de la versión anterior
↓
sustitución
↓
QA externa

Solo después de esa última comprobación consideré terminado el deployment.

Qué protegía realmente este procedimiento

El proceso no intentaba cubrir todos los fallos imaginables.

No protegía frente a una caída completa del servidor, un problema externo de red o un error en infraestructura ajena al propio despliegue.

Sí intentaba controlar los riesgos que introducía este cambio concreto.

En particular:

Es un ámbito mucho más pequeño que “hacer producción segura”.

Y precisamente por eso resulta manejable.

Lo que no hizo falta

Una parte interesante del ejercicio fue todo aquello que no fue necesario añadir.

No hizo falta ejecutar Astro en producción. Tampoco mantener un proceso Node, un gestor de procesos para la aplicación ni un sistema de despliegue complejo.

El servidor tenía una responsabilidad muy sencilla: servir archivos estáticos.

El procedimiento de publicación podía respetar esa sencillez.

Esto no significa que todas las aplicaciones puedan desplegarse así. Una aplicación con estado, migraciones de base de datos, procesos en segundo plano o múltiples servicios necesita resolver problemas bastante más difíciles.

Pero tampoco tiene sentido importar esas soluciones a un sistema que no tiene esos problemas.

La complejidad del deployment debería responder a la complejidad real del producto, no a la lista de herramientas que podríamos utilizar.

Qué cambiaría si el proyecto creciera

Este procedimiento es deliberadamente pequeño y manual.

Si la frecuencia de despliegues aumentara, si intervinieran varias personas o si el sitio adquiriera componentes dinámicos, tendría sentido automatizar parte del flujo. Por ejemplo, podría generarse la release en integración continua, añadir verificaciones automáticas, mantener un historial formal de versiones o convertir la activación y el rollback en operaciones gestionadas.

Pero automatizarlo no debería cambiar sus propiedades fundamentales:

construir antes
validar antes
preservar antes
cambiar poco
verificar después
poder volver atrás

La automatización debería ejecutar mejor el procedimiento, no ocultar qué procedimiento estamos ejecutando.

Resultado

La publicación se realizó siguiendo esa secuencia y la nueva versión quedó operativa sin necesidad de convertir el servidor en un entorno de desarrollo. La versión anterior permaneció disponible como punto inmediato de retorno y se conservó además una copia independiente de respaldo.

Lo importante del experimento no fue demostrar una técnica novedosa de deployment. Fue comprobar que incluso un despliegue manual y pequeño puede diseñarse alrededor de propiedades bastante sólidas.

Validar antes de cambiar.

Conservar antes de sustituir.

Hacer pequeño el momento del corte.

Comprobar desde fuera.

Y decidir cómo volver atrás antes de descubrir que hace falta.

Para una web estática, no necesitaba mucho más.

Pero sí necesitaba todo eso.