#Automatizar el razonamiento que ya entiendes
Una entrega automática no debería introducir magia nueva. Debe ejecutar la secuencia que ya has comprobado manualmente: validar configuración, construir una imagen, arrancarla con sus dependencias, comprobar comportamiento, publicar el artefacto y desplegar esa versión exacta.
Integración continua, o CI, significa ejecutar verificaciones cuando cambia el código. Entrega continua prepara artefactos listos para desplegar; que se publiquen automáticamente a producción es una decisión adicional. Puedes automatizar construcción y pruebas y mantener una decisión humana antes de reemplazar el servicio público.
#Qué comprobar antes de publicar una imagen
Hay errores que aparecen en capas diferentes. Probar solo las funciones de Python no revela que falte una biblioteca del sistema en la imagen. Probar solo docker build no revela que la API no pueda autenticar con PostgreSQL.
| Verificación | Detecta | No demuestra |
|---|---|---|
| Pruebas de lógica | Reglas y casos de aplicación | Red, permisos de archivos o imagen completa |
| Construcción | Receta ejecutable y dependencias disponibles | Que el comando de arranque funcione |
compose config --quiet |
YAML e interpolación válidos | Semántica de consultas o permisos reales |
| Prueba entre servicios | Arranque, DNS, credenciales y una operación útil | Comportamiento bajo cualquier carga |
| Ensayo de fallo/restore | Recuperación de los fallos ensayados | Ausencia de fallos desconocidos |
Un test útil tiene un resultado esperado observable. Para un catálogo: sin token no se puede escribir; con token aparece un identificador; al recrear API el dato permanece; una base caída genera 503 dentro de un tiempo limitado; restaurar el dump recupera el dato. Estas pruebas comprueban contratos diferentes.
#Un guion de CI independiente del proveedor
Este es un esqueleto para el proyecto del capítulo siguiente, una vez existan sus archivos y secretos temporales. No lo ejecutes sobre el proyecto de producción. El nombre y puerto de prueba deben ser exclusivos del trabajo de CI.
#!/usr/bin/env bash
set -euo pipefail
export COMPOSE_PROJECT_NAME="catalogo-ci-${CI_RUN_ID:?Define un identificador único}"
export HTTP_PORT=18080
cleanup() {
docker compose down -v --remove-orphans
}
trap cleanup EXIT
docker compose config --quiet
docker compose build api
docker compose up -d --wait --wait-timeout 120
curl --fail --max-time 10 http://127.0.0.1:18080/health/ready
set -e termina ante un comando fallido en los casos normales del shell; -u detecta variables no definidas; pipefail propaga errores de tuberías. Ninguna de esas opciones sustituye a comprobar resultados cuando una herramienta devuelve éxito con un contenido erróneo.
trap … EXIT ejecuta limpieza al salir del script. Aquí down -v es deliberado porque los datos pertenecen a una prueba desechable, con un proyecto exclusivo. No copies ese comando a un deploy. CI_RUN_ID debe contener caracteres válidos para el nombre de proyecto; en un proveedor concreto puedes construirlo con su identificador numérico. Si varias pruebas comparten host, el puerto 18080 también debe variar o la entrada debe probarse desde una red aislada sin puerto fijo.
Después de readiness, añade las peticiones de lectura/escritura del proyecto y comprueba su JSON. Ante fallo, recoge compose logs y estado antes de la limpieza, ocultando secretos. No conserves dumps con datos reales como artefactos de un pipeline público.
#La imagen probada debe ser la imagen desplegada
Una mala secuencia prueba una imagen, vuelve a construir otra usando una etiqueta base mutable y despliega la segunda. Aunque el código no cambie, el contenido puede hacerlo. Construye una vez, prueba ese artefacto y publica o promueve ese mismo contenido.
Un registro puede almacenar caché de BuildKit para acelerar CI. Una caché es reutilización de resultados, no una fuente de verdad para versiones. Mantén separados el identificador del artefacto y la política de caché. Si un cambio de dependencias no invalida un paso que debería, revisa qué archivos entran en su clave; no ocultes el problema haciendo siempre construcciones lentas a ciegas.
Conserva, por entrega, revisión de código, digest de imagen, configuración no secreta aplicada, versión de esquema y resultado de verificación. Esto convierte "desde ayer falla" en una comparación concreta.
#Arquitecturas: portátil y servidor pueden diferir
Una imagen local arm64 puede no ejecutarse en un servidor amd64. Buildx permite declarar plataformas:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t registro.ejemplo/equipo/catalogo:revision-abc123 \
--push .
Es un ejemplo de publicación a tu registro configurado. Algunas construcciones necesitan emulación o constructores nativos y pueden tardar más. Publicar ambas plataformas no demuestra que ambas funcionen: prueba las variantes que vas a desplegar. Para un único servidor conocido, construir y verificar solo su plataforma puede ser la decisión más sencilla.
#Escalar exige encontrar el cuello de botella
Añadir más contenedores ayuda cuando el servicio puede repartir trabajo y el límite está en él. Si todos esperan una consulta lenta de base de datos, multiplicar APIs puede multiplicar conexiones y empeorar la base.
Empieza midiendo latencia, concurrencia, CPU, memoria y tiempo de consultas. Optimiza el trabajo más caro, añade límites de concurrencia y define cuántas conexiones puede sostener la base. Una API sin estado local puede replicarse con mayor facilidad que una que guarda sesiones o archivos solo en su memoria/disco interno.
En Compose puedes crear varias instancias con docker compose up -d --scale api=2, si la definición lo permite. Para que sea útil:
- No uses
container_namefijo ni un puerto de host fijo por cada instancia. - El proxy debe descubrir y repartir tráfico entre las instancias; un único upstream estático no prueba que ya lo haga correctamente.
- Las sesiones o datos compartidos deben estar en un almacenamiento accesible con consistencia adecuada.
- El presupuesto total de conexiones y memoria debe incluir todas las réplicas y workers.
Si quieres un ejemplo explícito y fácil de inspeccionar, define temporalmente api_a y api_b con la misma imagen y un proxy con ambos destinos. Ganas claridad a costa de duplicación. Para operaciones frecuentes, automatiza descubrimiento o utiliza una plataforma que lo gestione.
#Cuándo separar un worker
Una petición HTTP tiene un presupuesto de espera. Generar un informe pesado puede excederlo. En ese caso puedes guardar un trabajo en una cola, devolver un identificador y dejar que un worker lo procese. El worker suele usar la misma imagen de aplicación con otro comando; no necesita ser otra tecnología por obligación.
Separarlo permite limitar concurrencia y recursos por tipo de tarea. A cambio, introduces estado del trabajo, reintentos, duplicados, errores y observación del progreso. La cola no convierte una operación en exactamente una vez. Diseña identificadores estables y efectos repetibles antes de aumentar trabajadores.
No añadas Redis o una cola solo para que el diagrama tenga más cajas. Si una tarea tarda cien milisegundos y su fallo puede comunicarse directamente, hacerla síncrona puede ser más mantenible. Si ya necesitas tareas durables, consulta los fundamentos de Redis y Asincronía cuando esos recorridos correspondan a tu diseño.
#Un host, varios hosts y el coste de cada salto
En un servidor, Compose permite operar varios servicios con una complejidad controlada. Aumentar RAM/CPU o mover la base a un servicio gestionado puede resolver el siguiente escalón sin introducir un clúster. Un servicio gestionado sigue necesitando roles, backups, límites de conexión y una red bien configurada.
Varios hosts aportan capacidad y permiten tolerar una máquina caída si repartes réplicas y datos de forma correcta. También requieren descubrimiento, balanceo, almacenamiento, gestión de secretos y actualizaciones coordinadas. Kubernetes puede automatizar parte de eso, pero aprender sus recursos no sustituye el diseño de recuperación que ya has practicado.
La pregunta de salida no es "¿cuántos contenedores tengo?", sino "¿qué requisito ya no puedo cumplir con este sistema?". Si la respuesta es que una caída de host no puede interrumpir el servicio ni un minuto, necesitarás una arquitectura distinta. Si la respuesta es que no sabes por qué crece la memoria de una API, añadir un clúster solo distribuye la incertidumbre.
#Referencias para profundizar
- Docker y CI con GitHub Actions: una implementación concreta del flujo.
- Caché en CI: backends y separación entre caché e imagen.
- Construcciones multiplataforma: plataformas, emulación y construcción nativa.
- Escalado en Compose: opción
--scaley ciclo de actualización.