Porque elegirlo?

  • usas Docker Compose para versionar tu despliegue;
  • no quieres exponer directamente el puerto de tu aplicación;
  • quieres una configuración más corta que una pila NGINX + Certbot tradicional.

No reemplaza a una arquitectura de alta disponibilidad ni a un balanceador administrado, pero es una excelente base para proyectos productivos simples.

Requisitos previos

Vas a necesitar:

  • un Cloud Server con Ubuntu o Debian;
  • acceso SSH con usuario sudo o root;
  • Docker y Docker Compose instalados;
  • un dominio o subdominio apuntando con registro A a la IP pública del servidor;
  • puertos 80/tcp y 443/tcp abiertos en el firewall;
  • el puerto SSH habilitado solo para administración.
Importante: para validación HTTP-01 de Let's Encrypt, el puerto 80 debe estar accesible. Luego Caddy puede redirigir el tráfico a HTTPS.

Paso 1: Preparar el servidor


Actualiza paquetes e instala herramientas básicas:

sudo apt updatesudo apt upgrade -ysudo apt install -y curl ca-certificates ufw

Configura el firewall local. Antes de activar UFW, asegúrate de permitir tu puerto SSH real:

sudo ufw allow OpenSSHsudo ufw allow 80/tcpsudo ufw allow 443/tcpsudo ufw enablesudo ufw status

Si usas el Firewall Virtual de DonWeb Cloud, abre también 80/tcp y 443/tcp desde el panel del Cloud Server. Si tu SSH usa un puerto personalizado, habilita ese puerto específico.

Paso 2: Crear la estructura del proyecto

Crea un directorio para el despliegue:

sudo mkdir -p /opt/mi-appsudo chown -R "$USER":"$USER" /opt/mi-appcd /opt/mi-app

Para el ejemplo, vamos a usar una app Node.js mínima.

Crea package.json:

{  "name": "mi-app-cloud",  "version": "1.0.0",  "type": "module",  "scripts": {    "start": "node server.js"  },  "dependencies": {    "express": "^5.1.0"  }}

Crea server.js:

import express from "express";const app = express();const port = process.env.PORT || 3000;app.get("/", (req, res) => {  res.json({    status: "ok",    message: "Aplicacion publicada con Docker Compose y Caddy"  });});app.listen(port, "0.0.0.0", () => {  console.log(`App escuchando en puerto ${port}`);});

Crea Dockerfile:

FROM node:22-alpineWORKDIR /appCOPY package*.json ./RUN npm install --omit=devCOPY server.js .ENV NODE_ENV=productionENV PORT=3000EXPOSE 3000USER nodeCMD ["npm", "start"]

Paso 3: Configurar Docker Compose



Crea compose.yml:

services:  app:    build: .    restart: unless-stopped    environment:      NODE_ENV: production      PORT: 3000    networks:      - web  caddy:    image: caddy:2-alpine    restart: unless-stopped    depends_on:      - app    ports:      - "80:80"      - "443:443"    volumes:      - ./Caddyfile:/etc/caddy/Caddyfile:ro      - caddy_data:/data      - caddy_config:/config    networks:      - webnetworks:  web:volumes:  caddy_data:  caddy_config:

Observa que el servicio app no publica el puerto 3000 hacia Internet. Solo Caddy puede alcanzarlo dentro de la red Docker.

Paso 4: Configurar Caddy como proxy reverso

Crea Caddyfile y reemplaza app.tudominio.com por tu dominio real:

app.tudominio.com {  encode zstd gzip  reverse_proxy app:3000}




Con esta configuración, Caddy recibe tráfico público en 80 y 443, emite el certificado TLS y reenvía las solicitudes al contenedor app.

Paso 5: Levantar el despliegue

Construye y levanta los servicios:

docker compose up -d --build

Verifica el estado:

docker compose psdocker compose logs -f caddy

Prueba desde tu equipo:

curl -I https://app.tudominio.comcurl https://app.tudominio.com

Si todo está correcto, deberías recibir una respuesta HTTPS válida y el JSON de la aplicación.



Paso 6: Actualizar la aplicación

Cuando cambies código, reconstruye solo la app:

docker compose up -d --build app

Si cambias el Caddyfile, recarga Caddy:

docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile




Paso 7: Reforzar seguridad

Aplica estas recomendaciones antes de usar este esquema en producción:

  • No publiques el puerto interno de la app con ports.
  • Mantén abiertos hacia Internet solo 80, 443 y el puerto SSH necesario.
  • Usa variables de entorno seguras y no subas secretos al repositorio.
  • Ejecuta backups si la app tiene base de datos o archivos persistentes.
  • Actualiza imágenes periódicamente.
  • Revisa logs después de cada deploy.
  • Usa contraseñas robustas y, cuando sea posible, acceso SSH con llaves.
  • Limita el acceso SSH por IP desde el Firewall Virtual si tu operación lo permite.









Problemas frecuentes

El certificado no se emite

Revisa que:

  • el dominio apunte a la IP pública correcta;
  • los puertos 80 y 443 estén abiertos;
  • no haya otro servicio usando esos puertos;
  • el Caddyfile tenga el dominio real, no un placeholder.

La app funciona por Docker pero no por HTTPS

Verifica que el nombre del servicio en reverse_proxy app:3000 coincida con el servicio definido en compose.yml.

También puedes revisar logs:

docker compose logs appdocker compose logs caddy

El puerto 80 parece innecesario

Aunque todo el tráfico final use HTTPS, el puerto 80 sigue siendo importante para la validación HTTP-01 y para redirigir tráfico HTTP a HTTPS.

Conclusión

Docker Compose y Caddy forman una combinación muy cómoda para publicar aplicaciones en un Cloud Server: Compose ordena los servicios y Caddy simplifica el proxy reverso con HTTPS automático.

Si ya trabajas con contenedores, este flujo te permite pasar de una app local a un despliegue público con menos piezas manuales, manteniendo una configuración clara y versionable. En DonWeb Cloud puedes usar este enfoque sobre un Cloud Server con Docker, abrir solo los puertos necesarios y mantener el control de tu infraestructura.

Cloud Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Descubre la mejor solución de Cloud Hosting