Contactar →
← Todas las entradas
Operaciones2026.07.01

Dos rutas de deploy, dos artefactos

Hay dos formas de deploy funcionando en esta infraestructura, y durante un tiempo sentí una ligera presión por colapsarlas en una sola. Un sitio estático (este mismo, de hecho, junto con un par de otros) se despliega mediante una sincronización directa de archivos hasta donde sea que se sirva: compilar la salida, hacer rsync al destino, listo. Una aplicación en contenedores se despliega por la ruta más familiar: compilar una imagen, subirla (push) a un registry, y luego hacer pull o ejecutar esa imagen en el destino. Dos formas distintas, dos mecanismos distintos, y las he mantenido así a propósito en lugar de forzar los dos tipos de artefacto por el mismo pipeline.

Por qué existe la presión por unificar

El tirón hacia “tener solo un pipeline de deploy” es real y entiendo de dónde viene. Un solo pipeline significa una sola cosa que mantener, un solo conjunto de credenciales y secretos que gestionar, un solo modelo mental de “cómo ocurre un deploy aquí”. Si todo pasa por build-push-pull, entonces añadir un nuevo sitio estático es solo otra entrada en un sistema que ya conozco, en lugar de un segundo sistema que tengo que mantener por separado en la cabeza. La consistencia tiene un valor genuino, y buena parte de los consejos de plataforma por defecto recomiendan “elige una forma de deploy y haz que todo encaje en ella” precisamente por esa razón.

Consideré esa opción en serio: envolver la salida de compilación del sitio estático en una imagen de contenedor mínima (una base de nginx o Caddy más los archivos compilados), subirla al mismo registry que usa cualquier otra aplicación, y desplegarla con el mismo mecanismo de pull-and-run. Habría funcionado. También habría sido puro overhead sin nada al otro lado de la balanza.

El contenido estático toma la ruta de sincronización directa; las aplicaciones en contenedores toman la ruta del registry.

Por qué un sitio estático no tiene ninguna razón para tocar un registry

Un sitio estático es una carpeta de archivos. No necesita runtime, no necesita supervisión de procesos dentro de un contenedor, no necesita una imagen que cargue con capas de un sistema operativo y un servidor web solo para que un servidor HTTP devuelva HTML y assets que ya estaban completamente compilados en el momento del deploy. Todo el valor que da un contenedor (empaquetar una aplicación junto con su entorno de ejecución para que se comporte igual dondequiera que se ejecute) no aplica cuando no hay comportamiento de aplicación que empaquetar. No hay deriva de dependencias de runtime que resolver, porque no hay runtime. El “entorno” que necesita un sitio estático es “existe un servidor web y puede leer estos archivos”, que es exactamente igual de cierto antes de envolverlo en un contenedor que después.

En cuanto te fijas en eso, hacer pasar la salida estática por un registry de contenedores deja de parecer consistencia y empieza a parecer un rodeo innecesario. Compilar el sitio, luego construir una imagen alrededor del sitio, luego subir esa imagen a algún sitio, luego bajarla de nuevo, luego arrancar un contenedor cuyo único trabajo es servir archivos que ya estaban ahí mismo en la salida de compilación desde el principio. Cada uno de esos pasos extra es algo que puede fallar, algo que consume tiempo, y algo que existe únicamente para satisfacer la forma de un pipeline en lugar de un requisito real del artefacto. La ruta de rsync se salta todo eso: compilar el sitio, copiar los archivos a donde se sirven, listo. Menos piezas móviles, y un deploy que falla en menos sitios porque hay menos sitios donde pueda fallar.

Por qué la ruta de contenedores sigue mereciendo su complejidad para aplicaciones reales

La ruta en contenedores no es complejidad por sí misma: resuelve un problema real que solo existe cuando hay una aplicación real con runtime, dependencias y un comportamiento que necesita ser idéntico en todos los entornos. El build produce una imagen que empaqueta la aplicación con todo lo que necesita para ejecutarse. Subir esa imagen a un registry te da un artefacto único, direccionable y versionado: algo que puedes señalar por tag o por digest y saber exactamente qué se está ejecutando, revertir a una versión anterior volviendo a hacer pull de un tag más antiguo, o ejecutar exactamente lo mismo en varios hosts sin volver a compilar en cada uno. Ese pipeline es también el que, en un momento dado, intentó saltarse el redeploy de componentes que parecían no haber cambiado y acabó, en silencio, sin desplegar nada, parte de por qué ahora se mantiene deliberadamente simple en lugar de listo. Hacer pull y ejecutar en el destino es la parte que realmente necesita semántica de contenedores: aislamiento de procesos, un entorno de ejecución definido, límites de recursos, políticas de reinicio.

Nada de esa maquinaria aporta nada a una carpeta de HTML y CSS ya compilados. No es que la ruta de contenedores esté equivocada: es exactamente la correcta para el artefacto para el que fue diseñada. El desajuste solo aparece cuando intentas forzar un segundo tipo de artefacto, fundamentalmente distinto, por el mismo pipeline porque tener un solo pipeline parecía más ordenado que tener dos.

Lo que cuesta realmente mantenerlas separadas

No creo que esto sea gratis, y merece la pena ser concreto sobre el coste en lugar de fingir que no lo hay. Sí que mantengo dos mecanismos de deploy en lugar de uno. Eso significa dos cosas que recordar cuando algo falla, dos conjuntos de suposiciones sobre qué significa “desplegado” (un conjunto de archivos actualizado en disco frente a un proceso en contenedor reiniciado), y una pequeña cantidad de tooling duplicado: la ruta basada en registry y la ruta basada en rsync no comparten mucha maquinaria, así que las mejoras en una (mejor logging, mejor rollback, mejores notificaciones de fallo) no se trasladan automáticamente a la otra.

En la práctica esto no ha sido un coste pesado, porque el número de formas de deploy que necesito es pequeño (dos, no cinco) y cada una es genuinamente simple por sí sola. La ruta estática es un paso de build y un comando de rsync. La ruta de contenedores es un build, un push y un pull. Ninguna de las dos intenta ser lista ni lo bastante genérica como para absorber el caso de uso de la otra, así que ninguna ha acumulado el tipo de complejidad accidental que viene de un pipeline que intenta serlo todo para todos los artefactos. Si algún día tuviera un tercer tipo de artefacto fundamentalmente distinto (un job por lotes programado, digamos, o algo que necesite ejecutarse en el edge en lugar de en un host fijo) querría pensarlo bien: si es realmente una tercera forma, o si es la ruta de contenedores con un disparador distinto. Pero no lo forzaría por defecto en una de las dos formas existentes solo por evitar escribir un tercer pipeline pequeño.

La forma general de la decisión

La regla a la que he llegado, dicho sin rodeos: hacer que el mecanismo de deploy coincida con lo que realmente es el artefacto, no con lo que haría el tooling más uniforme. Un artefacto de build que es solo archivos quiere un mecanismo que mueva archivos. Un artefacto de build que es una aplicación empaquetada con su runtime quiere un mecanismo diseñado para ejecutar imágenes. Forzar el primero por el pipeline del segundo compra uniformidad y nada más, al coste de pasos extra, puntos de fallo extra, y un artefacto envuelto en maquinaria que nunca necesitó. La uniformidad es una propiedad agradable cuando surge de que dos cosas son genuinamente la misma forma. Es un peor trade cuando se fabrica fingiendo que dos formas distintas son una sola.

El rollback tiene un aspecto distinto en cada ruta, y está bien así

Un lugar donde las dos rutas divergen genuinamente, y donde tuve que dejar de esperar que se sintieran simétricas, es el rollback. En la ruta de contenedores, el rollback es casi gratis: el registry ya guarda la imagen anterior bajo su propio tag o digest, así que revertir es “ejecutar el tag antiguo en lugar del nuevo”, y la misma maquinaria que hizo que el deploy fuera versionado y direccionable hace que el rollback también lo sea, por la misma razón. En la ruta estática, no hay por defecto ningún registry que guarde versiones anteriores: el destino del rsync simplemente tiene lo último que se sincronizó ahí. Si quiero la misma facilidad de rollback para el sitio estático, tengo que construirla por separado: conservar la salida de compilación anterior, o apoyarme en que el origen está en control de versiones y un rollback es “recompilar desde un commit anterior y volver a sincronizar”, lo cual cuesta un ciclo de build en lugar de ser instantáneo.

Consideré tratar esa brecha como una razón para replantearme toda la división: si el registry de la ruta de contenedores da rollback gratis, ¿no es eso una ventaja real que merece el overhead para todos los artefactos, incluidos los estáticos? No lo creo, sobre todo porque el deploy del sitio estático es lo bastante rápido y barato (un build más una sincronización) como para que “recompilar y volver a sincronizar un commit anterior” sea una ruta de rollback medida en el mismo orden de magnitud aproximado de tiempo que la basada en registry, sin necesidad de cargar con infraestructura de registry cuyo único trabajo para este artefacto sería guardar algo que un git checkout ya guarda. Las dos rutas no necesitan historias de rollback idénticas para ser, cada una, una buena historia de rollback para lo que despliega.

La observabilidad es el otro punto donde no encajan

La ruta de contenedores me da un proceso en ejecución que puedo consultar: logs del contenedor, un contador de reinicios, un endpoint de health check, uso de recursos a lo largo del tiempo. La ruta estática, una vez completa la sincronización, es solo archivos servidos por un servidor web que no es específico de este sitio; no hay ningún proceso por deploy que inspeccionar, porque no hay ningún proceso por deploy en absoluto. Lo que realmente monitorizo difiere en consecuencia: para la aplicación en contenedores, me importa si el contenedor está activo, reiniciándose o en mal estado. Para el sitio estático, la señal significativa se acerca más a “¿terminó la sincronización sin error, y el servidor web sigue respondiendo?”, una comprobación más superficial pero suficiente para un artefacto que no tiene ningún comportamiento de runtime propio que pueda ir mal. Intentar acoplar observabilidad al estilo contenedores sobre la ruta estática significaría fabricar un proceso que observar solo para que el tooling de observabilidad tuviera algo familiar a lo que apuntar, que es el mismo tipo de maquinaria innecesaria que la propia ruta de deploy evitó al no envolver el sitio en un contenedor desde el principio.

← Todas las entradas