Ubuntu Server como host de Docker en 2026: los valores por defecto que conviene cambiar antes de produccion



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.

Terminal mostrando logs json-file de contenedores Docker que crecieron a cientos de megabytes, y un daemon.json con log-opts max-size y max-file configurados
Logs json-file sin limite a la izquierda, la correccion en daemon.json a la derecha.
{
  "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.

How Docker's iptables rules bypass ufw for published portsInternetufw INPUT chainiptables filter table, user rulesDOCKER chainnat/FORWARD, written by dockerdsshd :22blocked: ufw deny 22container :5432reachable: docker run -p 5432:5432host-bound trafficsame traffic, -p 5432:5432ufw rules never consulted

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:5432 o -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:
    sudo iptables -I DOCKER-USER -s 10.20.0.0/16 -j ACCEPT
    sudo iptables -I DOCKER-USER -j DROP
    Esta es la forma soportada de aplicar restricciones estilo ufw a puertos de contenedores publicados -- no edites la cadena DOCKER directamente, dockerd la reescribe.
  • Poner "iptables": false en daemon.json y 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=1 en XFS)
  • Configura log-opts en daemon.json y recrea los contenedores existentes para que lo tomen
  • Agrega reglas en DOCKER-USER o publica en una interfaz privada -- no confies solo en ufw
  • Fija docker-ce/containerd.io y programa tu propia ventana de actualizacion

Dejar un comentario

Los comentarios anónimos se publican tras ser revisados.