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
sudooroot; - Docker y Docker Compose instalados;
- un dominio o subdominio apuntando con registro
Aa la IP pública del servidor; - puertos
80/tcpy443/tcpabiertos 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 update
sudo apt upgrade -y
sudo apt install -y curl ca-certificates ufwConfigura 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 statusSi 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-app
sudo chown -R "$USER":"$USER" /opt/mi-app
cd /opt/mi-appPara 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

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 5: Levantar el despliegue
Construye y levanta los servicios:
docker compose up -d --buildVerifica el estado:
docker compose ps
docker compose logs -f caddyPrueba desde tu equipo:
curl -I https://app.tudominio.com
curl https://app.tudominio.comSi 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 appSi 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,443y 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
80y443estén abiertos; - no haya otro servicio usando esos puertos;
- el
Caddyfiletenga 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 caddyEl 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.