Totes les construccions del curs les ha fet BuildKit sense que te n'adonessis. En aquesta lliçó en prens el control: muntatges de memòria cau que eviten reinstal·lar npm a cada build, secrets que no deixen rastre en cap capa, memòria cau compartida entre la teva màquina i CI, imatges que funcionen alhora en amd64 i arm64, i una sola comanda per construir tota la plataforma Aurora Libros.

Contingut

  1. Què és BuildKit i en què es diferencia del constructor clàssic
  2. El graf d'una construcció multietapa
  3. Activar-lo, verificar-lo i llegir-ne la sortida
  4. Buildx i els builders
  5. Muntatges a RUN: type=cache
  6. type=bind i type=tmpfs
  7. type=secret: credencials que no deixen rastre
  8. type=ssh: clonar repositoris privats
  9. Memòria cau remota compartida
  10. Multiarquitectura: QEMU i manifest lists
  11. TARGETPLATFORM i compilació creuada
  12. Sortides amb --output, --load i --push
  13. docker buildx bake

  1. Què és BuildKit i en què es diferencia del constructor clàssic

El constructor clàssic executava el Dockerfile línia a línia, en ordre estricte, creant un contenidor intermedi per instrucció. BuildKit el substitueix per un motor que primer analitza el fitxer sencer i construeix un graf de dependències.

Aspecte Constructor clàssic BuildKit
Execució Seqüencial, instrucció a instrucció Graf de dependències: només el que cal
Etapes independents Una rere l'altra En paral·lel
Memòria cau Per capa, cadena lineal Per contingut; sobreviu a reordenacions
Context S'envia sencer en començar Es transfereix sota demanda
Secrets Impossibles sense filtrar-los --mount=type=secret, sense rastre
Sortida Registre pla al final Progrés en directe, per pas i amb temps
Memòria cau externa No --cache-from / --cache-to amb registres d'imatges
Multiarquitectura Un docker build per plataforma Una ordre, diverses plataformes

La diferència més útil en el dia a dia: si una etapa no aporta res a la imatge final, BuildKit ni tan sols l'executa. Per això l'etapa proves de la lliçó 05-04 no s'executa en construir producció.

  1. El graf d'una construcció multietapa

flowchart LR
  CTX["Context<br/>(sota demanda)"] --> D1["dependencies:<br/>apk add build-base"]
  CTX --> D2["desenvolupament:<br/>npm ci complet"]
  D1 --> D3["dependencies:<br/>npm ci --omit=dev"]
  D2 --> T["proves:<br/>npm test"]
  D3 --> P["produccio:<br/>COPY --from=dependencies"]
  CTX --> P
  P --> IMG["Imatge final"]
  T -.->|"només amb --target proves"| X["no entra al graf<br/>de la imatge final"]

dependencies i desenvolupament no depenen l'una de l'altra: BuildKit les executa alhora. I proves penja de desenvolupament, però res de la imatge final no depèn de proves, així que es poda del graf.

  1. Activar-lo, verificar-lo i llegir-ne la sortida

Des de Docker 23, BuildKit és el constructor per defecte a Linux, macOS i Windows.

docker buildx version
docker build --no-cache -t aurora-api:prova ./api 2>&1 | head -3
github.com/docker/buildx v0.19.3
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.42kB done

Les línies #n numerades són la signatura de BuildKit; el constructor antic mostrava Step 1/12 : FROM .... Si necessites tornar enrere per algun motiu puntual, DOCKER_BUILDKIT=0 docker build ..., però considera-ho un diagnòstic, no una opció.

Per depurar una construcció, el mode de progrés ho canvia tot:

docker build --progress=plain --no-cache -t aurora-api:dep ./api 2>&1 | grep -A3 'npm ci'
#12 [dependencies 4/4] RUN npm ci --omit=dev && npm cache clean --force
#12 3.412 added 64 packages in 3s
#12 4.108 npm warn using --force Recommended protections disabled.
#12 DONE 4.3s

--progress=plain imprimeix la sortida completa de cada comanda amb marques de temps relatives, en lloc de la vista interactiva que col·lapsa les línies. És el primer que cal activar quan una construcció falla i no entens per què.

  1. Buildx i els builders

Buildx és el client modern de construcció. Un builder és la instància de BuildKit que fa la feina, i no totes tenen les mateixes capacitats.

docker buildx ls
NAME/NODE       DRIVER/ENDPOINT   STATUS   PLATFORMS
default *       docker            running  linux/amd64, linux/386
  default       default           running
Driver On corre Multiarquitectura Memòria cau remota Sortides
docker (per defecte) Dins del daemon No Només inline Només imatge local
docker-container En un contenidor propi Totes Totes (--output)
kubernetes Pods d'un clúster Totes Totes
remote Instància BuildKit externa Totes Totes

Per a tot el que ve després cal docker-container:

docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx inspect aurora | grep -E 'Name|Status|Platforms'
Name:      aurora
Status:    running
Platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/386

Aquell builder és un contenidor normal (buildx_buildkit_aurora0) amb la seva pròpia memòria cau, independent de la del daemon. Es gestiona amb docker buildx use/stop/rm, i es neteja amb docker buildx prune --filter until=168h.

  1. Muntatges a RUN: type=cache

Un muntatge de memòria cau és un directori persistent entre construccions que no forma part de cap capa. És la solució al malbaratament més habitual: reinstal·lar dependències senceres perquè ha canviat una línia de package.json.

# syntax=docker/dockerfile:1
FROM node:22-alpine AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
    npm ci --omit=dev
docker buildx build --target dependencies -t aurora-api:deps ./api    # primera vegada
# canvia una versió a package.json i repeteix
time docker buildx build --target dependencies -t aurora-api:deps ./api
Primera construcció (memòria cau freda):   42.7s
Després de canviar package.json, sense memòria cau de muntatge: 39.1s
Després de canviar package.json, amb memòria cau de muntatge:    6.8s

De 39 a 7 segons. La memòria cau d'npm no s'ha tornat a descarregar: npm ha trobat els paquets a /root/.npm i només ha resolt l'arbre. I com que el muntatge no s'incorpora a la capa, la imatge final no creix ni un byte.

Paràmetre Valors Significat
target Ruta On es munta dins del RUN
id Text Identificador de la memòria cau; comparteix-lo entre Dockerfiles
sharing shared (per defecte), locked, private Què passa si dues builds la fan servir alhora
mode 0755 Permisos del directori
uid/gid Números Propietari, útil si el RUN no és root

sharing=locked és el valor correcte per a gestors de paquets que no toleren escriptures concurrents (npm, apt, pip). Els directoris de memòria cau habituals: /root/.npm (npm), /var/cache/apt i /var/lib/apt/lists (apt), /root/.cache/pip (pip), /go/pkg/mod (Go), /root/.m2 (Maven).

Avís. La memòria cau de muntatge és local al builder. No viatja amb la imatge ni entre màquines; per compartir-la entre el teu portàtil i CI es fa servir la memòria cau remota de l'apartat 9.

  1. type=bind i type=tmpfs

type=bind munta fitxers del context (o d'una altra etapa) sense copiar-los a una capa, i type=tmpfs dona un directori a memòria per a feina temporal:

RUN --mount=type=bind,source=package-lock.json,target=/tmp/lock.json \
    node -e "console.log(require('/tmp/lock.json').lockfileVersion)"

# Des d'una altra etapa, sense arrossegar-ne les capes
RUN --mount=type=bind,from=dependencies,source=/app/node_modules,target=/deps du -sh /deps

RUN --mount=type=tmpfs,target=/tmp/treball \
    tar xzf /origen.tar.gz -C /tmp/treball && cp /tmp/treball/binari /usr/local/bin/

Serveixen per llegir o verificar alguna cosa durant la construcció sense que formi part de la imatge. Descomprimir a tmpfs és ràpid i garanteix que els fitxers intermedis no arriben a cap capa.

  1. type=secret: credencials que no deixen rastre

Aquest és l'apartat que resol el problema demostrat a les lliçons 05-03 i 05-04. Comparem les dues maneres de passar un token d'npm privat.

# ❌ INSEGUR: l'ARG queda a les metadades per sempre
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
    npm ci && rm .npmrc
# ✅ CORRECTE: el secret es munta, es fa servir i desapareix
RUN --mount=type=secret,id=npm_token \
    --mount=type=cache,target=/root/.npm,sharing=locked \
    NPM_TOKEN="$(cat /run/secrets/npm_token)" \
    npm ci --omit=dev
echo "npm_tok_a91f3c" > /tmp/npm_token.txt
docker buildx build --secret id=npm_token,src=/tmp/npm_token.txt \
  --load -t aurora-api:segur ./api

# Apareix el token en algun lloc?
docker history --no-trunc aurora-api:segur | grep -c 'npm_tok_a91f3c'
docker image inspect aurora-api:segur --format '{{json .Config.Env}}' | grep -c 'npm_tok'
docker save aurora-api:segur | strings | grep -c 'npm_tok_a91f3c'
0
0
0

Zero coincidències a les tres comprovacions. Compara-ho amb el mateix experiment fent servir ARG, on docker history retornava el token en clar. El mecanisme: BuildKit munta el fitxer a /run/secrets/<id> com un tmpfs que existeix només durant aquell RUN; no hi ha capa, no hi ha metadada, no hi ha rastre.

El secret també pot venir d'una variable d'entorn, cosa que encaixa millor amb els gestors de secrets de CI:

export NPM_TOKEN=npm_tok_a91f3c
docker buildx build --secret id=npm_token,env=NPM_TOKEN --load -t aurora-api:segur ./api

  1. type=ssh: clonar repositoris privats

Quan una dependència viu en un repositori Git privat, l'instint és copiar una clau privada al build. No ho facis mai: type=ssh reenvia el teu agent SSH sense que la clau entri a la construcció.

RUN --mount=type=ssh \
    mkdir -p ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts && \
    npm ci --omit=dev
ssh-add -l >/dev/null || ssh-add ~/.ssh/id_ed25519
docker buildx build --ssh default --load -t aurora-api:privat ./api

La clau no surt mai del teu agent: BuildKit exposa un socket temporal dins del RUN i les operacions de signatura passen a la teva màquina. En acabar la instrucció, el socket desapareix.

  1. Memòria cau remota compartida

La memòria cau local només serveix a qui la va generar. A CI, cada treball comença amb una màquina neta i ho reconstrueix tot des de zero. La memòria cau remota ho resol: es publica en un registre d'imatges i qualsevol constructor la pot reutilitzar.

docker buildx build \
  --cache-to   type=registry,ref=auroralibros/aurora-api:buildcache,mode=max \
  --cache-from type=registry,ref=auroralibros/aurora-api:buildcache \
  -t auroralibros/aurora-api:1.3.0 --push ./api
Backend On viu Avantatge Inconvenient
registry Al teu registre d'imatges, com una etiqueta més Compartida entre màquines i amb CI Necessita permisos d'escriptura al registre
inline Dins de la imatge mateixa Zero configuració; funciona amb el driver docker Només memòria cau de l'última etapa (mode=min)
gha Memòria cau de GitHub Actions Integrada, sense registre extra Limitada a GitHub i amb quota de 10 GB
local Directori del disc Ràpida i sense xarxa No es comparteix entre màquines
s3 / azblob Emmagatzematge d'objectes Escalable i barata Configuració de credencials

mode=max desa la memòria cau de totes les etapes, incloses les intermèdies; mode=min (per defecte) només les capes de la imatge final. Per a multietapa, mode=max és el que de debò estalvia temps, a canvi de més espai al registre d'imatges.

# Màquina neta: reutilitza la memòria cau publicada
docker buildx build --cache-from type=registry,ref=auroralibros/aurora-api:buildcache \
  -t auroralibros/aurora-api:1.3.0 --load ./api 2>&1 | grep -c CACHED
9

Nou passos resolts des de la memòria cau en una màquina que no havia construït mai aquesta imatge. Aquesta és la peça que farà que el pipeline de la lliçó 06-02 trigui segons en lloc de minuts.

  1. Multiarquitectura: QEMU i manifest lists

Els portàtils amb Apple Silicon són arm64 i bona part dels servidors de núvol també (Graviton, Ampere), mentre que la majoria de CI continua sent amd64. Una imatge construïda només per a amd64 falla o va lentíssima sota emulació en arm64.

docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx inspect aurora --bootstrap | grep Platforms
Platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/riscv64, linux/386

binfmt_misc és la funcionalitat del kernel que associa un intèrpret als binaris d'una altra arquitectura; aquell contenidor privilegiat registra els emuladors QEMU corresponents (i sí, el --privileged d'aquí està justificat: registra gestors al kernel, i és una operació puntual de configuració del host).

docker buildx build --platform linux/amd64,linux/arm64 \
  -t auroralibros/aurora-api:1.3.0 --push ./api
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0
Name:      docker.io/auroralibros/aurora-api:1.3.0
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:c41f8a2b...

Manifests:
  Name:      auroralibros/aurora-api:1.3.0@sha256:9e2a17f4...
  Platform:  linux/amd64
  Name:      auroralibros/aurora-api:1.3.0@sha256:5b70c3d8...
  Platform:  linux/arm64

Un manifest list (o image index) és un índex que apunta a una imatge per plataforma. Quan algú fa docker pull auroralibros/aurora-api:1.3.0, el client informa de la seva arquitectura i el registre d'imatges li lliura el manifest adequat: la mateixa etiqueta funciona al Mac de l'equip i al servidor Graviton, sense sufixos ni branques.

Nota important: --platform amb diverses arquitectures obliga a --push, perquè el magatzem local del daemon no sap desar un índex multiplataforma. Amb --load només pots carregar una plataforma cada vegada.

  1. TARGETPLATFORM i compilació creuada

Emular una arquitectura amb QEMU funciona, però és lent —de tres a deu vegades més—. El patró professional és compilar de manera nativa i fer servir l'emulació només per a l'etapa final. BuildKit exposa variables automàtiques per fer-ho:

Variable Què conté
BUILDPLATFORM Plataforma de la màquina que construeix (p. ex. linux/amd64)
TARGETPLATFORM Plataforma destinació (linux/arm64)
TARGETOS, TARGETARCH, TARGETVARIANT Els seus components per separat
# L'etapa de compilació corre SEMPRE a l'arquitectura nativa: ràpid
FROM --platform=$BUILDPLATFORM node:22-alpine AS constructor
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Construint a $BUILDPLATFORM per a $TARGETPLATFORM"
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
COPY src/ ./src/

# L'etapa final sí que és de l'arquitectura destinació
FROM node:22-alpine AS produccio
WORKDIR /app
COPY --from=constructor --chown=node:node /app .
USER node
CMD ["node", "src/server.js"]
docker buildx build --platform linux/amd64,linux/arm64 --progress=plain \
  -t auroralibros/aurora-api:1.3.0 --push ./api 2>&1 | grep Construint
#8 0.312 Construint a linux/amd64 per a linux/amd64
#9 0.298 Construint a linux/amd64 per a linux/arm64

Les dues construccions passen en amd64: la segona compila per a arm64 sense emular. Amb llenguatges compilats (Go, Rust) el guany és enorme, perquè n'hi ha prou amb GOARCH=$TARGETARCH. Amb Node.js el benefici és menor —el codi és interpretat—, però el patró continua evitant emular npm ci, que és la part lenta.

  1. Sortides amb --output, --load i --push

Amb el driver docker-container, el resultat no va automàticament al teu daemon: cal dir on el vols.

docker buildx build --load -t aurora-api:local ./api                 # al daemon local
docker buildx build --push -t auroralibros/aurora-api:1.3.0 ./api    # al registre d'imatges
docker buildx build --output type=local,dest=./sortida ./api         # fitxers solts
docker buildx build --output type=oci,dest=aurora-api-oci.tar ./api  # format OCI
Sortida Què produeix Per a què
--load (= type=docker) Imatge al teu daemon Desenvolupament i proves locals
--push (= type=image,push=true) Imatge al registre d'imatges Publicació i multiarquitectura
type=local Els fitxers del sistema de l'última etapa Extreure artefactes compilats
type=oci / type=tar Arxiu OCI o tar del sistema de fitxers Signatura, escaneig, entorns sense daemon
type=cacheonly Res; només escalfa la memòria cau Preescalfar CI

type=local té un ús elegant: fer servir Docker com a sistema de compilació reproduïble i quedar-te només amb el resultat, sense imatge pel mig. I type=tar,dest=aurora-api.tar produeix un tar del sistema de fitxers per a transport o anàlisi.

  1. docker buildx bake

Construir Aurora Libros són diverses ordres llargues i fàcils de teclejar malament. Bake les declara en un fitxer:

# docker-bake.hcl — Aurora Libros S.L.
variable "VERSION"    { default = "1.3.0" }
variable "REGISTRE"   { default = "auroralibros" }
variable "PLATAFORMES" { default = "linux/amd64,linux/arm64" }

group "default" {
  targets = ["api", "web"]
}

target "comu" {
  platforms  = split(",", PLATAFORMES)
  cache-from = ["type=registry,ref=${REGISTRE}/buildcache"]
  cache-to   = ["type=registry,ref=${REGISTRE}/buildcache,mode=max"]
  labels = {
    "org.opencontainers.image.version" = VERSION
    "org.opencontainers.image.vendor"  = "Aurora Libros S.L."
  }
}

target "api" {
  inherits   = ["comu"]
  context    = "./api"
  target     = "produccio"
  tags       = ["${REGISTRE}/aurora-api:${VERSION}", "${REGISTRE}/aurora-api:latest"]
  args       = { VERSION = VERSION }
}

target "web" {
  inherits = ["comu"]
  context  = "./web"
  tags     = ["${REGISTRE}/aurora-web:${VERSION}"]
}

# Matriu: la mateixa imatge amb diverses versions de Node
target "api-matriu" {
  inherits = ["comu"]
  context  = "./api"
  name     = "api-node${node}"
  matrix   = { node = ["20", "22", "23"] }
  args     = { NODE_VERSION = node }
  tags     = ["${REGISTRE}/aurora-api:${VERSION}-node${node}"]
}
docker buildx bake --print                     # veure el pla sense construir
docker buildx bake                             # grup "default": api i web
docker buildx bake api --set api.tags=aurora-api:local --load
VERSION=1.4.0 docker buildx bake --push
docker buildx bake api-matriu
[+] Building 51.2s (34/34) FINISHED
 => [api] exporting to image
 => [web] exporting to image

Tres coses fan que bake valgui la pena: inherits evita repetir la configuració comuna, els objectius es construeixen en paral·lel (aquí, api i web alhora), i --print t'ensenya el pla resolt abans d'executar res. Bake també llegeix un compose.yaml directament (docker buildx bake -f compose.yaml), aprofitant les seccions build que ja tens escrites.

Errors Habituals i Consells

Fer servir el driver docker i esperar multiarquitectura o memòria cau remota. Crea un builder docker-container amb docker buildx create --use.

Oblidar --load o --push amb docker-container. La construcció acaba bé i la imatge no apareix enlloc.

Intentar --load amb diverses plataformes. El magatzem local no desa índexs multiplataforma. Fes servir --push o carrega'n una de sola.

Passar secrets amb --build-arg. Queden a docker history. --mount=type=secret, sempre.

Creure que la memòria cau de muntatge viatja amb la imatge. És local al builder. Entre màquines, memòria cau remota.

Fer servir mode=min en memòria cau remota d'una build multietapa. Només posa a la memòria cau la imatge final; les etapes cares es refan. mode=max.

Deixar créixer la memòria cau del builder sense límit. docker buildx prune --filter until=168h de manera periòdica, o --keep-storage.

Consell: quan una construcció falli de manera incomprensible, l'ordre és --progress=plain per veure la sortida completa, --no-cache per descartar una memòria cau enverinada i --target <etapa> per aïllar el punt exacte de la fallada. Amb aquests tres es resol la pràctica totalitat dels casos.

Exercicis

Exercici 1. Crea un builder docker-container, afegeix un muntatge type=cache a la instal·lació d'npm d'aurora-api i mesura la diferència: construeix una vegada, canvia package.json i reconstrueix, amb i sense el muntatge. Explica per què la imatge final no creix.

Exercici 2. Demostra que --mount=type=secret no deixa rastre: construeix la mateixa imatge passant un token amb --build-arg i amb --secret, i busca el token a l'historial, a l'entorn i al tarball exportat de cadascuna.

Exercici 3. Publica auroralibros/aurora-api:1.3.0 per a linux/amd64 i linux/arm64, inspecciona el manifest list resultant i explica què passa exactament quan un servidor arm64 i un portàtil amd64 fan docker pull de la mateixa etiqueta.

Solucions

Solució 1.

docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx build --no-cache --target dependencies --load -t a:v1 ./api 2>&1 | tail -1
sed -i 's/"express": "4.19.3"/"express": "4.19.2"/' api/package.json

# Sense muntatge de memòria cau (Dockerfile.sensecache)
time docker buildx build -f api/Dockerfile.sensecache --target dependencies --load -t a:v2 ./api
# Amb muntatge de memòria cau
time docker buildx build --target dependencies --load -t a:v3 ./api
docker image ls a --format "{{.Tag}}\t{{.Size}}"
real    0m39.108s      <- sense memòria cau de muntatge
real    0m6.821s       <- amb memòria cau de muntatge
v2   198MB
v3   198MB

Un canvi a package.json invalida la capa COPY package.json i tot el que ve després, així que npm ci es torna a executar en tots dos casos. La diferència és dins d'aquella execució: sense muntatge, npm descarrega els 64 paquets un altre cop; amb muntatge, troba /root/.npm poblada de la construcció anterior i només resol l'arbre i enllaça. La xarxa desapareix del camí crític i queden 6,8 segons.

I la imatge pesa exactament el mateix (198 MB) perquè un muntatge de memòria cau no participa en la capa: BuildKit el munta abans d'executar la comanda i el desmunta després, de manera que quan es consolida el sistema de fitxers de la capa, /root/.npm ja no hi és. És la diferència essencial amb COPY: un deixa petjada, l'altre no.

Solució 2.

TOKEN="npm_tok_a91f3c"
echo "$TOKEN" > /tmp/tok.txt

# A) Amb ARG
docker buildx build -f api/Dockerfile.arg --build-arg NPM_TOKEN="$TOKEN" --load -t fuita:arg ./api
# B) Amb secret
docker buildx build --secret id=npm_token,src=/tmp/tok.txt --load -t segur:sec ./api

for img in fuita:arg segur:sec; do
  h=$(docker history --no-trunc "$img" | grep -c "$TOKEN")
  e=$(docker image inspect "$img" --format '{{json .Config}}' | grep -c "$TOKEN")
  t=$(docker save "$img" | strings | grep -c "$TOKEN")
  echo "$img -> history:$h config:$e tarball:$t"
done
fuita:arg  -> history:1 config:1 tarball:2
segur:sec  -> history:0 config:0 tarball:0

La imatge construïda amb ARG filtra el token per tres vies independents: l'historial de construcció (on queda la línia del RUN amb el valor substituït), la configuració de la imatge (on l'ARG es registra com a metadada) i el contingut de les capes (on el .npmrc escrit i esborrat continua present sota un whiteout). N'hi ha prou que se te n'escapi una perquè el secret quedi publicat.

Amb --secret les tres donen zero. BuildKit munta el fitxer com a tmpfs a /run/secrets/npm_token únicament mentre dura aquell RUN; el muntatge no forma part del sistema de fitxers que es consolida a la capa, i el valor no apareix en cap metadada perquè mai no va ser un argument de construcció. És, literalment, l'única manera correcta de fer servir credencials durant un build.

Solució 3.

docker run --privileged --rm tonistiigi/binfmt --install arm64 >/dev/null
docker buildx build --platform linux/amd64,linux/arm64 \
  -t auroralibros/aurora-api:1.3.0 --push ./api
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0 \
  --format '{{range .Manifest.Manifests}}{{.Platform.OS}}/{{.Platform.Architecture}} {{.Digest}}{{"\n"}}{{end}}'
docker buildx imagetools inspect auroralibros/aurora-api:1.3.0 --raw | head -3
linux/amd64 sha256:9e2a17f4c8b3...
linux/arm64 sha256:5b70c3d8a1e6...
{
  "mediaType": "application/vnd.oci.image.index.v1+json",

L'etiqueta 1.3.0 no apunta a una imatge: apunta a un índex (image.index.v1+json) que conté dos manifests, cadascun amb el seu propi digest i la seva plataforma declarada.

Quan el servidor arm64 executa docker pull auroralibros/aurora-api:1.3.0, el seu client envia a la capçalera Accept els tipus que entén i informa de la seva plataforma; el registre d'imatges retorna l'índex, el client busca l'entrada linux/arm64, i descarrega només el manifest sha256:5b70c3d8... i les seves capes. El portàtil amd64, amb la mateixa comanda i la mateixa etiqueta, s'emporta sha256:9e2a17f4.... Cap dels dos no descarrega bytes de l'altra arquitectura.

Les tres conseqüències pràctiques: el compose.prod.yaml pot portar una sola referència d'imatge vàlida per a tota la flota; si fixes per digest (lliçó 05-03) has de fixar el de l'índex, no el d'una plataforma concreta, o trencaràs la portabilitat; i si un dia algú construeix sense --platform al seu Mac i fa push amb aquella mateixa etiqueta, substituirà l'índex per una imatge arm64 solta i els servidors amd64 fallaran amb exec format error. Per això la construcció multiarquitectura pertany al pipeline i no al portàtil de ningú.

Conclusió

BuildKit ha deixat de ser una caixa negra. Saps que no executa el Dockerfile línia a línia, sinó que construeix un graf de dependències, paral·lelitza les etapes independents, transfereix el context sota demanda i poda el que no aporta a la imatge final —d'aquí que l'etapa proves no s'executi en construir producció—. Saps verificar-lo, llegir-ne la sortida i, quan alguna cosa falla, activar --progress=plain per veure l'execució real. I coneixes Buildx i els seus builders: el driver docker per al bàsic i docker-container per a tota la resta, amb la seva memòria cau pròpia i les seves plataformes emulades.

Domines els quatre muntatges de RUN. type=cache va convertir una reinstal·lació de dependències de 39 segons en 7 sense que la imatge creixi ni un byte, perquè el muntatge no forma part de la capa. type=bind llegeix fitxers sense copiar-los, type=tmpfs dona espai temporal a memòria, i type=secret tanca per fi el problema que arrossegaves des de la lliçó 02-04: el token passat amb --build-arg apareixia a l'historial, a la configuració i al tarball; passat amb --secret, zero coincidències a les tres. type=ssh completa el quadre reenviant el teu agent per clonar repositoris privats sense que cap clau entri a la construcció.

I saps treballar més enllà de la teva màquina: memòria cau remota amb --cache-from/--cache-to i els seus backends —registry, gha, local, inline— amb mode=max perquè les etapes intermèdies també es reutilitzin, cosa que deixarà el pipeline de la lliçó 06-02 en segons; construcció multiarquitectura amb QEMU i binfmt_misc, manifest lists que fan que una sola etiqueta serveixi per al Mac de l'equip i per al servidor Graviton, i el patró --platform=$BUILDPLATFORM amb TARGETARCH per compilar de manera nativa en lloc d'emular. Tanques amb les sortides d'--output i amb docker buildx bake, que redueix tota la construcció d'Aurora Libros a un docker buildx bake --push amb herència, variables i matrius.

A la lliçó 05-06 deixes de construir i comences a observar. Veuràs el registre d'una plataforma en marxa: els drivers de logging amb la seva taula comparativa, la rotació obligatòria i què passa exactament si no la configures, logs estructurats en JSON amb identificador de petició, i una pila d'agregació amb Loki, Promtail i Grafana aixecada com a perfil observabilitat d'Aurora Libros. Després, les mètriques: docker stats i els seus límits, l'endpoint Prometheus del daemon, cAdvisor i node-exporter, les mètriques que de debò es vigilen, les alertes que mereixen despertar algú i el patró d'endpoint de salut ric que converteix /salut en la font de veritat de tota la plataforma.

Docker: De Principiant a Avançat

Mòdul 1: Introducció a Docker

Mòdul 2: Treballant amb Imatges Docker

Mòdul 3: Contenidors Docker

Mòdul 4: Docker Compose

Mòdul 5: Conceptes Avançats de Docker

Mòdul 6: Docker en Producció

Mòdul 7: Ecosistema i Eines de Docker

© Copyright 2026. Tots els drets reservats