Despliegue de Caddy como proxy inverso para contenedores Docker en un VPS con HTTPS automático
TL;DR
En esta guía detallada, configuraremos Caddy como un servidor proxy inverso altamente eficiente para sus contenedores Docker en un servidor privado virtual (VPS), asegurando la obtención y renovación automática de certificados SSL/TLS a través de Let's Encrypt. Aprenderá cómo desplegar y vincular Docker, Docker Compose y Caddy para proporcionar acceso seguro y escalable a sus aplicaciones web que se ejecutan en contenedores, con un esfuerzo mínimo de configuración.
- Instalación y configuración básica del sistema operativo (Ubuntu 24.04/26.04 LTS).
- Despliegue de Docker y Docker Compose para la gestión de contenedores.
- Instalación y configuración de Caddy como proxy inverso.
- Configuración de Caddy para la emisión automática de certificados HTTPS.
- Ejemplo de despliegue de una aplicación en Docker y su proxy a través de Caddy.
- Recomendaciones de seguridad, copias de seguridad y mantenimiento del sistema.
¿Qué configuramos y por qué?
En esta guía, nos dedicaremos a crear una infraestructura robusta y segura para alojar aplicaciones web en su propio VPS. La tarea principal es proporcionar acceso a las aplicaciones que se ejecutan dentro de contenedores Docker desde el exterior a través de un nombre de dominio, protegiendo automáticamente la conexión con HTTPS. Para ello, utilizaremos una combinación de tres componentes clave:
- Docker: Plataforma para la contenerización de aplicaciones. Docker permite empaquetar aplicaciones con todas sus dependencias en "contenedores" aislados, lo que garantiza su portabilidad y previsibilidad en cualquier entorno. Utilizaremos Docker Compose para orquestar múltiples contenedores que componen una sola aplicación o servicio.
- Caddy: Un servidor web moderno y proxy inverso. Caddy se distingue por su simplicidad de configuración y, lo más importante, por su soporte integrado para la emisión y renovación automática de certificados SSL/TLS a través de Let's Encrypt. Esto elimina la necesidad de configurar manualmente Certbot u otras herramientas para HTTPS, simplificando significativamente el proceso.
- VPS (Virtual Private Server): Un servidor privado virtual que nos proporcionará la potencia de cómputo y los recursos de red necesarios para alojar nuestra infraestructura.
En resumen, el lector obtendrá un sistema completamente configurado, capaz de alojar de forma segura y eficiente una o varias aplicaciones web (por ejemplo, GitLab, Mattermost, WordPress o aplicaciones SaaS personalizadas) en contenedores Docker, accesibles mediante nombres de dominio con HTTPS automático. Esta es una solución ideal para desarrolladores, startups y entusiastas que desean tener control total sobre su infraestructura sin los altos costos de los servicios gestionados en la nube.
Alternativas: Cloud-managed vs. Self-hosted en VPS
Existen dos enfoques principales para el despliegue de aplicaciones web:
-
Servicios Cloud-managed: Plataformas como Heroku, Netlify, AWS Elastic Beanstalk, Google App Engine, ofrecen alta automatización, escalabilidad y un esfuerzo mínimo de administración. Simplemente sube su código y la plataforma se encarga de los servidores, el escalado, las bases de datos y el HTTPS.
Ventajas: Simplicidad, alta disponibilidad, escalado automático.
Desventajas: Alto costo con el crecimiento, menor control sobre la infraestructura, dependencia de un proveedor específico, a veces opciones de personalización limitadas. -
Self-hosted en un servidor VPS/Dedicado: Este enfoque implica alquilar un servidor "bare-metal" (virtual o físico) y configurar de forma independiente toda la pila: sistema operativo, servidor web, base de datos, contenerización, etc.
Ventajas: Control total sobre cada aspecto del sistema, costo significativamente menor a largo plazo (especialmente con cargas moderadas), flexibilidad en la elección de tecnologías y configuraciones, privacidad de los datos.
Desventajas: Requiere conocimientos técnicos y tiempo para la configuración y el mantenimiento, la responsabilidad de la seguridad y las copias de seguridad recae en usted.
La elección de self-hosted en un VPS con Caddy y Docker es el punto intermedio ideal para quienes buscan un equilibrio entre control, costo y facilidad de uso. Caddy reduce significativamente la barrera de entrada para la configuración manual de HTTPS, y Docker simplifica la gestión de aplicaciones.
¿Qué configuración de VPS se necesita para esta tarea?
La elección de una configuración de VPS adecuada es crucial para el funcionamiento estable de sus aplicaciones. Los requisitos dependerán del número y la intensidad de recursos de los contenedores Docker que planee ejecutar.
Requisitos mínimos para una instalación básica (Caddy + 1-2 contenedores ligeros)
- CPU: 1-2 núcleos (por ejemplo, Intel Xeon E3/E5 o AMD EPYC). Para la mayoría de las aplicaciones web, un núcleo es suficiente, pero dos proporcionarán un margen de rendimiento y una mejor capacidad de respuesta.
- RAM: 2 GB. Docker y Caddy por sí solos consumen poco, pero cada aplicación en un contenedor requiere su propia memoria. 2 GB es un mínimo cómodo para el SO, el demonio de Docker y un par de servicios poco exigentes (por ejemplo, un pequeño blog de WordPress sin una SGBD pesada o un servicio API simple).
- Disco: 40-60 GB NVMe SSD. Un SSD acelera significativamente las operaciones de entrada/salida, lo cual es importante para las bases de datos y el inicio rápido de contenedores. NVMe ofrece aún mayor velocidad. 40-60 GB son suficientes para el SO, imágenes de Docker, contenedores y un pequeño volumen de datos. Si se planean grandes volúmenes de datos (por ejemplo, almacenamiento de archivos, registros, copias de seguridad), se necesitará más espacio.
- Red: 100-200 Mbit/s. Para la mayoría de las tareas, esto es más que suficiente. Más importante es la estabilidad del canal y la baja latencia.
Plan de VPS específico para la tarea
Para el despliegue de Caddy con 3-5 contenedores Docker (por ejemplo, GitLab, Mattermost o varios microservicios) se recomienda el siguiente plan de configuración:
- CPU: 2-4 núcleos (por ejemplo, Intel Xeon E5-2690v4 o AMD EPYC serie 7002). Esto proporcionará suficiente rendimiento para procesar solicitudes y tareas en segundo plano de los contenedores.
- RAM: 4-8 GB. Para aplicaciones más exigentes, como GitLab, Mattermost o un servidor de Minecraft, 4 GB es el mínimo, 8 GB proporcionarán mucha más libertad y estabilidad.
- Disco: 80-160 GB NVMe SSD. GitLab, Minecraft y otras aplicaciones similares pueden ocupar un espacio considerable en el disco. Un NVMe SSD garantizará una alta velocidad de trabajo con los datos.
- Red: 500 Mbit/s - 1 Gbit/s. Un alto ancho de banda será útil para aplicaciones con un gran número de usuarios o tráfico intenso.
Se puede considerar un VPS con las características indicadas para obtener una relación óptima entre precio y rendimiento para esta tarea. Asegúrese de que el plan elegido incluya una dirección IPv4 pública, que es necesaria para acceder a sus servicios desde Internet y para el funcionamiento de Let's Encrypt.
Cuándo se necesita un dedicado, no un VPS
Un servidor dedicado se convierte en la opción preferida cuando:
- Alto rendimiento y estabilidad: Se requiere el máximo rendimiento predecible sin "vecinos ruidosos" en un mismo servidor físico.
- Grandes volúmenes de datos: Se planea almacenar cientos de gigabytes o terabytes de datos.
- Cálculos complejos: Se necesitan procesadores especializados (por ejemplo, GPU para aprendizaje automático) o un número muy grande de núcleos.
- Requisitos estrictos de seguridad/cumplimiento: Se requiere un control físico total sobre el hardware.
- Tráfico masivo: Se espera un tráfico de red muy alto (decenas de TB al mes).
Para la mayoría de las tareas descritas en esta guía (GitLab para un equipo, Mattermost, Minecraft para amigos), un VPS potente será suficiente. Sin embargo, si está ejecutando un gran proyecto SaaS con miles de usuarios o un criptonodo de alta carga, considere un servidor dedicado adecuado.
Ubicación: qué factores influye
La elección de la ubicación del VPS tiene varios aspectos clave:
- Latencia: Cuanto más cerca esté el servidor de su audiencia principal, menor será la latencia y más rápido se cargarán las páginas para los usuarios finales. Para una audiencia europea, elija un servidor en Europa; para una audiencia americana, en Norteamérica.
- Requisitos regulatorios: En algunas jurisdicciones existen leyes estrictas sobre el almacenamiento de datos (por ejemplo, GDPR en la UE). La elección de la ubicación del servidor puede afectar el cumplimiento de estos requisitos.
- Costo: Los precios de los VPS pueden variar ligeramente según la región.
Para la mayoría de las tareas, si su audiencia está distribuida, elija una ubicación que esté geográficamente cerca de usted o de su grupo principal de usuarios.
Preparación del servidor
Después de obtener acceso a su nuevo VPS, es necesario realizar una serie de configuraciones iniciales para garantizar la seguridad y la comodidad de uso. Utilizaremos Ubuntu Server 24.04/26.04 LTS como sistema operativo.
Se asume que ha obtenido acceso al servidor por SSH como usuario root o como otro usuario con privilegios sudo.
1. Actualización del sistema
Primero, actualizaremos todos los paquetes a sus versiones más recientes.
sudo apt update # Actualiza las listas de paquetes disponibles
sudo apt upgrade -y # Actualiza todos los paquetes instalados
sudo apt autoremove -y # Elimina paquetes innecesarios
2. Creación de un nuevo usuario con privilegios sudo (si trabaja como root)
Trabajar como usuario root no es seguro. Crearemos un nuevo usuario y le otorgaremos privilegios sudo.
sudo adduser username # Reemplace 'username' con el nombre de usuario deseado. Siga las instrucciones para crear una contraseña.
sudo usermod -aG sudo username # Agrega el usuario al grupo sudo para obtener privilegios de administrador.
Ahora, cierre la sesión SSH actual (exit) e inicie sesión con el nuevo usuario:
ssh username@your_vps_ip # Reemplace 'username' y 'your_vps_ip'
3. Configuración de claves SSH para un acceso seguro
El uso de claves SSH es mucho más seguro que las contraseñas. Si aún no tiene una clave SSH, genérela en su máquina local:
ssh-keygen -t ed25519 -C "[email protected]" # Crea una nueva clave SSH (ed25519 es más seguro)
Luego, copie la clave pública a su VPS:
ssh-copy-id username@your_vps_ip # Copia la clave pública al servidor
Ahora puede iniciar sesión en el servidor sin contraseña. Después de verificar el funcionamiento de las claves SSH, se recomienda deshabilitar el inicio de sesión por contraseña para root y su nuevo usuario, y también prohibir el inicio de sesión para root.
sudo nano /etc/ssh/sshd_config # Abre el archivo de configuración del servidor SSH
Encuentre y modifique las siguientes líneas:
PermitRootLogin no
PasswordAuthentication no
Guarde los cambios (Ctrl+X, Y, Enter) y reinicie el servidor SSH:
sudo systemctl restart sshd # Reinicia el servicio SSH
Importante: ¡Asegúrese de poder iniciar sesión con la clave SSH antes de deshabilitar el inicio de sesión por contraseña! De lo contrario, podría perder el acceso al servidor.
4. Instalación y configuración del firewall (UFW)
UFW (Uncomplicated Firewall) es una interfaz fácil de usar para configurar iptables. Es necesario para restringir el acceso al servidor solo a los puertos necesarios.
sudo apt install ufw -y # Instala UFW
sudo ufw allow OpenSSH # Permite SSH (puerto 22)
sudo ufw allow http # Permite HTTP (puerto 80)
sudo ufw allow https # Permite HTTPS (puerto 443)
sudo ufw enable # Habilita UFW. Confirme con 'y'.
sudo ufw status verbose # Verifica el estado de UFW
5. Instalación de Fail2ban para protección contra ataques de fuerza bruta
Fail2ban escanea los registros de servicios (SSH, servidores web, etc.) en busca de actividad sospechosa (por ejemplo, múltiples intentos fallidos de inicio de sesión) y bloquea temporal o permanentemente las direcciones IP de los atacantes utilizando reglas de firewall.
sudo apt install fail2ban -y # Instala Fail2ban
sudo systemctl enable fail2ban # Habilita el inicio automático de Fail2ban al arrancar
sudo systemctl start fail2ban # Inicia el servicio Fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Crea una copia local del archivo de configuración para realizar cambios
sudo nano /etc/fail2ban/jail.local # Edita la configuración
En el archivo jail.local, puede configurar parámetros como bantime (tiempo de bloqueo), findtime (período durante el cual se consideran los intentos fallidos) y maxretry (número máximo de intentos). Asegúrese de que la sección [sshd] esté habilitada (enabled = true). Puede aumentar bantime, por ejemplo, a 1h o incluso 1d.
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
Guarde los cambios y reinicie Fail2ban:
sudo systemctl restart fail2ban # Reinicia Fail2ban para aplicar los cambios
sudo fail2ban-client status sshd # Verifica el estado de la protección SSH
Instalación de software — paso a paso
Ahora que el servidor está preparado, pasemos a la instalación del software principal: Docker, Docker Compose y Caddy.
1. Instalación de Docker Engine (válido para 2026)
Instalaremos Docker desde el repositorio oficial de Docker para tener siempre las versiones más recientes. Las versiones actuales de Docker Engine para 2026 probablemente estarán en el rango 25.x-26.x.
# Actualiza los paquetes e instala las dependencias
sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release
# Agrega la clave GPG oficial de Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Agrega el repositorio de Docker a las fuentes de APT
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Actualiza las listas de paquetes con el nuevo repositorio
sudo apt update
# Instala Docker Engine, Docker CLI y containerd
sudo apt install -y docker-ce docker-ce-cli containerd.io
# Agrega el usuario actual al grupo docker para trabajar sin sudo
sudo usermod -aG docker "$USER"
# Aplica los cambios para el grupo Docker (es necesario volver a iniciar sesión o reiniciar)
# Pero es mejor simplemente volver a iniciar sesión o ejecutar 'newgrp docker'
newgrp docker
# Verifica la instalación de Docker
docker run hello-world
El comando docker run hello-world debería descargar y ejecutar con éxito un contenedor de prueba, confirmando la correcta instalación de Docker.
2. Instalación de Docker Compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker de múltiples contenedores. Se incluye como parte de Docker Engine a partir de la versión 2.x.
# Verifica la versión de Docker Compose (Docker Compose v2.x se incluye como un plugin de Docker CLI)
docker compose version
Si el comando docker compose version devuelve un error o una versión muy antigua, es posible que deba instalarlo por separado. Sin embargo, para Ubuntu 24.04/26.04 y Docker Engine 25.x+, debería estar disponible por defecto.
3. Instalación de Caddy (válido para 2026)
Caddy se puede instalar desde el repositorio oficial de Caddy, lo que garantiza la obtención de las últimas versiones estables (Caddy 2.x).
# Instala las dependencias para agregar el repositorio HTTPS
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
# Agrega la clave GPG de Caddy
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# Agrega el repositorio de Caddy a las fuentes de APT
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Actualiza las listas de paquetes con el nuevo repositorio
sudo apt update
# Instala Caddy
sudo apt install -y caddy
# Verifica el estado del servicio Caddy
sudo systemctl status caddy
El servicio Caddy debería estar instalado y en ejecución. Si no está en ejecución, inícielo: sudo systemctl start caddy.
Configuración
Ahora que todos los componentes están instalados, es hora de configurar su interacción. Configuraremos Caddy como un proxy inverso para los contenedores Docker, utilizando Docker Compose para definir nuestras aplicaciones.
1. Preparación de la infraestructura de red de Docker
Para que Caddy pueda interactuar con sus contenedores Docker por sus nombres internos, deben colocarse en una misma red de usuario de Docker.
# Creamos una red de usuario de Docker para Caddy y las aplicaciones
docker network create caddy_network
Esta red la especificaremos en los archivos docker-compose.yml para nuestras aplicaciones y para el propio Caddy.
2. Configuración de Caddyfile
Caddy utiliza el archivo Caddyfile para su configuración. Lo configuraremos para proxyficar el tráfico a nuestros contenedores Docker.
sudo nano /etc/caddy/Caddyfile # Abrimos el archivo de configuración principal de Caddy
Elimine el contenido existente e inserte lo siguiente. Reemplace your-app.example.com y another-app.example.com con sus nombres de dominio reales, y app_container_name y another_app_container_name con los nombres de sus contenedores Docker.
# Ajustes globales de Caddy
{
# Indicamos que Caddy debe usar los servidores DNS de Google para Let's Encrypt
# Esto puede ser útil si su VPS tiene problemas con la resolución DNS
# acme_dns google # Descomente si usa un proveedor de DNS que soporte el desafío ACME DNS
# email [email protected] # Especifique su correo electrónico para las notificaciones de Let's Encrypt
}
# Configuración para la primera aplicación
your-app.example.com {
# Proxyficamos todo el tráfico al contenedor Docker
reverse_proxy app_container_name:80
# Encabezados adicionales para una mejor compatibilidad y seguridad
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
}
# Registro de solicitudes
log {
output file /var/log/caddy/your-app.log
format json
}
# Compresión Gzip (opcional, pero recomendada)
encode gzip
}
# Configuración para la segunda aplicación (ejemplo)
another-app.example.com {
reverse_proxy another_app_container_name:8080 # Especifique el puerto en el que escucha su segunda aplicación
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
}
log {
output file /var/log/caddy/another-app.log
format json
}
encode gzip
}
# También puede añadir una directiva para archivos estáticos, si Caddy los va a servir
# example.com {
# root /var/www/html
# file_server
# }
Explicaciones del Caddyfile:
your-app.example.com: Este es el nombre de dominio por el cual su aplicación será accesible. Caddy obtendrá automáticamente un certificado HTTPS para él. Asegúrese de que el registro DNS (registro A) para este dominio apunte a la dirección IP de su VPS.reverse_proxy app_container_name:80: Caddy redirigirá todas las solicitudes entrantes a la dirección IP interna del contenedor Docker con el nombreapp_container_nameen el puerto80(o cualquier otro en el que su aplicación escuche dentro del contenedor). Es importante queapp_container_namesea el nombre del servicio en su archivodocker-compose.yml.header: Añade encabezados HTTP para mejorar la seguridad.log: Configura el registro de solicitudes. Asegúrese de que el directorio/var/log/caddy/exista y que Caddy tenga permisos sobre él.encode gzip: Habilita la compresión Gzip para los datos transmitidos.
Creemos el directorio para los logs de Caddy:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy # Damos permisos al usuario caddy
sudo systemctl restart caddy # Reiniciamos Caddy para aplicar el nuevo Caddyfile
sudo systemctl status caddy # Verificamos que Caddy se haya iniciado correctamente
3. Ejemplo de despliegue de una aplicación con Docker Compose
Creemos un ejemplo de una aplicación web simple (por ejemplo, Nginx con una página de prueba) que estará disponible a través de Caddy.
mkdir ~/my_web_app
cd ~/my_web_app
nano docker-compose.yml
Contenido de docker-compose.yml:
version: '3.8'
services:
# Nombre del servicio que Caddy utilizará para el proxy
# Asegúrese de que coincida con el nombre en el Caddyfile (por ejemplo, 'app_container_name')
mywebapp:
image: nginx:latest # Usamos la imagen actual de Nginx
container_name: mywebapp_container # Nombre explícito del contenedor
restart: unless-stopped
ports:
- "80" # Nginx escucha en el puerto 80 dentro del contenedor
networks:
- caddy_network # Conectamos el contenedor a la red de Caddy
networks:
caddy_network:
external: true # Indicamos que la red ya existe y fue creada manualmente
Guarde el archivo (Ctrl+X, Y, Enter) e inicie la aplicación:
docker compose up -d # Iniciamos el contenedor en segundo plano
Ahora edite su Caddyfile para que haga proxy a mywebapp:
sudo nano /etc/caddy/Caddyfile
Añada o modifique la sección para su dominio:
# ... otras configuraciones ...
# Configuración para su nueva aplicación
my-nginx-app.example.com { # Reemplace con su dominio real
reverse_proxy mywebapp:80 # Nombre del servicio de docker-compose.yml y puerto interno
log {
output file /var/log/caddy/my-nginx-app.log
format json
}
encode gzip
}
# ... el resto de las configuraciones ...
Reinicie Caddy:
sudo systemctl reload caddy # Recargamos la configuración de Caddy sin detener el servicio
Asegúrese de que el registro DNS para my-nginx-app.example.com apunte a la dirección IP de su VPS. En unos segundos (o minutos, mientras Caddy obtiene el certificado) su aplicación debería estar disponible por HTTPS.
4. Configuración de variables de entorno (.env) para secretos
Nunca almacene datos confidenciales (contraseñas, claves API) directamente en docker-compose.yml. Utilice variables de entorno a través de un archivo .env.
Cree un archivo .env en el mismo directorio que docker-compose.yml:
nano ~/my_web_app/.env
Ejemplo de contenido de .env:
DB_PASSWORD=MyStrongPassword123
API_KEY=supersecretkeyabc123
Luego, en docker-compose.yml puede hacer referencia a estas variables:
version: '3.8'
services:
mywebapp:
image: mycustomapp:latest
container_name: mywebapp_container
restart: unless-stopped
environment:
- DB_PASSWORD=${DB_PASSWORD} # Pasamos la variable de .env
- API_KEY=${API_KEY}
networks:
- caddy_network
networks:
caddy_network:
external: true
Al ejecutar docker compose up -d, Docker Compose sustituirá automáticamente los valores de .env.
5. Verificación de la operatividad
Después de todas las configuraciones, es necesario asegurarse de que todo funciona correctamente.
-
Verificación de Caddy:
sudo systemctl status caddy # Asegúrese de que Caddy esté funcionando sudo journalctl -u caddy --since "10 minutes ago" # Revise los logs de Caddy en busca de errores -
Verificación de contenedores Docker:
docker ps # Asegúrese de que sus contenedores estén en ejecución docker logs mywebapp_container # Revise los logs de un contenedor específico -
Verificación de accesibilidad a través del dominio:
En su máquina local, use
curlpara verificar la conexión HTTPS:curl -vI https://my-nginx-app.example.com # Verificamos los encabezados HTTP y el estado del certificado SSLDebería ver el estado
HTTP/2 200e información sobre el certificado SSL emitido por Let's Encrypt. Si hay problemas, asegúrese de que el registro DNS sea correcto y que los puertos 80/443 estén abiertos en UFW.