
Docker Bake, uv.lock y reproducibilidad: del código al artefacto desplegable
Este artículo es la narrativa pública del documento de trabajo interno correspondiente. Conserva hallazgos, semántica, fallos reales y límites; omite o generaliza únicamente identificadores operativos privados que no aportan valor editorial.
Objetivo
Este documento explica por qué Docker Bake es una pieza útil en el delivery de ixatu-operations, cómo se relaciona con uv.lock y package-lock.json, qué valida cada gate del CI/CD y por qué esta separación mejora la reproducibilidad entre desarrollo, CI, DEV y PROD.
La idea central es distinguir cuatro preguntas diferentes:
Tests prueban el código; Bake prueba el paquete; Deploy prueba el entorno; Acceptance prueba el producto.
Versión corta:
| Gate | Pregunta que responde |
|---|---|
| Tests | ¿Funciona el código? |
| Docker Bake | ¿Se pueden fabricar correctamente las imágenes? |
| Deploy DEV | ¿Arrancan y funcionan en Azure? |
| Human Acceptance | ¿Sirve correctamente al usuario? |
1. Qué es Docker Bake
Docker Bake es una capa declarativa sobre Docker Buildx / BuildKit que permite definir y ejecutar varios builds relacionados como un único plan.
En ixatu-operations tenemos, simplificando, dos artefactos desplegables:
Código
│
├── API Python / FastAPI
│ └── Dockerfile ──► imagen API
│
└── Admin Web React/Vite + nginx
└── Dockerfile ──► imagen Web
Docker Bake permite expresar esas construcciones como un conjunto:
docker-bake.hcl
│
┌───────────┴───────────┐
▼ ▼
API WEB
En lugar de mantener comandos manuales separados del tipo:
docker build -f docker/api/Dockerfile ...
docker build -f docker/admin-web/Dockerfile ...
podemos centralizar la definición y ejecutar:
docker buildx bake
Regla mental
Docker Compose organiza cómo se ejecutan contenedores. Docker Bake organiza cómo se construyen sus imágenes.
2. Por qué Bake es útil en nuestro CI/CD
2.1. Valida el artefacto real que se desplegará
Los tests de Python y frontend validan código, pero todavía puede existir un fallo de empaquetado:
pytest ✅
npm build ✅
Docker ❌
Ejemplos típicos:
COPYincorrectos en el Dockerfile;- rutas que han cambiado;
- ficheros no incluidos en el build context;
- dependencias incoherentes;
- error al instalar con
uv; - error en el build de Vite dentro de la imagen;
- configuración nginx incorrecta;
- argumentos o variables de build mal definidos.
Por eso Docker Bake actúa como una prueba de empaquetado real.
2.2. Centraliza la definición de build
Sin una definición centralizada podríamos acabar con ligeras divergencias:
local CI deploy
───── ── ──────
flags A flags B flags C
context X context X context Y
tag local tag SHA tag latest
Con docker-bake.hcl buscamos que todos utilicen el mismo contrato de construcción:
docker-bake.hcl
│
┌──────────┴──────────┐
▼ ▼
API WEB
Esto reduce drift entre:
- desarrollo local;
- CI;
- build DEV;
- artefactos promovidos posteriormente.
2.3. Es especialmente útil con varias imágenes
Bake empieza a aportar bastante valor cuando una aplicación tiene más de una imagen:
ixatu-operations
├── API
└── Admin Web
En vez de coordinar manualmente cada build, BuildKit puede gestionar el conjunto y ejecutar trabajo en paralelo cuando corresponde:
Sin Bake
build API ───────►
build WEB ───────►
Con Bake
build API ───────►
build WEB ───────►
No significa necesariamente que el tiempo total se reduzca a la mitad, pero sí permite una orquestación más limpia y consistente.
2.4. Encaja con nuestro uso de REGISTRY y TAG
Un fichero Bake puede parametrizar valores como:
targets
├── api
│ ├── context
│ ├── dockerfile
│ └── tags
│
└── web
├── context
├── dockerfile
└── tags
variables
├── REGISTRY
└── TAG
GitHub Actions puede proporcionar después:
REGISTRY = Azure Container Registry
TAG = SHA exacto del commit
Esto encaja especialmente bien con el DIL, donde el SHA exacto forma parte del contrato de integración y promoción.
3. Relación entre Docker Bake y uv.lock
La relación es indirecta.
Docker Bake no sabe qué es
uv.lock.
Bake simplemente ordena construir la imagen de la API.
Es el Dockerfile de la API el que, durante esa construcción, utiliza uv y depende de que pyproject.toml y uv.lock sean coherentes.
El flujo real es aproximadamente:
Docker Bake
│
│ "construye la imagen API"
▼
Dockerfile API
│
├── copia pyproject.toml
├── copia uv.lock
│
└── ejecuta instalación con uv
│
▼
uv comprueba
pyproject.toml ↔ uv.lock
│
┌──────┴──────┐
│ │
coherentes incoherentes
│ │
▼ ▼
✅ ❌
Docker build Docker build
continúa falla
Regla mental
uv.lockfija qué entra en la imagen; Docker Bake comprueba que la imagen pueda fabricarse con eso.
Y la precisión importante:
Bake no valida
uv.lockpor sí mismo; descubre el problema porque el proceso de construcción depende de ese lock.
4. Qué hace uv.lock
pyproject.toml declara el proyecto Python y sus dependencias.
uv.lock congela la resolución concreta utilizada para instalar de manera reproducible.
Podemos pensar en la pareja así:
pyproject.toml
│
│ declara intención
▼
uv.lock
│
│ fija resolución exacta
▼
instalación reproducible
Cuando usamos:
uv sync --locked
estamos diciendo:
Instala exactamente conforme al lock actual y falla si el lock tendría que modificarse.
Esto evita que CI “arregle” silenciosamente una incoherencia.
5. El caso real de v0.7.2
Durante la preparación de v0.7.2 se actualizó primero:
version = "0.7.2"
en pyproject.toml, pero inicialmente uv.lock todavía conservaba la versión anterior:
pyproject.toml → 0.7.2
uv.lock → 0.7.1
Entonces:
uv sync --locked
falló con un mensaje equivalente a:
The lockfile at uv.lock needs to be updated,
but --locked was provided.
Eso era correcto y deseable: CI detectó que el repositorio no era todavía un candidato reproducible.
La solución fue regenerar el lock:
uv lock
y obtener:
pyproject.toml → 0.7.2
uv.lock → 0.7.2
A partir de ahí:
uv sync --locked ✅
Docker Bake ✅
6. Por qué podían fallar Python CI y Docker Bake por la misma causa
El mismo problema puede aparecer por dos caminos distintos:
Python CI
└── uv sync --locked
└── ❌ lock incoherente
y:
Docker Bake
└── construir imagen API
└── Dockerfile
└── uv ...
└── ❌ lock incoherente
No son necesariamente dos errores diferentes.
Pueden ser dos gates independientes detectando la misma incoherencia.
Esto es positivo: aumenta la probabilidad de detener un candidato defectuoso antes del deploy.
7. Relación equivalente con package-lock.json
En el frontend tenemos el mismo principio:
package.json
│
▼
package-lock.json
La pareja equivalente en Python es:
pyproject.toml
│
▼
uv.lock
Por tanto, antes de fabricar imágenes queremos:
package.json ↔ package-lock.json
pyproject.toml ↔ uv.lock
coherentes.
El flujo conceptual queda:
FUENTES
│
┌──────────┴──────────┐
▼ ▼
pyproject.toml package.json
│ │
▼ ▼
uv.lock package-lock.json
│ │
└──────────┬──────────┘
▼
Docker Bake
│
▼
imágenes API/Web
8. Docker Bake dentro del flujo completo de delivery
Nuestro objetivo no es simplemente que el código compile, sino que el artefacto validado llegue a DEV y después a PROD con trazabilidad.
Flujo simplificado:
CANDIDATO
│
┌───────────┼───────────┐
▼ ▼ ▼
Python Admin Web Docker Bake
lógica frontend packaging
│ │ │
└───────────┼───────────┘
▼
merge
│
▼
Deploy DEV
│
▼
smokes
│
▼
Human Acceptance
│
▼
Release Gate
│
▼
PROD
Cada gate responde a una pregunta distinta.
9. Relación con DEV → PROD
Un principio importante del delivery de ixatu-operations es:
PROD no debería reconstruir una versión diferente de la que validamos en DEV.
El ideal es:
DEV prueba imagen A
│
▼
Release Gate
│
▼
PROD recibe imagen A
No:
DEV prueba imagen A
│
▼
PROD reconstruye
│
▼
obtiene imagen A'
La definición centralizada de builds mediante Bake ayuda a reducir diferencias entre entornos.
Nuestro flujo conceptual es:
PR
│
Docker Bake ✅
│
merge
│
▼
Deploy DEV
│
build + push
│
┌─────────┴─────────┐
▼ ▼
API digest WEB digest
│ │
└─────────┬─────────┘
│
DEV validado
│
Release Gate
│
▼
PROD
mismo par de digests
Docker Bake no realiza por sí mismo toda esta promoción, pero facilita que la construcción sea declarativa, repetible y auditable.
10. Qué valida cada gate
Python CI
Valida aspectos como:
- sincronización con
uv.lock; - lint;
- tests;
- build del paquete Python.
Pregunta principal:
¿La parte Python es coherente y funcional?
Admin Web CI
Valida:
- instalación Node;
- typecheck;
- build Vite;
- tests de navegador/sintéticos.
Pregunta principal:
¿El frontend compila y se comporta como esperamos?
Docker Bake
Valida:
- Dockerfiles;
- contextos;
COPY;- instalación dentro de imagen;
- build real API;
- build real Web;
- configuración de build;
- compatibilidad entre metadatos, locks y Dockerfiles.
Pregunta principal:
¿Podemos fabricar correctamente los artefactos que queremos desplegar?
Deploy DEV
Valida:
- login Azure;
- ACR;
- imágenes;
- Container Apps;
- configuración de entorno;
- comunicación entre Web y API;
- smoke tests.
Pregunta principal:
¿Los artefactos funcionan realmente en Azure DEV?
Human Acceptance
Valida:
- casos reales;
- UX;
- expectativas funcionales;
- comportamiento visto por un usuario.
Pregunta principal:
¿La solución sirve para el trabajo real?
11. Ventajas prácticas para IXATU
Menos drift
La receta de build vive en un lugar central en vez de duplicarse entre scripts.
Reproducibilidad
Los lockfiles reducen variaciones entre máquinas y ejecuciones.
Fallo temprano
Problemas de empaquetado se detectan antes del merge o del deploy.
Trazabilidad
Podemos asociar:
commit SHA
│
▼
build
│
▼
imagen
│
▼
digest
│
▼
DEV
│
▼
PROD
Mejor separación de responsabilidades
No confundimos:
- que el código funcione;
- que pueda empaquetarse;
- que arranque en Azure;
- que satisfaga al usuario.
Mejor trabajo con agentes IA
El Implementador puede ejecutar un contrato reproducible sin inventar comandos diferentes para cada imagen.
Mejor soporte local
Desde WSL se puede ejecutar un build muy parecido al de CI:
docker buildx bake
Esto reduce diferencias entre “funciona en mi máquina” y “funciona en CI”.
12. Qué Docker Bake NO hace
Conviene no atribuirle más responsabilidad de la que tiene.
Docker Bake no demuestra que:
- la lógica de negocio sea correcta;
- los tests sean suficientes;
- Azure esté bien configurado;
- PROD vaya a funcionar;
- la aplicación sea usable.
Sólo demuestra principalmente:
La definición de build permite fabricar correctamente las imágenes solicitadas.
Por eso no sustituye a los demás gates.
13. Reglas nemotécnicas
Tests prueban el código; Bake prueba el paquete; Deploy prueba el entorno; Acceptance prueba el producto.
uv.lockfija qué entra en la imagen; Docker Bake comprueba que la imagen pueda fabricarse con eso.
Bake no entiende
uv.lock; el Dockerfile sí depende de él.
Un mismo error puede caer en varios gates y eso es una ventaja, no una duplicidad inútil.
DEV debe validar el mismo artefacto que posteriormente llega a PROD.
Los locks hacen reproducible la instalación; Bake hace reproducible la construcción del artefacto.
14. Resumen visual final
pyproject.toml ─────► uv.lock
│ │
└────────┬────────┘
│
▼
instalación Python
│
▼
Dockerfile API
│
│
package.json ───┼────► package-lock.json
│ │ │
└────────┴──────┬──────┘
▼
Docker Bake
│
┌─────────┴─────────┐
▼ ▼
imagen API imagen Web
│ │
└─────────┬─────────┘
▼
Deploy DEV
│
smokes
│
Human Acceptance
│
Release Gate
│
▼
PROD
15. Conclusión
En ixatu-operations, Docker Bake es interesante porque convierte el build de nuestras imágenes en un contrato declarativo y repetible.
Los lockfiles (uv.lock y package-lock.json) garantizan que las dependencias sean coherentes y reproducibles. Docker Bake usa indirectamente esa coherencia al construir las imágenes definidas por los Dockerfiles.
La combinación de:
locks
+
tests
+
Docker Bake
+
Deploy DEV
+
smokes
+
Human Acceptance
+
Release Gate
nos permite avanzar hacia un delivery donde cada paso valida una capa diferente y donde PROD recibe artefactos con trazabilidad y menor riesgo de desviación respecto a lo ya probado.