Un Ubuntu Server recien instalado mas apt install docker-ce docker-compose-plugin te deja con un host Docker funcionando en dos minutos. Tambien te deja con cuatro valores por defecto que estan bien para un portatil y son un problema en una maquina que sirve trafico real. Ninguno se nota hasta que algo se rompe: un disco lleno, un puerto que supuestamente estaba bloqueado por el firewall, o un contenedor que se reinicia en medio de una ventana de despliegue.
overlay2 ya es el default -- revisa sobre que esta montado
overlay2 es el driver de almacenamiento por defecto de Docker desde mucho antes de 2026, asi que no hace falta configurarlo a mano. Lo que si hay que revisar es el sistema de archivos debajo de /var/lib/docker. En ext4 funciona sin tocar nada. En XFS, overlay2 necesita ftype=1 -- sistemas XFS mas viejos (o creados con los defaults de hace unos anios) suelen tener ftype=0, y Docker cae a un driver mas lento o falla directamente segun la version.
docker info --format '{{.Driver}}'
# overlay2
findmnt -no FSTYPE,SOURCE /var/lib/docker
# ext4 /dev/sda1 -- bien
# xfs /dev/sda1 -- revisar ftype abajo
xfs_info /var/lib/docker | grep ftype
# ftype=1 -- correcto
# ftype=0 -- reconstrui el filesystem antes de meter cargas reales
Si /var/lib/docker vive en un volumen separado (algo comun en imagenes cloud), corre xfs_info sobre ese punto de montaje, no sobre el filesystem raiz.
los logs json-file no tienen limite de tamanio por defecto
El driver de logging por defecto de Docker es json-file, y de fabrica guarda cada linea que un contenedor escribe en stdout/stderr, para siempre, en /var/lib/docker/containers/<id>/<id>-json.log. Un contenedor callado no da problemas. Un worker que loguea cada job, o una app a la que se le olvido configurar niveles de log, termina llenando el disco -- y siempre es el mismo disco donde vive /var/lib/docker, asi que cuando se llena, el daemon deja de poder hacer casi nada, incluida la limpieza facil.

{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
Pone eso en /etc/docker/daemon.json y reinicia el daemon. Un detalle que le cuesta la tarde a mas de uno: esto solo aplica a los contenedores creados despues del cambio. Lo que ya estaba corriendo conserva su configuracion de log original (sin limite) hasta que se recrea -- un reinicio no alcanza, hace falta docker compose up -d --force-recreate o equivalente.
ufw dice deny, Docker dice otra cosa
Este es el que realmente causa incidentes. ufw administra la cadena INPUT de la tabla filter. Docker, cuando publicas un puerto con -p, escribe sus propias reglas en las tablas nat y filter, en la cadena DOCKER, a la que se llega a traves de FORWARD y no de INPUT -- porque, desde el punto de vista del kernel, el trafico del contenedor se esta enrutando a otro namespace de red, no entregando localmente. Las reglas de ufw nunca se consultan para ese trafico.
En concreto: corres ufw deny 5432, te quedas tranquilo, despues levantas un contenedor con -p 5432:5432, y Postgres queda accesible desde cualquier parte de internet. El estado de ufw va a seguir diciendo, muy contento, que el puerto esta denegado.
Tres formas de arreglarlo de verdad, de menor a mayor cambio en tu flujo de trabajo:
- Publicar en loopback o una interfaz privada:
-p 127.0.0.1:5432:5432o-p 10.20.0.5:5432:5432, y poner un reverse proxy o VPN delante de lo que necesite ser alcanzable de verdad. Es el cambio menos disruptivo y sirve para la mayoria de los contenedores de aplicacion. - Usar la cadena
DOCKER-USER, que Docker garantiza evaluar antes que sus propias reglas y que no toca en reinicios ni actualizaciones:
Esta es la forma soportada de aplicar restricciones estilo ufw a puertos de contenedores publicados -- no edites la cadenasudo iptables -I DOCKER-USER -s 10.20.0.0/16 -j ACCEPT sudo iptables -I DOCKER-USER -j DROPDOCKERdirectamente, dockerd la reescribe. - Poner
"iptables": falseendaemon.jsony manejar cada regla vos mismo. Totalmente consistente con ufw, pero perdes la publicacion automatica de puertos y pasas a ser dueno de cada regla NAT a mano. Vale la pena solo en hosts donde la red ya esta muy controlada.
unattended-upgrades y un host Docker no combinan bien por defecto
unattended-upgrades viene habilitado de fabrica en Ubuntu, trayendo actualizaciones de seguridad del repositorio -security todas las noches. docker-ce y containerd.io normalmente vienen del repositorio apt propio de Docker, asi que unattended-upgrades no los toca salvo que agregues ese origen a mano -- revisa /etc/apt/apt.conf.d/50unattended-upgrades antes de dar nada por sentado. El riesgo real esta un paso mas alla: una actualizacion de kernel o de libc dispara Automatic-Reboot, y todos los contenedores del host se caen sin drenar y sin aviso, en el momento en que esa actualizacion decida llegar.
# /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Origins-Pattern {
"origin=Ubuntu,archive=${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Fija esa ventana de reinicio a un horario que realmente conozcas, y por separado fija los paquetes del motor de Docker para que un salto de docker-ce nunca llegue a mitad del dia sin que lo decidas vos:
sudo apt-mark hold docker-ce docker-ce-cli containerd.io
Despues hace las actualizaciones de motor e imagenes con tu propia cadencia -- un timer de systemd que corre docker compose pull && docker compose up -d fuera de horario, o un apt-mark unhold && apt upgrade && apt-mark hold programado en una ventana de mantenimiento -- en vez de dejar que apt decida por vos.
antes de llamarlo listo para produccion
- Confirma que el filesystem de overlay2 tiene las opciones de montaje correctas (
ftype=1en XFS) - Configura
log-optsendaemon.jsony recrea los contenedores existentes para que lo tomen - Agrega reglas en
DOCKER-USERo publica en una interfaz privada -- no confies solo en ufw - Fija
docker-ce/containerd.ioy programa tu propia ventana de actualizacion

