Qué cambia al usar Docker en un VPS
Docker en un VPS usa el mismo motor, pero con menos margen: la RAM se agota, los puertos publicados omiten UFW, los contenedores no vuelven solos y el disco se llena.
Qué cambia al ejecutar Docker en un VPS
Docker en un VPS utiliza el mismo motor y las mismas imágenes que Docker en el equipo portátil, por lo que todos los comandos que ya conoce siguen funcionando. Lo que cambia es el margen disponible. Un equipo portátil tiene memoria libre, un firewall que nadie está escaneando y un disco lo bastante grande como para no tener que revisarlo. Un servidor alquilado tiene un límite de memoria fijo, una dirección IP pública que recibe escaneos a los pocos minutos de iniciar y un sistema de archivos raíz que Docker llenará sin pedir confirmación.
Cuatro diferencias causan la mayoría de los problemas en un servidor pequeño:
- La memoria es limitada y el kernel resuelve la falta de memoria terminando un proceso.
- Un puerto publicado atraviesa directamente UFW (uncomplicated firewall), porque Docker escribe sus propias reglas de firewall.
- Los contenedores no vuelven a iniciarse después de un reinicio a menos que se haya configurado de antemano.
- Las imágenes, los contenedores, los volúmenes y la caché de compilación crecen hasta llenar el disco.
Cada sección siguiente identifica el fallo, la cadena que verá realmente y la guía que lo corrige en profundidad. Si todavía no ha escrito un archivo de compose, lea primero Conceptos básicos de Docker Compose en un VPS y vuelva después. Esta página supone que ya puede iniciar un stack.
¿Cuánta RAM usa un contenedor de Docker?
Menos de lo que espera la mayoría. Un contenedor es un proceso dentro de un cgroup (grupo de control), no una máquina virtual. Por tanto, no tiene un kernel invitado ni una asignación fija. El consumo depende de lo que use el proceso que se ejecuta dentro. Por eso, una pila completa cabe en 2 GB cuando la misma pila basada en máquinas virtuales no cabría.
Las cifras siguientes son valores de inactividad habituales para imágenes sin modificar en Ubuntu 24.04 con la configuración predeterminada. Se han obtenido de docker stats unos minutos después del arranque. Sirven como punto de partida para planificar, no como referencia de rendimiento para su carga de trabajo. Ejecute docker stats --no-stream en su propio equipo antes de confiar en cualquier cifra, incluidas estas.
Los datos de este gráfico
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]Las dos columnas tienen funciones distintas. idle_mb indica lo que usa el contenedor cuando no hace nada. budget_mb indica lo que debe reservar al planificar, porque el uso real no corresponde al estado de inactividad. PostgreSQL usa cerca de 45 MB en inactividad y necesita 512 MB cuando las conexiones, las operaciones de ordenación y la caché están activas. Planifique con la columna de reserva. Depure con la columna de inactividad.
Observe la distribución de esas 7 filas. nginx usa 8 MB en inactividad y Nextcloud 210 MB. El proxy situado delante de las aplicaciones apenas consume memoria. El tamaño del servidor debe determinarse principalmente por la base de datos y la aplicación PHP.
Una advertencia sobre docker stats: la cifra de memoria incluye la caché de páginas que las lecturas de archivos del propio contenedor han cargado. Por eso aumenta durante un tiempo después del arranque y luego se estabiliza. Monitorícela durante una hora antes de concluir que existe una fuga de memoria.
Dimensionar un VPS: qué cabe en 2 GB, 4 GB y 8 GB
Descuente primero la parte que consume el host. El kernel, systemd, journald, sshd y el daemon de Docker usan la misma RAM que los contenedores, y dockerd con containerd consume alrededor de 100 MB. También necesita memoria libre para la caché de páginas y para el pico de consumo que se produce cuando se crea una imagen o se ejecuta un volcado de la base de datos.
Los datos de este gráfico
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb cubre el sistema operativo, el daemon de Docker y el margen que mantiene el servidor operativo bajo carga. Lo que queda es container_mb, y ese es el único valor que puede asignar. La reserva aumenta con el plan, desde 768 MB en el servidor más pequeño hasta 1536 MB en el más grande, porque un servidor más grande ejecuta más contenedores, escribe más registros y necesita más caché de páginas.
Un plan de 2 GB deja 1280 MB para los contenedores. Asigne 512 MB a PostgreSQL y 128 MB a Traefik, y ya habrá consumido la mitad. El resto permite ejecutar dos aplicaciones pequeñas de unos 256 MB cada una. Es un servidor real y útil. No hay espacio para añadir Nextcloud y un clúster de búsqueda.
Un plan de 4 GB deja 3072 MB. Con esa cantidad caben al mismo tiempo una base de datos, un reverse proxy, tres aplicaciones y un contenedor de monitorización. Es el tamaño mínimo que merece la pena usar para algo importante, porque la memoria disponible absorbe los efectos de un despliegue defectuoso.
Un plan de 8 GB deja 6656 MB de sus 8192 MB, y el límite normalmente pasa de ser la memoria a ser la CPU o el rendimiento del disco. SSD Nodes no ofrece nada más pequeño: su plan mínimo tiene 8 GB de RAM (página de precios comprobada el 2026-10-11), por lo que en ese proveedor se aplica esta fila; las filas de 2 GB y 4 GB describen servidores más pequeños de otros proveedores. Una clase de contenedores dimensiona su consumo a partir de su configuración y no de la carga: un servidor de modelos local reserva la caché KV en proporción a su ventana de contexto, por lo que aumentar num_ctx de Ollama puede añadir gigabytes al presupuesto antes de que llegue una sola petición. Si los cálculos indican que su stack no cabe, compre el plan más grande en lugar de ajustar la configuración al límite: cuánto cuesta realmente un VPS explica cuánto valen al mes esos gigabytes adicionales.
Dos reglas mantienen los cálculos ajustados a la realidad. Establezca un límite de memoria para cada servicio, de modo que un proceso descontrolado no consuma toda la memoria del servidor. Y no asigne todo el presupuesto, porque docker compose build y pg_dump también necesitan memoria en el peor momento. Límites de memoria en Docker Compose explica la sintaxis y los errores habituales.
¿Por qué mi contenedor termina con el código 137?
Porque el kernel lo terminó. 137 es 128 más 9, y la señal 9 es SIGKILL. El contenedor solicitó más memoria de la permitida y el killer de falta de memoria (OOM) lo terminó.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoConfirme la causa en lugar de hacer suposiciones:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true indica que el contenedor alcanzó su propio límite de cgroup, y el registro del kernel identifica el proceso que seleccionó:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBEse es el caso favorable, porque el impacto quedó limitado a un contenedor. El caso problemático es un contenedor sin ningún límite. Sin un límite, su techo es toda la máquina. Por tanto, una fuga de memoria en un servicio consume los recursos del host y el kernel selecciona después una víctima según su tamaño en todo el sistema. La línea del registro pierde el prefijo Memory cgroup y muestra Out of memory: Killed process 2417 (postgres). El proceso seleccionado suele ser la base de datos, mientras el contenedor que tiene la fuga sigue ejecutándose. Por eso es más importante establecer un límite para cada servicio que acertar con el valor exacto de un límite concreto.
La swap cambia el momento del fallo, no el cálculo. La mayoría de las imágenes de VPS se distribuyen sin swap. Compruébelo con swapon --show, que no muestra nada cuando no existe. Un archivo de swap proporciona al kernel un lugar donde colocar páginas inactivas. Esto le da unos minutos para detectar el problema.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h debería mostrar ahora un total distinto de cero en la fila Swap. La swap no añade RAM. Un equipo sometido a presión constante de memoria se vuelve tan lento que no podrá conectarse por SSH para solucionarlo. Por tanto, trate la swap como un margen de seguridad y corrija el dimensionamiento.
¿Por qué UFW no bloquea mi puerto publicado por Docker?
Porque el tráfico nunca llega a la cadena que controla UFW. Cuando publica un puerto con -p 5432:5432 o con una entrada ports: de Compose, el daemon escribe una regla DNAT (traducción de direcciones de red de destino) en la tabla nat y una regla de aceptación en su propia cadena DOCKER. Un paquete dirigido a un contenedor se reenvía a ese contenedor en lugar de entregarse al host, por lo que se procesa en la ruta FORWARD y nunca pasa por las reglas INPUT que escribe UFW.
Puede observarlo en el servidor:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW puede mostrar 5432 DENY IN Anywhere mientras la tabla nat contiene una regla DNAT tcp ... to:172.18.0.2:5432 para el mismo puerto. Desde otra máquina, nc -vz your.server.ip 5432 sigue conectándose. La base de datos está en Internet público y el firewall indica que no lo está.
La solución es publicar menos puertos. Los contenedores de un mismo proyecto de Compose comparten una red y se comunican mediante el nombre del servicio, por lo que una base de datos que sólo presta servicio a la aplicación que la acompaña no necesita ninguna entrada ports:. Si necesita acceso local, vincule la publicación a loopback:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"Después de docker compose up -d, nc -vz your.server.ip 5432 falla desde el exterior y psql -h 127.0.0.1 -p 5432 sigue funcionando en el host. En una pila pequeña correctamente configurada, sólo el reverse proxy publica puertos: 80 y 443. Por qué los puertos publicados por Docker evitan UFW explica la cadena DOCKER-USER para los casos en los que debe publicar un puerto y filtrarlo igualmente. Conceptos básicos del firewall UFW explica las reglas del host subyacentes.
¿Por qué han desaparecido mis contenedores después de reiniciar?
Porque nada les indicó que volvieran a iniciarse. Un contenedor se crea con la política de reinicio no si no se especifica otra. Por eso, tras un reinicio, queda detenido y el daemon no interviene. Los reinicios no son poco frecuentes en un VPS: las actualizaciones del kernel mediante actualizaciones desatendidas, el mantenimiento del proveedor y la secuencia OOM anterior pueden provocarlos.
Deben cumplirse dos condiciones. El daemon debe iniciarse durante el arranque:
systemctl is-enabled dockerEn una instalación estándar de Ubuntu, esto muestra enabled. Después, cada servicio necesita una política:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedunless-stopped vuelve a iniciar el contenedor después de un reinicio y respeta los contenedores que haya detenido intencionadamente. always también reinicia los contenedores que haya detenido deliberadamente cada vez que se reinicia el daemon. Esto puede ser inesperado durante la depuración. Editar el archivo no basta, porque la política de reinicio se establece cuando se crea el contenedor. Ejecute docker compose up -d para recrearlo y compruebe después el valor activo:
docker inspect my-app | grep -A3 RestartPolicyA continuación, reinicie el servidor de forma intencionada y ejecute docker compose ps en el directorio del proyecto. Una pila que sobrevive a un reinicio planificado también sobrevive a uno imprevisto. Si la pila necesita garantizar un orden de inicio o ejecutar un trabajo de una sola vez durante el arranque, una unidad de systemd es una herramienta más adecuada: iniciar Docker Compose durante el arranque incluye el archivo de unidad. Para saber si un contenedor que ha vuelto a iniciarse realmente está atendiendo solicitudes, añada comprobaciones de estado de Compose.
¿Por qué está lleno el disco de mi VPS?
Porque Docker conserva todo hasta que se le indica lo contrario. Cada etiqueta de imagen que haya descargado, cada contenedor detenido, cada volumen anónimo que haya quedado tras recrear un servicio y cada capa de la caché de compilación permanecen en el disco. Un disco más grande sólo da más tiempo. Incluso el SSD de 160 GB del plan más pequeño de SSD Nodes (página de precios comprobada el 2026-10-11) se llena de la misma forma en un servidor que descarga y compila imágenes cada semana, y un sistema de archivos raíz más pequeño convierte el problema en una interrupción antes.
Un disco lleno no se manifiesta necesariamente como un bloqueo. Recibe no space left on device de un contenedor, de apt, de journald y de docker pull durante la misma hora. PostgreSQL deja de aceptar escrituras. El servidor sigue activo, por lo que el problema es más difícil de detectar que un bucle de reinicios.
Compruebe antes de eliminar:
docker system df
df -h /docker system df desglosa el total en imágenes, contenedores, volúmenes locales y caché de compilación, con una columna RECLAIMABLE junto a cada elemento. En un servidor que compila sus propias imágenes, la caché de compilación suele ser el elemento más grande.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a elimina todas las imágenes que no usa ningún contenedor. docker builder prune borra la caché de compilación. Ambas operaciones son seguras mientras los servicios están en ejecución, porque omiten todo lo que está en uso. Lo que no es seguro es docker system prune --volumes. Elimina los contenedores detenidos y, después, todos los volúmenes anónimos que no use ningún contenedor restante. Un stack detenido durante el fin de semana tiene exactamente esa estructura. Si la base de datos del stack guardaba sus datos en un volumen anónimo, que es lo que crea Docker cuando una imagen declara un volumen y el archivo compose no monta nada en esa ruta, esos datos también se eliminan. Los volúmenes con nombre sobreviven a ese comando, pero docker volume prune --all también elimina los volúmenes con nombre que no se estén usando. Lea montajes bind frente a volúmenes con nombre antes de usar cualquiera de las dos opciones y haga primero una copia de seguridad.
Los registros de los contenedores provocan un crecimiento más silencioso. El controlador json-file predeterminado no tiene límite de tamaño, por lo que un contenedor que genere muchos mensajes escribe gigabytes en /var/lib/docker/containers. Establezca un límite para todos los contenedores en /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Aplíquelo con sudo systemctl restart docker. Esta operación reinicia los contenedores, así que elija el momento adecuado. El límite se aplica a los contenedores creados después del cambio. Por tanto, recree los contenedores en ejecución con docker compose up -d --force-recreate y confirme el resultado:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersLa salida de inspect debe mostrar max-size configurado. Si está vacío, ese contenedor es anterior al cambio y sigue escribiendo sin límite.
Hábitos para mantener sano un servidor Docker pequeño
Nada de esto requiere un panel ni una herramienta que tenga que aprender.
- Ejecute
docker system dfydf -h /el primer día de cada mes. Son dos comandos y treinta segundos. Así verá la tendencia mucho antes de que provoque una interrupción del servicio. - Asigne un límite de memoria a cada servicio, incluidos los que parecen pequeños. El límite convierte una interrupción de todo el host en un único contenedor que se reinicia.
- Supervise el servidor desde otro sistema para detectar la presión de memoria o de disco antes de que intervenga el kernel. Uptime Kuma se ejecuta en un contenedor y permanece inactivo con unos 95 MB.
- Haga copias de seguridad de los volúmenes, no de los contenedores. El contenedor es desechable y el volumen no. copias de seguridad de restic en un VPS incluye una programación y una prueba de restauración.
- Fije las etiquetas de imagen en el archivo compose y actualícelas el día que elija. Con
latest, la versión que obtenga del siguientedocker compose pullserá la que se haya publicado esa mañana.
Un VPS pequeño que ejecute Docker se mantiene sano durante años cuando cuatro valores permanecen dentro de los límites: el presupuesto de memoria, la lista de puertos publicados, la política de reinicio de cada servicio y el espacio libre en disco. Todo lo demás es el mismo Docker que ya ejecuta en casa.
FAQ
¿Cuánta RAM necesito para ejecutar Docker en un VPS?
Docker consume pocos recursos. El daemon y containerd juntos usan cerca de 100 MB; el resto de los requisitos corresponde a los contenedores. Reserve primero la parte del host: 768 MB en un equipo de 2048 MB para el sistema operativo, el daemon y margen operativo. Quedan 1280 MB para los contenedores. Una base de datos de 512 MB, un reverse proxy de 128 MB y dos aplicaciones pequeñas caben en ese presupuesto. Mida su propia pila con docker stats --no-stream en lugar de confiar en una cifra publicada.
¿Puedo ejecutar Docker en un VPS de 1 GB?
Sí, para uno o dos contenedores ligeros. Cree un archivo de swap antes de empezar. Aproximadamente la mitad de un equipo de 1 GB se consume cuando ya están en ejecución el sistema operativo y el daemon de Docker. Queda espacio para una aplicación pequeña y un reverse proxy, pero no para una base de datos con carga real. La creación de imágenes en un equipo de ese tamaño fallará o hará que otro proceso termine. Cree las imágenes en otro equipo y descargue la imagen terminada.
¿UFW protege un contenedor de Docker?
No en el caso de los puertos publicados. Docker escribe sus propias reglas DNAT y de reenvío. Por eso, un paquete dirigido a un puerto publicado del contenedor se reenvía al contenedor en lugar de entregarse al host, y nunca llega a las reglas INPUT que gestiona UFW. ufw deny 5432 puede estar activo mientras ese puerto responde desde Internet. Publique el puerto en loopback con 127.0.0.1:5432:5432, mantenga los servicios internos sin publicar o filtre en la cadena DOCKER-USER.
¿Se reiniciarán mis contenedores después de reiniciar el VPS?
Sólo si se crearon con una política de reinicio. Configure restart: unless-stopped en cada servicio, ejecute docker compose up -d para recrear los contenedores con esa política y confirme que systemctl is-enabled docker muestra enabled. Después, reinicie el sistema de forma intencionada y compruebe docker compose ps. Una política de reinicio que nunca se ha probado no es una política de reinicio fiable.
¿Con qué frecuencia debo eliminar las imágenes de Docker?
Una vez al mes es suficiente para la mayoría de los servidores pequeños. También puede hacerlo cuando docker system df informe de espacio recuperable que necesite. docker image prune -a y docker builder prune son seguros mientras los servicios están en ejecución, porque omiten las imágenes y la caché que están en uso. Evite docker system prune --volumes salvo que sepa exactamente qué volúmenes no tienen referencias. Ese comando elimina los volúmenes anónimos de cualquier pila que esté detenida, y docker volume prune --all también incluye los volúmenes con nombre que no se utilizan.