Respuesta rápida: Caddy se pone delante de la app como proxy reverso y emite y renueva solo el certificado HTTPS. Todo el proxy cabe en un Caddyfile:
app.tudominio.com {
  encode zstd gzip
  reverse_proxy app:3000
}

Levanta todo con docker compose up -d --build y comprueba el certificado con curl -I https://app.tudominio.com. El dominio tiene que apuntar a la IP del servidor y los puertos 80 y 443 deben estar abiertos.

Nivel: inicial · Requiere: VPS Ubuntu con Docker Compose y un dominio apuntando a su IP


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


Paso 1: preparar el servidor antes de desplegar nada

Actualiza paquetes e instala herramientas básicas:

sudo apt update
sudo apt upgrade -y
sudo 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 OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo 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

Paso 2: crear la estructura del proyecto con el Compose y el Caddyfile

Crea un directorio para el despliegue:

sudo mkdir -p /opt/mi-app
sudo chown -R "$USER":"$USER" /opt/mi-app
cd /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-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY server.js .
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
USER node
CMD ["npm", "start"]

Paso 3: Configurar Docker Compose


Paso 3: configurar los servicios en Docker Compose sobre una red interna compartida


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:
      - web
networks:
  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 4: configurar Caddy como proxy reverso hacia el servicio de la aplicación

Paso 5: Levantar el despliegue

Construye y levanta los servicios:

docker compose up -d --build

Verifica el estado:

docker compose ps
docker compose logs -f caddy

Prueba desde tu equipo:

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

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

Paso 5: levantar el despliegue y comprobar que responde por HTTPS



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 6: actualizar la aplicación reconstruyendo la imagen y recargando la configuración




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.






Paso 7: reforzar la seguridad dejando públicos solo los puertos necesarios




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 app
docker 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 Servers by Donweb

Ya tienes HTTPS automático y el puerto interno cerrado. Falta dónde apuntar el DNS.
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

Publica tu app en un Cloud Server