Llevas tres lecciones construyendo. Cada docker build de las anteriores ha dejado capas en tu disco, y a estas alturas tu máquina acumula auroralibros/aurora-api en varias versiones, las bases node, postgres, redis, nginx y alpine, las imágenes de laboratorio de los ejercicios y una caché de BuildKit que no has mirado nunca. Esta lección trata de administrar ese almacén: saber qué tienes, por qué ocupa lo que ocupa, cómo auditar de qué está hecha una imagen, cómo borrar sin llevarte por delante lo que necesitas, de dónde salen esas misteriosas imágenes <none>:<none> que aparecen build tras build, y cómo llevarte una imagen a otra máquina cuando no hay ningún registro por medio. Es la lección menos vistosa del módulo y la que más disgustos te va a evitar: un docker system prune -a --volumes mal entendido borra en diez segundos datos que costaron semanas.
Contenido
- Listar imágenes:
docker image lsy sus opciones - Inspeccionar:
docker image inspecty--format - Auditar cómo se hizo:
docker image history - Borrar imágenes:
docker image rm - Imágenes dangling: las
<none>:<none> - Limpieza con la familia
prune - Diagnóstico del espacio:
docker system df - Portar imágenes sin registro:
save/loadyexport/import - Rutina de mantenimiento recomendada
- Listar imágenes:
docker image ls y sus opciones
docker image ls y sus opcionesEl punto de partida, con la gramática docker <objeto> <acción> de la lección 01-04:
REPOSITORY TAG IMAGE ID CREATED SIZE
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 10 minutes ago 167MB
auroralibros/aurora-api 1.0.0 8c1e4a7f2b9d 35 minutes ago 167MB
auroralibros/aurora-api 0.1.0 6b4d2f8e1a3c 58 minutes ago 167MB
node 22-alpine 9f2c1a5e7b04 6 days ago 142MB
postgres 16-alpine b71c3d8f4a29 9 days ago 278MB
redis 7-alpine 3e5a9c1d7b82 9 days ago 41.4MB
nginx alpine c8d4f2a91e37 11 days ago 52.5MB
alpine 3.21 a1e7f9c34d02 3 weeks ago 8.17MB
registry 2 7f1e2b8c5a43 2 months ago 25.4MBdocker images es el alias corto y hace exactamente lo mismo.
Un aviso fundamental sobre la columna SIZE, que ya intuiste en la lección 01-05: los tamaños no se suman. Las tres versiones de aurora-api dicen 167 MB cada una, pero no ocupan 501 MB en disco. Comparten la base node:22-alpine y buena parte de sus capas; el espacio real lo dirá docker system df en el apartado 7.
Opciones de listado
# -a: incluye las capas intermedias (con el constructor clásico)
docker image ls -a
# --digests: muestra el digest sha256 de cada imagen
docker image ls --digests node
# -q: solo los IDs, ideal para encadenar comandos
docker image ls -q
# --no-trunc: IDs completos, sin abreviar
docker image ls --no-truncREPOSITORY TAG DIGEST IMAGE ID SIZE
auroralibros/aurora-api 1.1.0 <none> 4e9c7d2a8f31 167MB
auroralibros/aurora-api 1.0.0 <none> 8c1e4a7f2b9d 167MBEl digest sale <none> porque estas imágenes se construyeron en local y nunca se han publicado: el digest del manifiesto lo asigna el registro al recibir el push. En cuanto las publiques en la lección 02-06, esa columna se rellenará.
Filtrar con --filter
# Imágenes de un repositorio concreto
docker image ls auroralibros/aurora-api
# Con comodín en el nombre
docker image ls "auroralibros/*"
# Solo las dangling (apartado 5)
docker image ls --filter "dangling=true"
# Anteriores a una imagen dada
docker image ls --filter "before=auroralibros/aurora-api:1.0.0"
# Posteriores a una imagen dada
docker image ls --filter "since=node:22-alpine"
# Por etiqueta OCI, las que añadiste en 02-04
docker image ls --filter "label=org.opencontainers.image.vendor=Aurora Libros S.L."REPOSITORY TAG IMAGE ID CREATED SIZE
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 12 minutes ago 167MBSolo aparece la 1.1.0, porque es la única que lleva las etiquetas OCI. Aquí se ve el rendimiento práctico del trabajo de la lección anterior: los metadatos no eran decoración, son un índice consultable.
Formato personalizado con plantillas Go
Recuperando --format de la lección 01-04:
REPOSITORY:TAG SIZE CREATED
auroralibros/aurora-api:1.1.0 167MB 13 minutes ago
auroralibros/aurora-api:1.0.0 167MB 38 minutes ago
node:22-alpine 142MB 6 days agoCampos disponibles: .ID, .Repository, .Tag, .Digest, .CreatedSince, .CreatedAt, .Size.
Sin la palabra table, la salida es texto plano, perfecto para scripts:
Y en JSON, para procesar con jq:
{"Containers":"N/A","CreatedAt":"2026-08-04 11:42:03 +0200 CEST","CreatedSince":"14 minutes ago","Digest":"<none>","ID":"4e9c7d2a8f31","Repository":"auroralibros/aurora-api","Size":"167MB","Tag":"1.1.0"}
- Inspeccionar:
docker image inspect y --format
docker image inspect y --formatdocker image inspect vuelca todos los metadatos de una imagen en JSON. Sin filtro son cientos de líneas:
La gracia está en extraer solo lo que necesitas. La sintaxis de --format es la misma de la lección 01-04, y estos son los campos que más vas a usar:
# El proceso que arranca el contenedor
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'# Variables de entorno, una por línea
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{range .Config.Env}}{{println .}}{{end}}'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_VERSION=22.14.0
YARN_VERSION=1.22.22
NODE_ENV=production
PORT=3000
APP_VERSION=1.1.0Este es el comando con el que auditas que no hay ningún secreto dentro de una imagen antes de publicarla. Conviértelo en un reflejo.
# Usuario, directorio de trabajo y puertos declarados
docker image inspect auroralibros/aurora-api:1.1.0 \
--format 'Usuario: {{.Config.User}}
WorkDir: {{.Config.WorkingDir}}
Puertos: {{range $p, $_ := .Config.ExposedPorts}}{{$p}} {{end}}'# Arquitectura y sistema operativo: crítico en máquinas ARM
docker image inspect node:22-alpine --format '{{.Os}}/{{.Architecture}} · Docker {{.DockerVersion}}'Si esto dijera linux/amd64 y estuvieras en un Mac con Apple Silicon, la imagen funcionaría por emulación, mucho más lenta. Es la primera comprobación ante un contenedor inexplicablemente lento.
# Las capas: los digests del sistema de archivos
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{range .RootFS.Layers}}{{println .}}{{end}}'sha256:4a1b8c2f9e3d7a5b1c8f2e6d4a9b3c7e1f5d8a2b6c4e9f3a7d1b5c8e2f6a4d9b
sha256:8f3e1d7c5b9a2f4e8d6c1b3a7f9e5d2c8b4a6f1e3d7c9b5a2f8e4d6c1b3a7f9e
...# Cuenta rápida de capas: buen indicador de complejidad
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{len .RootFS.Layers}} capas'# Las etiquetas OCI de la lección 02-04
docker image inspect auroralibros/aurora-api:1.1.0 \
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}'Un comando, y ya sabes exactamente qué commit lleva dentro esa imagen.
Puedes inspeccionar varias a la vez y comparar:
docker image inspect --format '{{.RepoTags}} → {{.Size}} bytes, {{len .RootFS.Layers}} capas' \
auroralibros/aurora-api:1.1.0 node:22-alpine alpine:3.21[auroralibros/aurora-api:1.1.0] → 167142891 bytes, 7 capas
[node:22-alpine] → 142331904 bytes, 4 capas
[alpine:3.21] → 8172544 bytes, 1 capasAhí se ve la genealogía completa: Alpine aporta 1 capa, Node añade 3 más, y tu Dockerfile las 3 restantes (los dos COPY y el RUN).
- Auditar cómo se hizo:
docker image history
docker image historydocker image history reconstruye la historia de construcción de una imagen capa a capa. Es la herramienta de auditoría por excelencia y ya la usaste en la lección 02-01 para evaluar imágenes ajenas.
IMAGE CREATED CREATED BY SIZE COMMENT
4e9c7d2a8f31 18 minutes ago CMD ["server.js"] 0B buildkit.dockerfile.v0
<missing> 18 minutes ago ENTRYPOINT ["node"] 0B buildkit.dockerfile.v0
<missing> 18 minutes ago HEALTHCHECK &{["CMD-SHELL" "wget --quiet --… 0B buildkit.dockerfile.v0
<missing> 18 minutes ago EXPOSE map[3000/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 18 minutes ago USER node 0B buildkit.dockerfile.v0
<missing> 18 minutes ago ENV NODE_ENV=production PORT=3000 APP_VERSI… 0B buildkit.dockerfile.v0
<missing> 18 minutes ago COPY . . # buildkit 52.1kB buildkit.dockerfile.v0
<missing> 18 minutes ago RUN /bin/sh -c npm ci --omit=dev && npm cac… 24.7MB buildkit.dockerfile.v0
<missing> 18 minutes ago COPY package*.json ./ # buildkit 44.6kB buildkit.dockerfile.v0
<missing> 18 minutes ago WORKDIR /app 0B buildkit.dockerfile.v0
<missing> 18 minutes ago LABEL org.opencontainers.image.title=auror… 0B buildkit.dockerfile.v0
<missing> 6 days ago CMD ["node"] 0B buildkit.dockerfile.v0
<missing> 6 days ago ENTRYPOINT ["docker-entrypoint.sh"] 0B buildkit.dockerfile.v0
<missing> 6 days ago RUN /bin/sh -c apk add --no-cache --virtual… 7.82MB buildkit.dockerfile.v0
<missing> 6 days ago ENV NODE_VERSION=22.14.0 0B buildkit.dockerfile.v0
<missing> 6 days ago /bin/sh -c #(nop) ADD file:1b8a2c9e4d7f… in / 8.17MBCómo leerlo:
- Se lee de abajo arriba. La última línea es la primera capa (el sistema de archivos base de Alpine); la primera línea es la instrucción más reciente.
<missing>no es un error. Solo la capa superior tiene un ID propio; las intermedias de una imagen construida con BuildKit no lo exponen. Es normal.- La columna SIZE es la que importa. Ahí se ve que el
RUN npm ciaporta 24,7 MB y los dosCOPYapenas 52 kB y 44 kB juntos. Todos los metadatos —ENV,EXPOSE,USER,LABEL,HEALTHCHECK,ENTRYPOINT,CMD— pesan 0 B, exactamente como se anunció en la lección anterior. - La historia incluye la de la imagen base. Las líneas de "6 días" son de
node:22-alpine: ves cómo la construyeron sus mantenedores.
Localizar la capa que más pesa
Es el uso más rentable del comando:
docker image history auroralibros/aurora-api:1.1.0 --format "{{.Size}}\t{{.CreatedBy}}" --no-trunc | sort -rh | head -524.7MB RUN /bin/sh -c npm ci --omit=dev && npm cache clean --force # buildkit
8.17MB /bin/sh -c #(nop) ADD file:1b8a2c9e4d7f... in /
7.82MB RUN /bin/sh -c apk add --no-cache --virtual .build-deps ...
52.1kB COPY . . # buildkit
44.6kB COPY package*.json ./ # buildkitCuando una imagen pesa 900 MB y no sabes por qué, este comando te da la respuesta en un segundo. Los sospechosos habituales: cachés de gestores de paquetes sin limpiar, herramientas de compilación que se quedaron dentro y ficheros temporales borrados en otra capa (con el efecto nulo que demostraste en la lección 02-03).
--no-trunc: ver el comando completo
Por defecto la columna CREATED BY se corta. Para auditar de verdad, hace falta el texto entero:
Este es el comando con el que detectas en una imagen ajena un curl … | sh sospechoso, la instalación de una herramienta que no esperabas o, en tu propia imagen antes de publicarla, un --build-arg con una contraseña como el de la lección 02-04.
Una limitación honesta: history muestra las instrucciones, no el contenido. Un fichero copiado con COPY no se ve aquí. Para eso, arrancar la imagen y mirar (docker run --rm imagen ls -la /app) o herramientas de terceros como dive (lección 07-04).
- Borrar imágenes:
docker image rm
docker image rmUntagged: auroralibros/aurora-api:0.1.0
Deleted: sha256:6b4d2f8e1a3c...
Deleted: sha256:9a2f7c1e5b8d...docker rmi es el alias corto. Fíjate en la salida: Untagged y Deleted son dos cosas distintas, y entender la diferencia es la clave de todo este apartado.
Borrar una de varias etiquetas
Cuando dos etiquetas apuntan a la misma imagen (el mismo IMAGE ID), borrar una no borra la imagen:
docker image tag auroralibros/aurora-api:1.1.0 auroralibros/aurora-api:estable
docker image ls auroralibros/aurora-apiREPOSITORY TAG IMAGE ID SIZE
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 167MB
auroralibros/aurora-api estable 4e9c7d2a8f31 167MBMismo IMAGE ID: es una imagen con dos nombres, no dos imágenes de 167 MB. Ahora borra una:
Solo Untagged, sin ningún Deleted. Se ha quitado el nombre; los datos siguen ahí porque 1.1.0 los sigue referenciando. El Deleted solo aparece cuando desaparece la última referencia. Esta es la razón de que a veces borres una imagen y no recuperes ni un byte de disco.
Borrar por ID y por digest
# Por ID (basta un prefijo inequívoco)
docker image rm 4e9c7d2a
# Por digest, tras haber publicado la imagen
docker image rm node@sha256:9f2c1a5e7b04...Borrar por ID cuando hay varias etiquetas apuntando a esa imagen falla:
Error response from daemon: conflict: unable to delete 4e9c7d2a8f31 (must be forced)
- image is referenced in multiple repositoriesDocker se niega porque no sabe qué nombre quieres eliminar. O borras cada etiqueta por su nombre, o usas -f para eliminarlas todas de golpe.
El error de la imagen en uso
El más frecuente de todos:
docker run -d --name aurora-demo auroralibros/aurora-api:1.1.0
docker stop aurora-demo
docker image rm auroralibros/aurora-api:1.1.0Error response from daemon: conflict: unable to remove repository reference
"auroralibros/aurora-api:1.1.0" (must force) - container 3f8a1c9b7e2d is using its
referenced image 4e9c7d2a8f31El contenedor está parado, no en ejecución, y aun así bloquea el borrado. Tiene toda la lógica: como aprendiste en la lección 01-05, un contenedor parado conserva su capa de escritura, que se apila encima de las capas de la imagen. Borrar la imagen dejaría esa capa flotando sobre la nada, y el contenedor no podría volver a arrancar.
Las tres salidas posibles:
# a) Localizar los contenedores que la usan
docker ps -a --filter ancestor=auroralibros/aurora-api:1.1.0
# b) Borrar el contenedor y luego la imagen — LA FORMA CORRECTA
docker rm aurora-demo
docker image rm auroralibros/aurora-api:1.1.0
# c) Forzar
docker image rm -f auroralibros/aurora-api:1.1.0¿Cuándo es aceptable -f? Menos veces de las que se usa:
| Situación | ¿-f? |
Alternativa |
|---|---|---|
| Contenedor parado que ya no necesitas | Aceptable | Mejor docker rm primero: más explícito |
| Varias etiquetas apuntando a la misma imagen y quieres borrarlas todas | Sí | Es el uso legítimo |
| Contenedor en ejecución | No | Párala antes, con su ciclo de vida ordenado |
| No sabes por qué falla | No | Investiga con docker ps -a --filter ancestor=… |
El peligro de -f sobre un contenedor en ejecución: la imagen queda "borrada" del listado pero sus capas siguen ocupando disco mientras el contenedor viva, y el contenedor no podrá reiniciarse cuando se pare. Acabas con un servicio que funciona hasta el primer reinicio y luego es irrecuperable, sin ninguna forma de reconstruirlo salvo el registro. Es un incidente de producción clásico.
Borrado masivo
# Todas las imágenes de un repositorio
docker image rm $(docker image ls -q auroralibros/aurora-api)
# TODAS las imágenes (¡cuidado!)
docker image rm -f $(docker image ls -aq)El patrón $(docker image ls -q …) combina -q (solo IDs) con la sustitución de comandos del shell. Antes de ejecutar un borrado masivo, ejecuta primero solo la parte interna para ver qué vas a destruir:
docker image ls auroralibros/aurora-api # Mira qué hay
docker image rm $(docker image ls -q auroralibros/aurora-api) # Y entonces borra
- Imágenes dangling: las
<none>:<none>
<none>:<none>Tarde o temprano verás esto:
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 2f8e1a9c4b73 5 minutes ago 167MB
<none> <none> 7d3c9f2e8a51 22 minutes ago 167MB
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 30 minutes ago 167MBEsas <none>:<none> son imágenes dangling (colgantes): imágenes reales, completas y perfectamente funcionales, que han perdido su nombre.
De dónde salen
La causa principal, con diferencia, es reconstruir con la misma etiqueta:
cd ~/aurora-libros/api
docker build -t auroralibros/aurora-api:1.1.0 . # Imagen A ← 1.1.0
echo "// un cambio" >> server.js
docker build -t auroralibros/aurora-api:1.1.0 . # Imagen B ← 1.1.0En la segunda build nace una imagen nueva (contenido distinto, ID distinto) y la etiqueta 1.1.0 se mueve a ella. La imagen A no se borra: se queda sin nombre. Es exactamente el modelo de la lección 02-01 —las etiquetas son punteros móviles a digests inmutables—, visto desde el lado local.
Compruébalo:
Las otras dos fuentes:
- Builds sin
-t.docker build .produce una imagen sin nombre desde el primer segundo. Es el motivo del consejo de la lección 02-02: etiqueta siempre. - Builds fallidas que dejaron capas intermedias.
Por qué importan
Porque se acumulan en silencio. Un desarrollador que reconstruye veinte veces al día con la misma etiqueta genera veinte imágenes dangling diarias. La mayoría comparten capas y no ocupan 167 MB cada una, pero las capas propias (el COPY . . y a veces el RUN npm ci) sí son exclusivas. En unas semanas son varios gigabytes, y el mensaje no space left on device llega en el peor momento.
No son basura absoluta: si sabes su ID, puedes ejecutarlas y hasta reetiquetarlas.
Eso salva la papeleta cuando borras por error la etiqueta de una imagen que aún no habías publicado. Pero como práctica habitual, se limpian.
- Limpieza con la familia
prune
pruneCuatro comandos, con cuatro radios de destrucción muy distintos. Esta tabla es la que hay que memorizar:
| Comando | Qué borra | Riesgo |
|---|---|---|
docker image prune |
Solo imágenes dangling | Bajo: es seguro casi siempre |
docker image prune -a |
Todas las imágenes sin ningún contenedor que las use | Medio: te obliga a redescargar bases |
docker builder prune |
La caché de BuildKit | Medio: las builds siguientes serán lentas |
docker system prune |
Contenedores parados + redes sin usar + dangling + caché de build | Medio-alto |
docker system prune -a --volumes |
Todo lo anterior + todas las imágenes sin contenedor + volúmenes | MUY ALTO: destruye datos |
docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:2f8e1a9c4b73...
deleted: sha256:7d3c9f2e8a51...
Total reclaimed space: 51.3MBEste es el que puedes ejecutar con confianza: solo se lleva imágenes sin nombre. Con -f se salta la confirmación (útil en scripts) y con --filter "until=168h" limita el borrado a las de más de una semana:
docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]Lee bien el aviso: borra todas las imágenes que no tengan un contenedor asociado, no solo las dangling. Si no tienes contenedores creados —lo normal después de limpiar—, esto se lleva node:22-alpine, postgres:16-alpine, redis:7-alpine, tus aurora-api locales y todo lo demás. No es catastrófico (se puede reconstruir y redescargar), pero significa gigabytes de descarga y, si estás sin conexión o cerca del límite de descargas de la lección 02-01, un mal rato.
docker builder prune
La caché de BuildKit es invisible en docker image ls y suele ser lo más voluminoso de la máquina:
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
Total: 2.847GBCasi tres gigas que no aparecían en ningún listado de imágenes. Variantes:
docker builder prune -a # También la caché en uso
docker builder prune --filter "until=72h" # Solo lo anterior a 3 días
docker builder prune --keep-storage=10GB # Deja hasta 10 GB de cachéLa contrapartida: la siguiente build de cada proyecto irá en frío. Recuerda de la lección 02-02 que ahí es donde la caché no ayuda.
docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N] y
Deleted Containers:
3f8a1c9b7e2d...
Deleted Networks:
aurora-red-pruebas
Deleted Images:
deleted: sha256:2f8e1a9c4b73...
Total reclaimed space: 3.12GBEl aviso lista exactamente lo que va a borrar. Léelo siempre: es la diferencia entre limpiar y perder trabajo. Lo que se lleva por sorpresa suele ser un contenedor parado que contenía datos en su capa de escritura, o una red que habías creado a mano para unas pruebas.
docker system prune -a --volumes: el comando peligroso
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all volumes not used by at least one container
- all images without at least one container associated to them
- all build cacheEsta es la línea que arruina el día: all volumes not used by at least one container.
Traducido a Aurora Libros: cuando en el módulo 3 tengas un volumen con el catálogo de PostgreSQL y hayas parado y borrado el contenedor de aurora-db para recrearlo, ese volumen queda temporalmente sin contenedor asociado. Un docker system prune -a --volumes en ese momento borra la base de datos entera. Sin papelera, sin deshacer, sin recuperación.
Reglas de uso:
- Nunca en un servidor. Nunca.
- En desarrollo, solo cuando tengas claro que no hay ningún volumen que te importe.
- Antes de ejecutarlo, mira qué volúmenes hay:
- Si solo quieres espacio, la escalera segura es:
docker image prune→docker builder prune→docker system prune. Rara vez hace falta llegar al último escalón.
- Diagnóstico del espacio:
docker system df
docker system dfAntes de borrar nada, mide.
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 9 2 842.3MB 601.7MB (71%)
Containers 4 1 12.4MB 9.1MB (73%)
Local Volumes 3 1 248.9MB 187.2MB (75%)
Build Cache 47 0 2.847GB 2.847GB (100%)Cómo leerlo:
| Columna | Significado |
|---|---|
| TOTAL | Número de objetos de ese tipo |
| ACTIVE | Los que están en uso (imágenes con contenedor, volúmenes montados…) |
| SIZE | Espacio real en disco, ya descontadas las capas compartidas |
| RECLAIMABLE | Cuánto liberarías al limpiar, y qué porcentaje del total es |
Dos conclusiones inmediatas de este ejemplo:
- Nueve imágenes ocupan 842 MB, no la suma de sus columnas SIZE en
docker image ls(que daría más de 1,2 GB). La diferencia son las capas compartidas de la lección 01-05, contadas una sola vez. - La caché de build es 2,85 GB, el 100 % recuperable, y es con diferencia lo más grande. Es lo primero que hay que limpiar, y no aparecía en ningún listado de imágenes.
El detalle con -v
Images space usage:
REPOSITORY TAG IMAGE ID SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 167MB 142MB 24.8MB 1
auroralibros/aurora-api 1.0.0 8c1e4a7f2b9d 167MB 142MB 24.7MB 0
node 22-alpine 9f2c1a5e7b04 142MB 142MB 0B 0
postgres 16-alpine b71c3d8f4a29 278MB 8.17MB 270MB 0
Containers space usage:
CONTAINER ID IMAGE SIZE STATUS
3f8a1c9b7e2d auroralibros/aurora-api:1.1.0 4.2MB Up 12 minutes
Local Volumes space usage:
VOLUME NAME LINKS SIZE
aurora-datos-db 1 187.2MB
f3a9c2e1b8d7460a2c5f8e1d4b7a3c9e2f6d8a1b 0 61.7MBLas columnas SHARED SIZE y UNIQUE SIZE son las que aclaran todo:
node:22-alpine: 142 MB de tamaño, 142 MB compartidos, 0 B únicos. Borrarla no liberaría nada, porque todas sus capas las usan tus imágenes deaurora-api.aurora-api:1.0.0: 167 MB, de los cuales solo 24,7 MB son exclusivos. Eso es lo que ganarías al borrarla.postgres:16-alpine: 278 MB con 270 MB únicos. Esta sí es un buen candidato si no la vas a usar.
Y mira la última línea de volúmenes: f3a9c2e1b8d7… con 0 links es un volumen anónimo huérfano, exactamente lo que anticipaba la lección 02-04 al desaconsejar VOLUME en el Dockerfile. Ocupa 61,7 MB y nadie sabe qué contiene.
- Portar imágenes sin registro:
save/load y export/import
save/load y export/importA veces hay que llevar una imagen a otra máquina sin registro por medio: un cliente con la red aislada, un entorno sin salida a Internet, una demo en un portátil sin conexión. Docker ofrece dos parejas de comandos que se confunden constantemente y que no son intercambiables.
| Aspecto | save / load |
export / import |
|---|---|---|
| Opera sobre | Una imagen | Un contenedor |
| Qué guarda | Todas las capas + manifiesto + metadatos | El sistema de archivos aplanado, sin capas |
| Conserva el historial | Sí | No |
Conserva CMD, ENTRYPOINT, ENV, USER, EXPOSE… |
Sí | No |
| Conserva etiquetas de imagen | Sí | No (hay que reetiquetar) |
| Varias imágenes en un fichero | Sí | No |
Tamaño del .tar |
Mayor (todas las capas) | Menor (un solo nivel) |
| Uso correcto | Portar imágenes | Extraer un sistema de archivos, aplanar |
La regla: para mover una imagen, siempre save/load. export/import sirve para otra cosa.
docker save / docker load con Aurora Libros
# 1. Exportar la imagen a un fichero tar
docker save -o aurora-api-1.1.0.tar auroralibros/aurora-api:1.1.0
ls -lh aurora-api-1.1.0.tar# 2. Comprimir: las capas son sobre todo texto y binarios, comprimen bien
gzip -9 aurora-api-1.1.0.tar
ls -lh aurora-api-1.1.0.tar.gzDe 168 MB a 62 MB: un 63 % menos, y eso es lo que viaja por el pendrive o el SCP. También se puede hacer en un solo paso con una tubería:
# 3. Transferir a la otra máquina
scp aurora-api-1.1.0.tar.gz operador@servidor-aurora:/tmp/
# 4. Cargar allí
ssh operador@servidor-aurora
gunzip -c /tmp/aurora-api-1.1.0.tar.gz | docker load# 5. Verificar que llegó completa
docker image ls auroralibros/aurora-api
docker image inspect auroralibros/aurora-api:1.1.0 --format '{{json .Config.Entrypoint}} · {{.Config.User}}'
docker run -d --name aurora-portada -p 3000:3000 auroralibros/aurora-api:1.1.0
curl -s http://localhost:3000/saludREPOSITORY TAG IMAGE ID CREATED SIZE
auroralibros/aurora-api 1.1.0 4e9c7d2a8f31 45 minutes ago 167MB
["node"] · node
{"servicio":"aurora-api","version":"1.0.0","db":"ko","cache":"ko",...}Mismo IMAGE ID, mismo ENTRYPOINT, mismo USER. La imagen ha llegado íntegra, con todos los metadatos que definiste en la lección 02-04.
Guardar varias imágenes en un solo fichero, útil para llevarse la pila entera de Aurora Libros:
docker save -o aurora-pila.tar \
auroralibros/aurora-api:1.1.0 \
postgres:16-alpine \
redis:7-alpine \
nginx:alpine
ls -lh aurora-pila.tarUn único fichero con todo lo necesario para levantar la plataforma en una máquina sin Internet.
docker export / docker import
Trabaja sobre contenedores, no imágenes, y aplana el resultado:
docker run -d --name para-exportar auroralibros/aurora-api:1.1.0
docker export -o aurora-plano.tar para-exportar
ls -lh aurora-plano.tarUna sola capa y ningún historial. Toda la genealogía ha desaparecido. Y lo grave:
Se han perdido el ENTRYPOINT, el CMD, el ENV, el USER y el EXPOSE. La imagen importada es un sistema de archivos sin instrucciones. Habría que reponerlos a mano al ejecutar:
Entonces, ¿para qué sirve? Para dos cosas legítimas:
- Aplanar una imagen con demasiadas capas o con un secreto enterrado en una capa intermedia que quieres eliminar de verdad. Al aplanar, esa capa desaparece. (La forma moderna y mejor de conseguirlo son las builds multi-etapa, lección 05-04.)
- Extraer el sistema de archivos de un contenedor para analizarlo forensemente o para construir una imagen base a partir de un sistema existente.
Se pueden reponer los metadatos al importar, con -c:
docker import \
-c 'ENTRYPOINT ["node"]' \
-c 'CMD ["server.js"]' \
-c 'WORKDIR /app' \
-c 'USER node' \
-c 'ENV NODE_ENV=production PORT=3000' \
-c 'EXPOSE 3000' \
aurora-plano.tar aurora-plana:1.1
docker run -d --name plana-ok -p 3001:3000 aurora-plana:1.1
curl -s http://localhost:3001/salud | head -c 60Funciona, pero has tenido que reconstruir a mano lo que save conservaba solo. Queda claro cuál es la herramienta adecuada para portar imágenes.
Limpieza:
docker rm -f para-exportar plana-ok aurora-portada 2>/dev/null
docker image rm aurora-plana:1.0 aurora-plana:1.1 2>/dev/null
rm -f aurora-plano.tar
- Rutina de mantenimiento recomendada
Con todo lo anterior, esta es una rutina razonable para una máquina de desarrollo.
Semanal (seguro, se puede automatizar):
Borra las dangling y la caché de build de más de una semana, y muestra cómo queda el espacio. No toca nada con nombre, ni contenedores, ni volúmenes.
Mensual (revisión manual):
docker system df -v | head -40 # ¿Qué ocupa y qué es único?
docker image ls --filter "before=$(date -d '30 days ago' +%Y-%m-%d)" 2>/dev/null
docker volume ls -f dangling=true # Volúmenes huérfanos: MIRA qué son
docker ps -a --filter status=exited # Contenedores parados olvidadosAquí no se automatiza nada: se mira y se decide. Los volúmenes huérfanos hay que inspeccionarlos antes de borrarlos, porque pueden contener datos.
Antes de una build importante:
Caché limpia y base actualizada, como se explicó en la lección 02-02.
Cuando falta espacio, la escalera segura:
docker system df # 1. Mide antes de tocar nada
docker builder prune -f # 2. Lo más grande y lo menos arriesgado
docker image prune -f # 3. Las dangling
docker container prune -f # 4. Contenedores parados (revísalos)
docker image prune -a --filter "until=720h" # 5. Imágenes sin usar de +30 díasEn cinco pasos ordenados de menor a mayor riesgo se recuperan casi siempre varios gigabytes sin llegar nunca a docker system prune -a --volumes.
Un script de mantenimiento con informe:
#!/bin/bash
# mantenimiento-docker.sh — limpieza semanal segura
set -e
echo "=== Espacio ANTES ==="
docker system df
echo "=== Limpiando imágenes dangling ==="
docker image prune -f
echo "=== Limpiando caché de build de más de 7 días ==="
docker builder prune -f --filter "until=168h"
echo "=== Espacio DESPUÉS ==="
docker system df
echo "=== Volúmenes huérfanos (REVISAR A MANO, no se borran) ==="
docker volume ls -f dangling=trueFíjate en la última sección: lista los volúmenes huérfanos pero no los borra. Esa es la línea que separa un script de mantenimiento de un script de destrucción de datos.
Errores Comunes y Consejos
- Sumar la columna SIZE de
docker image ls. No es espacio en disco: las capas compartidas se cuentan en cada fila. El dato real está endocker system df, y el desglose endocker system df -v. - Borrar una etiqueta y esperar recuperar espacio. Si la salida solo dice
Untaggedy noDeleted, no has liberado nada: la imagen tiene otra referencia. docker image rm -fsobre un contenedor en ejecución. La imagen "desaparece" pero sus capas siguen en disco y el contenedor no podrá reiniciarse nunca más. Para el contenedor primero.docker system prune -a --volumessin leer el aviso. Borra volúmenes sin contenedor asociado, y un volumen de base de datos entre recreaciones de contenedor está exactamente en ese estado. Nunca en un servidor.- Ignorar la caché de BuildKit. No aparece en
docker image lsy suele ser lo más voluminoso de la máquina.docker system dfla delata. - Confundir
save/loadconexport/import.exportpierdeENTRYPOINT,CMD,ENV,USERy el historial, y la imagen importada dano command specified. Para portar imágenes, siempresave/load. - Olvidar comprimir el
.tar. Ungzip -9recorta habitualmente entre un 50 % y un 65 %. - Consejo: audita las variables antes de publicar.
docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}'debe ser un reflejo antes de cualquierdocker push. - Consejo: usa
docker image history … | sort -rhpara cazar capas gordas. Es el diagnóstico de "por qué pesa tanto esta imagen" en un solo comando. - Consejo: etiqueta siempre con
-t. Cada build sin etiqueta es una imagen dangling desde el primer segundo. - Consejo: antes de un borrado masivo, ejecuta solo la parte interna del
$( … )para ver qué vas a destruir.
Ejercicios
Ejercicio 1: audita el espacio real de tu máquina
- Ejecuta
docker system dfy anota el total y el porcentaje recuperable de cada categoría. - Con
docker system df -v, identifica la imagen con mayor UNIQUE SIZE y la que tenga UNIQUE SIZE de 0 B. Explica qué significa cada caso y cuánto liberaría borrar cada una. - Localiza con
docker image historyla capa más pesada deauroralibros/aurora-api:1.1.0y depostgres:16-alpine. - Comprueba si tienes volúmenes huérfanos y averigua, sin borrarlos, de qué imagen salieron.
Ejercicio 2: demuestra el ciclo de vida de las etiquetas y las dangling
- Construye
auroralibros/aurora-api:experimentodesde tu Dockerfile y anota el IMAGE ID. - Crea una segunda etiqueta
auroralibros/aurora-api:copiaapuntando a la misma imagen. Comprueba que el ID coincide y quedocker system dfno ha crecido. - Borra la etiqueta
copia. ¿SaleUntagged,Deletedo ambos? ¿Por qué? - Modifica
server.js, reconstruye con la misma etiquetaexperimentoy comprueba que aparece una imagen<none>:<none>. Explica de dónde salió. - Recupera esa imagen dangling reetiquetándola como
auroralibros/aurora-api:rescataday verifica que funciona. - Limpia todo lo creado en el ejercicio.
Ejercicio 3: porta la pila de Aurora Libros a una máquina sin Internet
Simula un despliegue en un entorno aislado:
- Exporta en un único fichero
auroralibros/aurora-api:1.1.0,postgres:16-alpineyredis:7-alpine. - Comprímelo y anota el tamaño antes y después.
- Borra las tres imágenes de tu máquina (simulando la máquina destino en blanco).
- Cárgalas desde el fichero y verifica que
aurora-apiconservaENTRYPOINT,USER,ENVy elHEALTHCHECK. - Repite el ciclo con
export/importsobre un contenedor deaurora-apiy compara: tamaño del tar, número de capas delhistoryy comportamiento al ejecutardocker runsin argumentos. Concluye qué pareja usarías y por qué.
Soluciones
Solución al ejercicio 1
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 9 2 842.3MB 601.7MB (71%)
Containers 4 1 12.4MB 9.1MB (73%)
Local Volumes 3 1 248.9MB 187.2MB (75%)
Build Cache 47 0 2.847GB 2.847GB (100%)1. Total aproximado: 3,95 GB, de los cuales 3,65 GB son recuperables. La caché de build es el 72 % de todo y es 100 % recuperable: es el objetivo prioritario.
2.
- Mayor UNIQUE SIZE:
postgres:16-alpine, con 270 MB únicos de sus 278 MB. Solo comparte con las demás la capa base de Alpine (8,17 MB). Borrarla liberaría 270 MB reales. - UNIQUE SIZE de 0 B:
node:22-alpine. Sus 142 MB están íntegramente compartidos con tus imágenes deaurora-api, que se construyeron sobre ella. Borrarla no liberaría ni un byte mientras exista alguna imagen derivada; solo desaparecería del listado. Es la demostración práctica de las capas compartidas de la lección 01-05.
3.
docker image history auroralibros/aurora-api:1.1.0 --format "{{.Size}}\t{{.CreatedBy}}" | sort -rh | head -3
docker image history postgres:16-alpine --format "{{.Size}}\t{{.CreatedBy}}" --no-trunc | sort -rh | head -324.7MB RUN /bin/sh -c npm ci --omit=dev && npm cache clean --force # buildkit
8.17MB /bin/sh -c #(nop) ADD file:1b8a2c9e... in /
7.82MB RUN /bin/sh -c apk add --no-cache --virtual .build-deps ...
248MB RUN /bin/sh -c set -eux; apk add --no-cache --virtual .build-deps ... ; make -C /usr/src/postgresql ...
21.4MB RUN /bin/sh -c apk add --no-cache bash su-exec tzdata zstd
8.17MB /bin/sh -c #(nop) ADD file:1b8a2c9e... in /En aurora-api, el npm ci con 24,7 MB es el mayor contribuyente propio: son las dependencias de producción. En postgres, los 248 MB de la compilación de PostgreSQL explican por sí solos su tamaño.
4.
docker volume ls -f dangling=true
docker volume inspect f3a9c2e1b8d7460a2c5f8e1d4b7a3c9e2f6d8a1b --format '{{.Mountpoint}} · creado {{.CreatedAt}}'
sudo ls /var/lib/docker/volumes/f3a9c2e1b8d7460a2c5f8e1d4b7a3c9e2f6d8a1b/_dataEl contenido delata su origen: es un volumen anónimo creado por la imagen de PostgreSQL, que declara VOLUME /var/lib/postgresql/data en su Dockerfile. Es exactamente el escenario que la lección 02-04 daba como razón para no usar VOLUME. No lo borres sin comprobar el contenido: podría ser una base de datos con trabajo dentro.
Solución al ejercicio 2
cd ~/aurora-libros/api
# 1
docker build -q -t auroralibros/aurora-api:experimento .
docker image ls auroralibros/aurora-api:experimento --format "{{.ID}}"# 2
docker system df --format "{{.Type}}: {{.Size}}" | head -1
docker image tag auroralibros/aurora-api:experimento auroralibros/aurora-api:copia
docker image ls auroralibros/aurora-api --format "table {{.Tag}}\t{{.ID}}\t{{.Size}}"
docker system df --format "{{.Type}}: {{.Size}}" | head -1Mismo ID y el mismo espacio total. docker image tag solo crea una referencia; no copia un solo byte. Es la demostración local de lo que verás en el registro en la lección 02-06.
Solo Untagged. No hay Deleted porque la etiqueta experimento sigue apuntando a esa imagen: los datos siguen referenciados y el borrado se limita a eliminar el nombre.
# 4
echo "// cambio para generar una dangling" >> server.js
docker build -q -t auroralibros/aurora-api:experimento .
docker image ls --filter "dangling=true"El ID 4e9c7d2a8f31 es el mismo del paso 1: la imagen original no se ha borrado, ha perdido el nombre. La nueva build produjo una imagen distinta y la etiqueta experimento se movió a ella. Las etiquetas son punteros, no propietarios.
# 5
docker image tag 4e9c7d2a8f31 auroralibros/aurora-api:rescatada
docker run --rm auroralibros/aurora-api:rescatada --version
docker image ls --filter "dangling=true"Recuperada y funcional, y ya no figura como dangling porque vuelve a tener nombre. Fíjate en que --version funcionó gracias al ENTRYPOINT ["node"] de la lección 02-04.
# 6
docker image rm auroralibros/aurora-api:experimento auroralibros/aurora-api:rescatada
docker image prune -fSolución al ejercicio 3
# 1 y 2
cd /tmp
docker save -o aurora-pila.tar \
auroralibros/aurora-api:1.1.0 postgres:16-alpine redis:7-alpine
ls -lh aurora-pila.tar
gzip -9 aurora-pila.tar
ls -lh aurora-pila.tar.gz-rw------- 1 joan joan 481M ago 4 12:40 aurora-pila.tar
-rw------- 1 joan joan 173M ago 4 12:41 aurora-pila.tar.gzDe 481 MB a 173 MB: un 64 % menos. La compresión merece la pena siempre, sobre todo si el fichero viaja por SCP o por un pendrive.
# 3
docker image rm auroralibros/aurora-api:1.1.0 postgres:16-alpine redis:7-alpine
docker image ls | grep -E "aurora-api|postgres|redis" # sin resultados
# 4
gunzip -c aurora-pila.tar.gz | docker loadLoaded image: auroralibros/aurora-api:1.1.0
Loaded image: postgres:16-alpine
Loaded image: redis:7-alpinedocker image inspect auroralibros/aurora-api:1.1.0 --format \
'Entrypoint: {{json .Config.Entrypoint}}
Cmd: {{json .Config.Cmd}}
User: {{.Config.User}}
Env: {{index .Config.Env 3}} {{index .Config.Env 4}}
Health: {{json .Config.Healthcheck.Test}}
Revision: {{index .Config.Labels "org.opencontainers.image.revision"}}'Entrypoint: ["node"]
Cmd: ["server.js"]
User: node
Env: NODE_ENV=production PORT=3000
Health: ["CMD-SHELL","wget --quiet --tries=1 --spider http://localhost:3000/salud || exit 1"]
Revision: 7a3f912Absolutamente todo intacto: entrypoint, cmd, usuario, variables, healthcheck y hasta las etiquetas OCI con el hash del commit.
# 5. Comparación con export/import
docker run -d --name comparar auroralibros/aurora-api:1.1.0
docker export comparar | gzip -9 > aurora-plano.tar.gz
ls -lh aurora-plano.tar.gz
gunzip -c aurora-plano.tar.gz | docker import - aurora-plana:test
docker image history aurora-plana:test
docker run --rm aurora-plana:test-rw------- 1 joan joan 58M ago 4 12:48 aurora-plano.tar.gz
IMAGE CREATED CREATED BY SIZE COMMENT
9d2e4f8a1c73 3 seconds ago 158MB Imported from -
docker: Error response from daemon: no command specified.Tabla comparativa final:
| Aspecto | save/load |
export/import |
|---|---|---|
| Tamaño comprimido (solo aurora-api) | ~62 MB | ~58 MB |
| Capas conservadas | 7 | 1 |
| Historial | Completo | Ninguno |
ENTRYPOINT, CMD, USER, ENV |
Conservados | Perdidos |
HEALTHCHECK y etiquetas OCI |
Conservados | Perdidos |
docker run sin argumentos |
Arranca la API | no command specified |
| Varias imágenes por fichero | Sí | No |
Conclusión: para portar imágenes, save/load, sin discusión. El pequeño ahorro de tamaño de export no compensa perder toda la configuración de ejecución, el healthcheck y la trazabilidad que costó una lección entera construir. export/import es una herramienta para aplanar sistemas de archivos, no para mover imágenes.
Limpieza:
docker rm -f comparar
docker image rm aurora-plana:test
rm -f /tmp/aurora-pila.tar.gz /tmp/aurora-plano.tar.gzConclusión
Ya sabes administrar tu almacén de imágenes. Listas y filtras con docker image ls, sus --filter (incluido el filtrado por las etiquetas OCI que añadiste en 02-04) y sus plantillas Go. Extraes cualquier metadato concreto con docker image inspect --format, y tienes un reflejo nuevo que conviene conservar: auditar las variables de entorno antes de publicar cualquier imagen. Con docker image history reconstruyes cómo se hizo una imagen, capa a capa, y localizas en un solo comando la que se comió los megas —en aurora-api, el npm ci con 24,7 MB; en postgres, los 248 MB de su compilación—.
Sabes borrar entendiendo lo que pasa: Untagged quita un nombre y Deleted destruye datos, y solo aparece cuando cae la última referencia. Sabes por qué un contenedor parado bloquea el borrado de su imagen (conserva la capa de escritura apilada encima) y cuándo -f es aceptable y cuándo es una forma elegante de romper un servicio en el próximo reinicio. Entiendes de dónde salen las <none>:<none>: reconstruir con la misma etiqueta mueve el puntero y deja huérfana a la imagen anterior, que sigue en disco y hasta se puede rescatar reetiquetándola.
Dominas la escalera de limpieza, de menor a mayor riesgo —image prune, builder prune, system prune— con una advertencia grabada a fuego sobre el último escalón: docker system prune -a --volumes borra volúmenes sin contenedor asociado, y un volumen de base de datos entre recreaciones está exactamente en ese estado. Mides antes de tocar nada con docker system df y su versión -v, que revela lo que ningún listado de imágenes muestra: la caché de BuildKit ocupando gigabytes, las capas compartidas contadas una sola vez y las columnas SHARED/UNIQUE que dicen cuánto liberarías de verdad al borrar cada imagen. Y sabes llevarte una imagen a una máquina aislada con docker save/load, conservando entrypoint, usuario, healthcheck y etiquetas OCI, frente a export/import, que aplana el sistema de archivos y lo pierde todo.
Tu imagen está construida, es profesional y sabes mantenerla. Le falta lo único que justificaba todo el módulo: salir de tu máquina. En la última lección, Etiquetado y Publicación de Imágenes, cerrarás el ciclo. Verás que docker image tag no copia nada sino que crea referencias, decidirás una estrategia de etiquetado seria —versionado semántico con etiquetas móviles y fijas, por SHA de commit, por rama, por entorno— y publicarás auroralibros/aurora-api en Docker Hub y en GitHub Container Registry, leyendo la salida del push, verificando el resultado con docker manifest inspect y entendiendo por qué en producción se despliega por digest y jamás por latest.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
