Cómo securizar un panel de Portainer expuesto a internet
- portainer
- seguridad
- traefik
- docker
- infraestructura
Riesgos de exponer Portainer directamente a internet
Muchos desarrolladores y sysadmins montan Portainer en sus VPS para gestionar contenedores Docker de forma visual. Es una herramienta potente, que simplifica mucho la administración de un entorno Docker. Sin embargo, dejar su puerto abierto directamente a internet, incluso con una contraseña robusta, es un riesgo de seguridad inaceptable. Los bots de internet escanean constantemente puertos conocidos y tarde o temprano encontrarán tu panel, intentando accesos de fuerza bruta o explotando vulnerabilidades.
Mi regla es clara: cualquier panel de administración debe estar detrás de capas de seguridad adicionales, no solo de una contraseña. El objetivo no es solo tener un login seguro, sino que nadie llegue a ver ese login si no debe. Esto minimiza los vectores de ataque y protege tu infraestructura desde el primer contacto.
Un firewall no es suficiente para Portainer por sí solo
El primer impulso, y un paso necesario, es configurar un firewall. Herramientas como ufw o iptables son esenciales para cualquier servidor Linux. Para entender las diferencias y cuándo usar cada uno, puedes consultar mi artículo sobre UFW vs. IPTables en un VPS. Bloquear puertos que no necesitas y permitir explícitamente solo el tráfico esencial es la base de la seguridad en un VPS. Por ejemplo, permitir solo el puerto 80 y 443 para el tráfico web, y quizás el 22 para SSH desde IPs muy específicas.
Pero incluso si configuras tu firewall para solo permitir el acceso a Portainer desde tu IP, esto no es escalable ni seguro en todos los escenarios. ¿Qué pasa si trabajas desde diferentes ubicaciones, si tu IP cambia, o si necesitas que un compañero acceda? Además, un firewall solo filtra puertos; un atacante persistente podría intentar explotar vulnerabilidades de Portainer antes incluso de llegar al login, si el servicio está directamente expuesto.
# Ejemplo basico con UFW para permitir SSH y denegar todo lo demas por defecto
# Deshabilita UFW si ya esta activo para evitar conflictos, luego configura.
sudo ufw disable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment "Permitir SSH"
sudo ufw enable
sudo ufw status verbose
La capa de Traefik: un reverse proxy como primera defensa
Aquí es donde entra en juego un reverse proxy como Traefik. En lugar de exponer Portainer directamente abriendo su puerto, lo colocas detrás de Traefik. Este último se encarga de recibir todas las peticiones externas en los puertos 80 y 443 y las redirige de forma inteligente a los servicios internos de Docker. Esto significa que el contenedor de Portainer no es directamente accesible desde internet, sino solo a través de Traefik, que actúa como una puerta de enlace controlada.
Traefik puede manejar el cifrado SSL de forma automática con Let’s Encrypt, la redirección HTTP a HTTPS y, lo más importante para nuestro caso, la autenticación. Es una frontera inteligente que filtra, redirige y asegura el tráfico antes de que llegue a tu aplicación real, añadiendo una capa de abstracción y protección. Si ya usas Traefik para multiples dominios con SSL para otras aplicaciones, añadir Portainer a esta configuración es un paso natural y muy eficiente.
# docker-compose.yml para Traefik como reverse proxy (ejemplo simplificado)
version: '3.8'
services:
traefik:
image: traefik:v2.10
container_name: traefik
restart: unless-stopped
command:
- --api.dashboard=true # Habilita el dashboard de Traefik para monitorizar
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
- --certificatesresolvers.letsencrypt.acme.email=tu_email@tudominio.com
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
ports:
- "80:80"
- "443:443"
- "8080:8080" # Puerto del dashboard de Traefik, protegelo tambien
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt # Directorio para guardar los certificados
labels:
# Acceso al dashboard de Traefik, protegelo con autenticacion si lo expones
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.tudominio.com`)"
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.traefik-dashboard-auth.basicauth.users=admin:$$apr1$$HashDeTuContraseñaDashboard" # Reemplaza con tu hash
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-dashboard-auth"
networks:
- traefik-proxy
networks:
traefik-proxy:
external: true
En este docker-compose.yml, he habilitado el dashboard de Traefik y he puesto una autenticación básica también para él, que es lo que hago en mis propias configuraciones.
Añadir autenticación a Traefik para Portainer
Una vez que Portainer está detrás de Traefik, podemos añadir una capa extra de autenticación antes de que el tráfico llegue al panel de administración. Esto significa que un usuario deberá introducir credenciales para Traefik, y solo después verá el formulario de login de Portainer. Esto puede ser una autenticación básica HTTP, que es sencilla de implementar, o, si tienes una infraestructura más compleja y un sistema de gestión de identidades, una integración con OAuth o un proveedor de identidad.
Para una autenticación básica, Traefik puede usar un middleware que pida un usuario y contraseña. Este middleware se define en el docker-compose.yml y se aplica al router de Portainer. Es una forma muy efectiva de asegurar que solo los usuarios con las credenciales correctas pasen la primera barrera y vean la página de login de Portainer, añadiendo una capa de seguridad crucial. La contraseña generada debe ser un hash, no texto plano.
# docker-compose.yml para Portainer detras de Traefik con Basic Auth
version: '3.8'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
command: -H unix:///var/run/docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.tudominio.com`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt" # Usa el resolver de Let's Encrypt
- "traefik.http.routers.portainer.middlewares=portainer-auth" # Aplica el middleware de autenticacion
- "traefik.http.middlewares.portainer-auth.basicauth.users=tu_usuario:$$apr1$$HashDeTuContraseña" # Genera el hash con htpasswd
networks:
- traefik-proxy
volumes:
portainer_data:
networks:
traefik-proxy:
external: true # Asume que Traefik ya está en esta red
Para generar el hash de la contraseña de forma segura, usa la herramienta htpasswd en tu terminal:
# Instala htpasswd si no lo tienes (en Debian/Ubuntu)
sudo apt update && sudo apt install apache2-utils -y
# Genera el hash para tu usuario y contraseña. Reemplaza 'tu_usuario' y 'tu_contraseña'.
# El comando 'sed' es crucial para escapar correctamente los '$' en YAML.
echo $(htpasswd -nb tu_usuario tu_contraseña) | sed -e 's/\$/\$\$/g'
El resultado de este comando es lo que debes copiar y pegar en la línea basicauth.users de tu docker-compose.yml, asegurándote de reemplazar tu_usuario por el nombre de usuario que elijas.
Lo que ganas y lo que complicas con esta configuración
Implementar esta estrategia para Portainer ofrece una mejora sustancial en la seguridad, pero como cualquier decisión técnica en infraestructura, tiene sus tradeoffs. Antes de comprometerse con esta arquitectura, es necesario entenderlos.
Lo que ganas:
- Reducción drástica de la superficie de ataque: Portainer no está directamente expuesto a internet. Traefik, diseñado para ser un punto de entrada seguro, filtra y gestiona el tráfico antes de que llegue al panel de administración.
- Doble capa de autenticación: Necesitas pasar la autenticación de Traefik (HTTP Basic Auth o similar) y luego la autenticación propia de Portainer. Esto es una barrera adicional muy eficaz contra accesos no autorizados.
- Gestión centralizada de SSL: Traefik se encarga de obtener y renovar automáticamente los certificados Let’s Encrypt para todos tus servicios Docker, incluyendo Portainer, simplificando la gestión del cifrado.
- Flexibilidad de acceso: Puedes añadir reglas de Traefik para restringir el acceso por IP, user agent o cualquier otra cabecera HTTP, lo que te da un control granular sobre quién puede intentar acceder.
- Monitorización: El dashboard de Traefik te permite ver el estado de tus routers y servicios.
Lo que complicas:
- Mayor complejidad de configuración inicial: Añadir Traefik implica un
docker-compose.ymlmás elaborado y entender los conceptos de routers, middlewares y servicios de Traefik. No es un simple “instalar y usar”. - Un punto de fallo adicional: Si Traefik falla o se configura incorrectamente, todos los servicios que dependen de él (incluido Portainer) serán inaccesibles. Esto requiere una buena monitorización de Traefik.
- Dependencia de Traefik: Necesitas mantener Traefik actualizado y funcionando correctamente. Esto añade una pieza más a tu stack de infraestructura que requiere atención.
- Generación y gestión de hashes: Aunque
htpasswdsimplifica la tarea, es un paso adicional que hay que recordar y ejecutar correctamente.
Proteger Portainer con Traefik es una medida de seguridad proactiva. Para cualquier entorno de producción con Docker, esta configuración es un estándar.
Si estás montando tu infraestructura con Docker y quieres profundizar más en estas configuraciones, te puede interesar cómo migrar un stack Docker a un VPS de forma segura. Además, es crucial que sepas cómo gestionar los logs de tu VPS con rotación para no quedarte sin espacio, y te recomiendo mi artículo sobre por qué uso Docker y Traefik para entender mi enfoque y las ventajas de este stack.
Técnico freelance especializado en desarrollo a medida, automatizaciones con IA y gestión técnica para negocios en España. Más sobre mí →
¿Necesitas que alguien se ocupe de tu web?
Me encargo de que tu web funcione, esté segura y actualizada. Backups, actualizaciones y soporte directo. Planes desde 49 €/mes.