Docker en producción en un VPS: qué falla y cómo solucionarlo
Docker funciona en tu portátil. En un VPS público, ignora el firewall, llena el disco con logs, ejecuta todo como root y no tiene estrategia de actualización. Estos son los 8 problemas que debes resolver.
Instalaste Docker en tu VPS. Ejecutaste docker compose up. Tu aplicación está en línea. ¿Listo?
No del todo. En tu portátil, nadie escanea tus puertos, el espacio en disco es abundante y la seguridad apenas importa. En un VPS expuesto a Internet, los valores por defecto de Docker trabajan activamente en tu contra.
Esta página cubre los 8 problemas que vas a encontrar y enlaza a una guía dedicada para cada uno. Revísala, identifica lo que aplica a tu servidor, sigue la guía detallada.
Requisitos previos
Esta guía asume que ya conoces los conceptos básicos de Docker. Si necesitas ponerte al día:
- Comandos y conceptos de Docker
- Aplicaciones multi-servicio con Compose
Este artículo se dirige a Debian 12 y Ubuntu 24.04 con Docker Engine 29.x y Compose v2 (versión 5.x).
¿Qué falla cuando ejecutas Docker en un VPS público?
Ocho cosas fallan cuando mueves Docker de desarrollo a un VPS público: tu firewall deja de funcionar, los contenedores se ejecutan como root con acceso total al host, los logs llenan tu disco sin rotación, la red de contenedores entra en conflicto con la del host, los servicios no tienen límites de recursos ni health checks, los volúmenes no tienen estrategia de respaldo, los puertos están expuestos sin TLS y las imágenes de contenedores se vuelven obsoletas sin plan de actualización. Todos estos son valores por defecto de Docker. Cada uno te causará problemas en producción.
Resumen rápido antes de las guías detalladas:
| # | Problema | Comando de diagnóstico | Riesgo |
|---|---|---|---|
| 1 | Bypass del firewall | sudo iptables -L DOCKER-USER -n |
Crítico |
| 2 | Contenedores root | docker info --format '{{.SecurityOptions}}' |
Crítico |
| 3 | Disco lleno por logs | du -sh /var/lib/docker/containers/*/*-json.log |
Alto |
| 4 | Conflictos de red | docker network ls |
Medio |
| 5 | Sin límites de recursos | docker stats --no-stream |
Alto |
| 6 | Sin respaldo de volúmenes | docker volume ls |
Alto |
| 7 | Sin reverse proxy / TLS | ss -tlnp | grep -E ':80|:443' |
Crítico |
| 8 | Imágenes obsoletas | docker compose images |
Medio |
Ejecuta estos comandos en tu servidor ahora mismo. Si alguna salida te sorprende, lee la sección correspondiente más abajo.
¿Docker ignora tu firewall?
Sí. Docker manipula iptables directamente, insertando reglas en las tablas nat y filter que se evalúan antes de que UFW o firewalld vean el tráfico. Cuando publicas un puerto con -p 8080:80, ese puerto está abierto a todo Internet, incluso si tus reglas UFW dicen deny incoming.
Comprueba si esto te afecta:
sudo iptables -L DOCKER-USER -n -v
Si la salida muestra una cadena vacía o solo una regla RETURN incondicional sin filtrado, cada puerto publicado está completamente abierto.
La causa raíz: los paquetes destinados a contenedores Docker pasan por la cadena FORWARD y la cadena DOCKER. UFW opera en la cadena INPUT. Las dos nunca se encuentran.
La solución inmediata más simple es vincular los puertos publicados solo a 127.0.0.1:
ports:
- "127.0.0.1:8080:80"
Esto hace que el puerto sea accesible solo desde el propio host. Combínalo con un reverse proxy para manejar el tráfico externo.
Para un recorrido completo de la corrección de la cadena DOCKER-USER, la integración con UFW y la configuración de nftables.
¿Es peligroso ejecutar Docker como root en un VPS?
Por defecto, el demonio Docker se ejecuta como root y los contenedores se ejecutan como root dentro de sus namespaces. Si un atacante escapa de un contenedor, aterriza en el host como root. En un VPS público, esto es un camino directo a la compromisión total del servidor.
Comprueba tu configuración actual:
docker info --format '{{.SecurityOptions}}'
Busca name=rootless en la salida. Si no está, tu demonio se ejecuta como root.
También comprueba si tus contenedores se ejecutan como root internamente:
docker ps -q | xargs -I {} docker exec {} id
Si ves uid=0(root) para la mayoría de los contenedores, están ejecutándose como root dentro del contenedor. Una imagen que define USER appuser en su Dockerfile mostrará un uid no-root en su lugar.
Tienes tres capas de defensa:
- Modo rootless ejecuta el demonio Docker completo bajo un usuario no-root. Incluso una fuga de contenedor aterriza como un usuario sin privilegios en el host.
- Perfiles seccomp restringen qué syscalls pueden usar los contenedores. Docker incluye un perfil por defecto que bloquea unas 44 syscalls peligrosas, pero puedes reforzarlo.
- AppArmor / SELinux añade control de acceso obligatorio sobre todo lo demás.
Configuración completa del modo rootless, perfiles seccomp personalizados y no-new-privileges.
¿Por qué los contenedores Docker llenan tu disco?
El driver de logs por defecto de Docker es json-file sin límite de tamaño ni rotación. Cada línea que tu aplicación escribe en stdout se almacena en /var/lib/docker/containers/<id>/<id>-json.log. Una aplicación web activa puede generar gigabytes de logs en días.
Comprueba cuánto espacio están usando los logs ahora mismo:
sudo du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -rh | head -5
En un VPS que lleva unas semanas funcionando sin rotación de logs, podrías ver una salida como:
4.2G /var/lib/docker/containers/a1b2c3.../a1b2c3...-json.log
1.8G /var/lib/docker/containers/d4e5f6.../d4e5f6...-json.log
256M /var/lib/docker/containers/g7h8i9.../g7h8i9...-json.log
Los logs no son el único consumidor de disco. Comprueba el uso de disco general de Docker:
docker system df
Salida típica en un servidor con 5 servicios:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 5 4.2GB 2.8GB (66%)
Containers 8 5 52MB 12MB (23%)
Local Volumes 6 4 1.1GB 245MB (22%)
Build Cache 18 0 890MB 890MB (100%)
Ese build cache es 100% recuperable. Esas 7 imágenes sin usar ocupan 2,8 GB. En un disco de VPS de 40 GB, se acumula rápido.
La solución tiene dos partes: configurar la rotación de logs en /etc/docker/daemon.json y programar la limpieza periódica de imágenes sin usar y del build cache.
Configuración completa de rotación de logs, monitoreo de disco y limpieza automatizada:
¿Cómo funciona la red de Docker en un VPS individual?
Docker crea su propia red bridge (172.17.0.0/16 por defecto) y gestiona las IP de los contenedores internamente. En un VPS, esto puede entrar en conflicto con tu red del host, tus subredes VPN u otros servicios.
Mira qué redes ha creado Docker:
docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
f6e5d4c3b2a1 host host local
9i8h7g6f5e4d none null local
Cada docker compose up crea una red adicional. Después de unos cuantos proyectos, podrías tener una docena de redes con subredes superpuestas.
Decisiones clave para la red en un VPS:
- Modo bridge (por defecto): los contenedores obtienen IPs internas, los puertos se publican vía iptables. Funciona para la mayoría de las configuraciones. Añade una capa NAT.
- Modo host: los contenedores comparten directamente la pila de red del host. Sin overhead de NAT, pero sin aislamiento de puertos. Útil para servicios sensibles al rendimiento.
- Redes bridge personalizadas: los contenedores en la misma red personalizada pueden encontrarse por nombre de contenedor. Esto es lo que Compose crea automáticamente.
Lo importante en un VPS: define tus subredes explícitamente en docker-compose.yml en lugar de dejar que Docker elija. Esto evita conflictos con las redes internas de tu proveedor de hosting.
Guía detallada sobre bridge vs host, subredes personalizadas y DNS entre contenedores:
¿Qué pasa sin límites de recursos ni health checks?
Sin límites de memoria, un solo contenedor con una fuga de memoria consumirá toda la RAM disponible, activará el OOM killer de Linux y tumbará contenedores no relacionados o incluso el demonio SSH. Sin health checks, Docker no tiene forma de saber que un contenedor está roto. Se queda en estado "running" mientras devuelve errores.
Comprueba si tus contenedores tienen límites configurados:
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}"
Si la columna MEM % muestra valores pero nunca configuraste límites, esos porcentajes son respecto a la RAM total del host. Un contenedor que muestra 45% está usando el 45% de toda la memoria de tu VPS, y nada le impide tomar más.
La configuración mínima de producción en tu docker-compose.yml:
services:
app:
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
La política restart: unless-stopped reinicia automáticamente los contenedores que se caen pero respeta las paradas manuales. El healthcheck permite a Docker marcar contenedores no saludables y reiniciarlos según el número de reintentos.
Guía completa sobre límites de recursos en Compose, patrones de healthcheck y políticas de reinicio:
¿Cómo respaldar volúmenes Docker?
Los volúmenes Docker persisten datos fuera de los contenedores. Si un volumen se elimina o se corrompe, tu base de datos, archivos subidos o configuración desaparecen. Docker no tiene un mecanismo de respaldo integrado para volúmenes.
Lista tus volúmenes y sus tamaños:
docker system df -v | grep -A 100 "Local Volumes space usage"
O comprueba los puntos de montaje individuales:
docker volume ls -q | xargs -I {} docker volume inspect {} --format '{{.Name}}: {{.Mountpoint}}'
Los puntos de montaje están bajo /var/lib/docker/volumes/ por defecto. Puedes respaldarlos con herramientas estándar de Linux, pero necesitas detener o pausar el contenedor primero para evitar snapshots inconsistentes. Para bases de datos, una copia del sistema de archivos de una base de datos en ejecución no es un respaldo fiable. Necesitas un dump primero.
Estrategias de respaldo, procedimientos de restauración y programación automatizada:
¿Cómo exponer servicios Docker con TLS?
Publicar puertos directamente con -p 443:443 en cada contenedor no escala más allá de un servicio. Necesitas un reverse proxy que termine TLS, enrute el tráfico al contenedor correcto y gestione la renovación de certificados.
Comprueba qué está escuchando actualmente en tu VPS:
ss -tlnp | grep -E ':80|:443'
Si ves tus contenedores de aplicación vinculados directamente a los puertos 80 o 443, están expuestos sin una capa de proxy. Esto significa que cada servicio necesita su propia gestión de certificados, y no puedes alojar múltiples dominios en un solo VPS.
Tres opciones de reverse proxy dominan el ecosistema Docker:
| Proxy | Auto-discovery | TLS automático | Estilo de configuración |
|---|---|---|---|
| Traefik | Sí (labels Docker) | Let's Encrypt integrado | Labels + YAML |
| Caddy | Vía plugin | Automático por defecto | Caddyfile |
| Nginx Proxy Manager | Socket Docker | UI de Let's Encrypt | Interfaz web |
Traefik es la opción más común para despliegues Docker-nativos porque lee las labels de los contenedores y genera las reglas de enrutamiento automáticamente. No hay archivos de configuración que actualizar cuando añades un servicio.
Comparación, guías de instalación y configuración TLS para cada opción:
¿Cuál es la estrategia de actualización para contenedores Docker en un VPS?
Las imágenes Docker no se actualizan solas. Si ejecutas postgres:16 y sale un parche de seguridad en postgres:16.4, tu contenedor sigue ejecutando la imagen antigua hasta que la descargues y recrees el contenedor explícitamente.
Comprueba qué imágenes están usando tus servicios:
docker compose images
Compara los digests de las imágenes con las últimas versiones disponibles:
docker compose pull --dry-run 2>&1
Si alguna imagen aparece como "pulled" en la salida del dry-run, tu versión en ejecución está obsoleta.
Las decisiones clave:
- Fijar tags de imágenes: nunca uses
:latesten producción. Usa tags de versión específicos comopostgres:16.4o incluso digests SHA. - Pulls programados: ejecuta
docker compose pull && docker compose up -den un horario, pero prueba las actualizaciones primero en staging. - Reinicios sin interrupciones: usa health checks y
docker compose up -d --no-deps <service>para actualizar un servicio a la vez. - Escaneo de imágenes: herramientas como Trivy escanean tus imágenes en busca de CVEs conocidos antes del despliegue.
Estrategias de actualización, patrones de versionado de imágenes y despliegue sin interrupciones:
¿Necesitas Kubernetes para ejecutar Docker en producción?
No. Docker Compose es una herramienta de despliegue lista para producción para cargas de trabajo en un solo servidor. Kubernetes resuelve la orquestación multi-nodo, el escalado automático y la auto-reparación a través de un clúster. Si tu aplicación se ejecuta en un solo VPS, Compose te da todo lo que necesitas sin la carga operativa de Kubernetes.
Donde Compose funciona:
- Un solo VPS con 3-15 contenedores
- Patrones de tráfico estáticos o predecibles
- Equipo pequeño sin ingenieros de plataforma dedicados
- Servicios que toleran segundos de inactividad durante las actualizaciones
Donde superas Compose:
- Múltiples servidores necesarios para alta disponibilidad
- Auto-scaling basado en picos de tráfico
- Service meshes con cientos de microservicios
- Requisitos de cero inactividad con despliegues blue-green entre nodos
El punto medio ligero es K3s, un Kubernetes simplificado que corre en un solo VPS con unos 512 MB de overhead de RAM. Pero para la mayoría de los proyectos personales, MVPs de SaaS y pequeñas aplicaciones de producción, Compose es la herramienta correcta.
¿Cuánta RAM necesita un VPS para Docker en producción?
Un VPS ejecutando Docker en producción necesita como mínimo 4 GB de RAM para 3-5 contenedores. El demonio Docker en sí usa aproximadamente 100-200 MB. Cada contenedor añade su propia huella de memoria encima. Sin límites de memoria configurados, un solo contenedor problemático puede consumir todo.
| Tamaño del VPS | Contenedores | Adecuado para |
|---|---|---|
| 4 GB de RAM | 3-5 ligeros | Blog, API, base de datos, Redis |
| 8 GB de RAM | 5-10 mixtos | MVP SaaS, múltiples servicios, stack de monitoreo |
| 16 GB de RAM | 10-20 | Múltiples proyectos, runners CI, bases de datos pesadas |
| 32 GB de RAM | 20+ | Inferencia IA, bases de datos grandes, servidores de build |
El almacenamiento importa tanto como la RAM. Las imágenes Docker, los volúmenes, los logs y el build cache se acumulan. Planifica al menos 40 GB de almacenamiento NVMe y configura el monitoreo descrito en desde el primer día.
La CPU rara vez es el cuello de botella para servicios web en contenedores. 4 vCPU manejan la mayoría de las cargas de trabajo. Excepciones: transcodificación de video, inferencia IA y servidores de build.
Para un VPS con almacenamiento NVMe dimensionado para cargas de trabajo Docker en producción, consulta los planes VPS de Virtua Cloud.
Checklist de producción
Antes de pasar a producción con Docker en un VPS, verifica cada punto:
- Las reglas de firewall bloquean todos los puertos publicados por Docker excepto a través del reverse proxy
- Los contenedores se ejecutan como usuarios no-root donde sea posible
- El demonio Docker corre en modo rootless o los contenedores usan seccomp + no-new-privileges
- La rotación de logs está configurada en
/etc/docker/daemon.json -
docker system prunese ejecuta en un horario (cron semanal) - Cada servicio tiene límites de memoria y CPU en
docker-compose.yml - Cada servicio tiene un health check
- La política de reinicio está configurada (
unless-stoppedoon-failure) - Los volúmenes con datos importantes tienen respaldos automatizados
- Un reverse proxy gestiona la terminación TLS y la renovación de certificados
- Los tags de imágenes están fijados a versiones específicas, no
:latest - Tienes un procedimiento probado de actualización para descargar nuevas imágenes
-
journalctl -u dockerydocker logs <container>funcionan y sabes dónde buscar
Si no puedes marcar cada casilla, recorre los artículos enlazados en cada sección anterior.
Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.