Instalar Docker y Docker Compose en Debian y Ubuntu

Publicado el 17 min de lectura

Por qué docker.io es demasiado antiguo en Debian 12 pero sirve en Ubuntu, cómo se añade el repositorio oficial con keyring en lugar de apt-key, por qué Compose es un plugin y por qué el grupo docker significa root en la práctica.

Instalar Docker lleva cinco minutos. Instalarlo de forma que un año después el servidor siga recibiendo actualizaciones de seguridad, el disco del sistema no se llene y ningún usuario acabe por descuido con permisos de root, lleva algo más de tiempo. Este artículo trata la segunda variante, probada en Debian 13 (Trixie), Debian 12 (Bookworm), Ubuntu 24.04 (Noble) y Ubuntu 22.04 (Jammy).

docker.io o Docker CE: la diferencia que casi nadie explica con honestidad

Hay dos caminos para llegar a Docker. El paquete docker.io procede de los repositorios de la distribución y lo compilan y mantienen Debian o Ubuntu. El paquete docker-ce procede del repositorio del propio Docker. Ambos contienen el mismo software, pero con edades muy distintas.

Estas son las versiones que hay ahora mismo en los repositorios de las distribuciones (medidas con apt-cache policy en contenedores recién creados):

Sistemadocker.io en los repositorios de la distribución
Debian 13 (Trixie)26.1.5
Debian 12 (Bookworm)20.10.24
Ubuntu 24.04 (Noble)29.1.3
Ubuntu 22.04 (Jammy)29.1.3

Ese es el verdadero quid de la cuestión, y casi todas las guías lo reducen a una recomendación general. La realidad es más matizada:

  • Ubuntu 24.04 y 22.04: docker.io está en 29.1.3, es decir, prácticamente al nivel de la versión upstream actual. Si no tienes requisitos especiales, aquí puedes tomar el paquete de la distribución sin mala conciencia. Las actualizaciones de seguridad llegan entonces por el canal normal de Ubuntu.
  • Debian 13: 26.1.5 es utilizable, aunque queda bastante por detrás de upstream. Para la mayoría de los casos de uso resulta suficiente.
  • Debian 12: 20.10.24 es el caso problemático. Esa rama lleva años al final de su ciclo de vida en upstream. Debian sí sigue aplicando parches de seguridad, pero faltan sin más muchas funciones modernas, entre ellas una versión actual de BuildKit y buena parte de la compatibilidad con Compose.

A esto se suma una diferencia práctica: docker-ce trae Buildx y Compose como paquetes de plugin propios, ajustados a la versión del motor. Con docker.io tienes que reunir esas piezas por separado desde docker-buildx y docker-compose-v2, y docker-compose-v2 existe únicamente en los repositorios de Ubuntu: en Debian ese paquete no está disponible en absoluto.

Lo que no funciona es tener las dos cosas en paralelo. El paquete containerd.io del repositorio de Docker entra en conflicto con el paquete containerd de la distribución. Tienes que decidirte por uno.

Regla práctica: en Ubuntu, docker.io es una elección legítima. En Debian 12 no lo es. Para servidores que ejecutan stacks de Compose con sintaxis actual, Docker CE es la mejor decisión en todos los casos.

Eliminar los paquetes antiguos antes de hacer cualquier otra cosa

Si ya hay algún Docker presente de una forma u otra, tiene que desaparecer; en caso contrario la instalación fracasa por conflictos de paquetes. El siguiente bucle elimina a todos los sospechosos habituales y, de paso, tolera los paquetes que no están instalados o que ni siquiera existen en tu distribución:

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove -y $pkg || true; done

El || true dentro del bucle no es un adorno, sino algo necesario. En Debian, apt-get aborta al llegar a la entrada docker-compose-v2 con E: Unable to locate package docker-compose-v2 y código de salida 100, porque ese paquete existe únicamente en los repositorios de Ubuntu y no en bookworm ni en trixie (tampoco en bullseye, donde además falta podman-docker). El bucle sigue adelante, pero deja un código de salida distinto de 0, y justo ahí muere un script con set -e o una cadena de comandos unida con &&. Los mensajes del tipo "Unable to locate package" son normales en este punto y se pueden ignorar; así lo indica también la documentación oficial de Docker.

Conviene saberlo: con esto no se pierde nada. Tus imágenes, contenedores y volúmenes están en /var/lib/docker, y ese directorio no lo toca apt-get remove. Después de instalar Docker CE, tus contenedores vuelven a estar ahí. Solo sudo rm -rf /var/lib/docker borra de verdad, y eso es irreversible.

Guardar la clave donde toca: apt-key es historia

Muchas guías que circulan por la red siguen incluyendo esta línea:

curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Eso ya no tiene sentido en ninguno de los cuatro sistemas tratados aquí. apt-key está obsoleto y en Debian 13 ni siquiera existe. El motivo no es cosmético: una clave en /etc/apt/trusted.gpg firma todos los repositorios, no solo aquel para el que estaba pensada. Un servidor espejo comprometido podría colarte por esa vía cualquier paquete.

Lo correcto es un llavero propio en /etc/apt/keyrings/, asignado con Signed-By a una única fuente.

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings

El siguiente comando descarga la clave correspondiente y funciona tanto en Debian como en Ubuntu, porque lee el identificador de la distribución desde /etc/os-release:

sudo curl -fsSL "https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Y ahora el paso que prácticamente nadie describe: comprueba la huella digital antes de confiarle tu sistema a esa clave.

gpg --show-keys /etc/apt/keyrings/docker.asc

La salida debe contener la huella 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 y el identificador Docker Release (CE deb) <docker@docker.com>. Si no coincide, detente ahí. En ese caso hay algo que falla en tu conexión o en la fuente.

Instalar Docker CE con un solo bloque para los cuatro sistemas

La documentación oficial muestra bloques separados para Debian y para Ubuntu. Eso es innecesario. El siguiente bloque escribe el repositorio en el formato moderno deb822 y determina por sí mismo la distribución, el nombre en clave y la arquitectura:

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Dos detalles que ahorran tiempo. Primero, ${UBUNTU_CODENAME:-$VERSION_CODENAME}: en derivadas de Ubuntu como Linux Mint, VERSION_CODENAME contiene el nombre de la derivada y no el de Ubuntu. Segundo, la línea Architectures: sin ella, apt protesta en sistemas con la arquitectura ajena i386 activada y suelta un aviso larguísimo sobre listas de paquetes inexistentes.

Comprueba el resultado antes de seguir:

cat /etc/apt/sources.list.d/docker.sources

En Suites tiene que aparecer trixie, bookworm, noble o jammy. Si hay otra cosa, el siguiente paso terminará en error. Después vienen la actualización y la instalación:

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Los cinco paquetes son: el daemon, la herramienta de línea de comandos, el runtime de contenedores, el constructor de imágenes moderno y Compose.

Cómo saber que realmente está funcionando

Que apt-get haya terminado sin errores solo significa que hay archivos en el disco. Las cuatro comprobaciones siguientes muestran si el sistema trabaja de verdad.

Primero: ¿llega el cliente hasta el daemon?

docker version

Lo decisivo no es la sección Client, sino que debajo aparezca una sección Server: Docker Engine - Community con su número de versión. Si falta, o bien el daemon no está en marcha, o bien no tienes permiso para acceder al socket.

Segundo: ¿qué controlador de almacenamiento está activo?

docker info --format '{{.Driver}}'
docker info --format '{{.CgroupVersion}}'

Aquí acecha una novedad que muchas guías antiguas responden mal. Desde Docker Engine 29, en las instalaciones nuevas viene preconfigurado el almacén de imágenes de containerd. El controlador se llama entonces overlayfs y ya no overlay2. Ambos valores son correctos. Lo que no quieres ver es vfs: ese controlador de emergencia copia cada capa por completo, consume varias veces más espacio en disco y es exasperantemente lento. Suele aparecer cuando Docker se ejecuta en un entorno sin el soporte de kernel adecuado. La versión de cgroup tiene que dar 2 en los cuatro sistemas.

Si has actualizado un sistema existente y de pronto parece que han desaparecido todas las imágenes: no están borradas. Al cambiar de almacén de imágenes, el contenido del otro almacén simplemente deja de mostrarse y vuelve a aparecer al volver atrás. La vuelta atrás se hace en /etc/docker/daemon.json:

{
  "features": {
    "containerd-snapshotter": false
  }
}

Tercero: ¿arranca de verdad un contenedor?

docker run --rm hello-world

Cuarto: ¿funcionan la red y la resolución de nombres dentro del contenedor? Esta prueba falta en casi todas las guías, aunque es justo aquí donde nacen la mayoría de los problemas posteriores:

docker run --rm alpine:3 ping -c 2 1.1.1.1
docker run --rm alpine:3 nslookup deb.debian.org

Si el ping responde pero la resolución de nombres falla, la causa suele estar en un servidor DNS que solo escucha en 127.0.0.53. Desde el contenedor esa dirección no es accesible. La solución pasa por una entrada en /etc/docker/daemon.json con "dns": ["9.9.9.9"] y un reinicio del daemon.

Compose es un plugin, ya no un programa aparte

El antiguo docker-compose con guion era un programa de Python independiente. Ha llegado al final de su vida y ya no se distribuye. Su sucesor es un plugin escrito en Go que se invoca como subcomando de la CLI de Docker, es decir, docker compose con espacio.

docker compose version

Una aclaración sobre el número de versión, porque genera confusión con regularidad: la expresión "Compose V2" designa el desarrollo nuevo en Go, no el número de versión. Hoy la salida muestra una versión de la rama 5. Eso es correcto y no se trata de otro producto distinto.

Al hacer el cambio llaman la atención dos cosas. Por un lado, la clave version: al principio de docker-compose.yml ha quedado de sobra y genera un aviso:

WARN[0000] docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion

Simplemente borra esa línea. Por otro lado cambia la forma de nombrar: Compose deriva el nombre del proyecto del nombre del directorio y crea contenedores con guion en lugar de guion bajo, es decir, miproyecto-web-1 en vez de miproyecto_web_1. Los scripts que se dirigen a los contenedores por nombres fijos se rompen por eso. En esos casos, fija el nombre del proyecto de forma explícita con name: en el archivo de Compose o con -p.

Si prefieres quedarte a propósito con los paquetes de la distribución, el paquete adecuado se llama docker-compose-v2 y ofrece el mismo subcomando. Eso sí, solo está disponible en los repositorios de Ubuntu. Comprueba antes la versión disponible:

apt-cache policy docker-compose-v2

En Ubuntu 24.04 y 22.04 aparece una tabla con la versión instalada y la candidata. En Debian 13 y Debian 12, el comando no muestra absolutamente nada: una salida vacía con código de salida 0. Eso no es un fallo, sino la respuesta. En Debian ese paquete no existe y allí el camino hacia Compose pasa por docker-compose-plugin del repositorio de Docker.

El grupo docker es root, solo que dando un rodeo

Para que un usuario normal pueda manejar Docker sin sudo, lo habitual es añadirlo al grupo docker:

sudo groupadd -f docker
sudo usermod -aG docker $USER

La pertenencia al grupo no surte efecto hasta el siguiente inicio de sesión. Después de volver a entrar puedes comprobarlo con id -nG. Quien no quiera cerrar la sesión, abre con newgrp docker una shell que ya lleva el grupo nuevo.

Ahora viene la parte que tienes que haber entendido: pertenecer al grupo docker equivale a tener permisos de root en todo el servidor. No es una valoración teórica, sino una consecuencia directa de cómo funciona esto. Quien puede hablar con el socket de Docker puede darle al daemon (que se ejecuta como root) las órdenes que quiera. Basta un único comando:

docker run -it -v /:/hostfs alpine:3 chroot /hostfs sh

El resultado es una shell de root en el sistema anfitrión, sin sudo, sin petición de contraseña y sin entrada en el registro de sudo. Leer /etc/shadow, depositar claves SSH, sustituir servicios: todo es posible. La documentación de Docker lo formula de forma breve e inequívoca: "The docker group grants root-level privileges to the user."

Consecuencias prácticas para un servidor accesible desde internet:

  • Añade al grupo únicamente aquellas cuentas a las que de todos modos confiarías root.
  • El usuario bajo el que corre una aplicación web o un runner de CI no entra ahí. De lo contrario, una intrusión en la aplicación sería automáticamente una intrusión en el servidor.
  • Si necesitas trazabilidad, renuncia al grupo y llama a Docker mediante sudo docker. Así al menos la llamada queda registrada en el log.
  • Para una separación real existe el modo rootless. Se configura con el paquete docker-ce-rootless-extras y la herramienta dockerd-rootless-setuptool.sh install, y además necesita uidmap. El precio: los puertos por debajo de 1024 no se pueden ocupar sin configuración adicional, y algunas funciones de red se comportan de otra manera.

Arranque automático: la trampa se llama docker.socket

El comando estándar es de sobra conocido:

sudo systemctl enable --now docker.service
sudo systemctl enable --now containerd.service

Menos conocido es por qué desactivarlo a menudo no surte efecto. Junto a docker.service, Docker trae también una docker.socket. Esa unit escucha en el socket y arranca el daemon automáticamente al primer acceso. Así que quien ejecuta systemctl disable docker.service y después comprueba que Docker sigue funcionando no ha visto un fantasma: el primer docker ps volvió a levantar el daemon a través de la unit del socket. Para desactivarlo por completo hacen falta las dos:

sudo systemctl disable --now docker.service docker.socket

El estado se comprueba con systemctl is-enabled docker.service, que debe devolver enabled.

El segundo punto afecta a tus contenedores. Que un contenedor vuelva tras un reinicio no lo decide systemd, sino la política de reinicio. Y ahí hay una diferencia que sorprende con regularidad: always vuelve a arrancar un contenedor incluso cuando lo has parado a propósito antes del reinicio. unless-stopped respeta tu parada manual más allá del reinicio. Para servidores, unless-stopped es por lo general la elección correcta:

services:
  web:
    image: nginx:stable
    restart: unless-stopped

La prueba de esto no es docker ps, sino un reinicio real del servidor con su comprobación posterior.

Rotación de logs: la causa más frecuente de un disco de sistema lleno

Por defecto, Docker escribe la salida de cada contenedor en un archivo JSON dentro de /var/lib/docker/containers/. Ese archivo crece de forma ilimitada con la configuración predeterminada. Un reverse proxy hablador puede acumular así decenas de gigabytes a lo largo de los meses, hasta que el servidor se queda parado con no space left on device. Encontrar al culpable resulta entonces difícil, porque du no muestra nada llamativo en el directorio de la aplicación.

La solución debería estar en todos los servidores, y además antes de que aparezca el primer problema:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

Si ya existe un daemon.json, este comando lo sobrescribe. Míralo antes y, si hace falta, añade las claves a mano. Después:

sudo systemctl restart docker

Tres puntos en los que la cosa se tuerce igualmente:

  • Los valores tienen que ir como cadenas de texto entre comillas. "max-file": 3 sin comillas impide que el daemon vuelva a arrancar.
  • El ajuste solo se aplica a los contenedores creados de nuevo. Los existentes conservan su configuración antigua hasta que se vuelven a crear, con Compose mediante docker compose up -d --force-recreate.
  • Borrar con rm un archivo de log desbordado no devuelve espacio en disco, porque el daemon todavía lo mantiene abierto. Usa en su lugar sudo truncate -s 0 <ruta>.

Para comprobar si el ajuste surte efecto en un contenedor concreto:

docker inspect --format '{{json .HostConfig.LogConfig}}' micontenedor

Limpiar con docker system prune sin perder datos

Las imágenes sin usar, los builds interrumpidos y la caché de BuildKit se van sumando. Lo primero es mirar adónde se va el espacio:

docker system df
docker system df -v

El comando estándar de limpieza elimina los contenedores parados, las redes sin usar, las imágenes sin nombre y la caché de compilación:

docker system prune

Dos opciones merecen respeto. -a borra además todas las imágenes que en ese momento no esté usando ningún contenedor, incluidas las imágenes base que cuidas con esmero. En un servidor con poco ancho de banda, volver a descargarlas puede llevar su tiempo. Bastante más peligrosa es --volumes: esa opción elimina los volúmenes con nombre que no tengan un contenedor asociado. Si acabas de borrar el contenedor de la base de datos pero el volumen todavía contiene los datos, después ya no estarán. Aquí no hay papelera de reciclaje.

No uses nunca --volumes en un trabajo de limpieza automático.

Lo razonable es dejar un margen de tiempo para que solo desaparezca material realmente antiguo:

docker system prune -a --filter "until=168h"
docker builder prune --filter "until=168h"

Como trabajo semanal, de noche para no cargar el sistema y sin borrado de volúmenes, basta con una línea en /etc/cron.d/docker-prune:

15 4 * * 0 root /usr/bin/docker system prune -af --filter "until=168h" > /dev/null 2>&1

La prueba de que ha funcionado es un nuevo docker system df con la columna "RECLAIMABLE" más baja.

Mensajes de error literales y qué se esconde detrás

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
El usuario no está en el grupo docker, o la pertenencia al grupo todavía no es efectiva en la sesión actual. Vuelve a iniciar sesión o usa newgrp docker.

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
El daemon no está en marcha. La causa y el mensaje exacto los entrega systemctl status docker y, con más detalle, journalctl -u docker -n 50 --no-pager. Con mucha frecuencia detrás hay un /etc/docker/daemon.json defectuoso. Ese archivo tiene que ser JSON válido: basta una sola coma de más.

docker: 'compose' is not a docker command.
Falta el plugin. Instala después docker-compose-plugin del repositorio de Docker o, solo en Ubuntu, docker-compose-v2 de la distribución.

E: Conflicting values set for option Signed-By regarding source https://download.docker.com/linux/debian/ trixie: /etc/apt/keyrings/docker.asc != /etc/apt/keyrings/docker.gpg
El clásico después de haber seguido varias guías seguidas: existen a la vez un antiguo /etc/apt/sources.list.d/docker.list y el nuevo docker.sources. Borra el archivo antiguo y repite sudo apt-get update. Con ls -l /etc/apt/sources.list.d/ obtienes una visión de conjunto.

The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 8D81803C0EBFCD88
La clave no está donde Signed-By la espera, o el archivo descargado está incompleto (por ejemplo, porque un proxy ha devuelto una página HTML de error). gpg --show-keys /etc/apt/keyrings/docker.asc muestra al instante si dentro hay siquiera una clave.

E: The repository 'https://download.docker.com/linux/debian trixie Release' does not have a Release file.
El nombre en clave no encaja con la fuente. Eso ocurre en distribuciones derivadas y cuando se mezclan guías de Debian con guías de Ubuntu. Revisa las líneas URIs y Suites en docker.sources.

Bind for 0.0.0.0:80 failed: port is already allocated
Otro servicio ocupa el puerto, a menudo un servidor web instalado directamente en el sistema. sudo ss -tulpn | grep :80 señala al responsable.

Docker y el firewall: unas palabras sobre seguridad

Una particularidad que puede salir cara en un servidor accesible públicamente: Docker inscribe sus reglas de redirección en la tabla NAT y con ello se salta las reglas que has cuidado en ufw. Un contenedor arrancado con -p 5432:5432 queda accesible desde internet, aunque ufw status no permita ese puerto en ningún sitio. Esto sigue siendo así pese a que Docker Engine 28 endureció el comportamiento de red en conjunto y bloqueó el acceso desde fuera a los puertos no publicados.

La contramedida más sencilla y fiable consiste en enlazar de forma expresa a la dirección de loopback los servicios que solo se necesitan en local:

services:
  db:
    image: postgres:17
    ports:
      - "127.0.0.1:5432:5432"
    restart: unless-stopped

Mejor todavía: no publiques esos puertos en absoluto y deja que los contenedores se comuniquen entre sí a través de una red común de Docker. El resultado se comprueba mejor desde un segundo equipo, porque una prueba desde el propio servidor no responde a la pregunta decisiva.

En resumen

En Debian 12 usa sin dudarlo Docker CE; en Ubuntu puedes elegir entre docker.io y Docker CE. Añade el repositorio con un llavero propio y la huella comprobada, no con apt-key. Usa docker compose con espacio. Trata el grupo docker como una autorización de root, porque es exactamente eso. Y configura la rotación de logs y un trabajo de prune semanal antes de que el servidor falle por primera vez con el disco lleno.

En la misma línea: configurar el firewall UFW en Debian y Ubuntu e instalar Nginx en Debian y Ubuntu.

Preguntas frecuentes

¿Debo instalar docker.io o docker-ce?
En Debian 12, claramente Docker CE, porque allí el paquete de la distribución se queda en 20.10.24 y esa rama ya está descontinuada en upstream. En Debian 13, docker.io ofrece 26.1.5, y en Ubuntu 24.04 y 22.04 incluso 29.1.3, es decir, prácticamente el estado actual. Ahí el paquete de la distribución es una elección legítima, siempre que instales Buildx y Compose por separado. Eso sí, el paquete docker-compose-v2 solo existe en los repositorios de Ubuntu; en Debian el camino hacia Compose pasa por docker-compose-plugin del repositorio de Docker.
¿Por qué ya no funciona docker-compose con guion?
El antiguo docker-compose era un programa de Python independiente y ya no se distribuye. Su sucesor es un plugin en Go de la CLI de Docker y se invoca como docker compose, con espacio. Está en el paquete docker-compose-plugin (repositorio de Docker) o, solo en Ubuntu, en docker-compose-v2 de la distribución. Compruébalo con: docker compose version.
¿El grupo docker es de verdad tan peligroso como se dice?
Sí. Quien puede acceder al socket de Docker puede dar cualquier orden al daemon, que se ejecuta como root, por ejemplo montar el directorio raíz del host dentro de un contenedor y abrir ahí una shell de root. La documentación de Docker lo describe como root-level privileges. Añade solo cuentas a las que de todos modos confiarías root, o utiliza el modo rootless.
¿Por qué docker info me muestra overlayfs en vez de overlay2?
Desde Docker Engine 29, en las instalaciones nuevas viene preconfigurado el almacén de imágenes de containerd. Su snapshotter se llama overlayfs. Eso es correcto y no es ningún fallo. Solo sería problemático el valor vfs, que apunta a que falta soporte en el kernel y consume muchísimo espacio en disco.
Mis imágenes han desaparecido tras una actualización, ¿están borradas?
Por lo general no. Al cambiar entre el almacén de imágenes clásico y el de containerd, el contenido del otro almacén simplemente deja de mostrarse, pero los datos siguen en el disco. Vuelve atrás a modo de prueba con features containerd-snapshotter en /etc/docker/daemon.json y reaparecerán.
¿Cómo evito que los logs de los contenedores llenen el disco?
El controlador json-file no rota por defecto. Añade en /etc/docker/daemon.json un log-opts con max-size 10m y max-file 3; los valores tienen que ir como cadenas de texto entre comillas. Tras systemctl restart docker, el ajuste solo vale para los contenedores creados de nuevo: los existentes hay que recrearlos con docker compose up -d --force-recreate.
¿Es inofensivo docker system prune?
Sin opciones adicionales, en buena medida sí: elimina contenedores parados, redes sin usar, imágenes sin nombre y la caché de compilación. La opción -a borra además todas las imágenes que no se estén usando en ese momento. Lo peligroso es --volumes, porque con ella desaparecen los volúmenes con nombre que no tengan un contenedor asociado, es decir, posiblemente tu base de datos. En trabajos automáticos, --volumes no debe aparecer nunca.

Docker Docker Compose Debian Ubuntu Servidor Linux containerd apt Administración de servidores