Docker en un VPS: por dónde empezar
Docker permite ejecutar en un servidor un bot, una web, una base de datos y la monitorización sin que se estorben entre sí, y mudarse a otro VPS se reduce a copiar un par de archivos y los datos. La instalación lleva cinco minutos; los problemas de verdad llegan después: puertos de la base de datos abiertos al exterior, logs de decenas de gigas y datos que desaparecen junto con el contenedor. Veamos cómo prevenirlos desde el principio.
Un contenedor es una aplicación con todas sus bibliotecas y ajustes, empaquetada en una imagen. En el servidor se arranca con un solo comando y funciona igual en cualquier Linux. Para un VPS, eso significa que no hay que buscar versiones de Python, Node.js o PostgreSQL compatibles con el sistema, que varios proyectos conviven en el mismo servidor y que el archivo compose.yaml describe todo el conjunto de servicios de forma que puedes levantarlo en un servidor nuevo en minutos.
Qué VPS hace falta para Docker
Virtualización KVM. Docker usa funciones del kernel de Linux: espacios de nombres, cgroups y sus propias reglas de red. En KVM el servidor tiene su propio kernel, y Docker funciona sin restricciones. En OpenVZ y LXC el kernel se comparte con otros clientes, y allí Docker o no arranca o funciona con limitaciones que dependen del proveedor. Más detalles en KVM u OpenVZ.
Memoria y núcleos. Docker en sí ocupa poco; la memoria se la comen los contenedores. Referencias:
- 1 GB: uno o dos contenedores ligeros, como un bot, una API pequeña o la monitorización;
- 2 núcleos y 2 GB: una web con base de datos y proxy inverso, varios servicios pequeños;
- 2–4 núcleos y 4 GB: varios proyectos, PostgreSQL, Redis y compilación de imágenes en el propio servidor;
- desde 8 GB: GitLab, n8n con muchos flujos, Elasticsearch y otros servicios pesados.
Disco. Las imágenes ocupan espacio: una imagen base con Python o Node.js pesa cientos de megas, y las versiones antiguas se acumulan con cada actualización. Contrata desde 30 GB en NVMe, y más si vas a compilar imágenes en el servidor.
Según el catálogo de accsly a 1 de noviembre de 2026, un plan KVM con 2 núcleos y 2 GB de memoria cuesta en la mediana del mercado unos 15 USD (unos 13,5 EUR) al mes, y una cuarta parte de esos planes cuesta menos de 9 USD. Con 2 núcleos y 4 GB, unos 20 USD (unos 18 EUR), y una cuarta parte, menos de 12 USD. Los importes están convertidos de rublos y redondeados. Selecciones: VPS para Docker, VPS con KVM, VPS con 4 GB. Para hacer pruebas resulta práctico el pago por horas: un servidor para una tarde cuesta muy poco.
Antes de instalar
Parte de un Ubuntu 22.04 o 24.04 LTS o de un Debian 12 recién instalado y aplica primero la protección básica: actualizaciones, acceso por clave, un usuario aparte y firewall. Todo esto está en Primeros 10 pasos tras comprar un VPS. Un panel de control de webs normalmente no hace falta en un servidor con contenedores; el porqué, en Paneles de control de servidor.
Instalación desde el repositorio oficial
El paquete docker.io del repositorio de Ubuntu a menudo va por detrás de la versión actual y no siempre incluye Compose. Es más fiable instalarlo desde el repositorio de Docker. Comandos para Ubuntu según la documentación oficial:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
En Debian los comandos son los mismos, salvo que en las direcciones linux/ubuntu pasa a ser linux/debian y en lugar de UBUNTU_CODENAME se usa VERSION_CODENAME. Comprueba la instalación:
sudo docker run hello-world
docker compose version
También existe un instalador de un solo comando en get.docker.com, pero la propia documentación de Docker no lo aconseja para servidores en producción: instala muchos paquetes sin pedir confirmación y siempre toma la última versión.
El grupo docker. Para no escribir sudo delante de cada comando, se añade el usuario al grupo docker: sudo usermod -aG docker $USER y se vuelve a iniciar sesión. Recuerda que equivale a permisos de root: un miembro del grupo puede arrancar un contenedor con acceso a todo el sistema de archivos del servidor. Añade ahí solo tu propia cuenta de trabajo.
Primer proyecto con Docker Compose
Los comandos docker run sueltos con una docena de parámetros se olvidan enseguida. Es más cómodo describir el proyecto en un archivo compose.yaml dentro de su propia carpeta, por ejemplo /opt/app. Un ejemplo: una aplicación web con base de datos PostgreSQL:
services:
app:
image: ghcr.io/example/app:1.4.2
restart: unless-stopped
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
ports:
- "127.0.0.1:8080:8080"
depends_on:
- db
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
Pon la contraseña al lado, en un archivo .env con la línea DB_PASSWORD=..., y quita a los demás usuarios el permiso de lectura: chmod 600 .env. Para arrancar, docker compose up -d; para ver el log, docker compose logs -f app; para parar, docker compose down.
En este ejemplo importan tres cosas:
restart: unless-stopped: los contenedores vuelven a arrancar tras reiniciar el servidor;- la base de datos no tiene sección
ports: la aplicación la ve por el nombredbdentro de la red interna del proyecto, y desde fuera no es accesible; - el puerto de la aplicación solo está publicado en
127.0.0.1, y hacia fuera lo sirve nginx o Caddy con un certificado TLS.
Puertos y firewall
Es la trampa principal para quien empieza. Cuando un contenedor publica un puerto como "5432:5432", Docker añade por su cuenta reglas de iptables, y ese puerto queda abierto al exterior independientemente de las reglas de ufw o firewalld. La documentación de Docker lo advierte claramente: las reglas del firewall no se aplican a los puertos publicados de los contenedores. Así es como acaban expuestas en internet, con más frecuencia que de cualquier otra forma, bases de PostgreSQL, MongoDB y Redis sin contraseña.
La regla es sencilla: publica hacia fuera solo lo que debe ser accesible para todos, normalmente los puertos 80 y 443 del proxy inverso. Todo lo demás, sin sección ports o ligado a 127.0.0.1. Para ver qué escucha realmente el servidor, usa ss -tlnp, y desde fuera, desde otro ordenador, nc -zv IP-del-servidor 5432.
Logs y espacio en disco
Por defecto, Docker guarda la salida de los contenedores en archivos JSON sin límite de tamaño. Una aplicación muy habladora llena el disco en un par de meses y el servidor se para. Limita los logs de todos los contenedores en el archivo /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Después, sudo systemctl restart docker. El ajuste se aplica a los contenedores creados a partir de ese momento, así que vuelve a crear los proyectos: docker compose up -d --force-recreate.
El segundo devorador de espacio son las imágenes antiguas. docker system df muestra cuánto ocupan, y docker image prune -a borra las que no se usan. No ejecutes docker system prune --volumes sin saber lo que hace: borra también los volúmenes de los proyectos parados, con sus datos.
Dónde viven los datos y cómo copiarlos
Todo lo que la aplicación escribe dentro del contenedor desaparece al volver a crearlo. Solo se conserva lo que está en volúmenes (volumes) o en carpetas del servidor montadas en el contenedor. Antes de arrancar cualquier imagen, mira en su documentación qué carpetas hay que sacar a un volumen: para PostgreSQL es /var/lib/postgresql/data; otras aplicaciones tienen sus propias rutas.
Copiar los archivos de una base de datos en marcha no es fiable. Haz un volcado a través del propio contenedor:
docker compose exec -T db pg_dump -U app app | gzip > /backup/app-$(date +%F).sql.gz
Añade este comando a cron y envía las copias fuera del servidor, junto con compose.yaml y .env. Cómo organizar el almacenamiento externo y la verificación de las copias se explica en Copias de seguridad de un VPS.
Actualización de contenedores
Indica en compose.yaml una versión concreta de la imagen (postgres:16, app:1.4.2) y no latest: así la actualización ocurre cuando tú la planificas y no en un reinicio cualquiera. El orden de la actualización:
- hacer un volcado de la base de datos;
- cambiar la versión en
compose.yaml; docker compose pullydocker compose up -d;- revisar el log y el funcionamiento de la aplicación y después borrar la imagen antigua.
Las versiones mayores de las bases de datos, por ejemplo de PostgreSQL 16 a 17, no se actualizan así: la nueva versión necesita trasladar los datos mediante volcado y carga. El sistema del servidor y el propio Docker se actualizan con el habitual apt upgrade.
Siguientes pasos
Cuando en el servidor hay varios proyectos, conviene instalar un proxy inverso que obtenga los certificados por sí solo y una monitorización de disponibilidad. Para un bot en Python o Node.js en un contenedor basta un servidor pequeño; los detalles, en VPS para un bot de Telegram. Si los contenedores se multiplican y un servidor se queda corto, el siguiente paso son varios servidores con un orquestador; los planes están en la selección VPS para Kubernetes. Para automatización y CI en contenedores están las selecciones VPS para n8n y VPS para GitLab.