Saltar al contenido
infraestructura / · 4 min

Veinticinco contenedores en un solo VPS: un stack por aplicacion

Cinco plataformas Laravel, un microservicio en Java, tres bots y almacenamiento S3 en una sola maquina. Por que un docker-compose por aplicacion y no uno gigante, y por que todo escucha en loopback.

Administrador · Desarrollador Laravel
Veinticinco contenedores en un solo VPS: un stack por aplicacion

Tengo un solo VPS. Dentro corren veinticinco contenedores: cinco plataformas Laravel, un microservicio en Java, tres bots de n8n, almacenamiento S3 y monitoreo. Cada uno con su base de datos.

La pregunta que me hacen cuando lo cuento es siempre la misma: por qué no todo en una base y un solo docker-compose.yml. La respuesta corta es que lo intenté y lo desarmé a los dos meses.

Un stack por aplicación, no un compose gigante

Cada aplicación vive en su propia carpeta con su propio compose:

~/apps/
  pos/          docker-compose.yml  .env
  electoral/    docker-compose.yml  .env
  territorio/   docker-compose.yml  .env
  n8n-pipe/     docker-compose.yml  .env
  minio/        docker-compose.yml  .env

Lo que se gana no es elegancia, es poder equivocarse en pequeño. Cuando reconstruyo la imagen de electoral, el POS no se entera. Cuando una migración deja la base de territorio a medias, restauro esa base. Y cuando quiero apagar algo, es docker compose down en una carpeta, no editar un archivo de cuatrocientas líneas rezando por no borrar el servicio equivocado.

El costo es real: hay cinco Postgres corriendo en vez de uno. En una máquina con memoria contada eso importa. En la mía, cada Postgres en reposo ocupa bastante menos de lo que cuesta un domingo restaurando la base equivocada.

Todo escucha en loopback

Ninguna aplicación publica un puerto al mundo. En el compose se ve así:

ports:
  - "127.0.0.1:8082:80"    # y no "8082:80"

La diferencia entre esas dos líneas es si tu Postgres está en internet o no. Con 8082:80, Docker escribe una regla en iptables que pasa por encima de tu firewall: puedes tener firewalld impecable y el puerto abierto de todas formas. Es el error que más he visto en servidores ajenos, y no da ninguna señal — todo funciona, solo que también funciona para los demás.

Con el prefijo 127.0.0.1, al puerto solo llega quien ya está dentro de la máquina. Y quien está dentro es nginx.

Un nginx de verdad al frente

El reverse proxy del host es el único que habla con internet. Tiene un server por subdominio, cada uno con su certificado de Let's Encrypt, y todos redirigen HTTP a HTTPS:

server {
    server_name pos.laravelconmanuel.dev;
    location / {
        proxy_pass http://127.0.0.1:8082;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Agregar una aplicación nueva es: carpeta, compose, registro DNS, archivo de nginx, certbot. Cinco pasos, siempre los mismos. Esa repetición aburrida es la señal de que la arquitectura está bien: lo interesante debería pasar dentro de la aplicación, no en el despliegue.

Lo interno no sale a internet

El monitoreo y la consola de administración del almacenamiento no tienen subdominio. No están detrás de una contraseña: están detrás de no existirpara el exterior. Se llega a ellos por una red privada Tailscale o por un túnel SSH.

Es la regla que más tranquilidad me ha dado. Un panel de administración publicado con login es una superficie de ataque permanente que hay que parchar. Un panel que no responde desde internet no la tiene.

Versiones fijas, no "latest"

Todas las imágenes están pinneadas:

image: n8nio/n8n:2.34.6        # y no :latest
image: postgres:17-alpine

Con latest, un docker compose up -d rutinario puede traerte una versión mayor sin avisar. Lo descubres cuando algo dejó de funcionar y no sabes qué cambiaste, porque no cambiaste nada. Fijar la versión convierte una actualización en una decisión, que es lo que debería ser.

Lo que no haría otra vez

Reusar la misma contraseña de base de datos en dos stacks. La puse igual "mientras" y ese "mientras" duró tres semanas, hasta que una auditoría me la señaló. Si se filtra una, caen dos. Generar una aleatoria por stack cuesta un openssl rand -hex 24.

#seguridad #arquitectura #despliegue
Comentarios 0

Nadie ha comentado todavía. Estrena la sección.

Deja tu comentario
Tu correo no se publica. Reviso los comentarios antes de publicarlos.

Seguir leyendo