Todas las construcciones del curso las ha hecho BuildKit sin que lo notaras. En esta lección tomas el control: montajes de caché que evitan reinstalar npm en cada build, secretos que no dejan rastro en ninguna capa, caché compartida entre tu máquina y CI, imágenes que funcionan en amd64 y arm64 a la vez, y un solo comando para construir toda la plataforma Aurora Libros.
Contenido
- Qué es BuildKit y en qué se diferencia del constructor clásico
- El grafo de una construcción multietapa
- Activarlo, verificarlo y leer su salida
- Buildx y los builders
- Montajes en
RUN:type=cache type=bindytype=tmpfstype=secret: credenciales que no dejan rastrotype=ssh: clonar repositorios privados- Caché remota compartida
- Multiarquitectura: QEMU y manifest lists
TARGETPLATFORMy compilación cruzada- Salidas con
--output,--loady--push docker buildx bake
- Qué es BuildKit y en qué se diferencia del constructor clásico
El constructor clásico ejecutaba el Dockerfile línea a línea, en orden estricto, creando un contenedor intermedio por instrucción. BuildKit lo sustituye por un motor que primero analiza el fichero completo y construye un grafo de dependencias.
| Aspecto | Constructor clásico | BuildKit |
|---|---|---|
| Ejecución | Secuencial, instrucción a instrucción | Grafo de dependencias: solo lo necesario |
| Etapas independientes | Una tras otra | En paralelo |
| Caché | Por capa, cadena lineal | Por contenido; sobrevive a reordenaciones |
| Contexto | Se envía entero al empezar | Se transfiere bajo demanda |
| Secretos | Imposibles sin filtrarlos | --mount=type=secret, sin rastro |
| Salida | Registro plano al final | Progreso en vivo, por paso y con tiempos |
| Caché externa | No | --cache-from / --cache-to con registros |
| Multiarquitectura | Un docker build por plataforma |
Una orden, varias plataformas |
La diferencia más útil en el día a día: si una etapa no aporta nada a la imagen final, BuildKit ni siquiera la ejecuta. Por eso la etapa pruebas de la lección 05-04 no se ejecuta al construir producción.
- El grafo de una construcción multietapa
flowchart LR CTX["Contexto<br/>(bajo demanda)"] --> D1["dependencias:<br/>apk add build-base"] CTX --> D2["desarrollo:<br/>npm ci completo"] D1 --> D3["dependencias:<br/>npm ci --omit=dev"] D2 --> T["pruebas:<br/>npm test"] D3 --> P["produccion:<br/>COPY --from=dependencias"] CTX --> P P --> IMG["Imagen final"] T -.->|"solo con --target pruebas"| X["no entra en el grafo<br/>de la imagen final"]
dependencias y desarrollo no dependen la una de la otra: BuildKit las ejecuta a la vez. Y pruebas cuelga de desarrollo, pero nada de la imagen final depende de pruebas, así que se poda del grafo.
- Activarlo, verificarlo y leer su salida
Desde Docker 23, BuildKit es el constructor por defecto en Linux, macOS y Windows.
github.com/docker/buildx v0.19.3
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 1.42kB doneLas líneas #n numeradas son la firma de BuildKit; el constructor antiguo mostraba Step 1/12 : FROM .... Si necesitas volver atrás por algún motivo puntual, DOCKER_BUILDKIT=0 docker build ..., pero considéralo un diagnóstico, no una opción.
Para depurar una construcción, el modo de progreso lo cambia todo:
#12 [dependencias 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 imprime la salida completa de cada comando con marcas de tiempo relativas, en lugar de la vista interactiva que colapsa las líneas. Es lo primero que hay que activar cuando una construcción falla y no entiendes por qué.
- Buildx y los builders
Buildx es el cliente moderno de construcción. Un builder es la instancia de BuildKit que hace el trabajo, y no todas tienen las mismas capacidades.
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
default * docker running linux/amd64, linux/386
default default running| Driver | Dónde corre | Multiarquitectura | Caché remota | Salidas |
|---|---|---|---|---|
docker (por defecto) |
Dentro del daemon | No | Solo inline |
Solo imagen local |
docker-container |
En un contenedor propio | Sí | Todas | Todas (--output) |
kubernetes |
Pods de un clúster | Sí | Todas | Todas |
remote |
Instancia BuildKit externa | Sí | Todas | Todas |
Para todo lo que viene después hace falta docker-container:
docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx inspect aurora | grep -E 'Name|Status|Platforms'Ese builder es un contenedor normal (buildx_buildkit_aurora0) con su propia caché, independiente de la del daemon. Se gestiona con docker buildx use/stop/rm, y se limpia con docker buildx prune --filter until=168h.
- Montajes en
RUN: type=cache
RUN: type=cacheUn montaje de caché es un directorio persistente entre construcciones que no forma parte de ninguna capa. Es la solución al desperdicio más común: reinstalar dependencias enteras porque cambió una línea de package.json.
# syntax=docker/dockerfile:1
FROM node:22-alpine AS dependencias
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
npm ci --omit=devdocker buildx build --target dependencias -t aurora-api:deps ./api # primera vez
# cambia una versión en package.json y repite
time docker buildx build --target dependencias -t aurora-api:deps ./apiPrimera construcción (caché fría): 42.7s
Tras cambiar package.json, sin caché de montaje: 39.1s
Tras cambiar package.json, con caché de montaje: 6.8sDe 39 a 7 segundos. La caché de npm no se ha descargado de nuevo: npm ha encontrado los paquetes en /root/.npm y solo ha resuelto el árbol. Y como el montaje no se incorpora a la capa, la imagen final no crece ni un byte.
| Parámetro | Valores | Significado |
|---|---|---|
target |
Ruta | Dónde se monta dentro del RUN |
id |
Texto | Identificador de la caché; compártelo entre Dockerfiles |
sharing |
shared (por defecto), locked, private |
Qué ocurre si dos builds la usan a la vez |
mode |
0755… |
Permisos del directorio |
uid/gid |
Números | Propietario, útil si el RUN no es root |
sharing=locked es el valor correcto para gestores de paquetes que no toleran escrituras concurrentes (npm, apt, pip). Los directorios de caché habituales: /root/.npm (npm), /var/cache/apt y /var/lib/apt/lists (apt), /root/.cache/pip (pip), /go/pkg/mod (Go), /root/.m2 (Maven).
Aviso. La caché de montaje es local al builder. No viaja con la imagen ni entre máquinas; para compartirla entre tu portátil y CI se usa la caché remota del apartado 9.
type=bind y type=tmpfs
type=bind y type=tmpfstype=bind monta ficheros del contexto (o de otra etapa) sin copiarlos a una capa, y type=tmpfs da un directorio en memoria para trabajo temporal:
RUN --mount=type=bind,source=package-lock.json,target=/tmp/lock.json \
node -e "console.log(require('/tmp/lock.json').lockfileVersion)"
# Desde otra etapa, sin arrastrar sus capas
RUN --mount=type=bind,from=dependencias,source=/app/node_modules,target=/deps du -sh /deps
RUN --mount=type=tmpfs,target=/tmp/trabajo \
tar xzf /origen.tar.gz -C /tmp/trabajo && cp /tmp/trabajo/binario /usr/local/bin/Sirven para leer o verificar algo durante la construcción sin que forme parte de la imagen. Descomprimir en tmpfs es rápido y garantiza que los ficheros intermedios no llegan a ninguna capa.
type=secret: credenciales que no dejan rastro
type=secret: credenciales que no dejan rastroEste es el apartado que resuelve el problema demostrado en las lecciones 05-03 y 05-04. Comparemos las dos formas de pasar un token de npm privado.
# ❌ INSEGURO: el ARG queda en los metadatos para siempre
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
npm ci && rm .npmrc# ✅ CORRECTO: el secreto se monta, se usa y desaparece
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=devecho "npm_tok_a91f3c" > /tmp/npm_token.txt
docker buildx build --secret id=npm_token,src=/tmp/npm_token.txt \
--load -t aurora-api:seguro ./api
# ¿Aparece el token en algún sitio?
docker history --no-trunc aurora-api:seguro | grep -c 'npm_tok_a91f3c'
docker image inspect aurora-api:seguro --format '{{json .Config.Env}}' | grep -c 'npm_tok'
docker save aurora-api:seguro | strings | grep -c 'npm_tok_a91f3c'Cero coincidencias en las tres comprobaciones. Compáralo con el mismo experimento usando ARG, donde docker history devolvía el token en claro. El mecanismo: BuildKit monta el fichero en /run/secrets/<id> como un tmpfs que existe solo durante ese RUN; no hay capa, no hay metadato, no hay rastro.
El secreto también puede venir de una variable de entorno, lo que encaja mejor con los gestores de secretos de CI:
export NPM_TOKEN=npm_tok_a91f3c
docker buildx build --secret id=npm_token,env=NPM_TOKEN --load -t aurora-api:seguro ./api
type=ssh: clonar repositorios privados
type=ssh: clonar repositorios privadosCuando una dependencia vive en un repositorio Git privado, el instinto es copiar una clave privada al build. Nunca hagas eso: type=ssh reenvía tu agente SSH sin que la clave entre en la construcción.
RUN --mount=type=ssh \
mkdir -p ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts && \
npm ci --omit=devssh-add -l >/dev/null || ssh-add ~/.ssh/id_ed25519
docker buildx build --ssh default --load -t aurora-api:privado ./apiLa clave nunca sale de tu agente: BuildKit expone un socket temporal dentro del RUN y las operaciones de firma ocurren en tu máquina. Al terminar la instrucción, el socket desaparece.
- Caché remota compartida
La caché local solo sirve a quien la generó. En CI, cada trabajo empieza con una máquina limpia y reconstruye todo desde cero. La caché remota lo resuelve: se publica en un registro y cualquier constructor puede reutilizarla.
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 | Dónde vive | Ventaja | Inconveniente |
|---|---|---|---|
registry |
En tu registro, como una etiqueta más | Compartida entre máquinas y con CI | Necesita permisos de escritura en el registro |
inline |
Dentro de la propia imagen | Cero configuración; funciona con el driver docker |
Solo caché de la última etapa (mode=min) |
gha |
Caché de GitHub Actions | Integrada, sin registro extra | Limitada a GitHub y con cuota de 10 GB |
local |
Directorio del disco | Rápida y sin red | No se comparte entre máquinas |
s3 / azblob |
Almacenamiento de objetos | Escalable y barata | Configuración de credenciales |
mode=max guarda la caché de todas las etapas, incluidas las intermedias; mode=min (por defecto) solo las capas de la imagen final. Para multietapa, mode=max es lo que de verdad ahorra tiempo, a costa de más espacio en el registro.
# Máquina limpia: reutiliza la caché 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 CACHEDNueve pasos resueltos desde la caché en una máquina que nunca había construido esta imagen. Esa es la pieza que hará que el pipeline de la lección 06-02 tarde segundos en lugar de minutos.
- Multiarquitectura: QEMU y manifest lists
Los portátiles con Apple Silicon son arm64 y buena parte de los servidores de nube también (Graviton, Ampere), mientras que la mayoría de CI sigue siendo amd64. Una imagen construida solo para amd64 falla o va lentísima bajo emulación en arm64.
docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx inspect aurora --bootstrap | grep Platformsbinfmt_misc es la funcionalidad del kernel que asocia un intérprete a los binarios de otra arquitectura; ese contenedor privilegiado registra los emuladores QEMU correspondientes (y sí, el --privileged de ahí está justificado: registra manejadores en el kernel, y es una operación puntual de configuración 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.0Name: 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/arm64Un manifest list (o image index) es un índice que apunta a una imagen por plataforma. Cuando alguien hace docker pull auroralibros/aurora-api:1.3.0, el cliente informa de su arquitectura y el registro le entrega el manifiesto adecuado: la misma etiqueta funciona en el Mac del equipo y en el servidor Graviton, sin sufijos ni ramas.
Nota importante: --platform con varias arquitecturas obliga a --push, porque el almacén local del daemon no sabe guardar un índice multiplataforma. Con --load solo puedes cargar una plataforma cada vez.
TARGETPLATFORM y compilación cruzada
TARGETPLATFORM y compilación cruzadaEmular una arquitectura con QEMU funciona, pero es lento —de tres a diez veces más—. El patrón profesional es compilar de forma nativa y usar la emulación solo para la etapa final. BuildKit expone variables automáticas para ello:
| Variable | Qué contiene |
|---|---|
BUILDPLATFORM |
Plataforma de la máquina que construye (p. ej. linux/amd64) |
TARGETPLATFORM |
Plataforma destino (linux/arm64) |
TARGETOS, TARGETARCH, TARGETVARIANT |
Sus componentes por separado |
# La etapa de compilación corre SIEMPRE en la arquitectura nativa: rápido
FROM --platform=$BUILDPLATFORM node:22-alpine AS constructor
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Construyendo en $BUILDPLATFORM para $TARGETPLATFORM"
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
COPY src/ ./src/
# La etapa final sí es de la arquitectura destino
FROM node:22-alpine AS produccion
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 Construyendo#8 0.312 Construyendo en linux/amd64 para linux/amd64
#9 0.298 Construyendo en linux/amd64 para linux/arm64Las dos construcciones ocurren en amd64: la segunda compila para arm64 sin emular. Con lenguajes compilados (Go, Rust) la ganancia es enorme, porque basta con GOARCH=$TARGETARCH. Con Node.js el beneficio es menor —el código es interpretado—, pero el patrón sigue evitando emular npm ci, que es la parte lenta.
- Salidas con
--output, --load y --push
--output, --load y --pushCon el driver docker-container, el resultado no va automáticamente a tu daemon: hay que decir dónde lo quieres.
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 registro
docker buildx build --output type=local,dest=./salida ./api # ficheros sueltos
docker buildx build --output type=oci,dest=aurora-api-oci.tar ./api # formato OCI| Salida | Qué produce | Para qué |
|---|---|---|
--load (= type=docker) |
Imagen en tu daemon | Desarrollo y pruebas locales |
--push (= type=image,push=true) |
Imagen en el registro | Publicación y multiarquitectura |
type=local |
Los ficheros del sistema de la última etapa | Extraer artefactos compilados |
type=oci / type=tar |
Archivo OCI o tar del sistema de ficheros | Firma, escaneo, entornos sin daemon |
type=cacheonly |
Nada; solo calienta la caché | Precalentar CI |
type=local tiene un uso elegante: usar Docker como sistema de compilación reproducible y quedarte solo con el resultado, sin imagen de por medio. Y type=tar,dest=aurora-api.tar produce un tar del sistema de ficheros para transporte o análisis.
docker buildx bake
docker buildx bakeConstruir Aurora Libros son varias órdenes largas y fáciles de teclear mal. Bake las declara en un fichero:
# docker-bake.hcl — Aurora Libros S.L.
variable "VERSION" { default = "1.3.0" }
variable "REGISTRO" { default = "auroralibros" }
variable "PLATAFORMAS" { default = "linux/amd64,linux/arm64" }
group "default" {
targets = ["api", "web"]
}
target "comun" {
platforms = split(",", PLATAFORMAS)
cache-from = ["type=registry,ref=${REGISTRO}/buildcache"]
cache-to = ["type=registry,ref=${REGISTRO}/buildcache,mode=max"]
labels = {
"org.opencontainers.image.version" = VERSION
"org.opencontainers.image.vendor" = "Aurora Libros S.L."
}
}
target "api" {
inherits = ["comun"]
context = "./api"
target = "produccion"
tags = ["${REGISTRO}/aurora-api:${VERSION}", "${REGISTRO}/aurora-api:latest"]
args = { VERSION = VERSION }
}
target "web" {
inherits = ["comun"]
context = "./web"
tags = ["${REGISTRO}/aurora-web:${VERSION}"]
}
# Matriz: la misma imagen con varias versiones de Node
target "api-matriz" {
inherits = ["comun"]
context = "./api"
name = "api-node${nodo}"
matrix = { nodo = ["20", "22", "23"] }
args = { NODE_VERSION = nodo }
tags = ["${REGISTRO}/aurora-api:${VERSION}-node${nodo}"]
}docker buildx bake --print # ver el plan sin construir
docker buildx bake # grupo "default": api y web
docker buildx bake api --set api.tags=aurora-api:local --load
VERSION=1.4.0 docker buildx bake --push
docker buildx bake api-matrizTres cosas hacen que bake merezca la pena: inherits evita repetir la configuración común, los objetivos se construyen en paralelo (aquí, api y web a la vez), y --print te enseña el plan resuelto antes de ejecutar nada. Bake también lee un compose.yaml directamente (docker buildx bake -f compose.yaml), aprovechando las secciones build que ya tienes escritas.
Errores Comunes y Consejos
Usar el driver docker y esperar multiarquitectura o caché remota. Crea un builder docker-container con docker buildx create --use.
Olvidar --load o --push con docker-container. La construcción termina bien y la imagen no aparece en ninguna parte.
Intentar --load con varias plataformas. El almacén local no guarda índices multiplataforma. Usa --push o carga una sola.
Pasar secretos con --build-arg. Quedan en docker history. --mount=type=secret, siempre.
Creer que la caché de montaje viaja con la imagen. Es local al builder. Entre máquinas, caché remota.
Usar mode=min en caché remota de una build multietapa. Solo cachea la imagen final; las etapas caras se rehacen. mode=max.
Dejar crecer la caché del builder sin límite. docker buildx prune --filter until=168h de forma periódica, o --keep-storage.
Consejo: cuando una construcción falle de forma incomprensible, el orden es --progress=plain para ver la salida completa, --no-cache para descartar una caché envenenada y --target <etapa> para aislar el punto exacto del fallo. Con esos tres se resuelve la práctica totalidad de los casos.
Ejercicios
Ejercicio 1. Crea un builder docker-container, añade un montaje type=cache a la instalación de npm de aurora-api y mide la diferencia: construye una vez, cambia package.json y reconstruye, con y sin el montaje. Explica por qué la imagen final no crece.
Ejercicio 2. Demuestra que --mount=type=secret no deja rastro: construye la misma imagen pasando un token con --build-arg y con --secret, y busca el token en el historial, en el entorno y en el tarball exportado de cada una.
Ejercicio 3. Publica auroralibros/aurora-api:1.3.0 para linux/amd64 y linux/arm64, inspecciona el manifest list resultante y explica qué ocurre exactamente cuando un servidor arm64 y un portátil amd64 hacen docker pull de la misma etiqueta.
Soluciones
Solución 1.
docker buildx create --name aurora --driver docker-container --use --bootstrap
docker buildx build --no-cache --target dependencias --load -t a:v1 ./api 2>&1 | tail -1
sed -i 's/"express": "4.19.3"/"express": "4.19.2"/' api/package.json
# Sin montaje de caché (Dockerfile.sincache)
time docker buildx build -f api/Dockerfile.sincache --target dependencias --load -t a:v2 ./api
# Con montaje de caché
time docker buildx build --target dependencias --load -t a:v3 ./api
docker image ls a --format "{{.Tag}}\t{{.Size}}"Un cambio en package.json invalida la capa COPY package.json y todo lo que viene después, así que npm ci se vuelve a ejecutar en ambos casos. La diferencia está dentro de esa ejecución: sin montaje, npm descarga los 64 paquetes otra vez; con montaje, encuentra /root/.npm poblada de la construcción anterior y solo resuelve el árbol y enlaza. La red desaparece del camino crítico y quedan 6,8 segundos.
Y la imagen pesa exactamente lo mismo (198 MB) porque un montaje de caché no participa en la capa: BuildKit lo monta antes de ejecutar el comando y lo desmonta después, de modo que cuando se consolida el sistema de ficheros de la capa, /root/.npm ya no está. Es la diferencia esencial con COPY: uno deja huella, el otro no.
Solución 2.
TOKEN="npm_tok_a91f3c"
echo "$TOKEN" > /tmp/tok.txt
# A) Con ARG
docker buildx build -f api/Dockerfile.arg --build-arg NPM_TOKEN="$TOKEN" --load -t fuga:arg ./api
# B) Con secret
docker buildx build --secret id=npm_token,src=/tmp/tok.txt --load -t seguro:sec ./api
for img in fuga:arg seguro: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"
doneLa imagen construida con ARG filtra el token por tres vías independientes: el historial de construcción (donde queda la línea del RUN con el valor sustituido), la configuración de la imagen (donde el ARG se registra como metadato) y el contenido de las capas (donde el .npmrc escrito y borrado sigue presente bajo un whiteout). Basta con que se te escape una para que el secreto esté publicado.
Con --secret las tres dan cero. BuildKit monta el fichero como tmpfs en /run/secrets/npm_token únicamente mientras dura ese RUN; el montaje no forma parte del sistema de ficheros que se consolida en la capa, y el valor no aparece en ningún metadato porque nunca fue un argumento de construcción. Es, literalmente, la única forma correcta de usar credenciales durante un build.
Solución 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 -3linux/amd64 sha256:9e2a17f4c8b3...
linux/arm64 sha256:5b70c3d8a1e6...
{
"mediaType": "application/vnd.oci.image.index.v1+json",La etiqueta 1.3.0 no apunta a una imagen: apunta a un índice (image.index.v1+json) que contiene dos manifiestos, cada uno con su propio digest y su plataforma declarada.
Cuando el servidor arm64 ejecuta docker pull auroralibros/aurora-api:1.3.0, su cliente envía en la cabecera Accept los tipos que entiende e informa de su plataforma; el registro devuelve el índice, el cliente busca la entrada linux/arm64, y descarga solo el manifiesto sha256:5b70c3d8... y sus capas. El portátil amd64, con el mismo comando y la misma etiqueta, se lleva sha256:9e2a17f4.... Ninguno descarga bytes de la otra arquitectura.
Las tres consecuencias prácticas: el compose.prod.yaml puede llevar una sola referencia de imagen válida para toda la flota; si fijas por digest (lección 05-03) debes fijar el del índice, no el de una plataforma concreta, o romperás la portabilidad; y si un día alguien construye sin --platform en su Mac y hace push con esa misma etiqueta, sustituirá el índice por una imagen arm64 suelta y los servidores amd64 fallarán con exec format error. Por eso la construcción multiarquitectura pertenece al pipeline y no al portátil de nadie.
Conclusión
BuildKit ha dejado de ser una caja negra. Sabes que no ejecuta el Dockerfile línea a línea, sino que construye un grafo de dependencias, paraleliza las etapas independientes, transfiere el contexto bajo demanda y poda lo que no aporta a la imagen final —de ahí que la etapa pruebas no se ejecute al construir producción—. Sabes verificarlo, leer su salida y, cuando algo falla, activar --progress=plain para ver la ejecución real. Y conoces Buildx y sus builders: el driver docker para lo básico y docker-container para todo lo demás, con su caché propia y sus plataformas emuladas.
Dominas los cuatro montajes de RUN. type=cache convirtió una reinstalación de dependencias de 39 segundos en 7 sin que la imagen crezca un byte, porque el montaje no forma parte de la capa. type=bind lee ficheros sin copiarlos, type=tmpfs da espacio temporal en memoria, y type=secret cierra por fin el problema que arrastrabas desde la lección 02-04: el token pasado con --build-arg aparecía en el historial, en la configuración y en el tarball; pasado con --secret, cero coincidencias en las tres. type=ssh completa el cuadro reenviando tu agente para clonar repositorios privados sin que ninguna clave entre en la construcción.
Y sabes trabajar más allá de tu máquina: caché remota con --cache-from/--cache-to y sus backends —registry, gha, local, inline— con mode=max para que las etapas intermedias también se reutilicen, lo que dejará el pipeline de la lección 06-02 en segundos; construcción multiarquitectura con QEMU y binfmt_misc, manifest lists que hacen que una sola etiqueta sirva para el Mac del equipo y para el servidor Graviton, y el patrón --platform=$BUILDPLATFORM con TARGETARCH para compilar de forma nativa en vez de emular. Cierras con las salidas de --output y con docker buildx bake, que reduce toda la construcción de Aurora Libros a un docker buildx bake --push con herencia, variables y matrices.
En la lección 05-06 dejas de construir y empiezas a observar. Verás el registro de una plataforma en marcha: los drivers de logging con su tabla comparativa, la rotación obligatoria y qué pasa exactamente si no la configuras, logs estructurados en JSON con identificador de petición, y una pila de agregación con Loki, Promtail y Grafana levantada como perfil observabilidad de Aurora Libros. Después, las métricas: docker stats y sus límites, el endpoint Prometheus del daemon, cAdvisor y node-exporter, las métricas que de verdad se vigilan, las alertas que merecen despertar a alguien y el patrón de endpoint de salud rico que convierte /salud en la fuente de verdad de toda la plataforma.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
