Ara mateix tens tres o quatre contenidors a la teva màquina i encara els pots portar al cap. D'aquí a un mes en tindràs trenta: els quatre serveis d'Aurora Libros, mitja dotzena de proves oblidades, contenidors d'altres projectes i un munt d'Exited de fa setmanes ocupant disc i noms. Sense eines de gestió, aquell docker ps -a es converteix en una paret de text impossible de llegir, i el docker rm massiu que executes a les onze de la nit s'emporta per davant alguna cosa que necessitaves.

Aquesta lliçó és la de l'operació diària. Espremeràs docker ps fins al fons —columna per columna, amb els seus filtres i les seves plantilles—, muntaràs un tauler de control dels quatre serveis d'Aurora Libros, aprendràs a compondre comandes amb -q per operar sobre desenes de contenidors sense destrossar res, a copiar fitxers entre el host i un contenidor amb docker cp —amb la qual cosa per fi ompliràs la taula llibres amb els vuit títols—, a veure amb docker diff què ha canviat un contenidor respecte a la seva imatge, i a entendre per què docker commit és una temptació que cal resistir.

Contingut

  1. docker ps: les set columnes, una a una
  2. Opcions de llistat: -a, -q, -s, -n, -l
  3. Filtrar amb --filter
  4. Donar format amb --format i plantilles Go
  5. Etiquetes: organitzar la flota amb --label
  6. Compondre comandes amb -q (i fer-ho amb cap)
  7. Esborrar contenidors: rm, -f, -v i container prune
  8. Reanomenar i modificar en calent: rename i update
  9. docker cp: moure fitxers en els dos sentits
  10. docker diff: què ha canviat respecte a la imatge
  11. docker commit i per què no l'has de fer servir
  12. El tauler de control d'Aurora Libros

  1. docker ps: les set columnes, una a una

docker ps
CONTAINER ID   IMAGE                COMMAND                  CREATED          STATUS                    PORTS                      NAMES
3f8a1c9e7b2d   postgres:16-alpine   "docker-entrypoint.s…"   35 minutes ago   Up 35 minutes             127.0.0.1:5432->5432/tcp   aurora-db
9d4b7e2f1a6c   redis:7-alpine       "docker-entrypoint.s…"   32 minutes ago   Up 4 minutes (healthy)    127.0.0.1:6379->6379/tcp   aurora-cache
Columna Què és exactament Detalls que convé saber
CONTAINER ID Els 12 primers caràcters de l'ID de 64 N'hi ha prou d'escriure els primers que siguin únics: docker stop 3f8 funciona
IMAGE La referència de la imatge tal com es va escriure Si vas arrencar amb un digest, veuràs el digest; si l'etiqueta es va moure després, aquest text ja no diu la veritat
COMMAND La comanda efectiva: ENTRYPOINT + CMD Apareix truncada amb . Es veu sencera amb --no-trunc
CREATED Quan es va crear el contenidor No és quan es va arrencar: un contenidor creat fa un mes i arrencat fa un minut posa "5 weeks ago"
STATUS Estat i temps que hi porta Up 35 minutes, Exited (0) 2 hours ago, Up 4 minutes (healthy), Created, Paused
PORTS Publicacions actives 127.0.0.1:5432->5432/tcp és host→contenidor. Si només posa 5432/tcp, està exposat però no publicat
NAMES El nom assignat o inventat Únic a la màquina

Dues observacions sobre la sortida de dalt, que serveixen de repàs:

  • aurora-cache posa Up 4 minutes i no Up 32 minutes perquè el vas reiniciar a la lliçó anterior: el comptador de STATUS es posa a zero amb cada arrencada, mentre que CREATED no es mou.
  • El (healthy) d'aurora-cache no apareix per art de màgia: la imatge oficial de Redis 7 porta el seu propi HEALTHCHECK. aurora-db no en mostra res perquè la imatge de PostgreSQL no el defineix. La salut s'estudia a fons a la lliçó 03-04.

Per veure la comanda completa:

docker ps --no-trunc --format "{{.Names}}: {{.Command}}"
aurora-db: "docker-entrypoint.sh postgres"
aurora-cache: "docker-entrypoint.sh redis-server"

  1. Opcions de llistat: -a, -q, -s, -n, -l

Opció Què fa Ús típic
-a, --all Mostra tots, inclosos Exited i Created Trobar el contenidor que va morir
-q, --quiet Només els IDs, un per línia Compondre amb altres comandes
-s, --size Afegeix la columna SIZE Saber quant ocupa de debò
-n N Els N últims creats, corrent o no docker ps -n 3
-l, --latest L'últim creat docker logs $(docker ps -lq)
--no-trunc No trunca IDs ni comandes Copiar un ID complet

-s: la mida real d'un contenidor

docker ps -as --format "table {{.Names}}\t{{.Image}}\t{{.Size}}"
NAMES          IMAGE                SIZE
aurora-cache   redis:7-alpine       0B (virtual 41.2MB)
aurora-db      postgres:16-alpine   127kB (virtual 278MB)

Aquesta columna té dos números i la diferència entre tots dos és la lliçó 01-05 feta comanda:

  • El primer (0B, 127kB) és la capa d'escriptura: el que aquest contenidor ha escrit per damunt de la imatge. És l'única cosa que li pertany en exclusiva i l'única que es perd en esborrar-lo.
  • El virtual és la mida total que veu el contenidor: la seva capa d'escriptura més totes les capes de la imatge, que són compartides i de només lectura.

Deu contenidors de redis:7-alpine no ocupen 412 MB, sinó 41,2 MB més deu capes d'escriptura minúscules. I aurora-db amb els seus 127 kB d'escriptura crida l'atenció: acabes de crear una base de dades sencera, on són les dades? En un volum anònim que la imatge oficial declara, i que no compta com a capa d'escriptura. Retén aquesta pregunta: és el cor de la lliçó 03-06.

  1. Filtrar amb --filter

--filter (o -f) accepta parells clau=valor i es pot repetir. Els filtres disponibles:

Filtre Exemple Què selecciona
status --filter status=exited Per estat: created, running, paused, restarting, exited, dead
name --filter name=aurora Nom que conté aquesta cadena (és una subcadena, no una igualtat)
ancestor --filter ancestor=redis:7-alpine Contenidors creats a partir d'aquesta imatge
label --filter label=projecte=aurora-libros Per etiqueta, amb valor o sense
exited --filter exited=1 Els que van sortir amb aquell codi
health --filter health=unhealthy Per estat de salut: starting, healthy, unhealthy, none
before / since --filter since=aurora-db Creats abans/després d'aquell contenidor
id --filter id=3f8a1c9e7b2d Per ID
volume --filter volume=aurora-dades Els que munten aquell volum (lliçó 03-06)
network --filter network=aurora-net Els connectats a aquella xarxa (lliçó 03-05)
publish / expose --filter publish=5432 Els que publiquen o exposen aquell port

Receptes llestes per copiar:

# Tot el que sigui d'Aurora Libros, viu o mort
docker ps -a --filter name=aurora

# Només el que està corrent d'una imatge concreta
docker ps --filter ancestor=postgres:16-alpine

# Contenidors que van fallar: van sortir amb un codi diferent de 0
docker ps -a --filter status=exited --filter exited=1

# Contenidors malalts ara mateix
docker ps --filter health=unhealthy

# El creat després d'aurora-db (útil per acotar una sessió de treball)
docker ps -a --filter since=aurora-db

Exemple real:

docker ps -a --filter status=exited --format "table {{.Names}}\t{{.Status}}"
NAMES         STATUS
api-aturada   Exited (0) 18 minutes ago

Dos avisos sobre el comportament dels filtres:

  • Diversos --filter diferents es combinen amb I lògic. --filter status=running --filter name=aurora exigeix les dues condicions.
  • Diversos valors del mateix filtre es combinen amb O lògic. --filter status=exited --filter status=created retorna els de qualsevol dels dos estats.
  • name és una subcadena, no una igualtat. --filter name=aurora-db també troba aurora-db-copia. Si necessites exactitud, fes servir una expressió ancorada: --filter name=^aurora-db$.

  1. Donar format amb --format i plantilles Go

--format fa servir plantilles del llenguatge Go, les mateixes que ja vas emprar amb docker image ls a la lliçó 02-05.

Marcador Contingut
{{.ID}} ID curt
{{.Names}} Nom
{{.Image}} Imatge
{{.Command}} Comanda efectiva
{{.CreatedAt}} Data completa de creació
{{.RunningFor}} Temps transcorregut en text
{{.Status}} Estat amb el seu temps
{{.State}} Només l'estat (running, exited…)
{{.Ports}} Ports publicats
{{.Size}} Mida (requereix -s)
{{.Labels}} Totes les etiquetes
{{.Label "clau"}} El valor d'una etiqueta
{{.Mounts}} Volums muntats
{{.Networks}} Xarxes connectades

La paraula table al principi activa la capçalera i l'alineació en columnes:

docker ps --format "table {{.Names}}\t{{.State}}\t{{.RunningFor}}"
NAMES          STATE     RUNNING FOR
aurora-db      running   41 minutes ago
aurora-cache   running   10 minutes ago

Sense table, la sortida és text lliure, perfecta per fer-hi scripts:

docker ps --format "{{.Names}} -> {{.Image}}"
aurora-db -> postgres:16-alpine
aurora-cache -> redis:7-alpine

I hi ha dos formats preconstruïts molt socorreguts:

docker ps --format json | head -1
docker ps --format "{{json .}}" | jq -r '.Names + " | " + .Status'
{"Command":"\"docker-entrypoint.s…\"","CreatedAt":"2026-08-04 19:28:41 +0200 CEST","ID":"3f8a1c9e7b2d",...}
aurora-db | Up 41 minutes
aurora-cache | Up 10 minutes

Si un format t'agrada, fes-lo permanent a ~/.docker/config.json:

{
  "psFormat": "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
}

A partir d'aquell moment, un docker ps sense més fa servir el teu format. És un dels ajustos que més agraeix l'ús diari.

  1. Etiquetes: organitzar la flota amb --label

Quan a la mateixa màquina hi conviuen contenidors de tres projectes, filtrar per nom deixa de ser fiable. Les etiquetes són metadades arbitràries que assignes en crear el contenidor:

docker run -d --name prova-etiquetes \
  --label projecte=aurora-libros \
  --label component=demo \
  --label entorn=local \
  alpine:3.20 sleep 300

I després hi filtres amb precisió quirúrgica:

docker ps --filter label=projecte=aurora-libros --format "{{.Names}}"
docker ps --filter label=component --format "{{.Names}}"
docker rm -f prova-etiquetes
prova-etiquetes
prova-etiquetes

--filter label=clau=valor exigeix el valor exacte; --filter label=clau només exigeix que l'etiqueta existeixi.

Etiquetarem de debò la flota d'Aurora Libros. Com que les etiquetes, igual que els ports i les variables, es fixen en crear el contenidor i no s'hi poden afegir després (lliçó 03-01), cal recrear. No passa res: aurora-db encara no té dades a perdre.

docker rm -f aurora-db aurora-cache

docker run -d --name aurora-db \
  --label projecte=aurora-libros \
  --label component=base-de-dades \
  --label entorn=local \
  -e POSTGRES_USER=aurora \
  -e POSTGRES_PASSWORD=aurora_secreta \
  -e POSTGRES_DB=aurora_llibres \
  -p 127.0.0.1:5432:5432 \
  postgres:16-alpine

docker run -d --name aurora-cache \
  --label projecte=aurora-libros \
  --label component=cache \
  --label entorn=local \
  -p 127.0.0.1:6379:6379 \
  redis:7-alpine
docker ps --filter label=projecte=aurora-libros \
  --format 'table {{.Names}}\t{{.Label "component"}}\t{{.Status}}'
NAMES          COMPONENT            STATUS
aurora-cache   cache                Up 5 seconds
aurora-db      base-de-dades        Up 8 seconds

Una convenció d'etiquetes que funciona bé i que farem servir a tot el curs:

Etiqueta Valors a Aurora Libros Per a què serveix
projecte aurora-libros Separar d'altres projectes de la màquina
component base-de-dades, cache, api, web Identificar el paper de cada contenidor
entorn local, proves, produccio Distingir instàncies del mateix servei

Amb aquestes tres etiquetes, operacions com "atura tot el d'Aurora Libros en local sense tocar res més" es tornen trivials, i és exactament el que Docker Compose farà per tu automàticament al mòdul 4: les seves etiquetes com.docker.compose.project són aquest mateix mecanisme.

  1. Compondre comandes amb -q (i fer-ho amb cap)

docker ps -q imprimeix només IDs, i això el converteix en l'entrada de qualsevol altra comanda:

docker ps -q --filter ancestor=redis:7-alpine
9d4b7e2f1a6c

Receptes que es fan servir a diari:

# Aturar tots els contenidors d'una imatge concreta
docker stop $(docker ps -q --filter ancestor=postgres:16-alpine)

# Esborrar tots els contenidors aturats
docker rm $(docker ps -aq --filter status=exited)

# Aturar tota la flota d'Aurora Libros
docker stop $(docker ps -q --filter label=projecte=aurora-libros)

# Reiniciar només els que estan malalts
docker restart $(docker ps -q --filter health=unhealthy)

I ara les tres precaucions, perquè aquestes comandes són esmolades:

Primera: si el filtre no troba res, la comanda falla.

docker stop $(docker ps -q --filter name=no-existeix)
"docker stop" requires at least 1 argument.

Molest però inofensiu. S'evita comprovant abans:

IDS=$(docker ps -q --filter label=projecte=aurora-libros)
[ -n "$IDS" ] && docker stop $IDS || echo "No hi ha res per aturar"

Segona: docker rm -f $(docker ps -aq) és la comanda més perillosa d'aquesta lliçó. Esborra absolutament tots els contenidors de la màquina, inclosos els d'altres projectes, i sense preguntar. Si necessites fer neteja, fes-la acotada:

docker rm -f $(docker ps -aq --filter label=projecte=aurora-libros)

Tercera: mira abans de disparar. La regla d'or és executar primer la versió que només llista:

# 1. Què tocaré exactament?
docker ps -a --filter status=exited --format "table {{.Names}}\t{{.Status}}"

# 2. Si la llista és la que esperava, llavors sí
docker rm $(docker ps -aq --filter status=exited)

Aquest mig segon de més ha salvat moltes bases de dades de desenvolupament.

  1. Esborrar contenidors: rm, -f, -v i container prune

docker rm api-aturada
api-aturada

docker rm només funciona sobre contenidors aturats. Amb un en marxa:

docker rm aurora-cache
Error response from daemon: cannot remove container "aurora-cache":
container is running: stop the container before removing or force remove
Opció Efecte
(cap) Només esborra contenidors aturats
-f, --force Envia SIGKILL i esborra. Sense període de gràcia
-v, --volumes Esborra a més els volums anònims associats (mai els que tenen nom)
-l, --link Elimina un enllaç heretat, no el contenidor. Pràcticament en desús

Sobre -f, un matís important que enllaça amb la lliçó anterior: docker rm -f no és docker stop + docker rm. Envia SIGKILL directament, sense els deu segons de gràcia. Sobre una base de dades, això és desendollar el servidor. La seqüència correcta quan hi ha dades pel mig és:

docker stop --time 30 aurora-db && docker rm aurora-db

Què passa exactament en esborrar un contenidor:

Es destrueix Sobreviu
La capa d'escriptura i tot el que hi hagués La imatge de la qual va sortir
Els registres del contenidor Els volums amb nom (lliçó 03-06)
La seva configuració i el seu nom Els fitxers del host muntats com a bind mount
Els volums anònims, només si fas servir -v Els volums anònims, si no fas servir -v (queden orfes)

docker container prune

docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
7d3f9a1b2c48e6015a2c9f7b3e1d8a4c6f0b2d9e7a5c3f1b8d6a4e2c9f7b5d3a

Total reclaimed space: 41.9kB

Esborra tots els contenidors aturats. Val la pena repetir l'avís de la lliçó 02-05: un contenidor aturat pot ser alguna cosa que estaves depurant o una base de dades de desenvolupament que vas apagar ahir. Acota sempre que puguis:

# Només els aturats fa més de 24 hores
docker container prune --filter "until=24h"

# Només els aturats d'un projecte concret
docker container prune --filter "label=projecte=aurora-libros"

# Sense confirmació interactiva (per a scripts; fes-ho servir amb doble cura)
docker container prune -f --filter "until=168h"

  1. Reanomenar i modificar en calent: rename i update

Ja vas veure docker rename a la lliçó anterior. El seu company és docker update, que que pot canviar coses d'un contenidor en marxa, encara que una llista molt concreta:

docker update --restart=unless-stopped aurora-db
docker update --memory 512m --memory-swap 512m aurora-db
Es pot canviar amb update No es pot canviar mai
Memòria (--memory, --memory-swap, --memory-reservation) Ports publicats
CPU (--cpus, --cpu-shares, --cpuset-cpus) Variables d'entorn
Nombre de processos (--pids-limit) Muntatges i volums
Política de reinici (--restart) Imatge, CMD o ENTRYPOINT
Pes d'E/S de disc (--blkio-weight) Etiquetes i xarxes

El detall de totes aquestes opcions de recursos i de les polítiques de reinici és el contingut de la lliçó 03-07; aquí només importa retenir la frontera: update toca el que viu als cgroups; tot el que viu als namespaces (xarxa, muntatges, entorn) es fixa en crear el contenidor.

  1. docker cp: moure fitxers en els dos sentits

docker cp <origen> <destinació>

Un dels dos costats porta el prefix contenidor: i l'altre és una ruta del host. Funciona en les dues direccions i també amb contenidors aturats.

Del host al contenidor: carregar el catàleg

Ha arribat el moment d'omplir la base de dades. Tens ~/aurora-libros/db/init.sql des de la lliçó 01-07, amb la taula llibres i els vuit títols:

docker cp ~/aurora-libros/db/init.sql aurora-db:/tmp/init.sql
Successfully copied 3.07kB to aurora-db:/tmp/init.sql

I ara executa'l dins del contenidor:

docker exec aurora-db psql -U aurora -d aurora_llibres -f /tmp/init.sql
CREATE TABLE
CREATE INDEX
INSERT 0 8

Comprova el resultat:

docker exec aurora-db psql -U aurora -d aurora_llibres \
  -c "SELECT id, titol, autor, preu FROM llibres ORDER BY id LIMIT 4;"
docker exec aurora-db psql -U aurora -d aurora_llibres -t \
  -c "SELECT COUNT(*) FROM llibres;"
 id |                 titol                 |         autor          |  preu
----+---------------------------------------+------------------------+--------
  1 | El jardín de senderos que se bifurcan | Jorge Luis Borges      |  14.50
  2 | Rayuela                               | Julio Cortázar         |  19.90
  3 | Cien años de soledad                  | Gabriel García Márquez |  17.95
  4 | La sombra del viento                  | Carlos Ruiz Zafón      |  21.00
(4 rows)

     8

Els vuit llibres d'Aurora Libros són en una base de dades real, en un contenidor real. És la primera vegada en tot el curs que existeixen fora d'un fitxer .sql. Encara ningú no els pot llegir des de l'API —això arriba a la lliçó 03-05—, però ja hi són.

Un apunt de mètode: carregar l'esquema a mà amb docker cp funciona però no és la manera correcta, perquè cal repetir-ho cada vegada que recreïs el contenidor i ningú no se'n recorda. A la lliçó 03-06 ho resoldràs bé, muntant init.sql a /docker-entrypoint-initdb.d/ perquè PostgreSQL l'executi tot sol en inicialitzar-se.

Del contenidor al host: extreure un fitxer

El sentit invers és igual de fàcil i resulta imprescindible en investigar un incident:

docker cp aurora-db:/var/lib/postgresql/data/pg_hba.conf ~/aurora-libros/pg_hba.conf.copia
head -3 ~/aurora-libros/pg_hba.conf.copia
Successfully copied 5.12kB to /home/junior/aurora-libros/pg_hba.conf.copia
# PostgreSQL Client Authentication Configuration File
# ===================================================

I un cas molt habitual: rescatar el registre d'una aplicació que —contra tota bona pràctica— escriu en un fitxer en comptes de a la sortida estàndard.

docker cp aurora-api:/app/logs/error.log ./error-aurora-api.log

Detalls del comportament de docker cp que eviten sorpreses:

Situació Resultat
docker cp fitxer contenidor:/ruta/ (acaba en /) Copia dins del directori
docker cp fitxer contenidor:/ruta (sense /) Si /ruta és un directori, copia a dins; si no, crea o sobreescriu aquest fitxer
docker cp carpeta/. contenidor:/desti Copia el contingut de la carpeta
docker cp carpeta contenidor:/desti Copia la carpeta sencera dins de la destinació
Amb -a/--archive Conserva propietari i grup (UID/GID)
Sense -a Els fitxers queden com a root dins del contenidor

Aquest últim punt mossega sovint: si copies un fitxer a un contenidor que corre amb USER node, el fitxer pertanyerà a root i l'aplicació potser no el podrà llegir. I una limitació clara: docker cp no pot copiar d'un contenidor a un altre directament; cal passar pel host.

  1. docker diff: què ha canviat respecte a la imatge

docker diff compara el sistema de fitxers actual del contenidor amb el de la imatge de la qual va sortir:

docker diff aurora-db | head -12
C /tmp
A /tmp/init.sql
C /run
A /run/postgresql
A /run/postgresql/.s.PGSQL.5432
C /var/lib/postgresql

Tres lletres i el seu significat:

Lletra Significa Exemple
A Added: fitxer o directori nou /tmp/init.sql, el que acabes de copiar
C Changed: modificat (o un directori el contingut del qual va canviar) /tmp, perquè ara conté alguna cosa nova
D Deleted: esborrat respecte a la imatge Un fitxer de configuració que es va substituir

És la manifestació visible del copy-on-write de la lliçó 01-05: el que veus aquí és exactament el contingut de la capa d'escriptura, i per això docker diff i la columna SIZE de docker ps -s parlen del mateix.

I ara l'observació important:

docker diff aurora-db | grep "postgresql/data" | head -3

Ni una línia. Acabes de crear una taula i vuit registres, i el directori de dades de PostgreSQL no apareix al diff. La raó és que /var/lib/postgresql/data no està a la capa d'escriptura: la imatge oficial hi declara un VOLUME, així que Docker va crear un volum anònim i el va muntar en aquella ruta. I docker diff mai no mira dins dels muntatges.

Això és exactament el que anticipava la lliçó 02-04 en parlar de per què VOLUME en un Dockerfile té efectes secundaris, i és la porta d'entrada a la lliçó 03-06: hi ha dades que no viuen ni a la imatge ni a la capa d'escriptura, sinó en un tercer lloc. Un lloc que ara mateix, per a aurora-db, és anònim i fràgil.

Un cas d'ús molt pràctic de docker diff: descobrir què escriu una aplicació de la qual no et refies.

docker run -d --name diff-demo nginx:alpine
sleep 2 && curl -s localhost > /dev/null 2>&1
docker diff diff-demo
docker rm -f diff-demo
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
C /run
A /run/nginx.pid
C /var/cache/nginx
A /var/cache/nginx/client_temp

En deu línies saps que Nginx modifica la seva configuració en arrencar (ho fa el seu docker-entrypoint.sh), crea un fitxer de PID i diversos directoris de memòria cau. És informació valuosíssima quan vulguis fer el sistema de fitxers de només lectura per seguretat, cosa que veuràs a la lliçó 05-03.

  1. docker commit i per què no l'has de fer servir

docker commit converteix l'estat actual d'un contenidor en una imatge nova:

docker commit --message "Catàleg carregat a mà" aurora-db aurora-db:amb-dades
docker image ls aurora-db --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"
sha256:8f2e9c1a7b34d6058e2f9a1c7b3d5e8a0c2f4b6d8e0a2c4f6b8d0e2a4c6f8b0d2
REPOSITORY:TAG        SIZE
aurora-db:amb-dades   278MB

Funciona. I precisament per això és perillós. Aquests són els motius pels quals no es construeixen imatges així, reprenent tot el mòdul 2:

Problema Conseqüència
No és reproduïble No existeix cap fitxer que descrigui com es va arribar a aquell estat. Si demà necessites la mateixa imatge amb Node 22.14, no hi ha manera de regenerar-la
No és auditable docker image history mostrarà una capa enorme amb el comentari que vas escriure, sense dir què s'hi va instal·lar, què es va esborrar ni d'on venia
No es versiona Un Dockerfile viu a Git, es revisa en una pull request i té historial. Un commit viu al cap de qui el va fer
Arrossega brossa Fitxers temporals, historial de shell, registres, credencials escrites durant la sessió de depuració... tot es cou dins la imatge
Pesa de més És una capa monolítica que trenca la memòria cau i el compartir capes que tant vas cuidar a la lliçó 02-02
Trenca la cadena de confiança Ningú no pot respondre a "què hi ha aquí dins?" sense obrir-ho i mirar

Al vocabulari de l'ofici, una imatge així s'anomena imatge artesanal o snowflake, i és l'equivalent contenidoritzat d'aquell servidor que ningú no gosava reiniciar. Té, això sí, un ús legítim:

# Forense: congelar un contenidor trencat ABANS d'esborrar-lo, per investigar-lo amb calma
docker commit aurora-api aurora-api:forense-2026-08-04
docker rm aurora-api
docker run --rm -it --entrypoint sh aurora-api:forense-2026-08-04

Desar l'escena del crim abans de netejar-la. Per a això sí, i per a res més.

docker image rm aurora-db:amb-dades

  1. El tauler de control d'Aurora Libros

Tanquem ajuntant tot el de la lliçó en una comanda que faràs servir a diari:

docker ps -a --filter label=projecte=aurora-libros \
  --format 'table {{.Label "component"}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}'
COMPONENT           NAMES          STATUS          PORTS
cache               aurora-cache   Up 22 minutes   127.0.0.1:6379->6379/tcp
base-de-dades       aurora-db      Up 22 minutes   127.0.0.1:5432->5432/tcp

Converteix-la en un àlies permanent al teu ~/.bashrc o ~/.zshrc:

alias aurora='docker ps -a --filter label=projecte=aurora-libros --format "table {{.Label \"component\"}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"'
alias aurora-aturar='docker stop $(docker ps -q --filter label=projecte=aurora-libros)'
alias aurora-arrencar='docker start $(docker ps -aq --filter label=projecte=aurora-libros)'

I una versió amb salut i mida, per quan alguna cosa va malament:

docker ps -as --filter label=projecte=aurora-libros \
  --format 'table {{.Names}}\t{{.Status}}\t{{.Size}}'
NAMES          STATUS          SIZE
aurora-cache   Up 23 minutes   0B (virtual 41.2MB)
aurora-db      Up 23 minutes   3.14kB (virtual 278MB)

Fixa't que la capa d'escriptura d'aurora-db ha crescut de 127 kB a 3,14 kB… un moment, ha baixat. El que passa és que el contenidor és nou: el vas recrear en afegir-li les etiquetes, i aquests 3,14 kB són gairebé exclusivament l'init.sql que vas copiar amb docker cp. Les dades dels vuit llibres no hi són, sinó al volum anònim del qual parlàvem. Un recordatori més que hi ha una peça del trencaclosques que encara no has col·locat.

Errors Habituals i Consells

  • Executar docker rm -f $(docker ps -aq) "per netejar". S'emporta tot el de la màquina, inclosos contenidors d'altres projectes i bases de dades de desenvolupament amb setmanes de dades. Filtra sempre per label o per name.
  • Confiar en --filter name= com si fos una igualtat. És una subcadena: name=aurora-db també troba aurora-db-backup. Ancora amb ^...$ quan importi.
  • Llegir la columna CREATED com a "arrencat fa". És la data de creació. Per al temps en marxa, mira STATUS o {{.RunningFor}}.
  • Interpretar la mida virtual com a espai ocupat. Deu contenidors de la mateixa imatge comparteixen les seves capes: el disc real és la suma de les capes d'escriptura més una còpia de la imatge.
  • Fer servir docker rm -f sobre una base de dades. És un SIGKILL sense gràcia. Primer docker stop --time 30, després docker rm.
  • Oblidar -v en esborrar i acumular volums anònims orfes. Cada contenidor de PostgreSQL esborrat sense -v deixa enrere el seu volum. Es localitzen amb docker volume ls -f dangling=true (lliçó 03-06).
  • Copiar fitxers amb docker cp a un contenidor sense privilegis i no entendre el permission denied. El fitxer hi arriba com a root. Fes servir -a, o ajusta-ho després amb docker exec -u root ... chown.
  • Construir imatges amb docker commit. Funciona avui i et deixa sense explicació demà. El Dockerfile és la font de la veritat.
  • Consell: desa els teus formats preferits a ~/.docker/config.json amb psFormat. Un docker ps llegible per defecte és un regal diari.
  • Consell: abans de qualsevol operació massiva, executa la versió que només llista. Mira la llista, i només llavors canvia ps per rm.

Exercicis

Exercici 1: munta el teu propi tauler de control

Crea cinc contenidors de prova amb alpine:3.20 i sleep 300, etiquetats així: dos amb projecte=aurora-libros i entorn=local, dos amb projecte=aurora-libros i entorn=proves, i un amb projecte=altre-client. Després escriu una única comanda docker ps que mostri només els d'Aurora Libros en entorn de proves, en una taula amb les columnes nom, entorn i temps en marxa. Finalment, atura i esborra en una sola comanda exclusivament els d'altre-client, deixant la resta intacta, i demostra amb una altra comanda que els quatre d'Aurora Libros continuen vius.

Exercici 2: investiga què escriu un contenidor (i on no ho escriu)

Arrenca un contenidor de redis:7-alpine anomenat diff-redis. Abans de tocar res, executa docker diff i anota el resultat. Després escriu 100 claus amb redis-cli, força un desament a disc amb redis-cli SAVE, i torna a executar docker diff. Respon:

  1. Apareix el fitxer de dades de Redis al docker diff? I a la columna SIZE de docker ps -s?
  2. Esbrina amb docker inspect --format '{{json .Mounts}}' on és realment aquest fitxer.
  3. Copia'l al host amb docker cp i comprova'n la mida amb ls -lh.
  4. Esborra el contenidor amb docker rm -f (sense -v) i explica què ha passat exactament amb aquelles 100 claus. S'han perdut? On són?

Exercici 3: rescat i neteja segura

Simula el final d'una jornada de treball caòtica:

  1. Crea sis contenidors que acabin immediatament: tres amb codi 0 (alpine:3.20 true) i tres amb codi 1 (alpine:3.20 sh -c 'exit 1'), tots amb l'etiqueta projecte=proves-neteja.
  2. Escriu una comanda que llisti només els que van fallar, amb el seu nom i el seu estat.
  3. D'un dels fallits, extreu amb docker cp el fitxer /etc/os-release al host abans d'esborrar-lo.
  4. Esborra en una sola comanda només els que van fallar, deixant els tres correctes.
  5. Finalment, neteja la resta amb docker container prune acotat a l'etiqueta, i verifica que ni aurora-db ni aurora-cache no s'han vist afectats.

Solucions

Solució a l'exercici 1

for n in 1 2; do
  docker run -d --name prova-local-$n \
    --label projecte=aurora-libros --label entorn=local \
    alpine:3.20 sleep 300
done
for n in 1 2; do
  docker run -d --name prova-proves-$n \
    --label projecte=aurora-libros --label entorn=proves \
    alpine:3.20 sleep 300
done
docker run -d --name prova-altre --label projecte=altre-client alpine:3.20 sleep 300

La comanda del tauler, amb dos filtres combinats amb I lògic:

docker ps --filter label=projecte=aurora-libros --filter label=entorn=proves \
  --format 'table {{.Names}}\t{{.Label "entorn"}}\t{{.RunningFor}}'
NAMES             ENTORN    RUNNING FOR
prova-proves-2    proves    12 seconds ago
prova-proves-1    proves    14 seconds ago

Els d'entorn=local no hi apareixen: compleixen el primer filtre però no el segon, i tots dos s'han de complir.

Esborrat quirúrgic d'altre-client:

# Primer MIRAR
docker ps -a --filter label=projecte=altre-client --format "{{.Names}}"
# Després ACTUAR
docker rm -f $(docker ps -aq --filter label=projecte=altre-client)
# I COMPROVAR
docker ps --filter label=projecte=aurora-libros --format "{{.Names}}" | wc -l
prova-altre
c8e1a4f7b209
4

Els quatre d'Aurora Libros continuen drets. La seqüència mirar → actuar → comprovar és la que converteix una comanda perillosa en una operació rutinària.

docker rm -f $(docker ps -aq --filter name=prova-)

Solució a l'exercici 2

docker run -d --name diff-redis redis:7-alpine
sleep 2
docker diff diff-redis
C /run
A /run/redis_6379.pid

Pràcticament res: Redis acabat d'arrencar només escriu el seu fitxer de PID. Ara, càrrega i desament:

docker exec diff-redis sh -c 'for i in $(seq 1 100); do redis-cli SET llibre:$i "titol-$i" > /dev/null; done'
docker exec diff-redis redis-cli DBSIZE
docker exec diff-redis redis-cli SAVE
docker exec diff-redis ls -lh /data
docker diff diff-redis
docker ps -s --filter name=diff-redis --format "{{.Names}}: {{.Size}}"
(integer) 100
OK
-rw-r--r--    1 redis    redis       3.4K Aug  4 20:41 dump.rdb
C /run
A /run/redis_6379.pid
diff-redis: 0B (virtual 41.2MB)

1. Aquí hi ha la sorpresa de l'exercici: dump.rdb existeix (l'acabes de llistar amb ls -lh, 3,4 kB) però no apareix al docker diff, i la capa d'escriptura continua mesurant 0 B. Les dues eines estan d'acord entre elles i totes dues diuen la veritat: aquest fitxer no és a la capa d'escriptura del contenidor.

2. L'explicació és als muntatges:

docker inspect --format '{{json .Mounts}}' diff-redis | jq
[
  {
    "Type": "volume",
    "Name": "b41f7c9e2a8d05f31c6e8b4a2d97f0c5e3a1b8d6f4c2e0a9b7d5f3c1e8a6b4d2",
    "Source": "/var/lib/docker/volumes/b41f7c9e2a8d.../_data",
    "Destination": "/data",
    "Driver": "local",
    "RW": true
  }
]

La imatge oficial de Redis declara VOLUME /data, així que Docker va crear un volum anònim —aquest nom que sembla un hash és exactament això: un volum sense nom— i el va muntar a /data. Tot el que Redis hi escriu va al volum, no a la capa d'escriptura, i per això docker diff no ho veu.

3.

docker cp diff-redis:/data/dump.rdb /tmp/dump-aurora.rdb
ls -lh /tmp/dump-aurora.rdb
Successfully copied 5.63kB to /tmp/dump-aurora.rdb
-rw-r--r-- 1 junior junior 3.4K Aug  4 20:42 /tmp/dump-aurora.rdb

docker cp sí que llegeix a través dels muntatges: per a ell, /data/dump.rdb és simplement una ruta del sistema de fitxers del contenidor. Els 3,4 kB coincideixen amb l'ls de dins; els 5,63 kB que informa Docker són la mida del flux tar amb què es fa la còpia, amb les seves capçaleres i el seu farciment a blocs.

4.

docker rm -f diff-redis
docker volume ls -f dangling=true --format "{{.Name}}" | head -3
diff-redis
b41f7c9e2a8d05f31c6e8b4a2d97f0c5e3a1b8d6f4c2e0a9b7d5f3c1e8a6b4d2

La resposta correcta és més interessant que un simple "s'han perdut":

  • Les claus que eren en memòria van morir amb el procés, com a l'exercici de la lliçó anterior.
  • Però el dump.rdb amb les 100 claus continua existint, en un volum que ara està orfe: sense contenidor que el faci servir, amb un nom que ningú no recorda i ocupant disc indefinidament. Com que vas fer servir docker rm -f sense -v, el volum no es va esborrar.
  • Si demà arrenques un altre redis:7-alpine, Docker crearà un volum anònim nou i buit: no reutilitzarà l'anterior. Des del punt de vista pràctic, les dades estan perdudes encara que els bytes continuïn al teu disc.

Això és exactament el que li passa avui a aurora-db amb els vuit llibres que acabes de carregar, i és el problema que resol la lliçó 03-06 posant-li al volum un nom.

Solució a l'exercici 3

for n in 1 2 3; do
  docker run --name ok-$n --label projecte=proves-neteja alpine:3.20 true
  docker run --name fallada-$n --label projecte=proves-neteja alpine:3.20 sh -c 'exit 1'
done

2. Llistar només els que van fallar, combinant status i exited:

docker ps -a --filter label=projecte=proves-neteja --filter exited=1 \
  --format "table {{.Names}}\t{{.Status}}"
NAMES        STATUS
fallada-3    Exited (1) 6 seconds ago
fallada-2    Exited (1) 7 seconds ago
fallada-1    Exited (1) 8 seconds ago

El filtre exited=1 és el que fa la feina fina: distingeix "va acabar" de "va acabar malament", cosa que status=exited per si sol no pot.

3. Rescatar un fitxer d'un contenidor aturat:

docker cp fallada-1:/etc/os-release /tmp/os-release-fallada1.txt
head -2 /tmp/os-release-fallada1.txt
Successfully copied 3.07kB to /tmp/os-release-fallada1.txt
NAME="Alpine Linux"
ID=alpine

Detall important: docker cp funciona amb el contenidor aturat. No cal arrencar-lo, i això el converteix en l'eina ideal per fer autòpsies de contenidors que ja no s'aixequen.

4. Esborrat selectiu dels fallits:

docker rm $(docker ps -aq --filter label=projecte=proves-neteja --filter exited=1)
docker ps -a --filter label=projecte=proves-neteja --format "{{.Names}} {{.Status}}"
2f8c1a9e4b73
7d1e5b2c8f04
9a3f7c1e5d28
ok-3 Exited (0) 1 minute ago
ok-2 Exited (0) 1 minute ago
ok-1 Exited (0) 1 minute ago

Els tres correctes continuen allà. No va caldre -f perquè ja estaven aturats.

5. Neteja final acotada i verificació:

docker container prune -f --filter "label=projecte=proves-neteja"
docker ps -a --filter label=projecte=aurora-libros \
  --format 'table {{.Names}}\t{{.Status}}'
Deleted Containers:
1c7e9a3f5b28...
4b8d2f6a1c93...
6e0a4c8f2b17...

Total reclaimed space: 0B

NAMES          STATUS
aurora-cache   Up 41 minutes
aurora-db      Up 41 minutes

aurora-db i aurora-cache intactes, perquè el --filter label= va acotar el prune a l'etiqueta correcta. Compara mentalment amb el que hauria passat amb un docker container prune -f sense més: els tres ok-* haurien caigut igual, però també qualsevol contenidor aturat de qualsevol altre projecte de la teva màquina. Les etiquetes no són burocràcia: són el mecanisme que fa que la neteja sigui selectiva en comptes d'indiscriminada.

Conclusió

Ja no mires docker ps: el llegeixes. Saps què diu cadascuna de les seves set columnes i, sobretot, què no diu —que CREATED no és l'hora d'arrencada, que IMAGE pot haver quedat obsoleta si l'etiqueta es va moure, i que el número virtual de -s no és espai ocupat sinó espai vist—. Saps filtrar per estat, nom, imatge, etiqueta, codi de sortida i salut, combinant filtres amb I i amb O, i saps donar forma a la sortida amb plantilles Go fins a convertir-la en un tauler de control d'Aurora Libros que cap en un àlies.

Has après a compondre comandes amb -q i, més important, a fer-ho amb xarxa: mirar → actuar → comprovar, filtrant sempre per etiqueta perquè un esborrat massiu no s'emporti mai el que no era seu. Coneixes la diferència entre docker rm i docker rm -f —que no és stop + rm, sinó un SIGKILL sense gràcia—, saps què es destrueix i què sobreviu en esborrar un contenidor, i fas servir docker container prune acotat amb until i label en comptes de a pèl. Has etiquetat la flota amb projecte, component i entorn, que és exactament el mecanisme que Docker Compose automatitzarà per tu al mòdul 4.

Amb docker cp mous fitxers en els dos sentits, fins i tot amb el contenidor aturat, i això t'ha servit per a una cosa memorable: els vuit llibres d'Aurora Libros estan carregats a PostgreSQL, amb la seva taula, el seu índex i el seu INSERT 0 8. Amb docker diff saps veure la capa d'escriptura convertida en llista de fitxers —A, C i D— i has descobert la peça que falta: el directori de dades de PostgreSQL no apareix al diff, perquè no és a la capa d'escriptura, sinó en un volum anònim que la imatge declara i que ningú no controla. I saps per què docker commit no serveix per construir imatges, encara que sigui perfecte per congelar l'escena d'un crim abans de netejar-la.

Saps mirar la flota quan tot va bé. Falta l'altra cara. A la lliçó següent, Inspecció i Depuració de Contenidors, t'enfrontaràs als contenidors que no funcionen i no diuen per què: llegiràs docker logs a fons amb els seus filtres de temps i la seva regla d'or sobre stdout, entraràs amb docker exec fins i tot en imatges mínimes que no tenen ni ping —amb contenidors efímers que en comparteixen els namespaces—, espremeràs el JSON de docker inspect amb plantilles i jq, i vigilaràs en directe amb docker top, docker stats i docker events. I aplicaràs tot això a tres avaries reals d'Aurora Libros, entre elles la que fa dues lliçons que espera: per què aurora-api no troba aurora-db encara que tots dos estiguin corrent a la mateixa màquina.

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