Jesús Erro
Explorar conexiones
Locks de dependencias alimentan BuildKit/Bake, producen imágenes API y Web, y el mismo digest avanza de DEV a PROD.

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:

  • COPY incorrectos 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.lock fija qué entra en la imagen; Docker Bake comprueba que la imagen pueda fabricarse con eso.

Y la precisión importante:

Bake no valida uv.lock por 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.lock fija 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.