A la lliçó 03-06 vas aprendre a fer servir volums, bind mounts i tmpfs. Aquí veuràs què hi ha a sota: com el daemon apila les capes d'una imatge amb overlay2, quant costa de debò escriure a la capa d'un contenidor, què són els volume drivers i com muntar una estratègia de còpies de seguretat d'Aurora Libros amb restauració provada.

Contingut

  1. Què és un storage driver i què resol
  2. overlay2 per dins: els quatre directoris
  3. Inspecció real d'una imatge i d'un contenidor
  4. Taula comparativa dels storage drivers
  5. Consultar el driver actiu i canviar-lo
  6. El cost del copy-on-write, mesurat
  7. Per què les bases de dades exigeixen volums
  8. El driver local i els seus driver_opts
  9. Muntar NFS i tmpfs com a volum
  10. Plugins de tercers i declaració a Compose
  11. Volums external compartits entre piles
  12. Estratègia de còpies de seguretat d'Aurora Libros
  13. Script de còpia amb rotació i restauració verificada
  14. Espai al disc i data-root
  15. Rendiment i permisos (UID/GID)

  1. Què és un storage driver i què resol

Una imatge és una pila de capes de només lectura (lliçó 01-05). Un contenidor hi afegeix a sobre una capa d'escriptura. El problema tècnic és aquest: presentar totes aquelles capes com un únic sistema de fitxers coherent, i fer-ho sense copiar gigabytes cada vegada que arrenca un contenidor.

D'això se n'encarrega el storage driver del daemon. Té tres responsabilitats:

  • Apilar les capes de la imatge en un sol arbre de directoris visible des de dins.
  • Aplicar copy-on-write: en modificar un fitxer d'una capa inferior, copiar-lo a la capa superior i treballar sobre la còpia.
  • Compartir les capes comunes entre contenidors, perquè deu instàncies de node:22-alpine ocupin l'espai d'una.

És una peça del daemon, no del contenidor: a dins només s'hi veu un sistema de fitxers normal.

  1. overlay2 per dins: els quatre directoris

overlay2 fa servir OverlayFS, un sistema de fitxers d'unió inclòs al kernel de Linux. Treballa amb quatre conceptes:

Directori Què conté Mode
lowerdir Les capes de la imatge, apilades i separades per : Només lectura
upperdir La capa d'escriptura del contenidor Lectura i escriptura
merged La vista unificada: el que el contenidor veu com a / Lectura i escriptura
workdir Espai intern de treball del kernel per a operacions atòmiques Ús intern

Les regles de resolució són senzilles i expliquen tot el comportament que ja has observat:

  1. Llegir un fitxer: es busca a upperdir; si no hi és, es recorre lowerdir de dalt a baix. Guanya la primera coincidència.
  2. Escriure en un fitxer que només existeix a lowerdir: es copia sencer a upperdir (copy-up) i s'hi escriu. L'original no es toca.
  3. Esborrar un fitxer de lowerdir: no es pot esborrar el que és de només lectura, així que es crea a upperdir un fitxer especial de tipus whiteout que l'amaga. L'original continua ocupant espai.

Aquesta tercera regla és la raó, per fi explicada, que esborrar fitxers en una capa posterior d'un Dockerfile no redueixi la mida de la imatge. Ho veurem mesurat a la lliçó 05-04.

flowchart TB
  M["merged/  ← el que veu el contenidor"]
  U["upperdir/  capa d'escriptura<br/>fitxers nous, copy-up i whiteouts"]
  L3["lowerdir 3 · COPY src/ (aurora-api)"]
  L2["lowerdir 2 · npm ci --omit=dev"]
  L1["lowerdir 1 · node:22-alpine"]
  U --> M
  L3 --> M
  L2 --> M
  L1 --> M

  1. Inspecció real d'una imatge i d'un contenidor

Tot això és visible al disc del host. Cada capa viu a /var/lib/docker/overlay2/<id>/:

docker image inspect auroralibros/aurora-api:1.2.0 --format '{{json .GraphDriver.Data}}' | tr ',' '\n'
mount | grep overlay | head -1 | tr ',' '\n' | grep -E 'lowerdir|upperdir|workdir' | cut -c1-70
{"LowerDir":"/var/lib/docker/overlay2/4a8e77/diff:/var/lib/docker/overlay2/c93b2e/diff"
"MergedDir":"/var/lib/docker/overlay2/7d1f0a/merged"
"UpperDir":"/var/lib/docker/overlay2/7d1f0a/diff"
"WorkDir":"/var/lib/docker/overlay2/7d1f0a/work"}
lowerdir=/var/lib/docker/overlay2/l/QW3F:/var/lib/docker/overlay2/l/K7RT
upperdir=/var/lib/docker/overlay2/9be21c/diff
workdir=/var/lib/docker/overlay2/9be21c/work

Fixa't en els enllaços curts d'overlay2/l/: existeixen perquè la longitud dels arguments de muntatge del kernel està limitada, i una imatge amb moltes capes superaria el límit amb rutes completes.

Ara la demostració que ho tanca tot. Escriu un fitxer dins del contenidor i mira'l aparèixer a l'upperdir del host:

up=$(docker inspect aurora-libros-aurora-api-1 --format '{{.GraphDriver.Data.UpperDir}}')
docker compose exec aurora-api sh -c 'echo "prova de capes" > /tmp/aurora.txt'
sudo cat "$up/tmp/aurora.txt" && sudo find "$up" -type f | head -2
prova de capes
/var/lib/docker/overlay2/9be21c/diff/tmp/aurora.txt

El fitxer és físicament al host, dins del diff d'aquell contenidor i només d'aquell contenidor. És el mateix que docker diff mostrava a la lliçó 03-03, però ara saps on viu.

  1. Taula comparativa dels storage drivers

Driver Estat el 2026 Requisits Rendiment Notes
overlay2 Estàndard per defecte Kernel ≥ 4.0, ext4 o xfs amb ftype=1 Molt bo en lectura; copy-up costós en fitxers grans L'opció correcta llevat de motiu de pes
btrfs Suportat Sistema de fitxers Btrfs a /var/lib/docker Bo; instantànies natives i molt barates Útil si ja fas servir Btrfs; requereix manteniment
zfs Suportat ZFS instal·lat Bo; compressió i snapshots Consum de RAM alt (ARC); llicència fora del kernel
devicemapper Eliminat Dolent en mode loop-lvm Històric; retirat a Docker 25. Si el veus, migra
fuse-overlayfs Suportat Mode rootless en kernels antics Pitjor que overlay2: passa per espai d'usuari Kernels ≥ 5.13 ja no el necessiten
vfs Últim recurs Cap Molt dolent: copia sencera cada capa Sense CoW; només per a proves o entorns imbricats

Regla pràctica: si docker info diu overlay2, no toquis res; si diu vfs en un servidor, tens un problema de configuració que multiplicarà per deu l'espai al disc.

  1. Consultar el driver actiu i canviar-lo

docker info --format 'Driver: {{.Driver}} | Backing FS: {{index .DriverStatus 0 1}}'
Driver: overlay2 | Backing FS: extfs

Canviar el driver es fa a /etc/docker/daemon.json amb {"storage-driver": "overlay2"} i reiniciant el daemon.

Advertència. Canviar el storage driver fa que el daemon deixi de veure totes les imatges i contenidors existents: continuen al disc, sota el directori del driver antic, però són inaccessibles fins que tornis enrere. Abans de tocar-ho, publica les teves imatges en un registre d'imatges, desa les que no estiguin publicades amb docker save i fes còpia de seguretat dels volums. Els volums, això sí, no depenen del driver: viuen a /var/lib/docker/volumes/ i sobreviuen al canvi.

  1. El cost del copy-on-write, mesurat

La teoria diu que el copy-up copia el fitxer sencer la primera vegada que el toques. Anem a mesurar-ho amb un fitxer de 512 MB ficat en una imatge.

mkdir -p /tmp/cow && cd /tmp/cow
printf 'FROM alpine:3\nRUN dd if=/dev/zero of=/gran.bin bs=1M count=512\n' > Dockerfile
docker build -q -t prova-cow .

# Un byte sobre un fitxer que viu en una capa inferior
docker run --rm prova-cow sh -c 'time dd if=/dev/zero of=/gran.bin bs=1 count=1 conv=notrunc 2>/dev/null'
# El mateix byte, sobre un fitxer que ja és a la capa d'escriptura
docker run --rm prova-cow sh -c 'cp /gran.bin /c.bin; time dd if=/dev/zero of=/c.bin bs=1 count=1 conv=notrunc 2>/dev/null'

# I l'espai que ha costat aquell byte
docker run -d --name cow prova-cow sh -c 'dd if=/dev/zero of=/gran.bin bs=1 count=1 conv=notrunc; sleep 30'
sleep 3 && docker ps -s --filter name=cow --format '{{.Names}}\t{{.Size}}' && docker rm -f cow
real    0m 1.94s        <- primera escriptura: copy-up de 512 MB
real    0m 0.00s        <- el fitxer ja era a upperdir
cow    512MB (virtual 528MB)

Gairebé dos segons per escriure un byte, i 512 MB de capa d'escriptura consumits.

Aquest és el cost real del copy-on-write, i explica tres regles que ja havies aplicat sense conèixer-ne el perquè:

Regla Motiu real
No modificar fitxers grans de la imatge Cada primera escriptura copia el fitxer sencer
Fer servir COPY --chown en comptes d'un RUN chown -R posterior chown toca les metadades de cada fitxer i dispara un copy-up massiu
Les bases de dades van en volums Escriuen constantment en fitxers grans

  1. Per què les bases de dades exigeixen volums

PostgreSQL escriu sense parar en fitxers de dades de desenes o centenars de megabytes. Si /var/lib/postgresql/data fos a la capa d'escriptura, cada modificació d'una pàgina dispararia un copy-up del fitxer sencer la primera vegada, i el rendiment seria catastròfic.

Un volum evita OverlayFS del tot: és un bind mount a un directori del host (/var/lib/docker/volumes/<nom>/_data), muntat sobre el punt corresponent. Les escriptures van directes al sistema de fitxers del host, sense capes ni copy-up.

docker compose exec aurora-db sh -c 'mount | grep -E "postgresql| / "'
overlay on / type overlay (rw,relatime,lowerdir=...,upperdir=...)
/dev/sda2 on /var/lib/postgresql/data type ext4 (rw,relatime)

Aquí tens la prova: mentre / és overlay, /var/lib/postgresql/data és directament el sistema de fitxers del host. Per això les imatges oficials de bases de dades declaren aquell punt com a VOLUME: si t'oblides de muntar-lo, si més no eviten la penalització creant un volum anònim.

  1. El driver local i els seus driver_opts

El driver de volums per defecte s'anomena local i admet opcions que gairebé ningú no fa servir, tot i que són sorprenentment potents: són les mateixes de la comanda mount de Linux.

# Volum sobre un directori concret del host (bind mount amb nom)
docker volume create --driver local \
  --opt type=none --opt device=/srv/aurora/dades --opt o=bind aurora-a-srv

# Volum en memòria, limitat a 64 MB
docker volume create --driver local \
  --opt type=tmpfs --opt device=tmpfs --opt o=size=64m,uid=1000 aurora-temporal
Opció Significat
type Tipus de sistema de fitxers: none (bind), tmpfs, nfs, cifs
device Origen: ruta del host, tmpfs, o :/ruta/exportada a NFS
o Opcions de muntatge separades per comes: bind, ro, size, uid, addr

La diferència amb un bind mount corrent és de gestió: un volum amb nom apareix a docker volume ls, admet etiquetes, es pot declarar external i no depèn de la ruta relativa del projecte.

  1. Muntar NFS i tmpfs com a volum

Un volum NFS permet que diversos hosts comparteixin les mateixes dades, una cosa impossible amb un volum local:

# compose.yaml (fragment)
volumes:
  aurora-portades:
    driver: local
    driver_opts:
      type: nfs
      o: "addr=10.0.0.20,rw,nfsvers=4,soft,timeo=30"
      device: ":/exportat/aurora/portades"
docker compose exec aurora-api sh -c 'mount | grep portades'
10.0.0.20:/exportat/aurora/portades on /app/portades type nfs4 (rw,relatime,vers=4.2)

Tres avisos de camp. El muntatge passa en arrencar el contenidor: si el servidor NFS no respon, el contenidor no arrenca. Fes servir soft i un timeo raonable perquè una fallada de xarxa produeixi un error i no un procés penjat per sempre en estat D. I mai no posis les dades de PostgreSQL a NFS: el bloqueig de fitxers per xarxa no ofereix les garanties que una base de dades necessita, i la corrupció és qüestió de temps. Per a fitxers estàtics —les portades del catàleg— és perfecte.

  1. Plugins de tercers i declaració a Compose

Un volume driver és un plugin del daemon que atén les peticions de muntatge. Docker porta local; la resta s'instal·la:

docker plugin install --grant-all-permissions rclone/docker-volume-rclone:amd64
docker plugin ls          # ID  NAME  ENABLED -> 9f2c1a  rclone/docker-volume-rclone  true
Família de plugin Exemples Per a què
Emmagatzematge en xarxa local amb NFS/CIFS, netshare Compartir entre hosts de la mateixa xarxa
Objectes al núvol rclone, s3fs S3, GCS o Azure Blob com si fossin directoris
Blocs al núvol Plugins d'EBS, Cinder Discos que segueixen el contenidor entre nodes
Instantànies Plugins sobre ZFS o Btrfs Còpies instantànies i clonatge

A Compose es declaren igual que qualsevol altre volum, amb driver: rclone i els seus driver_opts (remote: "s3aurora:copies"). L'avís important: un plugin de volum és programari privilegiat dins del teu daemon. Instal·la només plugins d'origen verificat i tracta'ls amb el mateix criteri amb què aprovaries una dependència de producció.

  1. Volums external compartits entre piles

Compose anteposa el nom del projecte als volums que crea (aurora-libros_aurora-dades). Quan un volum ha d'existir fora del cicle de vida del projecte —o compartir-se amb una altra pila—, es declara external:

docker volume create --label projecte=aurora --label criticitat=alta aurora-dades
volumes:
  aurora-dades:
    external: true        # ja existeix; Compose no el crea ni l'esborra

Dues conseqüències molt pràctiques. La primera: docker compose down -v no esborra un volum external, cosa que el converteix en una xarxa de seguretat barata per a les dades que no et pots permetre perdre. La segona: una altra pila —per exemple, un projecte separat d'informes— pot muntar el mateix volum en mode :ro i llegir les dades sense tocar la plataforma principal.

  1. Estratègia de còpies de seguretat d'Aurora Libros

Hi ha dues maneres de copiar les dades d'aurora-db, i no són intercanviables:

Aspecte Còpia lògica (pg_dump) Còpia de fitxers (tar del volum)
Servei aturat No: en calent , perquè sigui consistent
Què produeix SQL o format propi de PostgreSQL Arxiu tar amb el directori de dades
Mida Petita (comprimeix molt bé) Gran: inclou índexs i espai lliure
Restauració Requereix un servidor arrencat Es bolca i s'arrenca
Canvi de versió major : de PostgreSQL 16 a 17 sense problema No: format binari lligat a la versió
Restaura una sola taula No
Velocitat en bases grans Lenta Ràpida

Còpia lògica, en calent:

docker compose exec -T aurora-db \
  pg_dump -U aurora -d aurora_llibres --format=custom --compress=9 \
  > ~/copies/aurora-$(date +%Y%m%d-%H%M).dump      # -> aurora-20260805-2310.dump, 18K

El -T és obligatori: sense ell, exec assigna un pseudoterminal i corromp el flux binari amb traduccions de final de línia. És l'error clàssic d'aquesta operació i no dona cap avís: el fitxer sembla correcte i falla en restaurar.

Còpia del volum a nivell de fitxer:

docker compose stop aurora-db                   # imprescindible per a la consistència
docker run --rm \
  -v aurora-libros_aurora-dades:/dades:ro \
  -v "$HOME/copies":/sortida \
  alpine:3 tar czf /sortida/volum-$(date +%Y%m%d).tar.gz -C /dades .
docker compose start aurora-db

Per què cal aturar el servei: PostgreSQL manté pàgines modificades en memòria i escriu de manera asíncrona. Un tar sobre un directori de dades viu captura uns fitxers en un instant i uns altres en un altre, produint un conjunt incoherent que pot restaurar sense errors i corrompre's dies després. Amb el servei aturat, el directori està quiet i la còpia és vàlida.

  1. Script de còpia amb rotació i restauració verificada

#!/usr/bin/env bash
# copia-aurora.sh — còpia lògica amb rotació i verificació de restauració
set -euo pipefail

DESTI="${DESTI:-/srv/copies/aurora}"
RETENCIO_DIES="${RETENCIO_DIES:-14}"
FITXER="$DESTI/aurora-$(date +%Y%m%d-%H%M).dump"
mkdir -p "$DESTI"

echo "==> Còpia lògica d'aurora_llibres"
docker compose exec -T aurora-db \
  pg_dump -U aurora -d aurora_llibres --format=custom --compress=9 > "$FITXER"

# Una còpia de 0 bytes és una fallada silenciosa: comprova-ho sempre
[ -s "$FITXER" ] || { echo "ERROR: còpia buida"; exit 1; }

echo "==> Verificant la restauració en una base de dades d'un sol ús"
docker run -d --rm --name verifica-copia \
  -e POSTGRES_USER=aurora -e POSTGRES_PASSWORD=verifica -e POSTGRES_DB=verifica \
  --tmpfs /var/lib/postgresql/data postgres:16-alpine >/dev/null

for i in $(seq 1 30); do
  docker exec verifica-copia pg_isready -U aurora -d verifica -q && break || sleep 1
done

docker exec -i verifica-copia pg_restore -U aurora -d verifica --no-owner < "$FITXER"
TOTAL=$(docker exec verifica-copia psql -U aurora -d verifica -tAc 'SELECT count(*) FROM llibres')
docker rm -f verifica-copia >/dev/null

[ "$TOTAL" -ge 9 ] || { echo "ERROR: la còpia només té $TOTAL llibres"; exit 1; }
echo "==> Restauració verificada: $TOTAL llibres"

echo "==> Rotació: eliminant còpies de més de $RETENCIO_DIES dies"
find "$DESTI" -name 'aurora-*.dump' -mtime "+$RETENCIO_DIES" -print -delete
==> Còpia lògica d'aurora_llibres
==> Verificant la restauració en una base de dades d'un sol ús
==> Restauració verificada: 9 llibres
==> Rotació: eliminant còpies de més de 14 dies

El que converteix aquest script en una cosa seriosa no és la còpia, és la verificació: cada execució restaura el fitxer acabat de crear en un PostgreSQL efímer a tmpfs i compta les files. Si la còpia era buida, truncada o corrupta, l'script falla avui, no el dia del desastre.

Grava't la regla: una còpia que no s'ha restaurat mai no és una còpia, és una carpeta amb esperança a dins.

  1. Espai al disc i data-root

Tot el que Docker desa viu sota /var/lib/docker:

Subdirectori Contingut Com es neteja
overlay2/ Capes d'imatges i de contenidors docker image prune -a, container prune
volumes/ Volums amb nom i anònims docker volume prune (amb compte)
containers/ Metadades i logs en JSON Rotació de logs (lliçó 05-06)
buildkit/ Memòria cau de construcció docker builder prune
image/, network/ Índexs i metadades Es gestionen sols
sudo du -sh /var/lib/docker/* 2>/dev/null | sort -rh | head -4
4.1G    /var/lib/docker/overlay2
1.3G    /var/lib/docker/volumes
612M    /var/lib/docker/buildkit
88M     /var/lib/docker/containers

Si containers/ és enorme, el culpable són els logs sense rotar (ho resoldràs a 05-06). Si ho és overlay2/, són imatges antigues. Per moure el magatzem a un disc més gran:

sudo systemctl stop docker
sudo rsync -aHAX /var/lib/docker/ /dades/docker/
printf '{\n  "data-root": "/dades/docker"\n}\n' | sudo tee /etc/docker/daemon.json
sudo systemctl start docker
docker info --format 'Docker Root Dir: {{.DockerRootDir}}'   # -> /dades/docker

Fes servir rsync -aHAX i no cp: cal preservar enllaços durs, atributs estesos i permisos, o les capes quedaran inservibles. Comprova que tot funciona abans d'esborrar el directori antic.

  1. Rendiment i permisos (UID/GID)

Elecció de muntatge per rendiment, de millor a pitjor per a escriptura intensiva:

Muntatge Rendiment d'escriptura Ús recomanat
tmpfs Màxim (RAM) Dades efímeres, proves, fitxers temporals
Volum amb nom Natiu del sistema de fitxers Bases de dades i tota dada persistent
Bind mount (Linux) Natiu Codi en desenvolupament, configuració
Bind mount (macOS/WSL 2 creuant la frontera) Dolent Evitar per a molts fitxers petits
Capa d'escriptura del contenidor El pitjor: copy-up Res que s'escrigui de manera repetida

Els permisos són l'altra meitat del problema. Un volum buit hereta el propietari del punt de muntatge de la imatge; un volum amb dades conserva els seus, i aurora-api corre com l'usuari node (UID 1000), així que un volum creat per root li resulta il·legible.

docker volume create aurora-adjunts
docker run --rm -v aurora-adjunts:/dades alpine:3 stat -c '%u:%g' /dades      # -> 0:0
docker run --rm -v aurora-adjunts:/dades alpine:3 chown -R 1000:1000 /dades   # una sola vegada
docker run --rm -u 1000 -v aurora-adjunts:/dades alpine:3 sh -c 'touch /dades/ok && echo OK'

Amb bind mounts la solució és diferent, perquè el propietari el fixa el host: o bé alinees l'UID del contenidor amb el del teu usuari (user: "${UID}:${GID}" a Compose), o bé ajustes els permisos al host. I recorda de la lliçó 03-06 que el kernel només entén números: el nom node no significa res fora del contenidor.

Errors Habituals i Consells

Escriure dades persistents a la capa del contenidor. Rendiment pèssim per copy-up i pèrdua total en recrear-lo.

Fer tar d'un volum amb la base de dades en marxa. La còpia sembla correcta i és incoherent. Atura el servei o fes servir pg_dump.

Oblidar -T a docker compose exec pg_dump. El pseudoterminal corromp el bolcat binari sense avisar.

No provar mai una restauració. El dia de l'incident descobreixes que fa mesos que guardes fitxers buits.

Executar docker volume prune sense mirar. Esborra tot volum no fet servir per cap contenidor, i un volum d'una pila aturada entra dins d'aquella definició. Declara external allò crític.

Canviar el storage driver sense còpia prèvia, o posar dades de PostgreSQL a NFS. En el primer cas desapareixen imatges i contenidors de l'inventari; en el segon, el bloqueig de fitxers per xarxa no dona les garanties que una base de dades necessita.

Consell: etiqueta els volums en crear-los (--label criticitat=alta) i filtra per aquella etiqueta abans de qualsevol neteja: docker volume ls --filter label=criticitat=alta. Un minut d'etiquetatge evita l'esborrat que no té marxa enrere.

Exercicis

Exercici 1. Demostra el copy-on-write en dues direccions: localitza l'upperdir d'aurora-api, crea un fitxer dins del contenidor i troba'l al disc del host; després mesura el cost de modificar un byte d'un fitxer de 256 MB que vingui a la imatge, i compara'l amb modificar un byte d'un fitxer ja present a la capa d'escriptura.

Exercici 2. Munta el cicle complet de còpia i restauració d'aurora-db: fes una còpia lògica, destrueix les dades esborrant el volum, restaura i comprova que els nou títols hi tornen a ser. Explica en quin punt exacte la plataforma hauria quedat inservible sense la còpia.

Exercici 3. Converteix aurora-dades en un volum external, demostra que docker compose down -v ja no l'esborra i explica quina protecció aporta això i quina n'és la contrapartida.

Solucions

Solució 1.

up=$(docker inspect aurora-libros-aurora-api-1 --format '{{.GraphDriver.Data.UpperDir}}')
docker compose exec aurora-api sh -c 'echo "hola capes" > /tmp/prova.txt'
sudo cat "$up/tmp/prova.txt"
docker diff aurora-libros-aurora-api-1 | grep prova
hola capes
A /tmp/prova.txt
printf 'FROM alpine:3\nRUN dd if=/dev/zero of=/g.bin bs=1M count=256\n' > /tmp/Dockerfile.cow
docker build -q -t cow256 -f /tmp/Dockerfile.cow /tmp
docker run --rm cow256 sh -c 'time dd if=/dev/zero of=/g.bin bs=1 count=1 conv=notrunc 2>/dev/null'
docker run --rm cow256 sh -c 'cp /g.bin /c.bin; time dd if=/dev/zero of=/c.bin bs=1 count=1 conv=notrunc 2>/dev/null'
real    0m 0.98s
real    0m 0.00s

Un segon davant de zero, per escriure exactament el mateix byte. La diferència és el copy-up: en el primer cas el fitxer viu en un lowerdir de només lectura i el kernel ha de copiar els 256 MB sencers a l'upperdir abans de permetre l'escriptura; en el segon, cp ja l'havia portat a la capa d'escriptura. Extrapola: PostgreSQL escrivint sense parar en fitxers de dades sense un volum pagaria aquell peatge una vegada i una altra, a més de multiplicar l'espai ocupat. Aquí es veu, en un segon de rellotge, per què la decisió de la lliçó 03-06 no era una convenció sinó una necessitat física.

Solució 2.

docker compose exec -T aurora-db pg_dump -U aurora -d aurora_llibres -Fc > /tmp/aurora.dump
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
docker compose down -v                                    # el desastre
docker volume ls --filter name=aurora-dades -q | wc -l
docker compose up -d --wait
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
9
0
9

Aquí hi ha un matís que confon molta gent: després del down -v la base de dades torna amb 9 llibres sense haver restaurat res, perquè el volum s'ha recreat buit i PostgreSQL ha executat de nou db/init.sql. Això és exactament el que fa perillosa la situació: sembla que no s'ha perdut res.

El que sí que s'ha perdut són les dades afegides després de la inicialització, que és tot el que importa en producció:

docker compose exec aurora-db psql -U aurora -d aurora_llibres \
  -c "INSERT INTO llibres (titol, autor, any, isbn) VALUES ('Trafalgar','B. Pérez Galdós',1873,'978-84-000001-0')"
docker compose exec -T aurora-db pg_dump -U aurora -d aurora_llibres -Fc > /tmp/aurora10.dump
docker compose down -v && docker compose up -d --wait
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
docker compose exec -T aurora-db pg_restore -U aurora -d aurora_llibres --clean --if-exists --no-owner < /tmp/aurora10.dump
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
9
10

Després del desastre en queden 9 (els d'init.sql); després de restaurar, els 10 reals. El punt exacte en què la plataforma hauria quedat inservible és el down -v: el -v esborra els volums, i amb ells l'únic lloc on existien les dades afegides pels usuaris. Sense /tmp/aurora10.dump, aquella fila no es recupera d'enlloc.

Solució 3.

docker compose down && docker volume create --label criticitat=alta aurora-dades-ext
docker run --rm -v aurora-libros_aurora-dades:/origen:ro -v aurora-dades-ext:/desti \
  alpine:3 sh -c 'cp -a /origen/. /desti/'
# compose.yaml
volumes:
  aurora-dades:
    name: aurora-dades-ext
    external: true
docker compose up -d --wait
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
docker compose down -v
docker volume ls --filter name=aurora-dades-ext --format '{{.Name}}'
docker compose up -d --wait
docker compose exec aurora-db psql -U aurora -d aurora_llibres -tAc 'SELECT count(*) FROM llibres'
10
aurora-dades-ext
10

El down -v no ha esborrat el volum i les dades continuen intactes: Compose es nega a destruir el que no ha creat. La protecció és real i costa dues línies de YAML, així que per al volum de dades d'una plataforma en producció és una decisió gairebé automàtica.

La contrapartida té dues cares. La primera, operativa: el volum deixa de crear-se sol, i qui desplegui per primera vegada en un servidor nou ha d'executar docker volume create abans de l'up, o Compose fallarà amb external volume not found. La segona, conceptual: perds la reproductibilitat d'un entorn des de zero. En desenvolupament això molesta —el reset de la lliçó 04-07 deixa de funcionar—, així que la pràctica sensata és declarar external només a compose.prod.yaml, deixant que l'entorn local continuï sent d'un sol ús.

Conclusió

Ja saps què passa sota -v. Un storage driver apila les capes d'una imatge i aplica copy-on-write, i overlay2 ho fa amb quatre directoris que has vist al disc: els lowerdir de només lectura de la imatge, l'upperdir on apareix cada fitxer que escriu el contenidor, el merged que és el que el procés veu com a / i el workdir intern del kernel. D'allà surten les tres regles de resolució, inclosa la dels whiteouts que explica per què esborrar un fitxer en una capa posterior no allibera espai. Coneixes la taula completa de drivers —btrfs, zfs, el retirat devicemapper, fuse-overlayfs per a rootless i el lentíssim vfs— i saps que canviar de driver deixa invisibles totes les teves imatges.

I has mesurat el copy-on-write: gairebé dos segons i 512 MB de capa per escriure un sol byte en un fitxer que venia a la imatge. Aquesta xifra converteix en física el que era una recomanació: una base de dades escriu constantment en fitxers grans, per tant va en un volum, que evita OverlayFS i escriu directe al sistema de fitxers del host. Saps esprémer el driver local amb els seus driver_opts —bind amb nom, tmpfs amb mida, NFS per a les portades i mai per a PostgreSQL—, declarar plugins de tercers a Compose i fer servir volums external perquè down -v no pugui endur-se per davant les dades de producció. T'endús a més una estratègia de còpies que és una estratègia i no una intenció: la taula que distingeix la còpia lògica en calent amb pg_dump de la còpia de fitxers amb tar —i per què la segona exigeix aturar el servei—, un script amb rotació que restaura i compta files a cada execució, la regla que una còpia que no s'ha restaurat mai no és una còpia, i el control de l'espai a /var/lib/docker amb data-root.

A la lliçó 05-03 arriba el torn de la seguretat, i amb ella l'enduriment complet d'Aurora Libros: el model d'amenaces i les seves quatre superfícies, per què pertànyer al grup docker equival a ser root —amb l'escalada demostrada—, el mode rootless, imatges mínimes i fixades per digest, --read-only, capacitats del kernel, seccomp, escaneig de vulnerabilitats amb Docker Scout i Trivy, SBOM i signatura amb Cosign, i un compose.prod.yaml endurit línia a línia amb la seva llista de comprovació accionable.

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