La plataforma funciona. curl http://localhost:3000/llibres retorna els vuit títols i la web carrega al navegador. Però hi ha una esquerda als fonaments amb la qual ja has ensopegat dues vegades sense donar-li importància: cada vegada que recrees aurora-db, el catàleg desapareix i has de tornar a carregar init.sql a mà amb docker cp. En una màquina de desenvolupament això és una molèstia. Al servidor on viu el catàleg real d'una llibreria, és una catàstrofe.
Aquesta lliçó resol el segon gran problema dels contenidors, després de la xarxa: les dades. Demostraràs la pèrdua amb un experiment que fa una mica de por, coneixeràs els tres tipus de muntatge que ofereix Docker i quan fer servir cadascun, entendràs on viuen de debò els volums i per què no els has de tocar a mà, i donaràs a PostgreSQL un volum amb nom perquè el catàleg d'Aurora Libros sobrevisqui a la destrucció del seu contenidor. Al final repetiràs l'experiment del principi i el resultat serà el contrari.
Contingut
- El problema, demostrat
- Els tres tipus de muntatge
- Volums gestionats per Docker
- Volums anònims i el problema dels orfes
- Bind mounts: fitxers de la teva màquina dins del contenidor
-venfront de--mount- Muntatges de només lectura
tmpfs: dades en memòria- Pràctica:
aurora-dadesi la prova de destrucció - Còpies de seguretat i restauració
- Bones pràctiques
- El problema, demostrat
Comencem per mirar el desastre a la cara. Afegeix un llibre nou al catàleg:
docker exec aurora-db psql -U aurora -d aurora_llibres -c \
"INSERT INTO llibres (titol, autor, isbn, preu) VALUES
('El Aleph', 'Jorge Luis Borges', '978-84-206-3311-4', 15.20);"
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"
curl -s http://localhost:3000/llibres | jq -r '.llibres | length'Nou llibres. L'API els serveix. Ara simula el que passa en qualsevol operació normal: actualitzar la imatge de PostgreSQL, canviar una variable d'entorn, afegir un límit de memòria... tot això obliga a recrear el contenidor, com saps des de la lliçó 03-01.
docker rm -f aurora-db
docker run -d --name aurora-db --network aurora-net \
--label projecte=aurora-libros --label component=base-de-dades \
-e POSTGRES_USER=aurora -e POSTGRES_PASSWORD=aurora_secreta -e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
postgres:16-alpine
sleep 6
docker exec aurora-db psql -U aurora -d aurora_llibres -c "SELECT COUNT(*) FROM llibres;"No és que falti El Aleph: és que ja no existeix la taula. Tot el catàleg d'Aurora Libros s'ha evaporat. I l'API, coherent:
Això no és una fallada de Docker: és el seu disseny, i ja el coneixies des de la lliçó 01-05. Un contenidor escriu en una capa d'escriptura que neix amb ell i mor amb ell. Les nou files eren en un volum anònim que Docker va crear automàticament —ho vas descobrir a l'exercici 2 de la lliçó 03-03— i que en recrear el contenidor va ser substituït per un altre nou i buit.
flowchart TB
subgraph SENSE["Sense volum: les dades moren amb el contenidor"]
direction TB
S1["docker run postgres"] --> S2["Capa d'escriptura<br/>+ volum anònim nou"]
S2 --> S3["INSERT ... 9 llibres"]
S3 --> S4["docker rm -f"]
S4 --> S5["Tot destruït<br/>o orfe i inabastable"]
end
subgraph AMB["Amb volum amb nom: les dades són independents"]
direction TB
V1["docker volume create aurora-dades"] --> V2["docker run -v aurora-dades:/var/lib/postgresql/data"]
V2 --> V3["INSERT ... 9 llibres"]
V3 --> V4["docker rm -f"]
V4 --> V5["El volum HI CONTINUA SENT"]
V5 --> V6["docker run ... mateix volum<br/>→ els 9 llibres tornen"]
end
La solució és treure les dades del contenidor. Anem-hi.
- Els tres tipus de muntatge
Docker ofereix tres maneres que una ruta dins del contenidor apunti a alguna cosa que no és a la seva capa d'escriptura:
| Volum | Bind mount | tmpfs | |
|---|---|---|---|
| On viuen les dades | En una àrea gestionada per Docker (/var/lib/docker/volumes/) |
En una ruta que tries tu de la teva màquina | A la memòria RAM del host |
| Qui el gestiona | Docker | Tu | El nucli |
Sobreviu a docker rm? |
Sí | Sí (és el teu fitxer) | No, s'evapora |
| Sobreviu a reiniciar el host? | Sí | Sí | No |
| Portabilitat entre màquines | Alta (mateixa comanda a tot arreu) | Baixa (depèn de les rutes del host) | Alta |
| Rendiment a Docker Desktop | Bo | Pitjor (creua la frontera amb la VM) | El millor |
| Còpies de seguretat | Amb docker run + tar, o docker volume |
Amb les eines del host | No aplica |
| Cas d'ús natural | Dades de producció: bases de dades, pujades d'usuaris | Desenvolupament i fitxers de configuració | Secrets i fitxers temporals sensibles |
Sintaxi --mount |
type=volume |
type=bind |
type=tmpfs |
flowchart LR
subgraph HOST["Host"]
VOL["/var/lib/docker/volumes/aurora-dades/_data<br/>(gestionat per Docker)"]
DIR["~/aurora-libros/db/init.sql<br/>(fitxer teu)"]
RAM["Memòria RAM"]
end
subgraph CONT["Contenidor aurora-db"]
M1["/var/lib/postgresql/data"]
M2["/docker-entrypoint-initdb.d/init.sql"]
M3["/tmp/sensible"]
end
VOL -- "volum" --> M1
DIR -- "bind mount :ro" --> M2
RAM -- "tmpfs" --> M3
La regla de decisió, en una frase: si les dades importen i les gestiona l'aplicació, un volum; si el fitxer és teu i el vols editar des del teu editor, un bind mount; si no ha de tocar el disc mai, tmpfs.
- Volums gestionats per Docker
aurora-dades
DRIVER VOLUME NAME
local aurora-dades
local b41f7c9e2a8d05f31c6e8b4a2d97f0c5e3a1b8d6f4c2e0a9b7d5f3c1e8a6b4d2Aquest segon nom il·legible és un volum anònim orfe, resta d'algun dels aurora-db que has esborrat. Hi tornarem.
| Comanda | Què fa |
|---|---|
docker volume create NOM |
Crea un volum |
docker volume ls |
Els llista tots |
docker volume ls -f dangling=true |
Només els que no fa servir cap contenidor |
docker volume inspect NOM |
Mostra la seva ruta real i les seves metadades |
docker volume rm NOM |
L'esborra (falla si està en ús) |
docker volume prune |
Esborra tots els volums sense fer servir |
On viuen realment?
[
{
"CreatedAt": "2026-08-04T22:03:11+02:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/aurora-dades/_data",
"Name": "aurora-dades",
"Options": {},
"Scope": "local"
}
]Mountpoint és la ruta al host. I aquí va l'avís més important de l'apartat:
No hi entris a tocar fitxers. Aquesta ruta és propietat del dimoni de Docker: pertany a root, els seus permisos i el seu context de seguretat els gestiona Docker, i a Docker Desktop (macOS i Windows) ni tan sols existeix a la teva màquina, sinó dins d'una màquina virtual Linux a la qual no tens accés directe. Escriure-hi a mà és una manera excel·lent de corrompre una base de dades.
La manera correcta de mirar dins d'un volum és amb un contenidor:
Buit, acabat de crear. Aquest patró —un contenidor efímer d'Alpine que munta el volum— és la navalla suïssa per inspeccionar, copiar i restaurar volums, i el faràs servir a l'apartat 10.
Etiquetes en volums
docker volume create --label projecte=aurora-libros aurora-copies
docker volume ls --filter label=projecte=aurora-librosIgual que amb els contenidors, etiquetar permet netejar de manera selectiva:
- Volums anònims i el problema dels orfes
Un volum anònim és el que Docker crea pel seu compte, sense que li ho demanis, en dues situacions:
- Quan la imatge declara
VOLUME /rutaal seu Dockerfile (ho fanpostgres,redis,mysql,mongo…). - Quan muntes amb
-v /rutaindicant només la destinació, sense origen.
docker run -d --name demo-anonim -v /dades alpine:3.20 sleep 60
docker inspect -f '{{range .Mounts}}{{.Type}} | {{.Name}} | {{.Destination}}{{end}}' demo-anonimAquest nom de 64 caràcters és el volum anònim. I aquí hi ha el seu problema:
demo-anonim
b41f7c9e2a8d05f31c6e8b4a2d97f0c5e3a1b8d6f4c2e0a9b7d5f3c1e8a6b4d2
e83f1c7a92b40d651fe8a3c9d7b2f4e6a0c8b1d5f3a7c9e1b8d6f4a2c0e8b6d4El volum hi continua sent, orfe. Sense contenidor que el faci servir, amb un nom que ningú no pot recordar, i ocupant disc indefinidament. Cada postgres que has esborrat en aquest mòdul en va deixar enrere un, amb les seves dades a dins, irrecuperables a la pràctica.
| Volum anònim | Volum amb nom | |
|---|---|---|
| Com es crea | Automàticament (VOLUME del Dockerfile o -v /desti) |
docker volume create o -v nom:/desti |
| Nom | Un hash de 64 caràcters | El que triïs tu |
| Es reutilitza en recrear el contenidor | No, se'n crea un altre de buit | Sí, és el mateix |
L'esborra docker rm -v |
Sí | No |
L'esborra docker volume prune |
Sí si està orfe | Sí si està orfe |
| Recomanació | Evitar-los per a dades que importin | Sempre per a dades que importin |
Neteja els orfes ara, comprovant abans què t'enduràs per davant:
Deleted Volumes:
b41f7c9e2a8d05f31c6e8b4a2d97f0c5e3a1b8d6f4c2e0a9b7d5f3c1e8a6b4d2
e83f1c7a92b40d651fe8a3c9d7b2f4e6a0c8b1d5f3a7c9e1b8d6f4a2c0e8b6d4
Total reclaimed space: 47.83MBCompte amb aquesta comanda: docker volume prune esborra dades de debò i no hi ha paperera. Un volum "sense fer servir" pot ser el d'una base de dades el contenidor de la qual està simplement aturat. Des de Docker 23, prune només toca els anònims per defecte; per incloure els que tenen nom cal --all, cosa que és una xarxa de seguretat que convé no desactivar a la lleugera.
- Bind mounts: fitxers de la teva màquina dins del contenidor
Un bind mount connecta una ruta concreta del teu host amb una ruta del contenidor. Ja el vas fer servir a la lliçó 01-06 amb el teu index.html.
mkdir -p ~/aurora-libros/proves
echo "Contingut des del host" > ~/aurora-libros/proves/nota.txt
docker run --rm -v ~/aurora-libros/proves:/dades alpine:3.20 cat /dades/nota.txt
docker run --rm -v ~/aurora-libros/proves:/dades alpine:3.20 \
sh -c 'echo "Escrit des del contenidor" >> /dades/nota.txt'
cat ~/aurora-libros/proves/nota.txtÉs el mateix fitxer vist des de dos llocs: els canvis van en les dues direccions i en temps real. Aquest és el seu superpoder per a desenvolupament —edites al teu IDE i el contenidor ho veu a l'instant— i el seu perill en producció.
Regles i paranys:
| Regla | Detall |
|---|---|
| Ruta absoluta obligatòria | -v ./web:/html no funcionava en versions antigues. Fes servir -v "$(pwd)/web":/html o ~/... (el shell expandeix la titlla) |
| Si la ruta del host no existeix, Docker la crea | I la crea com a directori buit i propietat de root. És la causa del clàssic "vaig muntar el meu fitxer i apareix una carpeta" |
| El muntatge amaga el que hi hagués a la destinació | Muntar un directori buit sobre /usr/share/nginx/html deixa Nginx servint un 403 |
| Els permisos són els del host | I aquí comença el problema dels UID |
El xoc d'UID
Aquest és el mal de cap clàssic dels bind mounts:
docker run --rm -u node --entrypoint sh -v ~/aurora-libros/proves:/dades \
auroralibros/aurora-api:1.2.0 -c 'id; ls -ln /dades'En aquest cas funciona per casualitat: el teu usuari del host té UID 1000 i l'usuari node de la imatge també. Però prova amb un usuari diferent:
docker run --rm -u 1500 -v ~/aurora-libros/proves:/dades alpine:3.20 \
sh -c 'touch /dades/prova.txt || echo "SENSE PERMÍS"'El nucli compara números, no noms. Dins del contenidor no existeix cap "usuari del host": només hi ha UID i GID, i els permisos del fitxer són els que tingui a la teva màquina. Les tres solucions habituals:
| Solució | Com | Quan |
|---|---|---|
| Ajustar l'UID del contenidor | -u $(id -u):$(id -g) |
Desenvolupament, la més pràctica |
| Ajustar els permisos al host | chmod 644 fitxer / chown |
Fitxers de configuració de només lectura |
| Fer servir un volum en comptes d'un bind mount | -v aurora-dades:/ruta |
Dades de producció: Docker gestiona els permisos |
I d'aquí surt una regla que convé memoritzar: els bind mounts són per a desenvolupament i per a fitxers de configuració; les dades de producció van en volums.
-v enfront de --mount
-v enfront de --mountDocker ofereix dues sintaxis per al mateix:
# Sintaxi -v: compacta, posicional, ambigua
docker run -v aurora-dades:/var/lib/postgresql/data postgres:16-alpine
# Sintaxi --mount: verbosa, explícita, amb clau=valor
docker run --mount type=volume,source=aurora-dades,target=/var/lib/postgresql/data postgres:16-alpine-v / --volume |
--mount |
|
|---|---|---|
| Llegibilitat | Compacta però críptica | Verbosa i autoexplicativa |
| Si l'origen no existeix | El crea (un directori buit en bind mounts) | Falla amb error |
| Tipus de muntatge | Es dedueix de la sintaxi | Explícit amb type= |
| Opcions avançades (tmpfs, propagació, drivers) | Limitades | Completes |
| Ús a Docker Compose i Swarm | Suportada | La forma nativa |
| Recomanació | Correcta per a exemples i comandes ràpides | Preferida, sobretot en scripts |
Aquest "si l'origen no existeix" és la diferència pràctica més important. Amb -v, un error de teclat a la ruta et deixa un directori buit i un contenidor que arrenca sense les seves dades; amb --mount, obtens un error clar i el contenidor no arrenca.
docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /ruta/que/no/existeixTaula d'equivalències per tenir-la a mà:
| Objectiu | Amb -v |
Amb --mount |
|---|---|---|
| Volum amb nom | -v dades:/var/lib/data |
--mount type=volume,src=dades,dst=/var/lib/data |
| Volum anònim | -v /var/lib/data |
--mount type=volume,dst=/var/lib/data |
| Bind mount | -v /host/ruta:/cont/ruta |
--mount type=bind,src=/host/ruta,dst=/cont/ruta |
| Bind mount de només lectura | -v /host/f:/cont/f:ro |
--mount type=bind,src=/host/f,dst=/cont/f,readonly |
| tmpfs | --tmpfs /tmp |
--mount type=tmpfs,dst=/tmp |
src i source són sinònims, igual que dst, destination i target.
- Muntatges de només lectura
Un fitxer de configuració no té per què ser escrivible pel contenidor. Marcar-lo com a només lectura és una defensa barata i eficaç:
docker run --rm -v ~/aurora-libros/proves/nota.txt:/config/nota.txt:ro alpine:3.20 \
sh -c 'cat /config/nota.txt; echo "intento escriure" >> /config/nota.txt || echo "BLOQUEJAT"'Contingut des del host
Escrit des del contenidor
sh: can't create /config/nota.txt: Read-only file system
BLOQUEJATEl nucli ho impedeix, no Docker. És el mateix mecanisme que vas fer servir amb index.html i nginx.conf a la lliçó anterior. Fes-lo servir sempre que muntis:
- Fitxers de configuració (
nginx.conf,redis.conf,postgresql.conf). - Scripts d'inicialització (
init.sql). - Certificats i claus públiques.
- Contingut estàtic que l'aplicació no ha de modificar.
tmpfs: dades en memòria
tmpfs: dades en memòriaUn muntatge tmpfs crea un sistema de fitxers a la RAM del host. Res no toca el disc i tot desapareix en aturar el contenidor:
docker run --rm --mount type=tmpfs,dst=/sensible,tmpfs-size=16m alpine:3.20 \
sh -c 'echo "token-temporal-abc123" > /sensible/token; df -h /sensible; cat /sensible/token'Filesystem Size Used Available Use% Mounted on
tmpfs 16.0M 4.0K 16.0M 0% /sensible
token-temporal-abc123| Opció | Per a què |
|---|---|
tmpfs-size |
Mida màxima. Sense límit, pot omplir la RAM del host |
tmpfs-mode |
Permisos del directori, p. ex. 0700 |
Casos d'ús reals: secrets desencriptats en temps d'execució, fitxers de sessió, directoris temporals d'aplicacions que s'executen amb el sistema de fitxers arrel en només lectura, i qualsevol dada que per normativa no hagi de quedar escrita en disc. Només funciona a Linux.
- Pràctica:
aurora-dades i la prova de destrucció
aurora-dades i la prova de destruccióÉs el moment d'arreglar aurora-db d'una vegada.
El pla
| Ruta al contenidor | Tipus de muntatge | Origen | Per què |
|---|---|---|---|
/var/lib/postgresql/data |
Volum aurora-dades |
Gestionat per Docker | Són dades de producció: han de sobreviure al contenidor |
/docker-entrypoint-initdb.d/init.sql |
Bind mount :ro |
~/aurora-libros/db/init.sql |
És un fitxer teu, versionat a Git, que el contenidor només llegeix |
La segona fila fa servir una convenció de la imatge oficial de PostgreSQL: tot el que hi hagi a /docker-entrypoint-initdb.d/ (fitxers .sql, .sql.gz o .sh) s'executa automàticament, en ordre alfabètic, la primera vegada que s'inicialitza la base de dades. És exactament la feina que fa dues lliçons que fas a mà amb docker cp.
Recrear aurora-db com cal
docker rm -f aurora-db
chmod 644 ~/aurora-libros/db/init.sql
docker run -d \
--name aurora-db \
--network aurora-net \
--label projecte=aurora-libros --label component=base-de-dades \
-e POSTGRES_USER=aurora \
-e POSTGRES_PASSWORD=aurora_secreta \
-e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
--mount type=volume,src=aurora-dades,dst=/var/lib/postgresql/data \
--mount type=bind,src=$HOME/aurora-libros/db/init.sql,dst=/docker-entrypoint-initdb.d/init.sql,readonly \
postgres:16-alpineEl chmod 644 no és decoratiu: el procés de PostgreSQL corre com l'usuari postgres (UID 70), i si el fitxer només fos llegible pel teu usuari, l'script no es podria executar. És el xoc d'UID de l'apartat 5, aplicat.
Comprova què ha passat a l'arrencada:
sleep 8
docker logs aurora-db 2>&1 | grep -A2 "initdb.d"
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"/usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/init.sql
CREATE TABLE
CREATE INDEX
INSERT 0 8
8Els vuit llibres s'han carregat sols. Sense docker cp, sense psql -f, sense recordar-se de res.
Verifica els muntatges:
docker inspect -f '{{range .Mounts}}{{.Type}} | {{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}} → {{.Destination}} | rw={{.RW}}{{println}}{{end}}' aurora-dbvolume | aurora-dades → /var/lib/postgresql/data | rw=true
bind | /home/junior/aurora-libros/db/init.sql → /docker-entrypoint-initdb.d/init.sql | rw=falseUn volum escrivible per a les dades i un bind mount de només lectura per a l'script. Exactament el pla.
La prova de destrucció
Afegeix un altre cop El Aleph:
docker exec aurora-db psql -U aurora -d aurora_llibres -c \
"INSERT INTO llibres (titol, autor, isbn, preu) VALUES
('El Aleph', 'Jorge Luis Borges', '978-84-206-3311-4', 15.20);"
curl -s http://localhost:3000/llibres | jq -r '.llibres | length'I ara, destrueix el contenidor. Sense por:
docker stop --time 30 aurora-db
docker rm aurora-db
docker ps -a --filter name=aurora-db -q
docker volume ls --filter name=aurora-dadesEl contenidor ja no existeix. El volum hi continua sent. Recrea'l amb exactament la mateixa comanda d'abans:
docker run -d \
--name aurora-db --network aurora-net \
--label projecte=aurora-libros --label component=base-de-dades \
-e POSTGRES_USER=aurora -e POSTGRES_PASSWORD=aurora_secreta -e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
--mount type=volume,src=aurora-dades,dst=/var/lib/postgresql/data \
--mount type=bind,src=$HOME/aurora-libros/db/init.sql,dst=/docker-entrypoint-initdb.d/init.sql,readonly \
postgres:16-alpine
sleep 6
docker exec aurora-db psql -U aurora -d aurora_llibres -c \
"SELECT id, titol FROM llibres WHERE titol = 'El Aleph';"
curl -s http://localhost:3000/llibres | jq -r '.llibres | length'El Aleph hi continua sent. Has esborrat el contenidor de la base de dades per complet i el catàleg ha sobreviscut intacte, amb els seus nou llibres. Compara-ho amb l'apartat 1 d'aquesta mateixa lliçó, on el mateix experiment va destruir fins i tot la taula.
I hi ha un detall als registres que mereix una mirada:
L'init.sql no s'ha tornat a executar. La imatge detecta que el directori de dades ja conté una base de dades i se salta la inicialització. Això és exactament el que vols: si s'executés sempre, el CREATE TABLE IF NOT EXISTS no faria mal, però un script menys curós podria esborrar dades reals a cada arrencada. I la conseqüència pràctica: si canvies init.sql, no n'hi haurà prou amb reiniciar; hauràs d'esborrar el volum perquè es torni a inicialitzar des de zero.
# Així es comença de zero, quan de debò vols perdre les dades
docker rm -f aurora-db && docker volume rm aurora-dades(No ho executis ara: ho necessites per a la lliçó següent.)
aurora-web, ja muntat
El contenidor de Nginx que vas crear a la lliçó anterior ja fa servir bind mounts; ara pots llegir aquella comanda amb altres ulls:
docker rm -f aurora-web
docker run -d \
--name aurora-web --network aurora-net \
--label projecte=aurora-libros --label component=web \
-p 8080:80 \
--mount type=bind,src=$HOME/aurora-libros/web/index.html,dst=/usr/share/nginx/html/index.html,readonly \
--mount type=bind,src=$HOME/aurora-libros/web/nginx.conf,dst=/etc/nginx/conf.d/default.conf,readonly \
--stop-signal SIGQUIT \
nginx:alpine
curl -s http://localhost:8080/api/llibres | jq -r '.llibres | length'Bind mounts de només lectura per a dos fitxers que viuen al teu repositori de Git. Edita index.html al teu editor, recarrega el navegador, i el canvi hi és sense reconstruir cap imatge: aquest és el flux de desenvolupament que es converteix en el protagonista de la lliçó 04-07.
- Còpies de seguretat i restauració
Un volum que no es copia és un volum que es perdrà. Hi ha dues estratègies i serveixen per a coses diferents.
Còpia a nivell de fitxers amb un contenidor auxiliar
El patró universal: un contenidor efímer que munta el volum i un directori del host, i executa tar.
mkdir -p ~/aurora-libros/copies
# 1) Aturar la base de dades perquè la còpia sigui COHERENT
docker stop --time 30 aurora-db
# 2) Empaquetar el volum
docker run --rm \
--mount type=volume,src=aurora-dades,dst=/dades,readonly \
--mount type=bind,src=$HOME/aurora-libros/copies,dst=/copia \
alpine:3.20 \
tar czf /copia/aurora-dades-2026-08-04.tar.gz -C /dades .
# 3) Tornar a arrencar
docker start aurora-db
ls -lh ~/aurora-libros/copies/Què fa cada part:
| Fragment | Motiu |
|---|---|
docker stop abans de copiar |
PostgreSQL manté dades a la memòria intermèdia. Copiar en calent pot donar una còpia corrupta |
--rm |
El contenidor auxiliar s'autodestrueix |
readonly al volum d'origen |
El procés de còpia no pot fer malbé l'original |
-C /dades . |
Empaqueta el contingut, no el directori, perquè la restauració sigui directa |
alpine:3.20 |
8 MB amb tar a dins; no cal res més |
La restauració és l'operació inversa:
docker stop aurora-db
docker run --rm \
--mount type=volume,src=aurora-dades,dst=/dades \
--mount type=bind,src=$HOME/aurora-libros/copies,dst=/copia,readonly \
alpine:3.20 \
sh -c 'rm -rf /dades/* /dades/..?* && tar xzf /copia/aurora-dades-2026-08-04.tar.gz -C /dades'
docker start aurora-db
sleep 6
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"El ..?* del rm és per incloure els fitxers ocults; sense ell quedarien restes de la instal·lació anterior barrejades amb la còpia.
Còpia lògica amb pg_dump
Per a una base de dades, gairebé sempre és millor una còpia lògica: un fitxer SQL amb l'esquema i les dades.
docker exec aurora-db pg_dump -U aurora -d aurora_llibres --clean --if-exists \
> ~/aurora-libros/copies/aurora_llibres-2026-08-04.sql
ls -lh ~/aurora-libros/copies/*.sql
head -20 ~/aurora-libros/copies/aurora_llibres-2026-08-04.sql | tail -4-rw-r--r-- 1 junior junior 4.2K Aug 4 22:45 aurora_llibres-2026-08-04.sql
DROP TABLE IF EXISTS public.llibres;
CREATE TABLE public.llibres (
id integer NOT NULL,I la restauració, amb -i perquè psql llegeixi de l'entrada estàndard:
docker exec -i aurora-db psql -U aurora -d aurora_llibres \
< ~/aurora-libros/copies/aurora_llibres-2026-08-04.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"Comparació de les dues estratègies:
Còpia amb tar del volum |
Còpia lògica amb pg_dump |
|
|---|---|---|
| Què copia | Tots els bytes del directori de dades | Sentències SQL que reconstrueixen les dades |
| Requereix aturar el servei | Sí, perquè sigui coherent | No, pg_dump és transaccional |
| Mida | Gran (8,4 MB aquí) | Petita (4,2 kB) |
| Portable entre versions de PostgreSQL | No: el format de dades és específic de la versió major | Sí |
| Llegible i inspeccionable | No | Sí, és text |
| Serveix per a qualsevol volum | Sí, és genèrica | No, només bases de dades |
| Quan fer-la servir | Volums d'aplicacions sense eina pròpia | Bases de dades, sempre que sigui possible |
La regla: fes servir l'eina nativa del servei si en té (pg_dump, mysqldump, redis-cli BGSAVE), i el truc del contenidor amb tar per a tota la resta.
- Bones pràctiques
| Pràctica | Motiu |
|---|---|
| Un volum amb nom per cada estat que importi | aurora-dades, aurora-pujades, aurora-copies. Mai anònims per a dades reals |
| Noms descriptius amb prefix de projecte | aurora-dades diu què és; data no diu res en una màquina amb vint volums |
| Etiqueta els volums | --label projecte=aurora-libros permet prune selectiu |
| Bind mounts només per a configuració i desenvolupament | Depenen de rutes del host: no són portables |
Tot el que el contenidor no hagi d'escriure, en :ro |
Configuració, scripts, certificats, contingut estàtic |
Prefereix --mount en scripts |
Falla sorollosament si l'origen no existeix, en comptes de crear un directori buit |
No escriguis mai dins de /var/lib/docker/volumes |
És territori del dimoni, i a Docker Desktop ni tan sols és a la teva màquina |
Còpia de seguretat abans de qualsevol prune |
docker volume prune no té desfer |
| Prova la restauració, no només la còpia | Una còpia que mai no s'ha restaurat no és una còpia, és una esperança |
Estat actual de l'emmagatzematge d'Aurora Libros:
docker volume ls --format "table {{.Name}}\t{{.Driver}}"
docker system df --format "table {{.Type}}\t{{.TotalCount}}\t{{.Size}}\t{{.Reclaimable}}"NAME DRIVER
aurora-dades local
TYPE TOTAL SIZE RECLAIMABLE
Images 11 1.021GB 612.4MB (59%)
Containers 4 3.87MB 0B (0%)
Local Volumes 1 48.2MB 0B (0%)
Build Cache 52 1.847GB 1.847GB (100%)Un sol volum, amb nom, i 0 B recuperables: ni un orfe. Aquest és l'objectiu.
El que aquesta lliçó no cobreix i on es veu: els storage drivers del dimoni (overlay2 i companyia), els drivers de volum per a NFS, SMB o emmagatzematge al núvol, i les opcions avançades de rendiment s'estudien a la lliçó 05-02. Com es declaren els volums en un fitxer compose.yaml, a la lliçó 04-02.
Errors Habituals i Consells
- Confiar en la capa d'escriptura per a dades que importen. Mor amb el contenidor, i recrear un contenidor és una operació rutinària.
- Fer servir volums anònims sense saber-ho. Tota imatge amb
VOLUMEal seu Dockerfile en crea un. Si no li poses nom, les teves dades depenen d'un hash que ningú no recorda. - Executar
docker volume prunesense mirar. Esborra dades reals sense confirmació per segona vegada i sense paperera. Llista abans ambdocker volume ls -f dangling=true. - Escriure a mà a
/var/lib/docker/volumes/.... Permisos incorrectes, corrupció, i a Docker Desktop ni tan sols existeix aquella ruta. - Rutes relatives en un bind mount. Fes servir rutes absolutes o
$(pwd); amb-v, una ruta mal escrita es converteix en un directori buit creat com a root. - Muntar un directori buit sobre un que tenia contingut. El muntatge amaga el de la imatge: Nginx retornarà 403 i PostgreSQL es reinicialitzarà.
- Esperar que
init.sqls'executi a cada arrencada. Només corre quan el directori de dades està buit. Si el canvies, esborra el volum. - Copiar un volum de base de dades en calent amb
tar. Pot sortir corrupte. Atura el contenidor, o fes servirpg_dump. - Consell: escriu el
docker rundels teus serveis amb dades en un fitxer de notes. És llarg i cal reproduir-lo exactament per no perdre el volum... fins al mòdul 4, on això es resol del tot. - Consell: anomena les còpies amb la data i automatitza'n la rotació. I restaura almenys una vegada, per saber que funciona.
Exercicis
Exercici 1: comprova les tres persistències
Crea un contenidor d'alpine:3.20 anomenat tres-muntatges amb sleep 600 i tres muntatges alhora: un volum amb nom prova-vol a /vol, un bind mount de ~/aurora-libros/proves a /bind, i un tmpfs de 8 MB a /mem. Escriu un fitxer diferent a cada ruta. Després:
- Reinicia el contenidor amb
docker restarti comprova quins dels tres fitxers hi continuen. - Esborra'l amb
docker rm -f(sense-v), crea'n un de nou amb els mateixos muntatges i comprova de nou els tres. - Explica els resultats i digues quin muntatge faries servir per a: el catàleg de llibres, el
nginx.conf, i un token de sessió que no ha de tocar el disc.
Exercici 2: migra aurora-cache a un volum amb nom
Redis desa la seva instantània a /data, que la imatge oficial declara com a VOLUME i per tant avui és un volum anònim. Arregla-ho:
- Comprova amb
docker inspectque l'aurora-cacheactual fa servir un volum anònim. - Recrea'l amb un volum amb nom
aurora-cache-dadesi activant la persistència amb la comandaredis-server --appendonly yes. - Escriu una clau, força un desament, destrueix el contenidor i recrea'l. Comprova si la clau sobreviu.
- Respon: té sentit persistir una memòria cau? Argumenta a favor i en contra.
Exercici 3: simula un desastre i recupera't
Amb la plataforma funcionant i els nou llibres al seu lloc:
- Fes una còpia de seguretat lògica amb
pg_dumpi una altra de física ambtar, anotant la mida de cadascuna. - Provoca el desastre: esborra el contenidor
aurora-dbi el volumaurora-dades. - Comprova què respon l'API en aquell moment.
- Recupera't pel camí més ràpid i explica quin has triat i per què. Verifica que els nou llibres —inclòs El Aleph, que no és a
init.sql— tornen a estar disponibles a través decurl http://localhost:8080/api/llibres.
Solucions
Solució a l'exercici 1
docker volume create prova-vol
docker run -d --name tres-muntatges \
--mount type=volume,src=prova-vol,dst=/vol \
--mount type=bind,src=$HOME/aurora-libros/proves,dst=/bind \
--mount type=tmpfs,dst=/mem,tmpfs-size=8m \
alpine:3.20 sleep 600
docker exec tres-muntatges sh -c 'echo volum > /vol/f.txt; echo bind > /bind/f.txt; echo memoria > /mem/f.txt'
docker exec tres-muntatges sh -c 'cat /vol/f.txt /bind/f.txt /mem/f.txt'1. Després de docker restart:
docker restart tres-muntatges && sleep 1
docker exec tres-muntatges sh -c 'cat /vol/f.txt 2>&1; cat /bind/f.txt 2>&1; cat /mem/f.txt 2>&1'El tmpfs ja s'ha perdut amb el simple reinici: vivia a RAM i el muntatge es recrea buit a cada arrencada.
2. Després d'esborrar i recrear:
docker rm -f tres-muntatges
docker run -d --name tres-muntatges \
--mount type=volume,src=prova-vol,dst=/vol \
--mount type=bind,src=$HOME/aurora-libros/proves,dst=/bind \
--mount type=tmpfs,dst=/mem,tmpfs-size=8m \
alpine:3.20 sleep 600
docker exec tres-muntatges sh -c 'cat /vol/f.txt 2>&1; cat /bind/f.txt 2>&1; cat /mem/f.txt 2>&1'
docker rm -f tres-muntatges && docker volume rm prova-vol3. Resultats i decisions:
| Muntatge | Sobreviu a restart? |
Sobreviu a rm + recrear? |
Per què |
|---|---|---|---|
Volum prova-vol |
Sí | Sí | És un objecte independent del contenidor, gestionat per Docker |
Bind ~/aurora-libros/proves |
Sí | Sí | És un directori de la teva màquina; Docker només l'ensenya |
tmpfs /mem |
No | No | Viu a RAM i es descarta amb el procés |
I les tres decisions: el catàleg de llibres → volum amb nom (dada de producció que ha de sobreviure i de la qual Docker gestiona els permisos); el nginx.conf → bind mount de només lectura (fitxer versionat a Git que vols editar al teu IDE i que el contenidor només llegeix); el token de sessió → tmpfs (no ha de quedar escrit en disc ni un instant, i s'ha d'evaporar en aturar el contenidor).
Solució a l'exercici 2
docker inspect -f '{{range .Mounts}}{{.Type}} | nom={{.Name}} | {{.Destination}}{{end}}' aurora-cacheVolum anònim confirmat: nom de 64 caràcters, creat pel VOLUME /data del Dockerfile oficial de Redis.
docker volume create aurora-cache-dades
docker rm -f aurora-cache
docker run -d --name aurora-cache --network aurora-net \
--label projecte=aurora-libros --label component=cache \
--mount type=volume,src=aurora-cache-dades,dst=/data \
redis:7-alpine redis-server --appendonly yesdocker exec aurora-cache redis-cli SET llibre:destacat "Rayuela"
docker exec aurora-cache redis-cli BGREWRITEAOF
sleep 2
docker exec aurora-cache ls /data
docker rm -f aurora-cache
docker run -d --name aurora-cache --network aurora-net \
--label projecte=aurora-libros --label component=cache \
--mount type=volume,src=aurora-cache-dades,dst=/data \
redis:7-alpine redis-server --appendonly yes
sleep 2
docker exec aurora-cache redis-cli GET llibre:destacatLa clau sobreviu. Nota que van caldre dues coses: el volum amb nom (perquè els fitxers persisteixin) i --appendonly yes (perquè Redis hi escrigui). Un volum sense persistència activada desaria un fitxer que mai no s'actualitza.
4. Té sentit persistir una memòria cau?
A favor:
- Arrencada en calent. Després d'un reinici, la memòria cau ja té dades i no hi ha una allau de peticions contra PostgreSQL (el fenomen conegut com a thundering herd).
- Si Redis es fa servir també per a sessions d'usuari o cues de treball, allà ja no és una memòria cau: són dades que perdre-les té conseqüències visibles.
En contra:
- Una memòria cau, per definició, ha de poder perdre's sense conseqüències. Si el teu sistema no funciona sense ella, no és una memòria cau: és una base de dades disfressada, i s'hauria de tractar com a tal.
- La persistència costa: E/S de disc a cada escriptura i una arrencada més lenta.
- Les dades persistides poden quedar obsoletes: en recuperar-se, Redis serveix un catàleg antic fins que expiri el TTL.
Per a Aurora Libros, amb un TTL de 60 segons i un catàleg petit, no compensa: és més net deixar aurora-cache sense persistència i que s'ompli sola a la primera petició. Aquest és el criteri que s'aplicarà al compose.yaml del mòdul 4.
docker rm -f aurora-cache && docker volume rm aurora-cache-dades
docker run -d --name aurora-cache --network aurora-net \
--label projecte=aurora-libros --label component=cache redis:7-alpineSolució a l'exercici 3
1. Les dues còpies:
mkdir -p ~/aurora-libros/copies
docker exec aurora-db pg_dump -U aurora -d aurora_llibres --clean --if-exists \
> ~/aurora-libros/copies/desastre.sql
docker stop --time 30 aurora-db
docker run --rm \
--mount type=volume,src=aurora-dades,dst=/dades,readonly \
--mount type=bind,src=$HOME/aurora-libros/copies,dst=/copia \
alpine:3.20 tar czf /copia/desastre.tar.gz -C /dades .
docker start aurora-db
ls -lh ~/aurora-libros/copies/desastre.*-rw-r--r-- 1 junior junior 4.3K Aug 4 23:02 desastre.sql
-rw-r--r-- 1 junior junior 8.4M Aug 4 23:03 desastre.tar.gzDues mil vegades més petita la lògica que la física, per exactament els mateixos nou llibres.
2 i 3. El desastre:
docker rm -f aurora-db
docker volume rm aurora-dades
curl -s http://localhost:8080/api/llibres | jq -cInteressant: l'error no és de base de dades, sinó de DNS. En no existir el contenidor, el nom aurora-db no es resol a aurora-net. És el mateix ENOTFOUND de la lliçó 03-04, ara amb una causa diferent: allà faltava la xarxa, aquí falta el contenidor.
4. La recuperació:
# Recrear el contenidor: el volum es crea buit i init.sql s'executa tot sol
docker run -d --name aurora-db --network aurora-net \
--label projecte=aurora-libros --label component=base-de-dades \
-e POSTGRES_USER=aurora -e POSTGRES_PASSWORD=aurora_secreta -e POSTGRES_DB=aurora_llibres \
-p 127.0.0.1:5432:5432 \
--mount type=volume,src=aurora-dades,dst=/var/lib/postgresql/data \
--mount type=bind,src=$HOME/aurora-libros/db/init.sql,dst=/docker-entrypoint-initdb.d/init.sql,readonly \
postgres:16-alpine
sleep 8
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"Vuit, no nou. L'init.sql va restaurar el catàleg original, però El Aleph no hi és: es va inserir després. Aquí és on la còpia de seguretat deixa de ser un tràmit i passa a ser l'única cosa que et salva:
docker exec -i aurora-db psql -U aurora -d aurora_llibres < ~/aurora-libros/copies/desastre.sql
docker exec aurora-db psql -U aurora -d aurora_llibres -t -c "SELECT COUNT(*) FROM llibres;"
curl -s http://localhost:8080/api/llibres | jq -r '.llibres[] | select(.titol=="El Aleph") | .titol'He triat la còpia lògica, i per tres motius:
- És més ràpida d'aplicar: un
psqlde 4 kB enfront d'aturar el servei, buidar el volum i desempaquetar 8,4 MB. - No exigeix aturar res: es restaura sobre la base de dades en marxa, mentre que la còpia física obliga a aturar el contenidor.
- És portable: si en recuperar-me hagués aprofitat per pujar a PostgreSQL 17, el
tardel volum no hauria servit —el format del directori de dades és específic de cada versió major— i el.sqlsí.
La còpia física continua tenint el seu paper: és l'única opció per a volums d'aplicacions que no tenen una eina de bolcat pròpia, i és una còpia bit a bit que inclou configuració i estat que pg_dump no recull.
Conclusió
El segon gran problema està resolt. Has vist amb els teus propis ulls que un contenidor destruït s'emporta les seves dades —la primera vegada ni tan sols quedava la taula llibres— i saps per què: la capa d'escriptura neix i mor amb el contenidor, i els volums anònims que creen pel seu compte imatges com postgres o redis no es reutilitzen, sinó que queden orfes amb les seves dades a dins, inabastables a la pràctica.
Coneixes els tres tipus de muntatge i quan fer servir cadascun: volums gestionats per Docker per a les dades que importen, bind mounts per a configuració i desenvolupament, i tmpfs per al que no ha de tocar el disc. Saps on viuen realment els volums —/var/lib/docker/volumes/…, territori del dimoni, invisible a Docker Desktop— i que la manera correcta de mirar-hi dins és un contenidor efímer d'Alpine, mai el teu editor. Entens el xoc d'UID dels bind mounts, perquè el nucli compara números i no noms, i tens les tres maneres de resoldre'l. I prefereixes --mount a -v en scripts per una raó molt concreta: falla sorollosament en comptes de crear un directori buit que arruïna l'arrencada en silenci.
Aurora Libros ha canviat de categoria. aurora-db desa el seu estat al volum amb nom aurora-dades i rep init.sql com a bind mount de només lectura a /docker-entrypoint-initdb.d/, així que el catàleg es carrega sol la primera vegada i mai més no es trepitja. La prova definitiva va sortir com havia de sortir: vas inserir El Aleph, vas destruir el contenidor sencer, el vas recrear amb la mateixa comanda i els nou llibres hi continuaven sent. I aurora-web serveix index.html i nginx.conf des del teu repositori en :ro, editables des del teu IDE sense reconstruir res. A més saps copiar i restaurar: amb un contenidor auxiliar i tar per a qualsevol volum, i amb pg_dump —més petita, en calent i portable entre versions— per a la base de dades.
Queden dos riscos drets, i són els últims d'aquest mòdul. El primer el vas veure a docker stats a la lliçó 03-04: cap dels teus contenidors no té límit de memòria, així que una consulta desbocada a PostgreSQL o una fuita a Node poden deixar sense RAM tota la màquina. El segon és que si un contenidor mor de matinada, ningú no l'aixeca. A la lliçó següent, Límits de Recursos i Polítiques de Reinici, posaràs setge a tots dos: límits de memòria i CPU amb les seves taules de combinacions, l'OOM killer provocat a propòsit per veure'l amb OOMKilled: true i el seu codi 137, --pids-limit com a defensa davant d'una fork bomb, i les quatre polítiques de reinici amb la diferència subtil entre always i unless-stopped. I en acabar, tindràs al davant la llista completa de comandes que cal per aixecar Aurora Libros des de zero... i entendràs per què existeix Docker Compose.
Docker: De Principiant a Avançat
Mòdul 1: Introducció a Docker
- Què és Docker?
- Instal·lant Docker
- Arquitectura de Docker
- Comandes Bàsiques de Docker
- Entenent les Imatges de Docker
- Creant el teu Primer Contenidor Docker
- El Projecte del Curs: la Plataforma Aurora Libros
Mòdul 2: Treballant amb Imatges Docker
- Docker Hub i Repositoris
- Construint Imatges Docker
- Conceptes Bàsics de Dockerfile
- Instruccions Avançades del Dockerfile
- Gestionant Imatges Docker
- Etiquetatge i Publicació d'Imatges
Mòdul 3: Contenidors Docker
- Executant Contenidors
- Cicle de Vida del Contenidor
- Gestionant Contenidors
- Inspecció i Depuració de Contenidors
- Xarxes a Docker
- Persistència de Dades amb Volums
- Límits de Recursos i Polítiques de Reinici
Mòdul 4: Docker Compose
- Introducció a Docker Compose
- Definint Serveis a Docker Compose
- Comandes de Docker Compose
- Aplicacions Multi-Contenidor
- Variables d'Entorn a Docker Compose
- Perfils, Overrides i Múltiples Entorns
- Desenvolupament Local amb Docker Compose
Mòdul 5: Conceptes Avançats de Docker
- Aprofundiment en Xarxes Docker
- Opcions d'Emmagatzematge Docker
- Millors Pràctiques de Seguretat a Docker
- Optimitzant Imatges Docker
- Builds Avançades amb BuildKit i Buildx
- Registre i Monitoratge a Docker
- El Runtime per Dins: Namespaces, Cgroups i Capes
Mòdul 6: Docker en Producció
- Preparar una Imatge per a Producció
- CI/CD amb Docker
- Orquestrant Contenidors amb Docker Swarm
- Introducció a Kubernetes
- Desplegant Contenidors Docker a Kubernetes
- Escalat i Balanceig de Càrrega
- Estratègies de Desplegament i Rollback
