Que docker compose up -d termine sin errores no significa que la aplicación ya esté preparada para producción. En un VPS también tienes que ocuparte del sistema operativo, el cortafuegos, el dominio, los certificados, las actualizaciones, los datos y la recuperación ante fallos. Esta guía parte de un repositorio que ya tiene un Dockerfile y una aplicación que escucha en 0.0.0.0:8080 dentro del contenedor y acepta las variables de base de datos del ejemplo. Comprueba la dirección de escucha de tu framework: si solo se enlaza a 127.0.0.1 o localhost, Caddy no podrá alcanzarla desde otro contenedor. Es un patrón que debes adaptar a tu aplicación, no un instalador de un clic ni una promesa de que cualquier proyecto funciona en cualquier servidor.
Si solo tienes una web estática, sin proceso que deba permanecer activo, compara primero Hostbrid Projects. Un VPS encaja cuando necesitas control del sistema y aceptas administrarlo. Hostbrid ofrece VPS; tú instalas y mantienes Docker y el software. Antes de elegir recursos, revisa tamaños y disponibilidad en la página de VPS.
Antes de contratar, sigue la guía para medir RAM, CPU y disco de una app Docker; esta página se centra en desplegar la pila y proteger sus servicios.
Para desplegar n8n, consulta la guía de n8n en un VPS, con sus opciones de instalación rápida o Docker Compose.
Diseña los caminos de red antes de arrancar
El navegador debe hablar con un proxy inverso, no con el puerto de la base de datos. En este ejemplo Caddy recibe el tráfico web y lo envía a la aplicación; PostgreSQL no publica ningún puerto en el host. Compose proporciona nombres DNS a los servicios de la misma red, así que la aplicación puede conectarse a db:5432 sin exponer PostgreSQL a Internet. Consulta la documentación de redes Docker y la guía oficial de Compose en producción.
Crea compose.yaml junto al Dockerfile de tu aplicación y un Caddyfile como el siguiente. Sustituye la configuración de app por las variables que tu programa admita de verdad. La imagen debe leer DB_HOST, DB_NAME, DB_USER y DB_PASSWORD; Compose no convierte por sí solo un programa en cliente de una base de datos.
services:
app:
build: .
restart: unless-stopped
environment:
PORT: "8080"
DB_HOST: db
DB_PORT: "5432"
DB_NAME: ${DB_NAME:?set DB_NAME in .env}
DB_USER: ${DB_USER:?set DB_USER in .env}
DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
expose: ["8080"]
networks: [edge, data]
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME:?set DB_NAME in .env}
POSTGRES_USER: ${DB_USER:?set DB_USER in .env}
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env}
volumes: [postgres_data:/var/lib/postgresql/data]
networks: [data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2
restart: unless-stopped
environment:
SITE_DOMAIN: ${SITE_DOMAIN:?set SITE_DOMAIN in .env}
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks: [edge]
depends_on: [app]
networks:
edge: {}
data:
internal: true
volumes:
postgres_data: {}
caddy_data: {}
caddy_config: {}
{$SITE_DOMAIN} {
encode zstd gzip
reverse_proxy app:8080
}
La red data está aislada y solo conecta la aplicación con la base. La aplicación también participa en edge para que Caddy pueda alcanzar su puerto; PostgreSQL no está unido ni a la interfaz pública del host ni a la red de Caddy. expose deja constancia del puerto de la aplicación para el stack, pero no lo publica en el host. Solo Caddy publica 80 y 443. Conserva el volumen de PostgreSQL: puedes reemplazar los contenedores, pero no debes perder los datos. Los volúmenes Docker persisten de forma independiente a los contenedores; un volumen del mismo VPS, sin embargo, no es una copia de seguridad.
Prepara servidor, dominio y acceso
Elige una imagen Linux mantenida, instala actualizaciones, crea un usuario administrador sin privilegios de root y configura acceso SSH con claves. Instala Docker Engine y el complemento Compose siguiendo las instrucciones oficiales para esa distribución. Antes de endurecer el cortafuegos, conserva una segunda sesión SSH abierta para poder corregir un bloqueo. Si es posible, limita SSH a direcciones de confianza y permite tráfico web solo cuando el sitio deba ser público. Docker advierte que los puertos publicados pueden interactuar con las reglas del cortafuegos de formas que el operador no espera. Comprueba cómo se comporta tu versión de Docker y el firewall del servidor; no des por hecho que una regla de UFW bloquea todos los puertos publicados. Revisa la guía oficial de publicación de puertos.
Configura el registro A del dominio hacia la IPv4 del VPS. Añade AAAA solo si IPv6 está realmente encaminado y configurado. Comprueba la resolución DNS desde fuera de tu red. El HTTPS automático de Caddy necesita un dominio público y puertos de validación accesibles. Permite TCP 80 y 443 tanto en el firewall del proveedor como en el sistema operativo. No publiques 5432 para facilitar una conexión ocasional; si necesitas administrar la base remotamente, usa una red privada o un túnel autenticado y limita quién puede acceder.
Crea un .env privado con SITE_DOMAIN, DB_NAME, DB_USER y una contraseña larga generada para DB_PASSWORD. Restringe los permisos del archivo, exclúyelo de Git y añádelo a .dockerignore: build: . envía un contexto al constructor. No pegues credenciales en tickets ni capturas. Un .env de Compose es configuración, no una bóveda de secretos: quien tenga acceso root o a Docker podría inspeccionar valores de los contenedores. Si una contraseña queda expuesta, cámbiala. Dentro del contenedor, localhost se refiere al propio contenedor; para esta base el hostname es db.
Valida la configuración y observa el servicio
Desde la carpeta del proyecto, ejecuta:
docker compose config -q
docker compose up -d --build
docker compose ps
docker compose logs --tail=100 app db caddy
config -q detecta errores de sintaxis y variables obligatorias ausentes, pero no confirma que la aplicación funcione. Comprueba que la base llega a estado saludable, que el proceso de la aplicación registra una conexión correcta y que Caddy puede emitir el certificado. Desde otra red, abre el dominio por HTTPS y prueba inicio de sesión, formularios, cargas y el recorrido importante para tus usuarios. Comprueba también que los puertos 80 y 443 sean accesibles y que 5432 no lo sea. Que un contenedor figure como activo no prueba que el producto responda bien ni que puedas recuperar sus datos.
Actualiza con una forma de volver atrás
Guarda el Compose y el código en un repositorio privado, pero deja .env y los datos reales fuera de Git. Anota la imagen o el commit actualmente desplegado antes de cambiarlo. Construye o descarga la versión nueva, valida la configuración combinada y actualiza de manera intencional. La guía de Docker sobre Compose en producción explica archivos de configuración adicionales y recreación selectiva de servicios. Para un cambio delicado, prueba primero la actualización sobre una copia restaurada. Volver al código anterior no revierte necesariamente una migración de base de datos; planifica el esquema por separado.
Haz copias con una herramienta consciente de PostgreSQL y envíalas fuera del VPS. Practica la restauración en un entorno aislado. Incluye archivos subidos, configuración y el directorio de datos de Caddy cuando corresponda. Una copia en caliente del volumen de base de datos puede quedar inconsistente: sigue las recomendaciones del motor. Define retención y cifra las copias externas. Recrea contenedores solo después de confirmar que los datos persistentes no están en la capa efímera del contenedor. Evita docker compose down -v como limpieza habitual: -v puede borrar volúmenes nominales y sus datos.
Esta configuración es un punto de partida; no incluye despliegue Docker automático de Hostbrid, monitorización, aplicación de parches, servicio de copias, garantía de disponibilidad ni recomendación de capacidad. Si quieres administrar tu propio servidor, compara los planes VPS de Hostbrid; si necesitas orientación para elegir, puedes contactar con el equipo. La guía de diagnóstico Node y Docker revisa el puerto y la salud de un proceso; no cubre este despliegue de varios servicios.