Docker Bake para un agente IA: un archivo, varios targets, cero CLI eterno
Resumen
Bake de Docker Buildx declara el build en HCL: group, target, inherits y matrix. Un docker buildx bake construye webhook y worker en paralelo; --print antes de CI; --load local y --push al registry. Distinto de cache de capas y de compose watch. Docs Bake, 7 sep 2026.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Bake es la capa declarativa de Docker Buildx: en vez de un docker build con veinte flags, dejas la receta en un archivo y docker buildx bake la ejecuta. Para un agente IA con webhook, worker y a veces un job de migrate, eso significa un archivo, varios targets, una invocación. Los targets del grupo corren en paralelo. Fuentes oficiales Docker consultadas el 7 de septiembre de 2026.
No es cache de capas (eso vive en el Dockerfile y en --cache-from/--cache-to). No es Compose watch (eso recarga un servicio en desarrollo). Bake orquesta qué se construye, con qué tags, plataformas y outputs.
El archivo, no la CLI
HCL es el formato preferido. Bake también lee JSON y un Compose con build:. Si no pasas --file, busca en este orden: compose.yaml, compose.yml, docker-compose.yml, docker-compose.yaml, docker-bake.json, docker-bake.hcl, y al final los docker-bake.override.*. Si hay varios, los fusiona. En el merge, tags, platforms, output, dockerfile y cache-to los gana el último archivo; labels se mezclan.
Eso importa si tu repo ya tiene compose.yaml para el VPS y añades docker-bake.hcl para CI: el HCL pisa los tags del Compose. Compruébalo con --print antes de empujar.
variable "TAG" {
default = "dev"
}
group "default" {
targets = ["webhook", "worker"]
}
target "webhook" {
context = "./webhook"
dockerfile = "Dockerfile"
args = {
NODE_VERSION = "22"
}
tags = ["ghcr.io/org/agente-webhook:${TAG}"]
}
target "worker" {
context = "./worker"
dockerfile = "Dockerfile"
tags = ["ghcr.io/org/agente-worker:${TAG}"]
}
docker buildx bake sin argumentos construye el grupo default (o el target llamado default). docker buildx bake worker construye solo el worker. Comillas en wildcards: docker buildx bake "agente-*"; sin comillas el shell expande archivos.
--print antes de CI
Bake evalúa variables, herencias y matrices antes de construir. --print imprime el JSON resuelto: context, dockerfile, args, tags, platforms. Si TAG sale latest y no lo querías, lo ves aquí, no en el registry.
Overrides útiles:
| Palanca | Qué hace | Cuándo |
|---|---|---|
--print | Resuelve y no construye | Siempre, local y en PR |
--set webhook.tags=ghcr.io/org/agente-webhook:sha | Pisa un atributo | Tag de commit en Actions |
--load | output=type=docker (condicional) | Probar la imagen en el daemon local |
--push | output=type=registry (condicional) | CI que publica |
--allow fs.read=../src | Entitlement de filesystem | Context fuera del cwd del bake file |
--load y --push son atajos. Un target con output explícito a type=local no se “carga” al daemon porque sí: el output del archivo manda salvo que lo pises con --set.
Si el context apunta a ../src, Bake no lee fuera del directorio de trabajo a menos que pases --allow fs.read=../src (o fs.read=*). El default es restrictivo a propósito.
inherits: DRY sin copiar flags
Un target puede heredar de otros. El último de la lista inherits gana en conflicto.
target "_base" {
args = {
NODE_VERSION = "22"
}
dockerfile = "Dockerfile"
}
target "webhook" {
inherits = ["_base"]
context = "./webhook"
tags = ["ghcr.io/org/agente-webhook:${TAG}"]
}
target "webhook-release" {
inherits = ["webhook"]
platforms = ["linux/amd64", "linux/arm64"]
}
El patrón _base (guion bajo) es convención: no lo metes en group "default". Release añade plataformas; dev se queda en la nativa del builder. Para un agente en Fly o un VPS amd64, no pagues el emulador arm64 en cada PR.
Contexts nombrados (contexts = { alpine = "docker-image://alpine:3.21" } o baseapp = "target:base") existen para Dockerfiles que no puedes fusionar. Si puedes, un multi-stage en un solo archivo es más simple.

Matrix de Bake ≠ matrix de GitHub Actions
Bake puede forkar un target:
target "runtime" {
name = "agente-${svc}"
matrix = {
svc = ["webhook", "worker"]
}
context = "./${svc}"
dockerfile = "Dockerfile"
tags = ["ghcr.io/org/agente-${svc}:${TAG}"]
}
--print runtime enseña agente-webhook y agente-worker. Varios ejes multiplican combinaciones; mapas en el matrix evitan el producto cartesiano que no quieres (webhook-1.0 + worker-2.0, no las cuatro mezclas).
Eso no sustituye strategy.matrix de Actions. GHA paraleliza jobs (lint / test / deploy). Bake paraleliza builds en un builder. Úsalos juntos: un job bake default, no un job por imagen a menos que el builder no dé abasto. Guía de matrix de workflows: strategy.matrix para agentes.
CI: bake-action y call = "check"
Docker publica docker/bake-action. El check de Build no pide un input especial: defines un target que hereda el build y pone call = "check".
target "webhook" {
dockerfile = "Dockerfile"
tags = ["ghcr.io/org/agente-webhook:${TAG}"]
}
target "validate" {
inherits = ["webhook"]
call = "check"
}
En el workflow: docker/setup-buildx-action, luego docker/bake-action con targets: validate y, si pasa, targets: default + push: true. --print en el job de PR; push solo en main. El pipeline genérico de Actions sigue en CI/CD del agente.
No pongas migrate en default. Un grupo tools o un target migrate que nadie invoca en el deploy automático evita que un bake de prod corra un job de esquema. El mismo criterio que profiles de Compose: lo opt-in no se dispara solo.
Qué no hace Bake (y qué sí debes pinchar)
| Confusión | Realidad |
|---|---|
Bake cachea mejor que docker build | El cache es BuildKit. Bake solo declara cache-from / cache-to si los pones |
tags = [":latest"] en default | Pin digest o tag de git; :latest es el mismo anti-patrón de pin de imagen |
Un docker build por servicio en el README | El README apunta a docker buildx bake --print y a bake default |
Context COPY . con el bake file dentro | Sigue aplicando .dockerignore |
Secretos en args | target.secret + RUN --mount=type=secret; el valor no va al historial de capas |
target.secret declara ids (type = "env" o type = "file"). El Dockerfile los monta. --set default.secret.aws=env=AWS_CREDENTIALS cambia la fuente sin reescribir el HCL.
Checklist
- Un
docker-bake.hclcongroup "default"= servicios de runtime, no tools. docker buildx bake --printen local y en el job de PR.inheritspara flags comunes; matrix solo si las variantes son reales.--loadpara dogfood local;--pushsolo en CI con registry auth.- Target
validateconcall = "check"antes del bake que publica. - Contexts fuera del cwd:
--allow fs.read=<path>, nunca*en prod. - Tags con git sha o semver; no solo
latest.
FAQ
¿Compose no alcanza? Compose describe runtime (redes, depends_on, restart). Bake describe builds. Puedes alimentar Bake con el mismo YAML, pero el HCL te da inherits, matrix, --print y entitlements. Si el agente ya vive en Compose en el VPS, deja Compose para up y Bake para CI.
¿Un target por stage del Dockerfile? No. Stages (target = "runner") son del Dockerfile. Targets de Bake son invocaciones. Un target webhook puede poner target = "runner" si el Dockerfile es multi-stage.
¿Puedo bakear tests que exportan a disco? Sí: output = ["type=local,dest=build/tests"]. Eso pide --allow fs.write=build/tests si el dest queda fuera del cwd.
El curso gratis arranca el agente; esta guía cubre cómo construirlo sin un one-liner de 14 flags: instalar el agente.

Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.

Build secrets Docker para un agente IA: el token no va en el layer

SBOM y attestations Docker para un agente IA: qué hay dentro, cómo se construyó

Compose Watch para un agente IA: sync no es bind mount
