La imatge auroralibros/aurora-api:1.2.0 pesa 142 MB. No és cap desastre, però tampoc no està optimitzada: a dins hi viatja un compilador de C, les dependències de desenvolupament i fitxers que ningú no executa. En aquesta lliçó la poses a règim mesurant a cada pas, fins a una xifra concreta, sense perdre res de l'enduriment de la lliçó 05-03.

Contingut

  1. Per què importa la mida i què no s'ha de sacrificar
  2. Mesurar primer: image ls i history
  3. dive: explorar capa a capa
  4. Elecció de la base, amb xifres
  5. Construccions multietapa: el concepte
  6. Multietapa aplicada a aurora-api
  7. Etapes amb nom i --target
  8. Imatge base comuna per a diverses imatges
  9. Reduir capes i contingut
  10. Esborrar un fitxer no redueix la mida (demostrat)
  11. Ordenar per a la memòria cau
  12. Verificació final i taula resum

  1. Per què importa la mida i què no s'ha de sacrificar

Motiu Impacte concret
Temps de descàrrega Cada node nou descarrega la imatge sencera la primera vegada
Cost d'emmagatzematge Registre d'imatges, memòria cau de cada node i cada versió conservada
Superfície d'atac Menys paquets, menys CVE (lliçó 05-03)
Velocitat de desplegament Un rollback urgent és tan ràpid com la descàrrega
Escalat automàtic Un pic de trànsit exigeix arrencar rèpliques ara

En una plataforma amb cinc nodes i quinze desplegaments al mes, abaixar 40 MB per imatge són uns 3 GB menys de transferència mensual i desenes de segons menys a cada arrencada en fred.

I el que no se sacrifica per aprimar: el HEALTHCHECK, l'usuari sense privilegis, les etiquetes OCI de traçabilitat, la capacitat de diagnosticar un problema en producció i la reproductibilitat de la construcció. Una imatge de 40 MB que ningú no sap depurar a les tres de la matinada és un mal negoci.

  1. Mesurar primer: image ls i history

docker image ls auroralibros/aurora-api --format "{{.Tag}}\t{{.Size}}"
docker image history auroralibros/aurora-api:1.2.0 --format "{{.Size}}\t{{.CreatedBy}}" | head -8
1.2.0   142MB
21.4MB   RUN npm ci --omit=dev
3.1MB    COPY src/ ./src/
1.8MB    RUN apk add --no-cache tini
0B       ENV NODE_ENV=production
0B       USER node
0B       HEALTHCHECK &{["CMD-SHELL" "wget -qO- ..."]}
115MB    /bin/sh -c #(nop) ADD file:... in /

El diagnòstic és immediat i sorprèn gairebé tothom la primera vegada: dels 142 MB, 115 MB són la imatge base. Les dependències són 21 MB i el codi propi, 3 MB. La palanca principal no és al teu codi: és en quina base tries i en què hi afegeixes a sobre.

Per comparar amb precisió fes servir la mida real (docker image inspect --format '{{.Size}}' | numfmt --to=iec) i no la que mostra el registre d'imatges, que està comprimida i sempre sembla menor.

  1. dive: explorar capa a capa

docker history diu quant pesa cada capa; dive diu què hi ha a dins i quant se'n malbarata.

docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest auroralibros/aurora-api:1.2.0
│ Image Details │
Total Image size: 142 MB
Potential wasted space: 9.7 MB
Image efficiency score: 93 %

Count   Total Space  Path
    2         6.1 MB /app/node_modules/.cache
    2         2.4 MB /root/.npm/_cacache
    3         1.2 MB /app/.git

Tres troballes i tres decisions: la memòria cau d'npm no hauria de ser a la imatge final, node_modules/.cache és brossa de compilació, i .git no hauria d'haver entrat mai al context de construcció (falta una línia al .dockerignore).

L'efficiency score mesura quant contingut s'escriu en una capa i se sobreescriu o s'esborra en una altra; per sota del 95 % hi ha feina a fer. Per automatitzar-ho a CI, afegeix -e CI=true i --lowestEfficiency=0.95: dive acaba amb codi diferent de zero si no s'assoleix el llindar.

  1. Elecció de la base, amb xifres

Base Mida libc Eines Quan triar-la
node:22 ~1,1 GB glibc Totes: git, compiladors, curl Només com a etapa de compilació
node:22-slim ~230 MB glibc Mínimes de Debian Dependències natives exigents amb glibc
node:22-alpine ~135 MB musl BusyBox, apk Cas general: l'equilibri
distroless/nodejs22 ~110 MB glibc Cap Producció endurida
scratch 0 MB Cap Només binaris estàtics (Go, Rust)

La contrapartida d'Alpine té nom: musl en lloc de glibc. Conseqüències reals:

  • Els paquets npm amb binaris precompilats per a glibc poden no funcionar i haver-se de compilar en instal·lar-los (més lent, i exigeix build-base).
  • Algunes càrregues intensives en DNS o en resolució de noms es comporten de manera diferent amb musl.
  • Certes biblioteques científiques i d'aprenentatge automàtic no suporten musl.

aurora-api fa servir Express, pg i redis, cap amb dependències natives problemàtiques: Alpine és l'elecció correcta. Si un dia una dependència donés problemes, node:22-slim costa 95 MB més i els resol.

  1. Construccions multietapa: el concepte

Una construcció multietapa fa servir diverses imatges base al mateix Dockerfile. Les etapes intermèdies compilen i preparen; l'etapa final copia només el resultat. Tota la resta —compiladors, memòries cau, dependències de desenvolupament, codi font— es queda fora de la imatge publicada.

flowchart LR
  subgraph E1["Etapa: dependencies (node:22-alpine)"]
    A["package*.json"] --> B["npm ci --omit=dev<br/>+ build-base per compilar"]
  end
  subgraph E2["Etapa: desenvolupament"]
    C["npm ci complet<br/>devDependencies, proves"]
  end
  subgraph E3["Etapa final (node:22-alpine)"]
    D["COPY --from=dependencies node_modules"]
    E["COPY src/"]
  end
  B -.->|"COPY --from"| D
  E3 --> F["Imatge publicada:<br/>sense compiladors ni memòries cau"]
  E2 -.->|"només amb --target"| G["Imatge de desenvolupament"]

Dues regles que resumeixen el mecanisme:

  • Només l'última etapa (o la indicada amb --target) acaba a la imatge; les altres es descarten.
  • COPY --from=<etapa> porta fitxers concrets d'una etapa anterior, i res més no hi viatja: ni les seves capes, ni el seu historial, ni els seus secrets.

  1. Multietapa aplicada a aurora-api

Punt de partida (una sola etapa, 142 MB) i destinació:

# syntax=docker/dockerfile:1
ARG NODE_VERSION=22

# ---------- Etapa 1: dependències de producció ----------
FROM node:${NODE_VERSION}-alpine AS dependencies
WORKDIR /app
# build-base només viu aquí: compila el que calgui i NO viatja a la final
RUN apk add --no-cache --virtual .build python3 make g++
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force

# ---------- Etapa 2: desenvolupament i proves (no es publica) ----------
FROM node:${NODE_VERSION}-alpine AS desenvolupament
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "--watch", "src/server.js"]

# ---------- Etapa 3: imatge final, mínima ----------
FROM node:${NODE_VERSION}-alpine AS produccio
ARG VERSION=1.3.0
ARG REVISION=desconeguda
LABEL org.opencontainers.image.title="aurora-api" \
      org.opencontainers.image.version="${VERSION}" \
      org.opencontainers.image.revision="${REVISION}" \
      org.opencontainers.image.source="https://github.com/aurora-libros/aurora-api"
WORKDIR /app
ENV NODE_ENV=production PORT=3000
COPY --from=dependencies --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node package.json ./
COPY --chown=node:node src/ ./src/
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD wget -qO- http://127.0.0.1:3000/salut || exit 1
ENTRYPOINT ["node"]
CMD ["src/server.js"]
docker build -t auroralibros/aurora-api:1.3.0 --build-arg VERSION=1.3.0 ./api
docker image ls auroralibros/aurora-api --format "{{.Tag}}\t{{.Size}}"
1.3.0   121MB
1.2.0   142MB

142 MB → 121 MB: un 15 % menys amb un sol canvi estructural. D'on surten els 21 MB? De build-base (python3, make, g++), que s'instal·lava a la imatge final i ara viu i mor a l'etapa dependencies, i de la memòria cau d'npm.

Fixa't que no s'ha perdut res del que estava endurit: USER node, el HEALTHCHECK, les etiquetes OCI i l'ENTRYPOINT/CMD en forma exec continuen allà. Optimitzar no és retallar seguretat.

  1. Etapes amb nom i --target

Les etapes amb nom no són només organització: són artefactes construïbles per separat.

docker build --target desenvolupament -t aurora-api:dev ./api
docker build --target dependencies -t aurora-api:deps ./api
docker build -t auroralibros/aurora-api:1.3.0 ./api      # última etapa
docker image ls aurora-api --format "{{.Tag}}\t{{.Size}}"
dev     318MB
deps    198MB

La imatge de desenvolupament pesa 318 MB i està bé que sigui així: porta les devDependencies, el marc de proves i les eines de depuració. És exactament el target: desenvolupament que compose.override.yaml fa servir des de la lliçó 04-07, i ara veus d'on surt.

Un patró molt útil és una etapa de proves que fa fallar la construcció si les proves fallen:

FROM desenvolupament AS proves
RUN npm test
docker build --target proves ./api || echo "BUILD TRENCADA: proves en vermell"

BuildKit no executa aquella etapa en construir la de producció, perquè no forma part del seu graf de dependències (lliçó 05-05). Només s'executa si la demanes amb --target.

  1. Imatge base comuna per a diverses imatges

Quan publiques diversos serveis en Node, convé una base pròpia amb el que és compartit:

# aurora-base/Dockerfile -> auroralibros/aurora-base-node:22
FROM node:22-alpine
RUN apk add --no-cache tini tzdata && \
    addgroup -g 1001 aurora && adduser -u 1001 -G aurora -s /bin/sh -D aurora
ENV TZ=Europe/Madrid NODE_ENV=production
ENTRYPOINT ["/sbin/tini", "--"]
FROM auroralibros/aurora-base-node:22 AS produccio

Avantatges: una sola actualització de seguretat es propaga a tots els serveis, la capa base es comparteix al disc i a la descàrrega entre ells, i les decisions comunes són en un sol lloc. Inconvenient: crees una dependència interna que cal mantenir i versionar. Compensa a partir de tres o quatre serveis.

  1. Reduir capes i contingut

# ❌ Tres capes, i la memòria cau d'apk queda a dins per sempre
RUN apk update
RUN apk add curl
RUN rm -rf /var/cache/apk/*

# ✅ Una capa, sense memòria cau: el que no s'escriu no ocupa
RUN apk add --no-cache curl
Tècnica Què fa Estalvi típic
apk add --no-cache Evita escriure l'índex de paquets 5-10 MB
apt-get ... && rm -rf /var/lib/apt/lists/* al mateix RUN Igual a Debian 30-50 MB
--no-install-recommends No instal·la paquets "suggerits" 20-100 MB
npm ci --omit=dev Sense dependències de desenvolupament 40-70 % de node_modules
npm cache clean --force Elimina ~/.npm/_cacache 10-30 MB
--virtual .build + apk del .build Compiladors temporals a la mateixa capa 100-200 MB
.dockerignore afinat Impedeix que hi entri el que no toca Variable, de vegades enorme

El .dockerignore d'aurora-api després del que va revelar dive:

.git
.gitignore
node_modules
npm-debug.log
Dockerfile*
compose*.yaml
.env
.env.*
!.env.example
coverage/
.nyc_output/
*.md
!README.md
.vscode/
.idea/
proves/
**/*.test.js
du -sh api/ && docker build -t aurora-api:ctx ./api 2>&1 | grep "transferring context"
94M     api/
=> transferring context: 812.43kB

De 94 MB de directori a 812 kB de context. Això no només fa la construcció més ràpida: garanteix que .git i .env no es puguin colar en cap capa, cosa que és un assumpte de seguretat a més de mida.

  1. Esborrar un fitxer no redueix la mida (demostrat)

FROM alpine:3
RUN dd if=/dev/zero of=/temporal.bin bs=1M count=100    # capa A: +100 MB
RUN rm /temporal.bin                                     # capa B: whiteout
docker build -q -t esborrat:mal /tmp/esborrat
docker image ls esborrat:mal --format "{{.Size}}"
docker run --rm esborrat:mal ls -la /temporal.bin 2>&1
docker image history esborrat:mal --format "{{.Size}}\t{{.CreatedBy}}" | head -3
113MB
ls: /temporal.bin: No such file or directory
0B       RUN rm /temporal.bin
105MB    RUN dd if=/dev/zero of=/temporal.bin bs=1M count=100
8MB      /bin/sh -c #(nop) ADD file:... in /

El fitxer no existeix i la imatge continua pesant 113 MB. L'explicació és la de la lliçó 05-02: les capes són immutables i rm en una capa posterior només crea un whiteout que amaga el fitxer; els 105 MB continuen allà baix, es descarreguen a cada pull i s'emmagatzemen a cada node.

La correcció és unir creació i esborrat a la mateixa instrucció:

FROM alpine:3
RUN dd if=/dev/zero of=/temporal.bin bs=1M count=100 && \
    echo "fer servir el fitxer..." && \
    rm /temporal.bin
docker build -q -t esborrat:be /tmp/esborrat2 && docker image ls esborrat:be --format "{{.Size}}"
8.15MB

De 113 MB a 8,15 MB. La mateixa lògica, el mateix resultat funcional, un && de diferència. I és exactament el motiu de --virtual .build al Dockerfile de l'apartat 6: instal·lar, compilar i desinstal·lar dins d'un sol RUN.

  1. Ordenar per a la memòria cau

Recordatori de la lliçó 02-02, ara combinat amb multietapa: les instruccions s'ordenen de menys a més canviant.

COPY package.json package-lock.json ./   # canvia poc
RUN npm ci --omit=dev                    # capa cara, reutilitzable
COPY src/ ./src/                         # canvia a cada commit

Amb multietapa l'efecte es multiplica: en tocar src/server.js, BuildKit reutilitza tota l'etapa dependencies —inclosa la instal·lació d'npm— i només refà les dues últimes capes de l'etapa final.

touch api/src/server.js
time docker build -t auroralibros/aurora-api:1.3.0 ./api
 => CACHED [dependencies 4/4] RUN npm ci --omit=dev
 => [produccio 5/5] COPY --chown=node:node src/ ./src/     0.2s
real    0m3.184s

Tres segons davant dels quaranta i escaig d'una construcció neta. Optimitzar la mida i optimitzar la velocitat de construcció són, gairebé sempre, la mateixa feina.

  1. Verificació final i taula resum

Una imatge optimitzada que no funciona no val res. La comprovació és obligatòria:

docker compose up -d --wait
docker compose exec aurora-api node --version
curl -s http://localhost:8080/api/salut | jq -c
curl -s http://localhost:8080/api/llibres | jq -c '{origen, total}'
curl -s http://localhost:8080/api/llibres/3 | jq -c '{titol, autor}'
docker inspect aurora-libros-aurora-api-1 --format 'user={{.Config.User}} salut={{.State.Health.Status}}'
v22.11.0
{"estat":"ok","bd":"connectada","cache":"connectada","version":"1.3.0"}
{"origen":"db","total":9}
{"titol":"Cien años de soledad","autor":"Gabriel García Márquez"}
user=node salut=healthy

I l'últim esglaó, si el teu context ho permet: canviar la base final a distroless.

FROM gcr.io/distroless/nodejs22-debian12 AS produccio
COPY --from=dependencies --chown=1000:1000 /app/node_modules /app/node_modules
COPY --chown=1000:1000 src/ /app/src/
WORKDIR /app
USER 1000
CMD ["src/server.js"]        # distroless ja té node com a ENTRYPOINT
docker build -t auroralibros/aurora-api:1.3.0-distroless ./api
docker image ls auroralibros/aurora-api --format "{{.Tag}}\t{{.Size}}"
1.3.0-distroless   102MB
1.3.0              121MB
1.2.0              142MB

142 MB → 102 MB: un 28 % menys. El preu, ja conegut de la lliçó 05-03: sense sh no hi ha docker exec ... sh i el HEALTHCHECK amb wget deixa de funcionar, així que la sonda passa a fer-se des de Compose o des de l'orquestrador.

Tècnica Estalvi típic Cost
Base -alpine en comptes de completa 800-900 MB musl en lloc de glibc
Multietapa (fora compiladors i devDependencies) 20-40 % Dockerfile una mica més llarg
--virtual + apk del al mateix RUN 100-200 MB Cap
npm ci --omit=dev + cache clean 40-70 % de node_modules Cap
.dockerignore afinat Variable (aquí, 93 MB de context) Cal mantenir-lo
Agrupar RUN amb && El que ocupi el que és temporal Menys granularitat de memòria cau
Base distroless 15-25 MB més Sense shell: depuració difícil
Fusionar totes les capes Poc Trenca la memòria cau: gairebé mai no compensa

Errors Habituals i Consells

Optimitzar sense mesurar. Sense docker history i dive, es retalla on no fa mal i es deixa intacta la capa de 100 MB.

Esborrar fitxers en un RUN posterior. El fitxer desapareix i l'espai no. Creació i esborrat, a la mateixa instrucció.

Copiar tot el projecte amb COPY . . sense .dockerignore. Hi entren .git, node_modules i, amb sort, algun .env.

Instal·lar eines de compilació a la imatge final. Compiladors en producció són pes i superfície d'atac. Van a l'etapa de dependències.

Sacrificar el HEALTHCHECK o l'USER per estalviar uns megues. L'intercanvi és pèssim: la seguretat i l'operabilitat valen més que 3 MB.

Perseguir el mínim absolut. Un binari a scratch que no pots depurar costa més car a la primera incidència del que va estalviar en un any de descàrregues.

Consell: integra el mesurament al flux de treball. Un pas de CI que compari la mida de la imatge amb la de la versió anterior i avisi si creix més d'un 10 % detecta la dependència inflada el dia que entra, no sis mesos després.

Exercicis

Exercici 1. Analitza auroralibros/aurora-api:1.2.0 amb history i amb dive: identifica la capa més gran, calcula quin percentatge del total representa la base, i troba com a mínim un malbaratament real que puguis eliminar amb .dockerignore.

Exercici 2. Converteix el Dockerfile d'aurora-api a multietapa amb tres etapes (dependencies, desenvolupament, produccio), mesura l'abans i el després, i demostra que la imatge final no conté el compilador que sí que és a l'etapa de dependències.

Exercici 3. Demostra que esborrar un fitxer en una capa posterior no redueix la mida i arregla-ho. Calcula l'estalvi exacte i explica què hauria passat si aquell fitxer hagués estat un fitxer de credencials.

Solucions

Solució 1.

docker image history auroralibros/aurora-api:1.2.0 --format "{{.Size}}\t{{.CreatedBy}}" \
  | sort -h -r | head -3
total=$(docker image inspect auroralibros/aurora-api:1.2.0 --format '{{.Size}}')
base=$(docker image inspect node:22-alpine --format '{{.Size}}')
echo "base: $((base * 100 / total)) % del total"
115MB    /bin/sh -c #(nop) ADD file:... in /
21.4MB   RUN npm ci --omit=dev
3.1MB    COPY src/ ./src/
base: 81 % del total

El 81 % de la imatge és la base. És la dada que reordena les prioritats: barallar-se pels 3 MB del codi propi és irrellevant mentre no es decideixi conscientment quina base es fa servir. Amb dive apareix el malbaratament concret:

Potential wasted space: 9.7 MB
    2         6.1 MB /app/node_modules/.cache
    2         2.4 MB /root/.npm/_cacache
    3         1.2 MB /app/.git

.git dins d'una imatge de producció no és només 1,2 MB de pes: és l'historial complet del repositori viatjant al registre d'imatges, amb qualsevol secret que algú hagués fet servir en un commit i després esborrat. Es corregeix amb una línia al .dockerignore, i la comprovació és directa:

docker run --rm auroralibros/aurora-api:1.3.0 ls -a /app | grep -c '^\.git$'   # -> 0

Solució 2.

docker build -t aurora-api:abans -f api/Dockerfile.mono ./api
docker build -t aurora-api:despres ./api
docker image ls aurora-api --format "{{.Tag}}\t{{.Size}}"
abans     142MB
despres   121MB
# Hi és, el compilador, a cada etapa?
docker build -q --target dependencies -t aurora-api:deps ./api >/dev/null
docker run --rm aurora-api:deps sh -c 'which g++ make python3 | wc -l'
docker run --rm aurora-api:despres sh -c 'which g++ make python3 2>/dev/null | wc -l'
docker run --rm aurora-api:despres sh -c 'ls node_modules | wc -l'
docker run --rm aurora-api:deps sh -c 'ls node_modules | wc -l'
3
0
64
64

L'etapa dependencies té els tres binaris de compilació; la final, cap. I tanmateix totes dues tenen els mateixos 64 paquets a node_modules: COPY --from=dependencies ha portat exactament el resultat de la feina i res de l'utillatge que la va produir.

Aquesta és la idea central de multietapa expressada de la manera més clara possible: el que necessites per construir no és el que necessites per executar. I el benefici no és només de mida; també és de seguretat, perquè un atacant amb execució de comandes a la imatge final no hi troba cap compilador amb què preparar la fase següent del seu atac.

Solució 3.

mkdir -p /tmp/b1 /tmp/b2
printf 'FROM alpine:3\nRUN dd if=/dev/zero of=/t.bin bs=1M count=100\nRUN rm /t.bin\n' > /tmp/b1/Dockerfile
printf 'FROM alpine:3\nRUN dd if=/dev/zero of=/t.bin bs=1M count=100 && rm /t.bin\n' > /tmp/b2/Dockerfile
docker build -q -t mal /tmp/b1 >/dev/null && docker build -q -t be /tmp/b2 >/dev/null
docker image ls mal be --format "{{.Repository}}\t{{.Size}}"
docker run --rm mal ls /t.bin 2>&1
mal     113MB
be      8.15MB
ls: /t.bin: No such file or directory

L'estalvi exacte són 104,85 MB, un 92,8 % del total, i el resultat funcional és idèntic: en totes dues imatges el fitxer no existeix. La diferència és si els 100 MB van arribar a formar part d'una capa consolidada. A mal, la capa A els va escriure i van quedar immutables; la capa B només hi va afegir un whiteout que els amaga. A be, el fitxer va néixer i va morir dins de la mateixa instrucció, així que quan BuildKit va consolidar la capa ja no hi era.

La pregunta sobre les credencials porta la demostració a la seva conclusió més incòmoda:

printf 'FROM alpine:3\nRUN echo "aurora:S3cr3t0_2026" > /cred.txt && cat /cred.txt >/dev/null\nRUN rm /cred.txt\n' > /tmp/b1/Dockerfile
docker build -q -t cred /tmp/b1 >/dev/null
docker run --rm cred ls /cred.txt 2>&1
docker save cred | tar -xO --wildcards '*/layer.tar' 2>/dev/null | strings | grep -o 'S3cr3t0_[0-9]*'
ls: /cred.txt: No such file or directory
S3cr3t0_2026

El fitxer no existeix i la contrasenya es recupera de la capa exportada amb una sola comanda. Si aquella imatge s'hagués publicat a Docker Hub, qualsevol podria extreure-la amb docker pull i docker save, sense necessitat d'executar el contenidor. Per això el mateix && que estalvia 105 MB és també un control de seguretat, i per això la solució correcta per als secrets de construcció no és un && sinó RUN --mount=type=secret, que veuràs a la lliçó següent.

Conclusió

aurora-api ha passat de 142 MB a 102 MB, un 28 % menys, sense perdre l'usuari sense privilegis, el HEALTHCHECK, les etiquetes OCI ni un sol endpoint: /salut, /llibres i /llibres/:id responen igual i el contenidor continua arribant a healthy. I, més important que la xifra, tens el mètode: mesurar primer. docker image ls per al total, docker image history per trobar la capa grassa —que va resultar ser la base, amb el 81 % del pes— i dive per veure el malbaratament real, que va destapar la memòria cau d'npm i un .git que mai no hauria d'haver entrat al context.

Saps triar la base amb criteri i no per costum, coneixent la contrapartida de musl davant de glibc i què costa cada esglaó entre node:22, -slim, -alpine, distroless i scratch. Domines les construccions multietapa: una etapa que instal·la i compila amb build-base, una de desenvolupament amb les devDependencies que alimenta el target: desenvolupament del teu compose.override.yaml, una etapa de proves que trenca la construcció si alguna cosa falla, i una de final que només rep node_modules i src/ mitjançant COPY --from —demostrat: tres compiladors a l'etapa intermèdia, zero a la publicada—. I saps construir cadascuna per separat amb --target, a més de compartir una imatge base pròpia entre diversos serveis.

Del costat del contingut: --no-cache, --no-install-recommends, --virtual amb el seu apk del, npm ci --omit=dev, npm cache clean i un .dockerignore que va reduir el context de 94 MB a 812 kB. I la demostració que més val la pena recordar: esborrar un fitxer en una capa posterior no redueix la mida —113 MB davant de 8,15 MB per un &&—, i aquest mateix mecanisme fa que una credencial escrita i esborrada continuï sent extraïble de la imatge publicada.

A la lliçó 05-05 entra en escena el constructor que ha estat fent tot això per sota: BuildKit, amb el seu graf de dependències i el seu paral·lelisme entre etapes, i Buildx amb els seus builders. Veuràs els muntatges a RUNtype=cache per no reinstal·lar npm a cada build, type=bind, type=tmpfs i sobretot type=secret, la resposta correcta al problema de credencials que acabes de demostrar—, la memòria cau remota compartida amb --cache-from i --cache-to, la compilació multiarquitectura per a amd64 i arm64 amb les seves manifest lists, i docker buildx bake per construir totes les imatges d'Aurora Libros d'una sola vegada.

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