SBOM y attestations Docker para un agente IA: qué hay dentro, cómo se construyó
Resumen
BuildKit adjunta provenance mode=min por defecto. El SBOM no va solo: --sbom=true, SPDX in-toto y Syft. Escanea el stage final salvo ARG BUILDKIT_SBOM_SCAN_STAGE. mode=max filtra ARG; secretos van en mounts. Docs Docker attestations, SBOM y GitHub Actions, 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.
Un agente IA empaquetado en Docker no es solo un binario Node. Es una imagen con runtime, lockfile, transitives y, a menudo, un stage de build que instala compiladores que no aparecen en el FROM scratch final. Las build attestations de BuildKit responden dos preguntas que un pin de digest no cubre: qué hay dentro (SBOM) y cómo se construyó (provenance). Docs oficiales Docker consultadas el 7 de septiembre de 2026.
BuildKit genera las attestations en el build y las envuelve en el formato in-toto JSON. Quedan pegadas al image index, no a una capa suelta. Por eso el store clásico de imágenes de Docker no las aguanta: o activas el containerd image store, o cambias de driver (docker-container, kubernetes, remote). docker buildx build --push conserva las attestations en el registry. --load al daemon clásico las pierde o falla.
Esta guía es la pieza de cadena de suministro del flujo que ya cubrimos en Docker para el agente, multi-stage y pin de digest. El pin dice cuál imagen; el SBOM dice qué paquetes hay dentro.
Qué genera BuildKit y qué no
Hay dos tipos:
| Tipo | Pregunta | Default |
|---|---|---|
| Provenance | ¿Cómo se construyó? timestamps, frontend, materiales, repo, revisión, plataforma | mode=min sí se añade |
| SBOM | ¿Qué artefactos contiene (o usó el build)? nombre, versión, licencia, PURL | no se añade hasta --sbom=true |
Personalizas el comportamiento:
docker buildx build --sbom=true .
docker buildx build --provenance=mode=max .
docker buildx build --provenance=false .
--attest type=sbom equivale a --sbom=true. --attest type=provenance,mode=max,version=v1 equivale a --provenance=mode=max,version=v1. Para apagar el default: BUILDX_NO_DEFAULT_ATTESTATIONS=1.
El schema de provenance es SLSA v0.2 por defecto; version=v1 pide SLSA Provenance v1. El SBOM de BuildKit sigue SPDX y se adjunta como predicado in-toto SPDX. El generador default es el plugin Syft de BuildKit (Anchore). Puedes cambiarlo con --attest type=sbom,generator=<imagen> si implementa el protocolo de scanner de BuildKit.
Provenance: min es seguro, max filtra ARG
mode=min incluye timestamps, frontend, materiales, repositorio y revisión, plataforma y si el build se declara reproducible. No incluye valores de build arguments, identidades de secrets ni metadata rica de capas. Docker lo considera seguro para todos los builds.
mode=max añade la definición LLB (pasos exactos), el Dockerfile completo en base64 y source maps capa↔instrucción. Docker recomienda max cuando puedes. La advertencia dura: mode=max expone los valores de ARG. Si pasas tokens o credenciales como build args, salen en la attestation. Refactoriza a secret mounts; los mounts de secretos nunca entran en provenance.
SBOM: el stage final miente si tu runtime es scratch
Por defecto BuildKit solo escanea el stage final. Un Dockerfile típico de agente —stage build con pnpm install y compiladores, stage runner FROM scratch o node:22-alpine con COPY --from=build— produce un SBOM del runtime, no de las tools de build. Una CVE en el compilador o en un paquete de apk add del stage de build no aparece.
Los ARG especiales BUILDKIT_SBOM_SCAN_CONTEXT y BUILDKIT_SBOM_SCAN_STAGE amplían el alcance. Son valores especiales: no hay substitución de variables ni env; solo un ARG explícito en el Dockerfile. Pasar --build-arg en la CLI sin el ARG en el Dockerfile no hace nada.
# syntax=docker/dockerfile:1
FROM node:22-alpine AS build
ARG BUILDKIT_SBOM_SCAN_STAGE=true
WORKDIR /src
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=build /src/dist ./dist
USER node
CMD ["node", "dist/index.js"]
Valores de BUILDKIT_SBOM_SCAN_STAGE: true (este stage), false (no), o lista base,bin (nombres de stages). Solo se escanean stages que sí se construyen: un stage que no es dependencia del target no se buildea ni se escanea. BUILDKIT_SBOM_SCAN_CONTEXT=true (global, antes del primer FROM) incluye el contexto de build.
Valida antes de pushear. El exporter local escribe JSON en disco:
docker buildx build --sbom=true --output type=local,dest=out .
ls -1 ./out | grep sbom
# sbom.spdx.json
# sbom-build.spdx.json # si escaneaste el stage build

Inspeccionar sin adivinar
Tras --push, lee el SBOM del índice:
docker buildx imagetools inspect org/agente:1.2.3 \
--format "{{ json .SBOM.SPDX }}"
Multi-plataforma: --format '{{ json (index .SBOM "linux/amd64").SPDX }}'. Lista de paquetes:
docker buildx imagetools inspect org/agente:1.2.3 \
--format "{{ range .SBOM.SPDX.packages }}{{ .name }}@{{ .versionInfo }}{{ println }}{{ end }}"
Un SBOM no sustituye el pin @sha256: el digest fija el blob; el SPDX te dice si undici o musl están en esa capa. Tampoco sustituye usuario no-root: el inventario no cambia el uid 0.
GitHub Actions: público = max, privado = min, load = cero
docker/build-push-action v4+ añade provenance solo:
- repo público →
mode=maxautomático - repo privado →
mode=minautomático load: trueo exporterdocker→ cero attestations (el store local del runner no las soporta)
En un repo público, mode=max automático publica valores de ARG. Misma regla: secretos en mounts, no en build-args. Fuerza max en privado y el SBOM (que nunca va solo) con push: true:
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
push: true
sbom: true
provenance: mode=max
tags: ${{ steps.meta.outputs.tags }}
Sin push: true no hay attestations que sobrevivan. Encaja con el pipeline de CI/CD del agente: el job de imagen publica digest + SPDX, no un tar al artifact del runner.

Checklist antes del push
- Driver que soporta índices (
docker-containero containerd image store). Si vesAttestation is not supported for the docker driver, cambia eso, no apagues attestations. --sbom=true(osbom: trueen Actions). Provenance min ya viene; el SBOM no.ARG BUILDKIT_SBOM_SCAN_STAGE=trueen el stage de build si el runner es scratch/alpine mínimo.- Cero secretos en
ARG.mode=maxy repos públicos los imprimen. - Valida con exporter
local(sbom.spdx.json) antes de--push. - Tras el push:
imagetools inspect --format "{{ json .SBOM.SPDX }}". Vacío = no se adjuntó. - Pin el digest publicado; el SBOM describe ese digest, no el tag
:latest.
FAQ
¿El SBOM reemplaza un scanner de CVE en el registry? No. El SBOM es el inventario. El scanner (Scout, Grype, el del registry) consume ese inventario. Sin SBOM, el scanner solo ve el filesystem final.
¿Puedo generar el SBOM después, con syft sobre la imagen ya pusheada? Sí, pero pierdes las dependencias de build que no quedaron en el runner. Indexar durante el build es el punto de BuildKit.
¿--provenance=false es más seguro? Solo si no quieres metadata. No oculta secretos mal pasados como ARG en un build previo ya publicado. Rota esos secretos.
¿Multi-arch? Cada plataforma tiene su SBOM bajo el índice. Inspecciona con index .SBOM "linux/amd64", no asumas que amd64 = arm64.
Siguiente paso
Si tu agente aún vive en un Dockerfile plano, empieza por multi-stage y no-root. Luego adjunta SBOM + provenance max (sin ARG secretos) y publica el digest. El curso libre cubre el loop mínimo en /curso/instalar-agente; el hub de seguridad, coste y operación agrupa el resto de gates de producción.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.

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

Docker Bake para un agente IA: un archivo, varios targets, cero CLI eterno

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