Has descarregat imatges, les has llistat i les has esborrat, però encara no saps què són per dins. I aquest "per dins" explica gairebé tot el que et sorprendrà de Docker als propers mòduls: per què descarregar la segona imatge és més ràpid que la primera, per què la suma de mides de docker images no quadra amb el disc realment ocupat, per què les dades que escrius dins d'un contenidor desapareixen en esborrar-lo, i per què l'etiqueta latest és un parany. En aquesta lliçó disseccionaràs una imatge: la seva naturalesa immutable, el seu model de capes, el mecanisme de copy-on-write que separa la imatge del contenidor, la nomenclatura completa amb registres i digests, les famílies d'imatges base, i una exploració pràctica amb docker image history i inspect sobre node:22-alpine, que serà la base de l'API d'Aurora Libros.

Contingut

  1. Què és exactament una imatge
  2. El model de capes
  3. La capa d'escriptura del contenidor i el copy-on-write
  4. La conseqüència clau: les dades es perden
  5. Nomenclatura completa: registre, repositori, etiqueta i digest
  6. Per què latest no significa "l'última"
  7. Imatges base i variants oficials
  8. Exploració pràctica amb node:22-alpine
  9. Comprovant que dues imatges comparteixen capes

  1. Què és exactament una imatge

Una imatge de Docker són dues coses empaquetades juntes:

  1. Un sistema de fitxers: el conjunt complet de directoris i fitxers que veurà l'aplicació quan s'executi. Binaris, llibreries, fitxers de configuració, el teu codi.
  2. Unes metadades d'arrencada: com s'ha d'executar. Quina comanda llançar, amb quines variables d'entorn, quin usuari, en quin directori de treball, quins ports declara.

I té tres propietats que defineixen el seu comportament:

  • És immutable. Un cop construïda, no canvia mai. Si necessites una versió diferent, en construeixes una altra.
  • És de només lectura. Cap contenidor no hi pot escriure.
  • És identificable de manera criptogràfica. El seu contingut produeix un hash SHA-256 que la identifica sense ambigüitat.

Aquestes tres propietats juntes són les que fan possible la reproduïbilitat: si dues màquines executen la imatge amb el mateix digest, estan executant exactament els mateixos bytes.

El que una imatge no conté, i convé tenir clar des del principi:

Sí que conté No conté
Binaris i llibreries d'espai d'usuari Un nucli (fa servir el de l'amfitrió)
Fitxers de configuració Dades de la teva base de dades en producció
El teu codi d'aplicació Secrets que hagin de variar per entorn
Metadades: comanda, variables, usuari, ports Estat d'execució

  1. El model de capes

Aquí hi ha la idea central. Una imatge no és un bloc monolític: és una pila de capes, cadascuna de les quals és un diff, un conjunt de canvis respecte a la capa anterior.

Imagina't com es construeix la imatge de l'API d'Aurora Libros:

  1. Capa 1: el sistema de fitxers base d'Alpine Linux (uns 8 MB).
  2. Capa 2: a sobre, s'hi instal·la Node.js 22 (s'hi afegeixen fitxers nous).
  3. Capa 3: a sobre, s'hi copia el package.json del projecte.
  4. Capa 4: a sobre, s'hi instal·len les dependències d'npm (node_modules).
  5. Capa 5: a sobre, s'hi copia el codi de l'API.
graph TB
    subgraph IMG["Imatge aurora-api:1.0.0 (només lectura)"]
        L5["Capa 5 · codi de l'API · 120 KB"]
        L4["Capa 4 · node_modules · 48 MB"]
        L3["Capa 3 · package.json · 2 KB"]
        L2["Capa 2 · Node.js 22 · 42 MB"]
        L1["Capa 1 · Alpine Linux base · 8 MB"]
    end
    L1 --- L2 --- L3 --- L4 --- L5

Quan el contenidor arrenca, totes aquestes capes es fusionen en una sola vista unificada del sistema de fitxers gràcies a un union filesystem (en Linux modern, overlay2, el driver que vas veure a docker info). Des de dins del contenidor no perceps capes: veus un / normal amb /usr, /etc, /app, etc.

Les regles de la fusió són simples:

  • Un fitxer que només existeix en una capa, hi apareix.
  • Si un fitxer existeix en diverses capes, guanya el de la capa superior.
  • Si una capa esborra un fitxer d'una d'inferior, es marca amb un fitxer especial whiteout i deixa de veure's (però continua ocupant espai a la capa inferior; aquesta conseqüència és important per a la mida de les imatges i s'explota a la lliçó 05-04).

I ara la propietat que ho fa tot eficient: les capes es comparteixen entre imatges. Si tens cinc imatges diferents construïdes sobre node:22-alpine, les capes d'Alpine i de Node s'emmagatzemen una sola vegada al disc i les cinc imatges les referencien.

graph TB
    BASE["Capa: Alpine 3.20"] --> NODE["Capa: Node.js 22"]
    NODE --> API1["aurora-api:1.0.0<br/>+ 2 capes pròpies"]
    NODE --> API2["aurora-api:1.1.0<br/>+ 2 capes pròpies"]
    NODE --> WORKER["aurora-worker:1.0.0<br/>+ 3 capes pròpies"]

D'aquí en surten tres beneficis molt concrets:

  • Estalvi de disc: no multipliques la base per cada imatge.
  • Descàrregues ràpides: en fer pull de la segona imatge, el daemon només baixa les capes que li falten. Per això de vegades veus Already exists en lloc de Pull complete.
  • Pujades ràpides: en publicar una versió nova de la teva API en què només va canviar el codi, només es puja aquesta capa.

Aquest últim punt tindrà conseqüències enormes quan escriguis el teu primer Dockerfile al mòdul 2: l'ordre de les instruccions determina quines capes es reaprofiten.

  1. La capa d'escriptura del contenidor i el copy-on-write

Si la imatge és de només lectura, com pot un contenidor escriure fitxers? La resposta és la peça que uneix els dos conceptes:

Quan crees un contenidor, Docker apila una capa fina de lectura i escriptura a sobre de les capes de només lectura de la imatge. Aquesta capa pertany només a aquell contenidor.

graph TB
    subgraph C1["Contenidor A"]
        WA["Capa d'escriptura A<br/>(lectura/escriptura)"]
    end
    subgraph C2["Contenidor B"]
        WB["Capa d'escriptura B<br/>(lectura/escriptura)"]
    end
    subgraph IMG["Imatge nginx:alpine (només lectura, COMPARTIDA)"]
        I3["Capa 3"]
        I2["Capa 2"]
        I1["Capa 1"]
    end
    WA --> I3
    WB --> I3
    I3 --- I2 --- I1

Dos contenidors de la mateixa imatge comparteixen totes les capes de la imatge i només tenen pròpia la seva capa superior. Per això arrencar el desè contenidor d'una imatge no costa pràcticament disc.

El mecanisme que fa això possible s'anomena copy-on-write (copiar en escriure), i funciona així:

Operació dins del contenidor Què passa realment
Llegir un fitxer de la imatge Es llegeix directament de la capa de la imatge. Cost zero
Crear un fitxer nou Es crea a la capa d'escriptura del contenidor
Modificar un fitxer de la imatge Es copia sencer a la capa d'escriptura i es modifica allà. La imatge no es toca
Esborrar un fitxer de la imatge Es marca com a esborrat a la capa d'escriptura (whiteout). Continua existint a la imatge

Conseqüències pràctiques que notaràs:

  • Modificar un fitxer gran per primera vegada és lent, perquè cal copiar-lo sencer abans. Les vegades següents ja és ràpid.
  • Les aplicacions amb escriptura intensiva (bases de dades, precisament) rendeixen pitjor si escriuen a la capa del contenidor. La solució és fer servir volums, i per això aurora-db en necessitarà un.

Pots veure la mida d'aquesta capa d'escriptura:

docker ps -as
CONTAINER ID   IMAGE          NAMES             SIZE
3f2a9c1b7e4d   nginx:alpine   aurora-web-demo   1.09kB (virtual 52.5MB)

Com es llegeix: 1.09kB és el que ha escrit aquest contenidor a la seva capa pròpia; virtual 52.5MB és el total incloent-hi les capes de la imatge, que són compartides. Si arrenques cinc contenidors de nginx:alpine, el disc no creix 5 × 52,5 MB, sinó 52,5 MB més cinc capes d'escriptura minúscules.

  1. La conseqüència clau: les dades es perden

Aquesta és la implicació pràctica més important de tot l'apartat anterior, i mereix el seu propi bloc perquè és la causa de la majoria dels ensurts dels principiants:

La capa d'escriptura viu i mor amb el contenidor. Quan esborres un contenidor, la seva capa d'escriptura s'esborra amb ell. Tot el que l'aplicació hi hagués escrit desapareix per sempre.

Comprova-ho tu mateix, pas a pas:

docker run --name prova-dades alpine:3.20 \
  sh -c "echo 'Catàleg Aurora Libros' > /dades.txt && cat /dades.txt"
Catàleg Aurora Libros

El contenidor ha escrit el fitxer a la seva capa d'escriptura i l'ha mostrat. Ara reprèn aquest mateix contenidor amb docker start -a (la -a és --attach, per veure'n la sortida):

docker start -a prova-dades
Catàleg Aurora Libros

Continua funcionant: la capa d'escriptura del contenidor persisteix mentre el contenidor existeixi, encara que estigui aturat. I ara la part reveladora:

docker rm prova-dades
docker run --name prova-dades alpine:3.20 cat /dades.txt
cat: can't open '/dades.txt': No such file or directory

Què acaba de passar: en esborrar el contenidor va desaparèixer la seva capa d'escriptura i amb ella /dades.txt. El contenidor nou neix de la imatge, que mai no va contenir aquest fitxer. La imatge és immutable: escriure dins d'un contenidor no modifica la imatge.

Traduït a Aurora Libros: si fiques PostgreSQL en un contenidor sense més i esborres aquest contenidor, perds el catàleg de llibres sencer. En un entorn de desenvolupament és molest; en producció és un incident greu.

La solució s'anomena volums: emmagatzematge que viu fora del cicle de vida del contenidor. És el tema complet de la lliçó 03-06, i a la lliçó 01-06 faràs un primer ús molt introductori de -v per servir una pàgina d'Aurora Libros amb Nginx. De moment, queda't amb la regla:

Tota dada que hagi de sobreviure al contenidor ha d'estar en un volum.

  1. Nomenclatura completa: registre, repositori, etiqueta i digest

Quan escrius nginx:alpine, estàs fent servir una forma abreujada. El nom complet d'una imatge és:

[registre[:port]/][usuari-o-organitzacio/]repositori[:etiqueta][@digest]

Exemples, de més abreujat a més explícit:

El que escrius Com ho resol Docker
nginx docker.io/library/nginx:latest
nginx:alpine docker.io/library/nginx:alpine
bitnami/postgresql:16 docker.io/bitnami/postgresql:16
ghcr.io/aurora-libros/aurora-api:1.2.0 Tal qual: registre GitHub, organització, repositori, etiqueta
registry.auroralibros.local:5000/aurora-api:1.2.0 Registre privat al port 5000

Les regles d'emplenat automàtic són:

  • Si no indiques registre, s'assumeix docker.io (Docker Hub).
  • Si no indiques usuari o organització i el registre és Docker Hub, s'assumeix library/, l'espai reservat a les imatges oficials. Per això nginx és oficial i bitnami/postgresql és d'un tercer.
  • Si no indiques etiqueta, s'assumeix :latest.

El digest

A més de per nom i etiqueta, tota imatge té un digest: el hash SHA-256 del seu manifest.

docker images --digests nginx
REPOSITORY   TAG      DIGEST                                                                    IMAGE ID       SIZE
nginx        alpine   sha256:c15da6c91de8d2f436196f3a768483ad32c258ed4e1beb3d367a27ed67253e66   3f8a4339aadd   52.5MB

La diferència entre els tres identificadors:

Identificador Exemple Naturalesa
Etiqueta nginx:alpine Mutable: es pot reassignar a una altra imatge en qualsevol moment
Image ID 3f8a4339aadd Hash local de la configuració; immutable, però només té sentit a la teva màquina
Digest sha256:c15da6c9... Immutable i global: identifica exactament els mateixos bytes a qualsevol registre i màquina

I per això pots ancorar una imatge sense cap ambigüitat:

docker pull nginx@sha256:c15da6c91de8d2f436196f3a768483ad32c258ed4e1beb3d367a27ed67253e66

Això descarrega aquella imatge exacta, passi el que passi amb les etiquetes. És la pràctica recomanada en producció i en pipelines de CI, on la reproduïbilitat importa més que la comoditat. Ho reprendrem a les lliçons 02-06 i 06-01.

  1. Per què latest no significa "l'última"

Aquest és un dels malentesos més cars de Docker, així que serem taxatius:

latest no és una funció que retorni la versió més recent. És simplement el nom d'etiqueta que Docker fa servir per defecte quan no n'especifiques cap.

Res no obliga que latest apunti a l'última versió. És una convenció, i una convenció que s'incompleix amb freqüència:

  • Un mantenidor pot publicar 2.0.0 sense actualitzar latest, que es queda a la 1.x.
  • Alguns projectes fan servir latest per a l'última versió estable i publiquen les noves majors només amb el seu número.
  • I a l'inrevés: latest pot apuntar a una versió de desenvolupament inestable.

Els problemes concrets que causa fer-la servir:

Problema Explicació
No reproduïble docker pull lamevaapp:latest avui i demà poden portar imatges diferents. El teu CI pot passar avui i fallar demà sense que ningú hagi tocat el codi
Actualitzacions silencioses Un canvi major amb ruptura de compatibilitat pot entrar al teu desplegament sense que ho decideixis
Memòria cau enganyosa Si ja tens latest en local, Docker no torna a comprovar el registre llevat que forcis el pull. Pots estar executant una versió de fa mesos creient que és l'última
Diagnòstic impossible "Quina versió hi ha en producció?" — "latest". Això no és cap resposta

La pràctica correcta:

# Malament: no saps què estàs executant
docker pull node

# Millor: versió major i variant fixades
docker pull node:22-alpine

# Encara millor per a producció: versió concreta
docker pull node:22.14.0-alpine3.21

# Màxim rigor: ancorat per digest
docker pull node:22-alpine@sha256:9d2b8c0...

Regla pràctica: en desenvolupament, node:22-alpine és un equilibri raonable entre estabilitat i comoditat. En producció i CI, versió completa o digest. Durant el curs farem servir etiquetes explícites sempre: node:22-alpine, postgres:16-alpine, redis:7-alpine, nginx:alpine.

  1. Imatges base i variants oficials

Una imatge base és el punt de partida sobre el qual construeixes. Aquestes són les famílies que trobaràs:

Imatge base Mida aprox. Gestor de paquets Libc Quan fer-la servir
scratch 0 bytes Cap Cap Binaris estàtics (Go, Rust). Màxima seguretat i mínima mida
alpine ~8 MB apk musl Per defecte quan la mida importa
debian:bookworm-slim ~75 MB apt glibc Compatibilitat àmplia amb mida continguda
debian:bookworm ~120 MB apt glibc Quan necessites eines del sistema
ubuntu:24.04 ~78 MB apt glibc Familiaritat, paquets d'Ubuntu
Imatges distroless ~20 MB Cap glibc Producció endurida, sense shell

Un avís important sobre Alpine: fa servir musl com a llibreria de C en lloc de la glibc habitual. El 95 % de les vegades tant se val, però pot causar problemes amb paquets de Python o de Node que portin binaris compilats contra glibc, i en casos concrets hi ha diferències de rendiment en la resolució de noms DNS. Si alguna cosa es comporta de manera estranya a Alpine i no a Debian, aquesta sol ser la causa.

Variants de les imatges oficials de llenguatges

Les imatges oficials publiquen diverses variants de cada versió, i triar bé és important. Prenguem Node.js 22, que és el que necessitarà aurora-api:

Etiqueta Base Mida aprox. Comentari
node:22 Debian complet ~1,1 GB Porta compiladors i eines. Còmoda, enorme
node:22-slim debian:slim ~230 MB Debian sense extres. Bon equilibri
node:22-alpine Alpine ~140 MB La més petita amb gestor de paquets. La que farem servir
node:22-bookworm Debian 12 ~1,1 GB Igual que node:22 però fixant la versió de Debian

Les bases de dades segueixen el mateix patró: postgres:16 (Debian) davant de postgres:16-alpine, i redis:7 davant de redis:7-alpine.

I una recomanació de seguretat que prendrà tot el seu sentit a la lliçó 05-03: prefereix sempre imatges oficials o verificades. Qualsevol pot publicar a Docker Hub una imatge anomenada postgresql-rapid amb el que li vingui de gust a dins.

  1. Exploració pràctica amb node:22-alpine

Disseccionem la imatge que serà la base d'aurora-api.

Descarregar-la

docker pull node:22-alpine
22-alpine: Pulling from library/node
f18232174bc9: Already exists
9c3b5e8d1a2f: Pull complete
7d4a2c9e0f31: Pull complete
b1e6f8a4c507: Pull complete
Digest: sha256:9d2b8c0f4e1a7b3d5c6e8f0a2b4d6e8f0a1c3e5d7f9b1d3f5a7c9e1b3d5f7a9c
Status: Downloaded newer image for node:22-alpine
docker.io/library/node:22-alpine

Fixa't en la primera línia de capes: Already exists. Aquesta capa (f18232174bc9) és el sistema base d'Alpine, i ja la tenies del docker pull alpine:3.20 de la lliçó anterior. Docker no l'ha tornada a descarregar. Acabes de veure la compartició de capes en funcionament.

Llistar-la

docker image ls node
REPOSITORY   TAG         IMAGE ID       CREATED       SIZE
node         22-alpine   c4b8e2f1a9d3   5 days ago    142MB

Veure el seu historial de capes

docker image history node:22-alpine
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
c4b8e2f1a9d3   5 days ago    CMD ["node"]                                    0B
<missing>      5 days ago    ENTRYPOINT ["docker-entrypoint.sh"]             0B
<missing>      5 days ago    COPY docker-entrypoint.sh /usr/local/bin/ #…    388B
<missing>      5 days ago    RUN /bin/sh -c apk add --no-cache --virtual …   7.66MB
<missing>      5 days ago    ENV YARN_VERSION=1.22.22                        0B
<missing>      5 days ago    RUN /bin/sh -c addgroup -g 1000 node && addu…   125MB
<missing>      5 days ago    ENV NODE_VERSION=22.14.0                        0B
<missing>      3 weeks ago   /bin/sh -c #(nop) CMD ["/bin/sh"]               0B
<missing>      3 weeks ago   /bin/sh -c #(nop) ADD file:8f4a3d2b1c… in /     8.83MB

Es llegeix de baix a dalt, en ordre de construcció. Anem amb les claus:

  • L'última línia (ADD file:... in /, 8,83 MB) és la capa base d'Alpine. Coincideix exactament amb la mida d'alpine:3.20 que vas veure abans: és literalment la mateixa capa.
  • RUN ... addgroup ... 125MB és la instal·lació de Node.js: la capa més pesada amb diferència. Aquí hi ha el gruix dels 142 MB.
  • Les capes de 0B (ENV, CMD, ENTRYPOINT) no afegeixen fitxers: només modifiquen les metadades d'arrencada. Ocupen zero perquè no canvien el sistema de fitxers.
  • <missing> a la columna IMAGE no és cap error: significa que aquesta capa intermèdia no té un ID d'imatge propi a la teva màquina, perquè vas descarregar la imatge en lloc de construir-la. És completament normal.

Per veure la sortida sense truncar:

docker image history node:22-alpine --no-trunc --format "table {{.Size}}\t{{.CreatedBy}}"

Inspeccionar-ne les metadades

docker image inspect node:22-alpine

Retorna un JSON llarg. Extraiem-ne l'important:

docker image inspect node:22-alpine --format '{{json .Config}}'
{
  "Env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "NODE_VERSION=22.14.0",
    "YARN_VERSION=1.22.22"
  ],
  "Cmd": ["node"],
  "Entrypoint": ["docker-entrypoint.sh"],
  "WorkingDir": "",
  "Labels": null
}

Aquestes són les metadades d'arrencada de l'apartat 1:

  • Env: variables d'entorn que tindrà tot contenidor d'aquesta imatge.
  • Cmd: la comanda per defecte. Si fas docker run node:22-alpine sense més, executa node i t'obre la seva consola interactiva.
  • Entrypoint: un script que s'executa abans i que rep Cmd com a argument.
  • WorkingDir: el directori de treball inicial.

La distinció entre Cmd i Entrypoint és subtil i important, i es tracta a fons a la lliçó 02-04.

Altres camps útils:

docker image inspect node:22-alpine --format 'Arquitectura: {{.Architecture}} / SO: {{.Os}}'
docker image inspect node:22-alpine --format 'Capes: {{len .RootFS.Layers}}'
docker image inspect node:22-alpine --format '{{range .RootFS.Layers}}{{println .}}{{end}}'
Arquitectura: amd64 / SO: linux
Capes: 4
sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
sha256:9c3b5e8d1a2f...
sha256:7d4a2c9e0f31...
sha256:b1e6f8a4c507...

L'última comanda llista els hashes de les capes de la imatge. Guarda't el primer d'aquesta llista: el compararàs a l'apartat següent.

  1. Comprovant que dues imatges comparteixen capes

Demostrem la compartició de manera inequívoca. Primer, assegura't de tenir les dues imatges:

docker pull alpine:3.20
docker pull node:22-alpine

Ara compara'n les llistes de capes:

docker image inspect alpine:3.20 --format '{{range .RootFS.Layers}}{{println .}}{{end}}'
sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
docker image inspect node:22-alpine --format '{{range .RootFS.Layers}}{{println .}}{{end}}'
sha256:f18232174bc91741fdf3da96d85011092101a032a93a388b79e99e69c2d5c870
sha256:9c3b5e8d1a2f...
sha256:7d4a2c9e0f31...
sha256:b1e6f8a4c507...

La primera capa és idèntica en totes dues. Mateix hash, mateixos bytes, emmagatzemats una sola vegada a /var/lib/docker/overlay2. La imatge de Node no conté la seva pròpia còpia d'Alpine: la referencia.

Pots automatitzar la comparació:

comm -12 \
  <(docker image inspect alpine:3.20 --format '{{range .RootFS.Layers}}{{println .}}{{end}}' | sort) \
  <(docker image inspect node:22-alpine --format '{{range .RootFS.Layers}}{{println .}}{{end}}' | sort)

Què fa: comm -12 compara dues llistes ordenades i mostra només les línies comunes (suprimeix les exclusives de la primera amb -1 i les de la segona amb -2). Els <(...) són substitucions de procés de bash: passen la sortida de cada comanda com si fos un fitxer. El resultat és la llista de capes compartides.

I la confirmació en xifres:

docker image ls
docker system df
REPOSITORY   TAG         IMAGE ID       SIZE
node         22-alpine   c4b8e2f1a9d3   142MB
nginx        alpine      3f8a4339aadd   52.5MB
alpine       3.20        a8560b36e8b8   8.83MB
TYPE      TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images    3         0         194.5MB   194.5MB (100%)

Suma les tres imatges: 142 + 52,5 + 8,83 = 203,3 MB. Però docker system df reporta 194,5 MB. La diferència de gairebé 9 MB és exactament la capa base d'Alpine, comptada tres vegades a docker image ls i una sola vegada al disc.

Aquí tens la resposta a una de les preguntes del principi de la lliçó: la columna SIZE de docker image ls mostra la mida total de cada imatge, sense descomptar el que és compartit. Per saber l'espai real, fes servir docker system df.

Errors Habituals i Consells

  • Creure que es pot modificar una imatge. No es pot: és immutable. El que escrius dins d'un contenidor va a la seva capa d'escriptura, no a la imatge. Per "canviar" una imatge se'n construeix una altra (mòdul 2).
  • Perdre dades per no fer servir volums. És *l'*error clàssic. Si el teu contenidor de PostgreSQL guarda el catàleg d'Aurora Libros a la seva capa d'escriptura, un docker rm ho esborra tot. Solució: volums (lliçó 03-06).
  • Fer servir latest en qualsevol lloc seriós. No significa "l'última", no és reproduïble i fa impossible respondre a "quina versió hi ha desplegada". Fixa etiquetes explícites, i digests en producció.
  • Sumar la columna SIZE de docker image ls. Dona un número més gran que el real per les capes compartides. Fes servir docker system df.
  • Pensar que esborrar un fitxer en una capa redueix la mida de la imatge. No: la capa inferior continua contenint-lo, només s'oculta. És la raó que una imatge mal construïda pesi de més (lliçó 05-04).
  • Confondre Image ID i digest. L'Image ID és local; el digest és global i és el que has de fer servir per ancorar una versió exacta.
  • Consell: docker image history abans que qualsevol optimització. Et diu d'un cop d'ull quina capa està engreixant la imatge.
  • Consell: tria la variant correcta. node:22 pesa unes vuit vegades més que node:22-alpine. En un pipeline que construeix vint vegades al dia, aquesta diferència es nota en temps i en factura.

Exercicis

Exercici 1: l'aritmètica de les capes

Descarrega aquestes tres imatges i respon:

docker pull alpine:3.20
docker pull node:22-alpine
docker pull redis:7-alpine
  1. Quant sumen les mides que mostra docker image ls?
  2. Quant ocupen realment al disc segons docker system df?
  3. Explica la diferència identificant quina capa concreta (per hash) n'és la responsable.
  4. A la sortida dels pull, en quins van aparèixer línies Already exists i per què?

Exercici 2: la lliçó de la persistència

Executa aquesta seqüència i explica el resultat de cada pas:

docker run --name llibres alpine:3.20 sh -c "echo 'Rayuela, Julio Cortazar' > /cataleg.txt && cat /cataleg.txt"
docker start -a llibres
docker rm llibres
docker run --name llibres alpine:3.20 cat /cataleg.txt

Després respon: (a) per què la segona comanda sí que troba el fitxer i la quarta no?, (b) en quin moment exacte es va perdre la dada?, (c) què hauria passat si en lloc de docker rm només haguessis fet docker stop?

Exercici 3: identifica una imatge sense ambigüitat

Per a la imatge node:22-alpine de la teva màquina, obtén: (a) el seu Image ID, (b) el seu digest, (c) el nombre de capes que té, (d) la seva comanda per defecte i les seves variables d'entorn, i (e) la mida de la capa més gran i quina instrucció la va generar. Després, escriu la comanda docker pull que descarregaria exactament aquesta mateixa imatge d'aquí a dos anys, encara que l'etiqueta 22-alpine hagi canviat de contingut.

Solucions

Solució a l'exercici 1

docker image ls --format "{{.Size}}\t{{.Repository}}:{{.Tag}}"
docker system df
  1. La suma de la columna SIZE rondarà els 200 MB (aproximadament 8,8 + 142 + 41 segons versions).
  2. docker system df reportarà una xifra menor, uns 9 MB per sota de la suma.
  3. La responsable és la capa base d'Alpine. Verifica-ho així:
for img in alpine:3.20 node:22-alpine redis:7-alpine; do
  echo "$img:"
  docker image inspect $img --format '{{index .RootFS.Layers 0}}'
done

Aquest bucle imprimeix la primera capa de cada imatge (index .RootFS.Layers 0 pren l'element 0 de l'array). Les tres mostren el mateix sha256:f1823217...: és la mateixa capa física emmagatzemada una vegada i referenciada tres vegades. docker image ls la compta a cada imatge; docker system df la compta un cop, i d'aquí la diferència.

  1. Already exists apareix al segon i al tercer pull per a aquesta capa base. Al primer (alpine:3.20) es va haver de descarregar; a partir d'aquí, el daemon comprova el hash, veu que ja la té i se la salta. Aquesta és la raó pràctica que les descàrregues successives d'imatges de la mateixa família siguin molt més ràpides.

Solució a l'exercici 2

Pas a pas:

  1. docker run --name llibres ... crea un contenidor nou, escriu /cataleg.txt a la seva capa d'escriptura i el mostra. Sortida: Rayuela, Julio Cortazar. El contenidor acaba i queda en Exited (0).
  2. docker start -a llibres reprèn el mateix contenidor (no en crea un altre). Torna a executar la seva comanda, que reescriu i mostra el fitxer. La capa d'escriptura continuava intacta.
  3. docker rm llibres elimina el contenidor i la seva capa d'escriptura amb ell. /cataleg.txt deixa d'existir.
  4. docker run --name llibres alpine:3.20 cat /cataleg.txt crea un contenidor nou a partir de la imatge. Sortida:
cat: can't open '/cataleg.txt': No such file or directory

Respostes:

(a) La segona comanda troba el fitxer perquè opera sobre el mateix contenidor, la capa d'escriptura del qual persisteix mentre el contenidor existeixi. La quarta no el troba perquè opera sobre un contenidor diferent, nascut de la imatge alpine:3.20, que mai no va contenir aquest fitxer. Escriure dins d'un contenidor no modifica la imatge: la imatge és immutable.

(b) La dada es va perdre exactament al docker rm. Aquest és el moment en què la capa d'escriptura s'elimina del disc.

(c) Amb docker stop el fitxer hi continuaria sent. Aturar un contenidor atura el seu procés però conserva la seva capa d'escriptura; un docker start -a llibres posterior hauria tornat a mostrar el contingut. Només rm destrueix les dades. Aquesta distinció entre "aturat" i "eliminat" és central en el cicle de vida que veuràs a la lliçó 03-02.

Solució a l'exercici 3

# (a) Image ID
docker image inspect node:22-alpine --format '{{.Id}}'

# (b) Digest
docker image inspect node:22-alpine --format '{{index .RepoDigests 0}}'

# (c) Nombre de capes
docker image inspect node:22-alpine --format '{{len .RootFS.Layers}}'

# (d) Comanda per defecte i variables d'entorn
docker image inspect node:22-alpine --format 'Cmd: {{.Config.Cmd}}{{println}}Env: {{.Config.Env}}'

# (e) Capa més gran i la seva instrucció
docker image history node:22-alpine --no-trunc --format "{{.Size}}\t{{.CreatedBy}}" | sort -h -r | head -1

Comentaris:

  • .Id retorna l'Image ID complet amb prefix sha256:; els 12 primers caràcters són els que veus a docker image ls.
  • .RepoDigests és un array perquè una imatge pot estar publicada en diversos repositoris; index ... 0 en pren el primer. El format és node@sha256:....
  • len .RootFS.Layers compta els elements de l'array de capes: normalment 4 per a aquesta imatge.
  • La capa més gran serà la del RUN que instal·la Node.js, al voltant de 125 MB. Aquesta única línia explica la major part de la mida de la imatge.

La comanda que descarrega exactament aquesta imatge d'aquí a dos anys:

docker pull node@sha256:9d2b8c0f4e1a7b3d5c6e8f0a2b4d6e8f0a1c3e5d7f9b1d3f5a7c9e1b3d5f7a9c

Substituint el digest pel que t'hagi retornat l'apartat (b). La clau del raonament: l'etiqueta 22-alpine és un punter mutable que el mantenidor pot reassignar a una imatge diferent cada vegada que publiqui un pedaç de Node 22. El digest, en canvi, és el hash del contingut: identifica aquests bytes concrets i res més. Mentre la imatge continuï existint al registre, aquest pull portarà sempre el mateix. És la pràctica estàndard per a la reproduïbilitat en CI i producció.

Conclusió

Una imatge és un sistema de fitxers més unes metadades d'arrencada, immutable, de només lectura i identificada criptogràficament. Internament és una pila de capes, cadascuna un diff de l'anterior, que es fusionen en una vista única i —això és l'important— es comparteixen entre imatges, d'on surten les descàrregues incrementals i l'estalvi de disc que has verificat amb les teves pròpies mans comparant els hashes d'alpine:3.20 i node:22-alpine.

En crear un contenidor, Docker hi afegeix a sobre una capa d'escriptura pròpia i aplica copy-on-write: llegir és gratis, modificar implica copiar. I d'aquí la regla que més disgustos evita: aquesta capa mor amb el contenidor, així que tota dada que hagi de sobreviure ha d'anar a un volum (lliçó 03-06).

També ja saps anomenar imatges amb precisió —registre/usuari/repositori:etiqueta@digest—, per què latest és un nom i no una promesa, i quines famílies d'imatges base existeixen, inclosa node:22-alpine, que serà la base de l'API d'Aurora Libros.

Amb la teoria d'imatges resolta, ja no hi ha res que t'impedeixi embrutar-te les mans. A la lliçó següent, Creant el teu Primer Contenidor Docker, faràs una pràctica guiada completa: un contenidor en primer pla, un servidor Nginx en segon pla amb el seu port publicat, un shell interactiu dins d'Alpine, i la teva primera pàgina d'Aurora Libros servida des d'un contenidor.

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