Els contenidors Docker són efímers per disseny: quan eliminam un contenidor, totes les dades que contenia desapareixen amb ell. Això és desitjable per a molts casos d’ús (aplicacions sense estat, entorns de proves), però és un problema quan necessitam persistir dades com ara bases de dades, fitxers pujats pels usuaris o configuracions.
En aquest article veurem com els volums resolen aquest problema, permetent que les dades sobrevisquin al cicle de vida dels contenidors.
La pèrdua de dades #
Per il·lustrar el problema, usarem MongoDB, una base de dades documental NoSQL. Començarem executant un contenidor sense cap volum:
docker run --name mongo-temp \
--env MONGO_INITDB_ROOT_USERNAME=admin \
--env MONGO_INITDB_ROOT_PASSWORD=secret \
--detach \
mongo:8.2Connectem-nos al contenidor i creem una col·lecció amb dades:
docker exec --interactive --tty mongo-temp mongosh \
--username admin --password secretDins del shell de MongoDB, cream una base de dades i una col·lecció:
use botigaInserim alguns documents:
db.productes.insertMany([
{ nom: "Teclat mecànic", preu: 89.99, estoc: 15 },
{ nom: "Ratolí ergonòmic", preu: 45.50, estoc: 30 },
{ nom: "Monitor 27 polzades", preu: 299.00, estoc: 8 }
])Comprovem que les dades s’han inserit:
db.productes.find()I sortim del shell:
exitAra eliminam el contenidor i el tornam a crear:
docker rm --force mongo-temp
docker run --name mongo-temp \
--env MONGO_INITDB_ROOT_USERNAME=admin \
--env MONGO_INITDB_ROOT_PASSWORD=secret \
--detach \
mongo:8.2Si intentam consultar les dades:
docker exec -it mongo-temp mongosh --username admin \
--password secret --eval "use botiga" \
--eval "db.productes.find()"Les dades han desaparegut. Cada vegada que eliminam i recream el contenidor, MongoDB comença amb una base de dades buida.
Netejem abans de continuar:
docker rm --force mongo-tempVolums Docker #
Els volums són el mecanisme recomanat per persistir dades generades i utilitzades pels contenidors. Un volum és un directori gestionat per Docker que existeix fora del sistema de fitxers del contenidor.
Característiques principals:
- Persistència: Les dades sobreviuen a l’eliminació del contenidor.
- Compartició: Múltiples contenidors poden accedir al mateix volum.
- Gestió per Docker: Docker s’encarrega de la ubicació i els permisos.
- Portabilitat: Els volums es poden migrar entre amfitrions.
La comanda que permet gestionar volums és docker volume.
Crear i llistar #
Per crear un volum:
docker volume create mongo-dataPer llistar els volums existents:
docker volume lsLa sortida mostra:
DRIVER VOLUME NAME
...
local mongo-dataInspeccionar #
Per veure informació detallada d’un volum:
docker volume inspect mongo-dataLa sortida inclou la ubicació física del volum al sistema amfitrió:
[
{
"CreatedAt": "2026-03-30T08:00:00Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/mongo-data/_data",
"Name": "mongo-data",
"Options": null,
"Scope": "local"
}
]El camp Mountpoint indica on Docker emmagatzema físicament les dades del volum.
Muntar un volum #
Ara executarem MongoDB amb el volum que acabem de crear:
docker run --name mongo-persistent \
--env MONGO_INITDB_ROOT_USERNAME=admin \
--env MONGO_INITDB_ROOT_PASSWORD=secret \
--volume mongo-data:/data/db \
--detach \
mongo:8.2L’opció --volume mongo-data:/data/db indica:
mongo-data: El nom del volum (que hem creat prèviament)./data/db: El directori dins del contenidor on es muntarà el volum (on MongoDB emmagatzema les dades).
docker run --volume, Docker el crea automàticament. Tanmateix, és bona pràctica crear-lo explícitament per tenir més control.
Verificar la persistència #
Ara comprovarem que les dades persisteixen. Començarem creant dades, seguint el mateix procediment que hem fet abans. En primer lloc, obrim un shell dins del contenidor:
docker exec -it mongo-persistent mongosh \
--username admin --password secretSeleccionam la base de dades botiga:
use botigaI inserim dades:
db.productes.insertMany([
{ nom: "Teclat mecànic", preu: 89.99, estoc: 15 },
{ nom: "Ratolí ergonòmic", preu: 45.50, estoc: 30 },
{ nom: "Monitor 27 polzades", preu: 299.00, estoc: 8 },
{ nom: "Auriculars amb micròfon", preu: 65.00, estoc: 22 },
{ nom: "Disc SSD 1TB", preu: 79.95, estoc: 45 },
{ nom: "Memòria RAM 16GB DDR5", preu: 52.50, estoc: 18 }
])Per sortir del shell, escrivim exit o premem Ctrl+D.
A continuació eliminam el contenidor i el tornam a crear amb el mateix volum:
docker rm --force mongo-persistent
docker run --name mongo-persistent \
--env MONGO_INITDB_ROOT_USERNAME=admin \
--env MONGO_INITDB_ROOT_PASSWORD=secret \
--volume mongo-data:/data/db \
--detach \
mongo:8.2Verificam que les dades encara hi són:
docker exec -it mongo-persistent mongosh --username admin \
--password secret --eval "use botiga" \
--eval "db.productes.find()"Les dades han sobreviscut a l’eliminació i recreació del contenidor.
Eliminar volums #
Per eliminar un volum que ja no necessitem:
docker volume rm mongo-dataPer eliminar tots els volums que no estiguin en ús:
docker volume prune --forceEl paràmetre --force farà que Docker no demani confirmació abans d’eliminar els volums.
Bind mounts #
Els bind mounts són una altra manera de persistir dades. En aquest cas, en comptes d’usar un volum gestionat per Docker, muntam directament un directori de l’amfitrió dins del contenidor.
Les diferències principals:
| Característica | Volums | Bind mounts |
|---|---|---|
| Ubicació | Gestionada per Docker1 | Qualsevol directori de l’amfitrió |
| Creació | docker volume create o automàtica |
El directori ha d’existir |
| Portabilitat | Alta (Docker ho gestiona tot) | Baixa (depèn de l’estructura de l’amfitrió) |
| Cas d’ús típic | Dades de producció, bases de dades | Desenvolupament, compartir codi font |
Exemple #
Suposem que volem compartir un directori de configuració amb un contenidor de Valkey. Per fer-ho, en primer lloc crearem el directori de configuració a l’amfitrió:
mkdir --parents ~/valkey-configI, en segon lloc, crearem un fitxer de configuració ~/valkey-config/valkey.conf amb el segÜent contingut:
maxmemory 100mb
maxmemory-policy allkeys-lru
appendonly yesAra executam un contenidor Valkey muntant aquest directori:
docker run --name valkey-demo \
--volume ~/valkey-config:/usr/local/etc/valkey:ro \
--detach \
valkey/valkey:9.0-alpine \
valkey-server /usr/local/etc/valkey/valkey.confAnalitzem l’opció --volume:
~/valkey-config: Directori de l’amfitrió (ruta absoluta o relativa a home)./usr/local/etc/valkey: Directori dins del contenidor.:ro: Muntar el volum en mode només lectura (read-only).
:ro és opcional però recomanat quan el contenidor no necessita escriure al directori, car afegeix una capa de seguretat.
El paràmetre canvia la comanda per defecte que s’executarà, en aquest cas indicant que s’usi el fitxer de configuració
/usr/local/etc/valkey/valkey.confdins el contenidor.
Podem verificar que Valkey ha carregat la configuració:
docker exec valkey-demo valkey-cli CONFIG GET maxmemoryDesenvolupament #
Un cas d’ús molt comú és muntar el codi font de l’aplicació durant el desenvolupament, permetent veure els canvis sense reconstruir la imatge.
Suposem que tenim una aplicació web estàtica al directori ~/Projects/web:
mkdir -p ~/Projects/web
cat << EOF > ~/Projects/web/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My First HTML Page</title>
</head>
<body>
<h1>Hola, Docker!</h1>
</body>
</html>
EOFPodem servir-la amb NGINX muntant el directori:
docker run --name nginx-dev \
--volume ~/Projects/web:/usr/share/nginx/html:ro \
--publish 8080:80 \
--detach \
nginx:1.29-alpineSi carregam http://localhost:8080/ al navegador veurem el contingut de l’aplicació. Ara, si modificam el fitxer ~/Projects/web/index.html, els canvis es reflecteixen immediatament sense reiniciar el contenidor:
sed --in-place 's|<h1>.*</h1>|<h1>Contingut actualitzat!</h1>|' \
~/Projects/web/index.htmlSi recarregam la pàgina al navegador podem veure el canvi.
Volums anònims #
Quan executam un contenidor amb --volume especificant només el punt de muntatge dins del contenidor, Docker crea un volum anònim:
docker run --name mongo-anon \
--volume /data/db \
--detach \
mongo:8.2Docker assigna un nom aleatori al volum (una cadena hexadecimal llarga). Podem veure’l amb:
docker volume lsEls volums anònims són útils per a proves ràpides, però dificulten la gestió perquè no tenen noms descriptius. És preferible usar volums amb nom.
Inspecció de dades #
De vegades necessitam accedir directament a les dades d’un volum sense passar pel contenidor. Hi ha diverses maneres de fer-ho.
Suposem que tenim tenim un volum nginx-web amb un contenidor NGINX que en fa ús:
docker volume create nginx-web
docker run --name nginx-temp \
--volume nginx-web:/usr/share/nginx/html \
--detach \
nginx:1.29-alpineAccés amb un contenidor auxiliar
Una forma habitual es muntar el volum en un contenidor auxiliar per inspeccionar-lo.
docker run --rm -it \
--volume nginx-web:/data:ro \
alpine \
ls -la /dataAmb aquesta comanda, estam muntant el mateix volum que usa el contenidor nginx-temp en mode només lectura a /data d’un nou contenidor auxiliar i temporal.
L’opció --rm de la comanda docker run elimina el contenidor automàticament quan surt.
Accés directe
Una altra forma és accedir al directori físic del volum a l’amfitrió (requereix permisos de root):
sudo ls -la /var/lib/docker/volumes/nginx-web/_dataCòpies de seguretat #
Per fer una còpia de seguretat d’un volum que conté fitxers estàtics (com ara pàgines web, configuracions o documents), podem usar un contenidor auxiliar que munti el volum i creï un arxiu:
docker run --rm \
--volume nginx-web:/source:ro \
--volume $(pwd):/backup \
alpine \
tar --create --gzip --file /backup/nginx-backup.tar.gz \
--directory /source .Aquesta comanda:
- Munta el volum
nginx-weben mode només lectura a/source - Munta el directori actual a
/backup - Crea un arxiu comprimit amb el contingut del volum
Per restaurar:
docker run --rm \
--volume nginx-web:/target \
--volume $(pwd):/backup:ro \
alpine \
tar --extract --gzip --file /backup/nginx-backup.tar.gz \
--directory /targetNeteja #
Per netejar tots els recursos creats durant aquesta lliçó començarem aturant i esborrant els contenidors:
docker rm --force mongo-anon mongo-persistent mongo-temp
docker rm --force nginx-dev nginx-temp valkey-demoI continuarem eliminant els volums:
docker volume rm mongo-data nginx-webPer a eliminar volums no utilitzats, incloent els anònims, podem usam:
docker volume prune --forceExercicis pràctics #
Es proposen tres exercicis pràctics per facilitar l’aprenentatge progressiu.
Exercici 1 #
Persistència amb PostgreSQL
- Crea un volum anomenat
pg-biblioteca. - Executa un contenidor PostgreSQL anomenat
pg-volumque utilitzi aquest volum per emmagatzemar les dades, amb una base de dadesbiblioteca, usuaribiblioi contrasenyadFeWm5y2dZCP9wKKWYAe. - Connecta’t al contenidor i crea una taula
llibreamb columnesid,titoliautor, i insereix un conjunt de registres. - Atura el contenidor.
Pista: Consulta la documentació de PostgreSQL a Docker Hub per saber on s’emmagatzemen les dades dins del contenidor.
Respostes
Creació del volum:
docker volume create pg-bibliotecaCreació del contenidor amb el volum:
docker run --name pg-volum \
--env POSTGRES_PASSWORD='dFeWm5y2dZCP9wKKWYAe' \
--env POSTGRES_USER=biblio \
--env POSTGRES_DB=biblioteca \
--volume pg-biblioteca:/var/lib/postgresql \
--detach \
postgres:18-alpineConnexió al contenidor:
docker exec -it pg-volum psql --user=biblio --dbname=bibliotecaCreació de la taula:
CREATE TABLE IF NOT EXISTS llibre (
id SERIAL PRIMARY KEY,
titol VARCHAR(100),
autor VARCHAR(100)
);Inserció de registres a la taula:
INSERT INTO llibre (titol, autor)
VALUES ('Red Sparrow', 'Jason Matthews'),
('A Feast for Crows', 'George R. R. Martin'),
('Foundation', 'Isaac Asimov'),
('1984', 'George Orwell'),
('The Lord of the Rings', 'J. R. R. Tolkien'),
('Woken Furies', 'Richard Morgan');Consulta de registres existents:
SELECT * FROM llibre ORDER BY id;Aturada del contenidor:
docker stop pg-volumExercici 2 #
Verificació de persistència
- Partint de l’exercici anterior, elimina el contenidor
pg-volumsense eliminar el volumpg-biblioteca. - Crea un nou contenidor amb el mateix nom i configuració, reutilitzant el volum existent.
- Connecta’t a la base de dades i verifica que els llibres de l’exercici anterior encara hi són.
- Insereix tres llibres nous a la taula.
- Executa una consulta que mostri tots els llibres ordenats alfabèticament per autor.
- Neteja tots els recursos creats (contenidor i volum).
Respostes
Eliminació del contenidor:
docker rm --force pg-volumCreació d’un nou contenidor amb el mateix volum:
docker run --name pg-volum \
--env POSTGRES_PASSWORD='dFeWm5y2dZCP9wKKWYAe' \
--env POSTGRES_USER=biblio \
--env POSTGRES_DB=biblioteca \
--volume pg-biblioteca:/var/lib/postgresql \
--detach \
postgres:18-alpineConnexió al contenidor:
docker exec -it pg-volum psql --user=biblio --dbname=bibliotecaVerificació de les dades existents:
SELECT * FROM llibre ORDER BY id;Inserció de tres llibres nous:
INSERT INTO llibre (titol, autor)
VALUES ('Brave New World', 'Aldous Huxley'),
('Dune', 'Frank Herbert'),
('Foundation and Empire', 'Isaac Asimov'),
('Leviathan Wakes', 'James S. A. Corey');Consulta de tots els llibres ordenats per autor i títol:
SELECT id, titol, autor
FROM llibre
ORDER BY autor ASC, titol ASCq;Neteja dels recursos:
docker rm --force pg-volum
docker volume rm pg-bibliotecaExercici 3 #
Bind mount per a desenvolupament web
- Crea un directori al teu directori
~/Projectsanomenatwebappamb un fitxerindex.htmlque contengui una pàgina HTML bàsica amb un títol i un paràgraf. - Executa un contenidor Caddy (
caddy:2-alpine) que munti aquest directori com a arrel del servidor web, publicant el port 8080. - Accedeix a la pàgina des del navegador a
http://localhost:8080/. - Modifica el fitxer
index.htmlafegint-hi més contingut i comprova que els canvis es reflecteixen sense reiniciar el contenidor. - Crea un subdirectori
cssamb un fitxerestils.cssi enllaça’l des de l’HTML. - Verifica que el servidor serveix correctament els fitxers estàtics, inclosos els de nova creació.
- Neteja tots els recursos creats.
Pista: Consulta la documentació de Caddy a Docker Hub per saber on s’ha de muntar el directori de fitxers estàtics.
Respostes
Creació del directori i el fitxer HTML:
mkdir -p ~/webapp
cat > ~/webapp/index.html << 'EOF'
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webapp de prova</title>
</head>
<body>
<h1>Hola, Docker!</h1>
<p>Aquesta pàgina es serveix des d'un contenidor Caddy.</p>
</body>
</html>
EOFExecució del contenidor Caddy:
docker run --name caddy-dev \
--volume ~/webapp:/usr/share/caddy:ro \
--publish 8080:80 \
--detach \
caddy:2-alpineModificació del fitxer HTML:
cat > ~/webapp/index.html << 'EOF'
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webapp de prova</title>
<link rel="stylesheet" href="css/estils.css">
</head>
<body>
<h1>Hola, Docker!</h1>
<p>Aquesta pàgina es serveix des d'un contenidor Caddy.</p>
<p>El contingut s'actualitza automàticament gràcies al bind mount.</p>
</body>
</html>
EOFCreació del directori CSS i el fitxer d’estils:
mkdir -p ~/webapp/css
cat > ~/webapp/css/estils.css << 'EOF'
body {
font-family: system-ui, sans-serif;
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
background-color: #f5f5f5;
}
h1 {
color: #2c3e50;
}
p {
color: #34495e;
line-height: 1.6;
}
EOFNeteja dels recursos:
docker rm --force caddy-dev
rm -rf ~/webapp-
Per defecte
/var/lib/docker/volumes/a Debian/Ubuntu. ↩︎