Docker “volume is in use”: eliminar o reemplazar sin pérdida de datos
Ese momento: ejecutas docker volume rm, esperando una limpieza rápida, y Docker responde con un pequeño error: volume is in use. Producción espera. Tu sistema de alertas se calienta. Y el nombre del volumen que quieres borrar es el que tu equipo de aplicaciones prometió “ya no se usa”.
El truco es que Docker suele decir la verdad—pero no toda la verdad. Los volúmenes pueden estar retenidos por contenedores detenidos, proyectos de Compose huérfanos, tareas de Swarm, drivers de plugin, o una definición antigua que olvidaste que existía. Esta guía explica cómo eliminar o reemplazar un volumen de Docker sin perder datos y sin recurrir al clásico “rm -rf y rezar”.
Qué significa Docker con “volume is in use”
El mensaje “volume is in use” de Docker no es poético. Es una negativa específica: Docker no eliminará un volumen si cualquier contenedor—en ejecución o detenido—aún lo referencia en su configuración de contenedor. Docker rastrea esto a nivel de metadatos (configuración del contenedor + referencias de montaje), no comprobando si algún proceso tiene archivos abiertos actualmente.
Esa distinción importa. Un volumen puede estar “en uso” incluso cuando:
- El contenedor está exited pero aún existe.
- El contenedor fue creado por Compose hace meses y nadie lo limpió.
- Una tarea de servicio de Swarm aún está registrada, incluso si se está reiniciando en otro nodo.
- Un contenedor fue reemplazado, pero el antiguo sigue ahí como un fantasma con un nombre significativo como
myapp_web_1.
Además: Docker hace una separación clara entre volúmenes (gestionados por Docker, usualmente bajo /var/lib/docker/volumes) y bind mounts (tu ruta en el host). Los bind mounts no tienen el mensaje “volume is in use” de la misma manera; en su lugar destruirás tus propios datos como adulto responsable.
Otra sutileza: el error suele activarse por la negativa de docker volume rm a proceder. Si intentas forzar la limpieza con comandos del sistema, puedes borrar más de lo que pretendías. El objetivo aquí es una eliminación o reemplazo dirigido, con un plan de migración de datos que puedas explicar a un auditor sin sudar.
Guion de diagnóstico rápido
Si solo tienes cinco minutos antes de que alguien pregunte “¿podemos reiniciar el host?”, haz esto. En orden. No improvises.
Primero: identifica el volumen y su driver
- Inspecciona el volumen (nombre, driver, etiquetas, punto de montaje).
- Si el driver no es
local, detente y lee dos veces: los drivers externos tienen semánticas diferentes y pueden implicar almacenamiento remoto.
Segundo: encuentra todos los contenedores que lo referencian (en ejecución o detenidos)
- Lista los contenedores unidos al volumen mediante un filtro.
- Inspecciona los contenedores sospechosos y confirma que la lista de mounts incluya tu volumen.
Tercero: decide entre tres acciones
- Desbloquear la eliminación: elimina el/los contenedor(es) que lo referencian, luego elimina el volumen.
- Reemplazar sin pérdida de datos: copia los datos a un volumen nuevo, luego apunta los contenedores/Compose al nuevo.
- Mantener pero desacoplar: detén los contenedores, actualiza la configuración para dejar de usar el volumen y consérvalo como copia de seguridad.
Cuarto: verifica la seguridad de los datos
- Haz una copia tipo snapshot (tar) del contenido del volumen.
- Realiza una comprobación de integridad económica: los archivos esperados existen, los tamaños parecen razonables y la app puede leerlos tras el remount.
Nada de esto es glamuroso. Ese es el punto. Las soluciones “glamurosas” son las de las que luego te arrepientes.
Algunos hechos y contexto histórico (porque cambia cómo depuras)
- Los volúmenes de Docker existieron temprano, pero maduraron después. Los primeros usuarios de Docker recurrían mucho a bind mounts; los volúmenes con nombre se convirtieron en el “lugar seguro” por defecto a medida que maduraron las prácticas operativas.
- La comprobación “in use” de Docker está basada en metadatos. Docker no eliminará un volumen referenciado por ninguna definición de contenedor, incluso si el contenedor está detenido y nada tiene los archivos abiertos.
- Las etiquetas de Compose cambiaron cómo se “abandonan” los volúmenes. Docker Compose adjunta etiquetas como project y service; puedes usar esas etiquetas para rastrear de dónde vino un volumen, pero también para descubrir stacks abandonados.
- Los sistemas de archivos overlay no son tu volumen. La capa escribible de un contenedor (overlay2) es diferente de un volumen. Eliminar un contenedor borra la capa escribible, pero no los volúmenes con nombre.
- Swarm introdujo programación distribuida, no almacenamiento distribuido. La gente los confunde. Swarm puede reprogramar tareas; tu volumen local no puede teletransportarse a otro nodo.
- Los drivers de volumen importan.
localse comporta como directorios locales. Los drivers de plugin (NFS, volúmenes cloud, integraciones tipo CSI) pueden tener sus propias reglas de attach/detach y modos de fallo. - Los “volúmenes anónimos” son una mina de errores conocida. Se crean automáticamente (especialmente en patrones antiguos de Compose) y pueden acumularse. Son más difíciles de rastrear porque el nombre parece un hash.
- Los comandos prune se hicieron populares después de incidentes por disco. La presión de disco empujó a los equipos hacia
docker system prune. Ayuda—hasta que elimina algo que pensabas que era “temporal”.
Reglas de oro: cómo no perder datos
- Asume que el volumen contiene datos que te importan hasta que se demuestre lo contrario. “Es solo caché” ha destruido más bases de datos que el malware.
- Nunca borres un volumen hasta que puedas nombrar qué lo usa. Si no puedes, no lo borres; estás apostando.
- Haz una copia de seguridad antes de migrar. “Pero lo estamos copiando” no es una copia de seguridad. Las operaciones de copia fallan de formas creativas: permisos, archivos especiales, symlinks rotos, transferencias parciales y truncamiento silencioso si el sistema de archivos está lleno.
- Prefiere crear un volumen nuevo y migrar los datos hacia él. Reemplazar en el lugar es tentador pero te deja sin historia de rollback.
- Sé preciso con los comandos de limpieza. Si vas a usar
docker system prune --volumesen producción, al menos redacta primero el esquema del postmortem. Te ahorrará tiempo luego.
Broma #1: Si alguna vez te sientes inútil, recuerda que hay un “botón de forzar borrado” en un entorno de producción. Está ahí principalmente para probar tu estrategia de backups.
Una cita, porque la operación es una disciplina, no una vibra. Tom Limoncelli tiene una idea parafraseada repetida en círculos SRE: si no está documentado y practicado, no es un procedimiento; es un deseo.
— Tom Limoncelli (idea parafraseada).
Tareas prácticas: comandos, salidas y decisiones
Estos son los movimientos prácticos. Cada tarea te da un comando, un tipo de salida realista y la decisión que tomas. Ejecútalos en orden cuando estés bajo presión; selecciona lo que necesites cuando hagas mantenimiento planificado.
Tarea 1: Confirma el error y el nombre exacto del volumen
cr0x@server:~$ docker volume rm pgdata
Error response from daemon: remove pgdata: volume is in use - [2d8c1f8b6a1d5e2f6f4e62b2d64a0d1b8a5b0a7c7d0f1a2b3c4d5e6f7a8b9c0d]
Qué significa: Docker encontró al menos un contenedor que referencia el volumen. El largo hex es un ID de contenedor.
Decisión: No vuelvas a intentarlo. Encuentra el/los contenedor(es) que lo referencian y decide si eliminarlos o migrar los datos.
Tarea 2: Inspecciona el volumen (driver, etiquetas, punto de montaje)
cr0x@server:~$ docker volume inspect pgdata
[
{
"CreatedAt": "2025-11-20T09:12:13Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "billing",
"com.docker.compose.volume": "pgdata"
},
"Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
"Name": "pgdata",
"Options": null,
"Scope": "local"
}
]
Qué significa: Es un volumen local con nombre creado por el proyecto Compose billing. Esta es tu primera pista.
Decisión: Si Driver no es local, párate e identifica el backend de almacenamiento y las semánticas de detach. Si es local, continúa con el descubrimiento de contenedores y las opciones de migración.
Tarea 3: Encuentra contenedores que referencian el volumen (filtro rápido)
cr0x@server:~$ docker ps -a --filter volume=pgdata --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID NAMES STATUS
2d8c1f8b6a1d billing-postgres-1 Up 3 days
c91a44dd210e billing-postgres-old Exited (0) 2 weeks ago
Qué significa: Dos contenedores lo referencian. Uno está en ejecución y otro está exited pero aún bloquea la eliminación.
Decisión: Si quieres eliminar el volumen, debes eliminar ambos contenedores (o al menos quitar su referencia eliminando los contenedores). Si quieres reemplazar el volumen, migrarás y luego recrearás contenedores con el nuevo volumen.
Tarea 4: Confirma el uso del mount dentro de la configuración del contenedor
cr0x@server:~$ docker inspect 2d8c1f8b6a1d --format '{{json .Mounts}}'
[{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Qué significa: El contenedor está definitivamente usando pgdata y montándolo en el directorio de datos de PostgreSQL.
Decisión: Trátalo como almacenamiento de base de datos. Planea una parada controlada, un backup consistente y la migración. Nada de copias improvisadas mientras la base de datos escribe.
Tarea 5: Identifica el proyecto Compose a partir de las etiquetas (quién es el propietario)
cr0x@server:~$ docker inspect 2d8c1f8b6a1d --format '{{json .Config.Labels}}'
{"com.docker.compose.project":"billing","com.docker.compose.service":"postgres","com.docker.compose.version":"2.24.6"}
Qué significa: Esto está gestionado por Compose, no es un docker run manual.
Decisión: Prefiere los cambios a través de Compose (o al menos mantén el estado de Compose consistente), de lo contrario el próximo docker compose up puede recrear cosas antiguas y sorprenderte.
Tarea 6: Mira qué hay dentro del volumen (chequeo rápido)
cr0x@server:~$ sudo ls -lah /var/lib/docker/volumes/pgdata/_data | head
total 132K
drwx------ 19 999 999 4.0K Jan 3 10:11 .
drwx-----x 3 root root 4.0K Nov 20 09:12 ..
-rw------- 1 999 999 3 Jan 3 10:11 PG_VERSION
drwx------ 6 999 999 4.0K Jan 3 10:11 base
drwx------ 2 999 999 4.0K Jan 3 10:11 global
drwx------ 2 999 999 4.0K Jan 3 10:11 pg_wal
Qué significa: Es un directorio real de datos de Postgres. Los permisos coinciden con el uso típico del UID del contenedor. Eliminar esto sería actualizar tu currículum.
Decisión: Si debes reemplazar el volumen, hazlo con un volcado/restore controlado de la base de datos o con una copia a nivel de sistema de archivos estando detenido, dependiendo del RPO/RTO y del tipo de BD.
Tarea 7: Detén los contenedores adecuados de forma limpia
cr0x@server:~$ docker stop billing-postgres-1
billing-postgres-1
Qué significa: Postgres se está deteniendo. Si tu contenedor tiene una señal de stop apropiada, debería hacer checkpoint y salir limpiamente.
Decisión: Si el stop se queda colgado, no recurras a docker kill inmediatamente. Primero revisa logs y health. Los kills forzados aumentan la probabilidad de copiar datos inconsistentes.
Tarea 8: Verifica que ningún contenedor siga referenciando el volumen (incluyendo los exited)
cr0x@server:~$ docker ps -a --filter volume=pgdata --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID NAMES STATUS
2d8c1f8b6a1d billing-postgres-1 Exited (0) 3 seconds ago
c91a44dd210e billing-postgres-old Exited (0) 2 weeks ago
Qué significa: Ambos contenedores aún existen y siguen referenciando el volumen.
Decisión: Si intentas eliminar el volumen, debes eliminar estos contenedores. Si estás migrando, consérvalos hasta completar el backup y luego elimínalos o recréalos con el nuevo volumen.
Tarea 9: Crea un tar de respaldo del volumen (seguro barato)
cr0x@server:~$ docker run --rm -v pgdata:/data -v $PWD:/backup alpine sh -c 'cd /data && tar -cpf /backup/pgdata-backup.tar .'
Qué significa: Usaste un contenedor temporal para leer el volumen y escribir un tar en tu directorio actual del host.
Decisión: Valida que el tar exista y tenga un tamaño plausible. Si tienes limitación de espacio, escríbelo en un filesystem de backup montado, no en tu disco raíz.
Tarea 10: Verifica tamaño del backup y contenido básico
cr0x@server:~$ ls -lh pgdata-backup.tar
-rw-r--r-- 1 cr0x cr0x 1.3G Jan 3 10:19 pgdata-backup.tar
cr0x@server:~$ tar -tf pgdata-backup.tar | head
./
./PG_VERSION
./base/
./global/
./pg_wal/
Qué significa: El tar existe y contiene la estructura esperada.
Decisión: Ahora tienes un artefacto de rollback. Todavía no es un backup perfecto de base de datos, pero suficiente para recuperar archivos si cometes un error durante el reemplazo del volumen.
Tarea 11: Crea el volumen nuevo de reemplazo
cr0x@server:~$ docker volume create pgdata_v2
pgdata_v2
Qué significa: Nuevo volumen con nombre creado y vacío.
Decisión: Usa un nombre claramente versionado. Evita llamarlo “new” a menos que disfrutes de la arqueología después.
Tarea 12: Copia datos del volumen antiguo al nuevo (copia offline)
cr0x@server:~$ docker run --rm -v pgdata:/from -v pgdata_v2:/to alpine sh -c 'cd /from && cp -a . /to'
Qué significa: Copia a nivel de archivos usando cp -a para preservar permisos y marcas de tiempo.
Decisión: Haz esto solo para datos que sea seguro copiar estando offline (contenedor detenido). Para bases de datos, la aproximación preferida es dump/restore cuando necesitas garantías de consistencia; la copia de archivos funciona si la BD está completamente detenida y no está recuperándose de un crash durante la copia.
Tarea 13: Compara rápidamente la forma de los directorios (detectar fallos de copia obvios)
cr0x@server:~$ docker run --rm -v pgdata:/from -v pgdata_v2:/to alpine sh -c 'cd /from && find . -maxdepth 2 -type d | sort | tail -n 5 && echo "---" && cd /to && find . -maxdepth 2 -type d | sort | tail -n 5'
./pg_commit_ts
./pg_dynshmem
./pg_logical
./pg_notify
./pg_stat_tmp
---
./pg_commit_ts
./pg_dynshmem
./pg_logical
./pg_notify
./pg_stat_tmp
Qué significa: La estructura de directorios coincide a primera vista.
Decisión: Procede, pero no confundas “parece similar” con “idéntico bit a bit”. Para sistemas críticos, realiza comprobaciones a nivel de aplicación tras el reinicio.
Tarea 14: Elimina los contenedores antiguos que mantenían el volumen “en uso”
cr0x@server:~$ docker rm billing-postgres-old
billing-postgres-old
cr0x@server:~$ docker rm billing-postgres-1
billing-postgres-1
Qué significa: Los contenedores son eliminados; sus referencias al volumen ya no existen.
Decisión: Ahora puedes eliminar pgdata si realmente quieres, o mantenerlo hasta que el nuevo setup demuestre ser estable.
Tarea 15: Recrea el contenedor (o el stack de Compose) apuntando al volumen nuevo
cr0x@server:~$ docker run -d --name billing-postgres-1 \
-e POSTGRES_PASSWORD=redacted \
-v pgdata_v2:/var/lib/postgresql/data \
postgres:16
8b2fa1d22b2c4b8f8f0e6e7f3a2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d
Qué significa: Nuevo contenedor en ejecución con el volumen de reemplazo.
Decisión: Valida la salud de la aplicación. Si esto está gestionado por Compose, refleja el cambio en el compose.yaml y ejecuta via Compose en lugar de un docker run ad-hoc a largo plazo.
Tarea 16: Confirma que el nuevo contenedor usa el volumen nuevo
cr0x@server:~$ docker inspect billing-postgres-1 --format '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{"\n"}}{{end}}'
pgdata_v2 -> /var/lib/postgresql/data
Qué significa: El mount del contenedor nuevo es correcto.
Decisión: Solo ahora considera eliminar el volumen antiguo.
Tarea 17: Elimina el volumen antiguo (opcional, tras la validación)
cr0x@server:~$ docker volume rm pgdata
pgdata
Qué significa: Docker permitió la eliminación porque ningún contenedor referencia el volumen.
Decisión: Si quieres una red de seguridad adicional, no lo borres todavía—renómbralo o consérvalo unos días, según tu política de cambios y presupuesto de disco.
Tarea 18: Busca volúmenes anónimos/huérfanos y entiende lo que vas a podar
cr0x@server:~$ docker volume ls --format 'table {{.Name}}\t{{.Driver}}'
NAME DRIVER
billing_cache local
pgdata_v2 local
b3f3b9d9d79e0a9dfb2a2d8c6b77d8b8c0a... local
Qué significa: Ese nombre largo parecido a un hash suele ser un volumen anónimo. A veces es inofensivo. A veces es la única copia del directorio de uploads de alguien porque era “temporal”.
Decisión: Inspecciona antes de podar. No te pagan por ser valiente; te pagan por tener razón.
Reemplazar un volumen de forma segura (tres patrones)
Patrón A: copia “lift-and-shift” de archivos (bueno para datos estáticos, con cuidado en BDs)
Esto es lo que hicimos arriba con cp -a vía un contenedor Alpine temporal. Es rápido, simple y correcto cuando la aplicación está totalmente parada y el formato de datos es consistente offline.
Funciona bien para:
- Directorios de contenido web
- Paquetes de configuración estática
- Caches de artefactos que puedes reconstruir (aun así, no borres a ciegas)
- Bases de datos solo cuando están completamente detenidas y aceptas la semántica de recuperación tras crash
Evitar para:
- Bases de datos en vivo (copiar mientras están activas) a menos que realmente disfrutes de segmentos WAL corruptos
- Apps que mantienen múltiples archivos con requisitos de consistencia cruzada mientras están en ejecución
Patrón B: migración a nivel de aplicación (dump/restore) para consistencia fuerte
Si necesitas corrección más que velocidad, haz la exportación/importación a nivel de aplicación. Para PostgreSQL suele ser pg_dump / pg_restore o herramientas de backup físico. Para MySQL, mysqldump o herramientas de backup físico. Para Elasticsearch, snapshot/restore. El punto es: migras los datos de forma soportada, no copiando los archivos internos del motor en caliente.
Operativamente, este patrón es más lento pero predecible. También es la única migración que te da una historia que puedes defender en una revisión de incidentes: “Usamos el método de backup soportado”.
Patrón C: conserva el volumen, reemplaza los contenedores (cuando el volumen no es el problema)
A veces no necesitas reemplazar el volumen en absoluto. Solo necesitas eliminar contenedores que lo referencian para poder quitar otro volumen, o necesitas reconstruir un contenedor de la app manteniendo los datos intactos.
En ese caso:
- Detén el contenedor.
- Elimina el contenedor (no el volumen).
- Recrea el contenedor apuntando al mismo volumen con nombre.
Este es el movimiento más seguro cuando los datos son valiosos y tu único problema es un cambio en la configuración del contenedor.
Compose y Swarm: maneras especiales de ser engañado
Compose: los volúmenes persisten incluso cuando crees que “bajaste el stack”
docker compose down elimina contenedores y redes por defecto, pero no elimina volúmenes con nombre a menos que añadas --volumes. Eso suele ser bueno. También significa que el volumen permanece y puede acumular deriva: upgrades de esquema, migraciones antiguas, archivos que nadie recuerda.
Compose también te ayuda a identificar propietarios mediante etiquetas. Úsalas. Están ahí por una razón.
Swarm: tareas y programación amplifican la confusión
En Swarm, una tarea de servicio puede estar “apagada” pero aún existir en el historial. Mientras tanto tu volumen con nombre existe en un solo nodo. Si piensas “Swarm lo moverá”, estás confundiendo orquestación con almacenamiento.
Swarm + volúmenes locales está bien si fijas servicios a nodos y entiendes el failover. Si quieres movilidad de almacenamiento, necesitas un driver/respaldo que lo soporte (y debes probarlo bajo fallo, no en una diapositiva).
Broma #2: Swarm reubicará gustosamente tu contenedor a un nodo que no tiene los datos. No es malicioso—solo optimista.
Errores comunes (síntoma → causa → solución)
1) “docker volume rm dice in use, pero docker ps no muestra nada en ejecución”
Síntoma: No hay contenedores en ejecución, el volumen aún está “en uso”.
Causa: Contenedores detenidos aún existen y siguen referenciando el volumen.
Solución: Lista todos los contenedores, incluidos los exited, filtrando por volumen. Elimina los contenedores (o recréalos con otro volumen).
cr0x@server:~$ docker ps -a --filter volume=pgdata
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c91a44dd210e postgres:16 "docker-entrypoint.s…" 2 weeks ago Exited (0) 2 weeks ago billing-postgres-old
2) “Borré el contenedor y Docker aún no elimina el volumen”
Síntoma: Eliminaste un contenedor y aún está bloqueado.
Causa: Más de un contenedor referencia el volumen (a menudo despliegues antiguos/renombrados/fallidos).
Solución: Usa el filtro por volumen y elimina todos los contenedores que lo referencian.
3) “Reemplazamos el volumen y la app arrancó… con un dataset vacío”
Síntoma: La app arranca fresca, falta estado.
Causa: Se montó un volumen nuevo, pero los datos nunca se migraron (o se migraron a la ruta/volumen equivocado).
Solución: Confirma nombres de mounts y destinos con docker inspect. Copia datos offline o restaura desde el tar de backup. Luego redepliega.
4) “La copia tuvo éxito, pero los permisos están rotos”
Síntoma: La app falla con errores de permiso after la migración.
Causa: El método de copia no preservó propiedad/mode bits (p. ej., usar cp sin -a, o extraer un tar como root con flags equivocados, o copiar a través de un host Windows).
Solución: Usa cp -a o tar preservando propiedad. Valida las expectativas de UID/GID dentro del contenedor.
5) “docker system prune –volumes eliminó algo crítico”
Síntoma: Pérdida de datos. Reinicilización repentina de la app. Gente enfadada.
Causa: Prune eliminó volúmenes no referenciados por contenedores, incluso si contenían estado valioso o se guardaban como backup intencionalmente.
Solución: Deja de usar prune como bisturí. Antes de podar, inventaría los volúmenes y etiqueta/marca cualquier cosa valiosa. Mantén backups explícitos fuera de la gestión de volúmenes de Docker.
6) “El volumen es local, pero el disco del host está lleno”
Síntoma: Escrituras fallan, contenedores crashloopean, migraciones fallan a mitad de transferencia.
Causa: Los volúmenes de Docker comparten el filesystem del host. Si root se llena, todo se vuelve extraño.
Solución: Revisa el uso de disco antes y después de la migración. Mueve Docker data root o el almacenamiento, o amplía la capacidad. No intentes crear un tar enorme en un disco raíz casi lleno.
Listas de verificación / plan paso a paso
Checklist A: Eliminar un volumen que realmente no necesitas (de forma segura)
- Inspecciona el volumen; confirma que el driver es el esperado.
- Encuentra todos los contenedores que lo referencian (en ejecución y detenidos).
- Confirma que el contenido no es crítico o que ya está respaldado.
- Elimina los contenedores que lo referencian.
- Elimina el volumen.
- Verifica que la aplicación sigue funcionando (porque a veces “no usado” significa “no usado hoy”).
Checklist B: Reemplazar un volumen sin pérdida de datos
- Inspecciona el volumen e identifica el stack propietario (las etiquetas de Compose ayudan).
- Programa una ventana de mantenimiento si la carga es con estado.
- Detén la app/contenedor limpiamente.
- Haz un tar backup del volumen antiguo (guárdalo en un lugar seguro).
- Crea un volumen nuevo con nombre versionado.
- Migra los datos (copia offline o restauración a nivel de app).
- Recrea contenedores/stack apuntando al volumen nuevo.
- Ejecuta validación: logs, health checks, consultas de sanidad de datos.
- Conserva el volumen antiguo por un periodo de gracia y luego elimínalo.
Checklist C: “Lo necesito fuera ya” modo emergencia
- Detén el contenedor que usa el volumen.
- Exporta un tar backup inmediatamente.
- Elimina los contenedores que lo referencian.
- Solo entonces elimina el volumen.
- Documenta lo que hiciste mientras aún lo recuerdas.
Tres micro-historias corporativas desde el terreno
Incidente causado por una suposición equivocada: “Exited significa no usado”
Una compañía SaaS mediana tenía un stack de billing gestionado por Compose. El equipo quiso limpiar espacio en disco en un host Docker ocupado. Alguien hizo una revisión rápida: docker ps parecía limpio. No vieron contenedores usando un nombre de volumen antiguo, así que intentaron eliminarlo. Docker dijo “volume is in use.” Molesto, pero al menos les bloqueó.
La suposición equivocada vino después: decidieron que Docker estaba confundido e intentaron una limpieza más amplia. Eliminaron contenedores que reconocían y luego ejecutaron comandos prune para “acabar con ello”. El volumen desapareció—no el que buscaban, sino otro “no usado” que contenía el estado de un sidecar pequeño que resultó ser la única copia de una caché de conciliación de pagos.
La caída no fue dramática; fue peor. Los pagos no fallaron de inmediato. Fueron derivando. Los trabajos de conciliación empezaron a producir informes desajustados, lo que provocó una revisión de cumplimiento. La corrección técnica llevó un día; reparar la confianza llevó semanas.
El postmortem concluyó de forma dolorosamente aburrida: un contenedor detenido aún puede referenciar un volumen, y “no usado por contenedores en ejecución” no es lo mismo que “seguro para borrar”. Añadieron una regla: cualquier eliminación de volumen requiere listar contenedores que lo referencian con docker ps -a --filter volume=... y hacer primero un tar backup. A nadie le gustaron los pasos extra. A todos les gustó no repetir el incidente.
Optimización que salió mal: “Purguemos volúmenes cada noche”
Otra organización intentó ser proactiva con la presión de disco. Añadieron un job nocturno que ejecutaba docker system prune -af --volumes. Funcionó durante semanas. Los gráficos de disco fueron más planos. El equipo se sentía responsable.
Luego un ingeniero desplegó una nueva versión de un servicio que hacía un swap blue/green de contenedores. El contenedor viejo fue removido como se esperaba y el nuevo arrancó. Pero el volumen que guardaba archivos subidos por usuarios fue temporalmente desacoplado durante la coreografía del despliegue—hubo una ventana corta donde ningún contenedor referenciaba el volumen.
El prune nocturno llegó en esa ventana y borró el volumen de uploads. A la mañana siguiente, el servicio estaba “saludable” pero servía silenciosamente imágenes faltantes y reconstruía índices sobre un directorio vacío. La solución no fue “restaurar desde backup”, porque no lo había. La solución fue “decir a los clientes que sus datos se perdieron”, que es una gran manera de conocer el tono de voz del asesor legal en una llamada.
La lección: prune no es una optimización; es una decisión de política. Si quieres limpieza automatizada, etiqueta volúmenes y poda por criterios explícitos, no por ausencia de referencias actuales. Además: si almacenas datos de usuarios en un volumen Docker, trátalo como una base de datos—backups, retención y ejercicios de restauración.
Práctica aburrida pero correcta que salvó el día: “Volúmenes versionados + ventana de rollback”
Una compañía relacionada con finanzas ejecutaba Postgres en Docker en unos pocos hosts dedicados. No era lo último en moda, pero era estable. Hacían upgrades rutinarios creando un volumen nuevo por cambio: pgdata_2025q4, pgdata_2025q4_patch1, ese tipo de cosas. Cada cambio incluía un tarball de backup almacenado fuera del host y un script de validación breve que ejecutaba comprobaciones SQL básicas.
Durante una actualización, el contenedor arrancó, pero las consultas se volvieron más lentas. No catastróficamente, solo lo suficiente para disparar alarmas de latencia. El equipo sospechó un regresión de almacenamiento subyacente, tal vez opciones de montaje o un problema sutil del kernel. No debatieron horas. Hicieron rollback.
El rollback fue simple: detener contenedor, apuntar al volumen antiguo, reiniciar. El volumen antiguo aún existía porque su proceso siempre conservaba el volumen previo por un periodo de gracia definido. El incidente visible al cliente fue mínimo.
Después, depuraron con calma y encontraron que un cambio “inocuo” en el paso de migración había alterado permisos en un subdirectorio, forzando a Postgres a un comportamiento menos óptimo. Arreglaron la migración, ejecutaron la validación de nuevo y siguieron adelante. La práctica aburrida—volúmenes versionados y ventana de rollback—hizo exactamente lo que debía: hizo el peor día menos emocionante.
Preguntas frecuentes
1) ¿Por qué Docker dice que un volumen está en uso cuando el contenedor está detenido?
Porque la comprobación de Docker se basa en referencias de configuración de contenedor, no en procesos en ejecución. Un contenedor detenido aún “usa” el volumen en los metadatos de Docker hasta que el contenedor sea eliminado.
2) ¿Cómo encuentro el contenedor que está usando mi volumen?
Usa el filtro de volumen en docker ps -a:
cr0x@server:~$ docker ps -a --filter volume=pgdata
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2d8c1f8b6a1d postgres:16 "docker-entrypoint.s…" 3 days ago Up 3 days billing-postgres-1
3) ¿Puedo “desacoplar” un volumen de un contenedor sin eliminar el contenedor?
No realmente. Docker no soporta editar la configuración de mounts de un contenedor en sitio. Recreas el contenedor con los mounts deseados.
4) ¿Es seguro copiar un volumen de base de datos con cp -a?
Sólo si la base de datos está totalmente detenida y entiendes las expectativas de consistencia a nivel de archivos del motor. Para garantías fuertes, usa herramientas nativas de backup y restore de la base de datos.
5) ¿Cuál es la forma más segura de reemplazar un volumen en Docker Compose?
Crea un volumen nuevo con nombre, migra datos hacia él, actualiza el archivo Compose para referenciar el nuevo nombre de volumen y luego docker compose up -d. Conserva el volumen antiguo para rollback hasta que estés satisfecho.
6) ¿Por qué tengo volúmenes con nombres largos que parecen aleatorios?
Esos suelen ser volúmenes anónimos creados implícitamente (por ejemplo, cuando una imagen declara un volumen y Compose/run no lo nombró explícitamente). Son fáciles de acumular y difíciles de atribuir. Inspecciónalos antes de eliminarlos.
7) ¿Qué significa realmente “Driver: local”?
Significa que Docker almacena los datos en el filesystem del host bajo Docker data root. No está replicado, no se comparte mágicamente entre nodos y no está protegido de fallos de disco del host a menos que añadas protección externa.
8) ¿Puedo renombrar un volumen de Docker?
Docker no tiene una operación de renombrado para volúmenes. El enfoque estándar es: crea un volumen nuevo con el nombre deseado, copia datos y luego actualiza contenedores/Compose para usarlo.
9) ¿Es seguro docker volume prune?
Elimina volúmenes no referenciados por ningún contenedor. Aun así puede ser peligroso si mantienes volúmenes de backup deliberadamente desacoplados o si tu despliegue desacopla temporalmente volúmenes. Úsalo sólo con política y consciencia.
Conclusión: próximos pasos prácticos
Cuando Docker dice “volume is in use”, normalmente te está protegiendo de ti mismo—al imponer que un volumen referenciado no puede ser borrado de debajo de una definición de contenedor. Tu trabajo es identificar la cadena de referencias, decidir si vas a eliminar o reemplazar, y hacer explícita la seguridad de los datos.
Próximos pasos que puedes tomar hoy:
- Elige un host e inventaría los volúmenes con etiquetas y propietarios. Los volúmenes desconocidos son incidentes futuros.
- Adopta un patrón estándar de reemplazo: volumen versionado nuevo + migración + validación + ventana de rollback.
- Escribe un runbook de una página que incluya: inspección de volumen, descubrimiento de contenedores, tar backup y el paso de aprobación para borrar.
- Deja de tratar prune como mantenimiento rutinario en producción a menos que puedas probar que no borrará datos “actualmente desacoplados” valiosos.
El objetivo no es nunca borrar volúmenes. El objetivo es borrarlos a propósito.