Consejos esenciales de seguridad para Docker en auto‑hosting
¡Asegura tus servicios auto‑alojados, de la defensa a la monitorización!
Tabla de contenidos
- 🧗♀️ Para los valientes
- 🔄 El baile del
:latest - 🔐 Gestión de secretos: la forma correcta
- 🌐 Peligro de red
- 🛡️ Controles de acceso
- 🔍 Monitoreo y verificación
- ⏰ Consejos que a menudo se pasan por alto
- 🚀 Lista de verificación para producción
- 📚 Lecturas adicionales
🧗♀️ Para los valientes
Si estás auto‑alojando servicios Docker, la seguridad es tu responsabilidad de extremo a extremo—no hay un proveedor de nube que te proteja de escaneos de puertos o configuraciones descuidadas. Ya sea que estés desplegando aplicaciones en tu red doméstica o alquilando VPS en proveedores como Vultr, DigitalOcean, Linode, AWS, Azure o Google Cloud, tendrás que endurecer todo y comprobar que lo hiciste bien.
En esta guía, repasaremos la seguridad en Docker—desde técnicas menos conocidas hasta otras difíciles de ejecutar correctamente; exploraremos tokens canarios, volúmenes de solo lectura, reglas de firewall, segmentación y endurecimiento de red, incorporación de proxies autenticados y mucho más.
También compararemos redes domésticas con configuraciones en la nube pública y te mostraremos cómo montar un proxy de autenticación básica con Nginx. Al final tendrás varias opciones para mantener fuera al riff‑raff (amigos, familia e incluso, a veces, a ti mismo…).
¡Es mucho material! Pero gran parte está interrelacionada, y puedes elegir lo que sea más relevante para tu entorno. 🍀
🔄 El baile del :latest
Mantener las imágenes actualizadas es crucial para la seguridad. Sin embargo, depender de :latest puede introducir cambios incompatibles o compilaciones vulnerables sin una fase de revisión.
La forma segura de actualizar
Combina los comandos de actualización con pull o build para que refresques las imágenes de forma deliberada, y luego reinícialas en una ventana donde puedas detectar cualquier ruptura.
#!/bin/bashdocker compose pull && \ docker compose up -dVersion Pinning vs Latest
Elegir la versión adecuada para fijar es un acto de equilibrio entre estabilidad y seguridad. Aquí hay algunas estrategias comunes:
# ... # Fijado de versión exacta, lo mejor para servicios críticos image: postgres:17.2
# Fijado de versión de parche, bueno para servicios no críticos image: postgres:17.2
# Fijado de versión mayor, perfecto para proyectos hobby image: postgres:17
# Yolo, evitar si es posible image: postgres:latestUsa Dependabot o Renovate para abrir PRs de actualización revisables. Para cualquier cosa que te molestaría reconstruir a las 2 a.m., fija una versión o digest específico y deja que la automatización te indique cuándo avanzar.
¡Cuéntame cuáles son tus herramientas favoritas para mantener actualizadas las imágenes Docker!
🔐 Gestión de secretos
Hay muchas formas de gestionar secretos, pero una de las reglas más importantes a respetar es: nunca incrustes secretos en tus imágenes Docker ni los comprometas en git. Es uno de los errores de seguridad más comunes, genera un riesgo a largo plazo y es un dolor de cabeza solucionarlo.
Almacenar secretos de forma segura es un tema amplio con muchísimas opciones, desde archivos .env, Docker secrets, 1Password/Bitwarden, o un gestor de secretos como HashiCorp Vault o AWS Secrets Manager.
Tendrás que elegir el nivel de esfuerzo y seguridad “adecuado” para tu caso de uso.
💡 Asegúrate de que los secretos sean siempre únicos. Intenta que sea imposible ejecutar con valores predeterminados inseguros o codificados.
Si utilizas marcadores de posición como __WARNING_REPLACE_ME__ en tus secretos, genial, ¡quizá alguien lo note!
Por si acaso, también puedes añadir una pequeña seguridad en tiempo de ejecución con poco esfuerzo. Así es como podrías hacerlo en JavaScript, Rust y Go:
const validateSecrets = () => { const unsafePlaceholder = /__WARNING_REPLACE_ME__/; const missingSecrets = Object.entries(process.env).filter( ([key, value]) => unsafePlaceholder.test(value) );
if (missingSecrets.length) { console.error("Unsafe secrets detected:", missingSecrets); process.exit(1); }};
validateSecrets();use std::env;
fn validate_secrets() { let unsafe_placeholder = "__WARNING_REPLACE_ME__"; for (key, value) in env::vars() { if value.contains(unsafe_placeholder) { panic!("Unsafe secret in {}", key); } }}
fn main() { validate_secrets();}package main
import ( "fmt" "os" "strings")
func validateSecrets() { placeholder := "__WARNING_REPLACE_ME__" for _, env := range os.Environ() { pair := strings.SplitN(env, "=", 2) if len(pair) == 2 && strings.Contains(pair[1], placeholder) { panic(fmt.Sprintf("Unsafe secret in %s", pair[0])) } }}
func main() { validateSecrets()}*/}
Generar secretos fuertes
Aquí tienes un script pequeño para generar nuevos secretos para un archivo .env:
#!/bin/bashgenerate_secret() { local length=${1:-30} local generate_length=$((length + 4)) openssl rand -base64 "$generate_length" | tr -d '+=/\n' | cut -c1-"$length"}
[ -f .env ] && { echo ".env file already exists!"; exit 1; }
cat > .env << EOLPOSTGRES_PASSWORD=$(generate_secret)JWT_SECRET=$(generate_secret 64)SESSION_KEY=$(generate_secret 24)REDIS_PASSWORD=$(generate_secret 20)UNSAFE_PLACEHOLDER=__WARNING_REPLACE_RANDOM_TEXT__EOL
echo "New .env file generated with secure random values!"Tokens Canary
Canary Tokens son una forma excelente de detectar si tus secretos han sido comprometidos (y usados). Funcionan como una trampa que puedes añadir a cualquier archivo sensible, URL o token.
Considera colocarlos junto a los secretos que realmente te preocupan: archivos .env, variables de CI, gestores de contraseñas, carpetas de copias de seguridad y credenciales en la nube. No lo conviertas en un espectáculo; pon trampas donde un atacante real o un futuro tú equivocadamente los tocaría.
Hay muchos tipos de “tokens” canarios para elegir, desde tokens de AWS, números de tarjeta de crédito falsa, archivos Excel y Word, archivos Kubeconfig, credenciales VPN, ¡incluso los volcados SQL pueden llevar una trampa!
Mejores Prácticas para Tokens Canary
- Colócalo en todas partes: En cada archivo
.env, pipeline CI/CD y “gestor de secretos” que se te ocurra.- Deja un archivo
passwords.xlsxopasswords.docxen tu directorio home. - Añade un perfil de AWS
billing_prodcon un token canario como secreto. - Genera un archivo
private.keypara tu directorio~/.ssh. - Crea un volcado SQL canario
all_credit_cards.sqlpara tu directorio~/backups.
- Deja un archivo
- Monitorea: Configura reglas o alertas de correo para detectar cuando se dispara un token canario.
Actualiza de .env a Keychain de macOS
Para usuarios de Mac, una de las opciones más simples es usar Keychain.
A continuación tienes una forma sencilla de automatizar la carga de secretos desde el llavero de macOS, con soporte para TouchID y un nivel de seguridad algo mayor que los archivos .env.
Original credit: Brian Hetfield and Jan Schaumann.
### Funciones para establecer y obtener variables de entorno desde el llavero de macOS ###### Adaptado de: https://www.netmeister.org/blog/keychain-passwords.html yOriginal credit: [Brian Hetfield](https://gist.github.com/bmhatfield/f613c10e360b4f27033761bbee4404fd) and [Jan Schaumann](https://www.netmeister.org/).
# Uso: get-keychain-secret VARIABLE_DE_ENTORNOfunction get-keychain-secret () { security find-generic-password -w -a ${USER} -D "environment variable" -s "${1}"}
# Uso: set-keychain-secret VARIABLE_DE_ENTORNO# ¡Se te pedirá que ingreses el valor del secreto!function set-keychain-secret () { [ -n "$1" ] || print "Falta el nombre de la variable de entorno"
# solicitar al usuario el secreto echo -n "Introduce el secreto para ${1}" read secret [ -n "$secret" ] || return 1
( [ -n "$1" ] || [ -n "$secret" ] ) || return 1 security add-generic-password -U -a ${USER} -D "environment variable" -s "${1}" -w "${secret}"}source ~/keychain-secrets.sh
# Cargar variables de entorno en la shell actualexport AWS_ACCESS_KEY_ID=$(get-keychain-secret AWS_ACCESS_KEY_ID);export AWS_SECRET_ACCESS_KEY=$(get-keychain-secret AWS_SECRET_ACCESS_KEY);# Nota: Si un atacante puede ejecutar `env` en tu shell, estos secretos podrían exponerse.#!/usr/bin/env bashsource ~/keychain-secrets.sh
# Especificar todos los secretos para este proyectoAWS_ACCESS_KEY_ID=$(get-keychain-secret AWS_ACCESS_KEY_ID) \AWS_SECRET_ACCESS_KEY=$(get-keychain-secret AWS_SECRET_ACCESS_KEY) \ "$@"
# Nota: Usar un wrapper de shell ayuda a evitar que los secretos permanezcan# en el entorno. Y es seguro comprometerlo al repositorio.
# Uso:# ./scripts/env-run.sh docker compose up -d# ./scripts/env-run.sh docker run -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY ...🌐 Riesgo de Red
Redes Personalizadas y Puertos Internos
Aislar correctamente los servicios con redes de Docker es una forma importante de reducir la superficie de ataque.
¡Cuidado al abrir agujeros en tu red! Un solo reenvío de puerto mal configurado puede terminar muy mal.
Por defecto, los servicios en una LAN privada no se exponen a Internet; debes reenviar puertos explícitamente desde tu router.
Docker en LAN
Ya seas un desarrollador que ejecuta servidores de desarrollo localmente, o estés auto‑alojando servicios desde tu red local, las suposiciones sobre el modelo de red de Docker pueden causar problemas.
A los devs a menudo les sorprende descubrir que los métodos “tradicionales” para asegurar servidores Linux (iptables, restringir opciones sysctl de tcp/ip) pueden fallar silenciosamente en hosts Docker. ¡Esto es especialmente crítico cuando auto‑alojas o trabajas en una red doméstica típica! (Para los que están al fondo: ¡esto puede permitir el acceso a contenedores de desarrollo en tu MacBook!)
⚠️ Advertencia #1: Los puertos publicados por Docker pueden eludir las reglas de firewall que creías que protegían el host, sobre todo con UFW en Ubuntu/Debian. Eso no vuelve inútiles todas las reglas de firewall, pero sí significa que “UFW dice deny” no es prueba suficiente. Ver issue #690: Docker bypasses ufw firewall rules.
⚠️ Advertencia #2: Vincular puertos a direcciones IP locales (p. ej.,
-p 127.0.0.1:8080:80) es el valor predeterminado correcto, pero versiones del motor Docker anteriores a 28.0.0 tenían casos en los que hosts en la misma red L2 aún podían alcanzar puertos publicados en localhost. Docker documenta la advertencia en su guía de publicación de puertos, y el hábito de verificar con nmap que se muestra a continuación sigue siendo importante.
Si esto te sorprende, ¡no estás solo!
Vincular a IPs locales sigue siendo una buena práctica y tiene un impacto significativo en entornos de nube gestionados y redes especialmente configuradas.
Ejemplo de Docker Compose
Aquí tienes un archivo docker-compose.yml de ejemplo que enlaza el servicio app a 127.0.0.1:8080 y conecta ambos contenedores a la red personalizada backend.
networks: backend:
services: app: networks: - backend ports: # Enlazar a localhost si es posible - "127.0.0.1:8080:8080" # ... otras configuraciones database: image: postgres:17.1 # No se necesitan puertos; accesible dentro de la red backend. networks: - backendMejores Prácticas de Red
- 🏆 No publiques NINGÚN puerto Recientemente descubrí que esto es más útil de lo que podrías esperar. Cuando usas una red nombrada (bridge), los contenedores tienen acceso sin filtrar entre sí. Se comportan como si estuvieran detrás de una red local (gateway NAT).
- Aunque no sea posible en todos los casos, puede ser útil para contenedores que ejecutan trabajos por lotes o que se acceden principalmente mediante
attachoexec.
- Aunque no sea posible en todos los casos, puede ser útil para contenedores que ejecutan trabajos por lotes o que se acceden principalmente mediante
- 🥇 Usa Docker Networks para aislar y controlar qué contenedores pueden comunicarse entre sí.
- 🥉 Enlaza a localhost: Aunque imperfeccionado, generalmente es mejor enlazar puertos a una dirección de loopback (p. ej.,
127.0.0.1:8080:80). Solo asegúrate de verificar tu configuración.
🛡️ Controles de Acceso
Los controles de acceso son una parte crítica para asegurar tus servicios Docker. Esto incluye limitar capacidades y permisos de los contenedores, restringir el acceso al socket de Docker y más.
- Limitando Capacidades de Contenedor
- Acceso al Socket de Docker
- ¡Bloqueo por País!
- Endureciendo el Host Proxy de CloudFlare
Limitando Capacidades de Contenedor
Otra práctica sólida de control de acceso es limitar las capacidades de tus contenedores. Esto reduce el radio de explosión de varias amenazas, desde escalada de privilegios hasta secuestro de tráfico. No es un campo de fuerza, pero elimina permisos que la mayoría de los contenedores nunca necesitó.
¿Qué son las capacidades? Permisos o habilidades nombradas definidas por el kernel de Linux. (La página del manual de capabilities contiene la lista completa.) Incluyen cosas como CAP_CHOWN (cambiar la propiedad de archivos), CAP_NET_ADMIN (configurar interfaces de red), CAP_KILL (matar cualquier proceso) y muchas más.
Las dos formas de determinar las capacidades necesarias son:
- Prueba y error: Este método más lento pero efectivo te hace comenzar sin capacidades y luego añadirlas una a una hasta que tu aplicación funcione.
- Buscar trabajo previo: Busca “
project-namecap_dropDockerfile” o “project-namecap_dropdocker-compose.yml” para ver si otros ya lo hicieron. Un LLM puede sugerir un punto de partida, pero trátalo como una conjetura hasta que pruebes el contenedor y leas la documentación de la imagen.
Mejores Prácticas de Capacidades
- Eliminar todas las capacidades: Usa
cap_drop: [ ALL ]para quitar todas las capacidades de Linux del contenedor. - Sin nuevos privilegios: Usa
security_opt: [ no-new-privileges=true ]para evitar que el contenedor adquiera nuevos privilegios.
services: database: image: postgres:17.1 networks: [ db-network ] security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - DAC_READ_SEARCH - FOWNER - SETGID - SETUID db-admin: image: dpage/pgadmin4:4.1 networks: [ db-network ] ports: - "8081:80" # ... otras configuracionesnetworks: db-network:Ahora tus servicios pueden comunicarse entre sí a través de la red db-network. Docker Compose creará esa red automáticamente.
Usa la opción --external/external: para unirte a una red preexistente. Omitela para crear una red nueva.
Acceso al Socket de Docker
⚠️ Advertencia: docker.sock es básicamente acceso de administrador al host
⚠️ La opción :ro no afecta la I/O enviada a través del socket!
Solo garantiza que la ruta del socket se monte como solo‑lectura. Las llamadas API enviadas por ese socket aún pueden crear contenedores, montar rutas del host y hacer otras cosas muy emocionantes que probablemente no pretendías delegar.
Mejores Prácticas para el Socket
- 🥇 Evita montar el socket de Docker, probablemente exista una alternativa mejor.
- 🫣 Si es indispensable, coloca un proxy estrecho delante y permite solo los endpoints de la API que la aplicación realmente necesita. Echa un vistazo al proyecto
docker-socket-proxyoriginado por Tecnativa, docker-socket-proxy. Luego verifica que las llamadas denegadas estén efectivamente bloqueadas. - 🤢 Vale, quizá compartirlo esté bien en un entorno de pruebas de alta confianza y bajo riesgo.
¡Bloqueando Países!
A veces útil, pero no constituye una verdadera barrera de seguridad.
Hablando de la entidad geopolítica, no de la música…
Si alojas aplicaciones mayormente para tu familia y amigos locales, puedes bloquear el tráfico de los países de los que no esperas recibir visitas. O bien permitir solo el tráfico de los países que sí esperas. Reduce el ruido; no detiene VPNs, proxies, botnets ni a nadie con paciencia.
Revisa este script para bloquear todo el tráfico procedente de China:
curl -fsSL https://www.ipdeny.com/ipblocks/data/countries/cn.zone | \ while read line; do ufw deny from $line to any; donecurl -fsSL https://www.ipdeny.com/ipblocks/data/countries/cn.zone | \ while read line; do ufw deny from $line to any; doneDe forma similar, puedes permitir solo el tráfico proveniente de EE. UU.:
curl -fsSL https://www.ipdeny.com/ipblocks/data/countries/us.zone | \ while read line; do ufw allow from $line to any; doneEndurecimiento del host proxy de CloudFlare
Si tu servidor doméstico está detrás de una IP de CloudFlare (proxy), puedes restringir el acceso únicamente a las IPs de CloudFlare y a tu red local.
Esto es algo parecido al bloqueo por país anterior, pero con un control mucho más estricto.
ufw default deny incoming # ¡Bloquear todo el tráfico entrante!ufw default allow outgoing # Permitir todo el tráfico salienteufw allow ssh # Permitir SSH
# Permitir acceso para la subred local (preferiblemente una DMZ/VLAN dedicada para los servicios alojados)ufw allow from 10.0.0.0/8 to any port 443Permitir IPs de CloudFlare
curl -fsSL https://www.cloudflare.com/ips-v4 |
while read line; do ufw allow from $line to any port 443; done
Añadir soporte IPv6
curl -fsSL https://www.cloudflare.com/ips-v6 | \
while read line; do ufw allow from $line to any port 443; done
Para probar cambios basados en geolocalización, una VPN con puntos de salida en el país deseado puede ser útil. Consulta más detalles en la sección [Monitoring & Verification](#-monitoring--verification).
### Seguridad a nivel de aplicación
Una vez que tu [network and host are security hardened,](#-network-hazard) puede que descubras que aún queda trabajo por hacer.
Ahora debemos considerar la capa “aplicación” de los propios servicios.
<p class="inset">¿Esa base de datos tiene una contraseña válida? ¿Este contenedor automatiza HTTPS/certificados? ¿La aplicación incluye autenticación incorporada? ¿Hay límites sobre qué correos pueden registrarse? ¿Existen credenciales predeterminadas o variables de entorno que cambiar?</p>
La única forma de _saberlo_ es revisarlo. En este caso, comienza con el `README` y otros archivos clave como `docker-compose.yml`, `Dockerfile` y `.env.*`. Tanto en el proyecto como, idealmente, en sus servicios de apoyo (p. ej., Postgres, Redis, etc.).
#### Proxy inverso
Otra capa de defensa es la autenticación básica. No la uses sin HTTPS. Para servicios heredados, colocar autenticación básica delante de una ruta de administración suele ser suficiente para detener peticiones casuales y rastreadores no autenticados que intenten acceder directamente.
```nginx
# /etc/nginx/conf.d/secure-admin.conflocation /admin { auth_basic "Restricted Access"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://internal_admin:80; proxy_set_header X-Real-IP $remote_addr;}Genera credenciales:
htpasswd -c /etc/nginx/.htpasswd adminCon un proxy de autenticación básica, los atacantes enfrentan un obstáculo adicional —nombre de usuario y contraseña— antes de llegar a tu servicio interno.
Otra opción es usar un servicio como Traefik o Caddy que pueda automatizar HTTPS y autenticación básica por ti.
Si deseas gestionar muchos dominios y servicios mediante una GUI, te recomendaría Nginx Proxy Manager.
🔍 Monitoreo y Verificación
Este es el paso más importante y más pasado por alto. Puedes contar con el mejor firewall, la mejor red y las mejores prácticas, pero si no verificas, no sabes si están funcionando.
Además, conocer solo un puñado de comandos —o saber dónde buscarlos— puede marcar la diferencia entre prevenir una brecha o no. Sentirte como un hacker es solo un extra. (Para detalles y ejemplos, avanza a la sección de Monitoring & Verification.)
No confíes, verifica dos veces
Verifica tus puertos
⚠️ IMPORTANTE: No escanees hosts que no poseas.
Ya sea que estés en una red doméstica o en un VPS, querrás saber qué puertos están abiertos al mundo.
Hay 2 formas de hacerlo:
- Revisar la red (
nmap,masscan) - Consultar el sistema operativo (
lsof,netstat,ss)
Probando fuera de tu red
Necesitarás tu IP (pública) actual, que puedes obtener fácilmente con servicios como ifconfig.me: curl https://ifconfig.me. O buscarla en el panel de control de tu proveedor de hosting.
curl -fsSL https://ifconfig.me# --> CURRENT PUBLIC IPUna vez que tengas tu IP pública, ahora debes conectarte a una red externa. Puedes usar la computadora de un amigo, un teléfono/punto de acceso 5G, o un servidor dedicado.
target_host="$(curl -fsSL https://ifconfig.me)"
# Nota: Asegúrate de que `target_host` sea la IP deseada
# Escanear puertos específicos:nmap -A -p 80,443,8080 --open --reason $target_host# Top 100 puertos:nmap -A --top-ports 100 --open --reason $target_host# Todos los puertosnmap -A -p1-65535 --open --reason $target_host#### Prueba dentro de tu red
Practica usando `nmap`, escanea tu red local o uno de tus servidores, revisa tu router, impresora, nevera inteligente.
{/* Mientras los escaneos de puertos son una constante, podrían violar la CFAA (Computer Fraud and Abuse Act) en EE. UU. Así que, escanea solo lo que posees. */}
#### Comandos de escaneo de ejemplo
```bash
# Escanea tu localhost en busca de todos los puertos abiertosnmap -sT localhost
# Escanea la IP privada de tu máquina para descubrir serviciosnmap -sV 192.168.1.10
# Encuentra detalles de servicios en tu rednmap -sn 192.168.0.0/24nmap -sn 10.0.0.0/24# O en una red Docker 172.18.0.1/16nmap -sn 172.18.0.1/16% nmap -A --open --reason 192.168.0.87
Starting Nmap 7.95 ( https://nmap.org ) at 2025-01-06 13:51 MSTNmap scan report for dev02.local (192.168.0.87)Host is up, received syn-ack (0.0067s latency).Not shown: 995 closed tcp ports (conn-refused)PORT STATE SERVICE REASON VERSION22/tcp open ssh syn-ack OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:|_ 256 {FINGERPRINT} (ED25519)80/tcp open http syn-ack Caddy httpd|_http-server-header: Caddy|_http-title: Dev02.DanLevy.net443/tcp open ssl/https syn-ack|_http-title: Dev02.DanLevy.net1234/tcp open http syn-ack Node.js Express framework|_http-cors: GET POST PUT DELETE PATCH|_http-title: Dev02.DanLevy.net (application/json; charset=utf-8).Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .Nmap done: 1 IP address (1 host up) scanned in 13.36 secondsVer puertos abiertos
Familiarízate con lsof; está disponible en macOS y Linux. Muestra el estado granular de la red y la actividad de disco.
# Monitorea un puerto específicosudo lsof -i:80 -PnMonitorar conexiones ESTABLISHED
sudo lsof -i -Pn | grep ESTABLISHED
Ver LISTEN
sudo lsof -i -Pn | grep LISTEN
para ver nombres de red en lugar de direcciones IP (puede ser muy lento hacer búsquedas DNS inversas)
sudo lsof -i -P | grep LISTEN
Monitorar todas las conexiones de red
sudo watch -n1 “lsof -i -Pn”
#### Salida de ejemplo

### Monitoreo de archivos
Para identificar qué **procesos** están consumiendo más **ancho de banda del disco**, puedes usar `iotop`:
```bashsudo iotopPara ver cambios individuales de archivos, puedes usar inotifywait en Linux o fswatch en macOS:
Esto puede ser útil para detectar comportamientos no autorizados o extraños por carpeta o a nivel del sistema.
# Monitorar todos los cambios de archivos en un directoriosudo inotifywait -m /path/to/directoryEn macOS puedes usar fswatch:
Instálalo con brew install fswatch
fswatch -r /path/to/directory## ⏰ Consejos que a menudo se pasan por alto
1. **Limitación de velocidad** para intentos de autenticación y cualquier otro punto crítico. Ya sea mediante el módulo `limit_req` de Nginx o `fail2ban` para acceso SSH, throttlear ataques de fuerza bruta es _probablemente_ una buena idea. Digo _probablemente_ porque en la era del IPv6 y los botnets baratos, ya no es lo que solía ser.
2. **Usar volúmenes de solo lectura** siempre que sea posible: ```yaml
services: webapp: volumes: - ./config:/config:roCombinado con otras buenas prácticas (usuarios sin privilegios, permisos mínimos en carpetas), la opción de montaje :ro brinda salvaguardas adicionales contra cambios accidentales y algunos intentos de escritura desde dentro del contenedor. No protege al host de un proceso que ya posee privilegios más amplios.
-
Auditar el acceso a contenedores de forma regular.
Si un contenedor no necesita un secreto, puerto o montaje, ¡elimínalo! -
Cuidado con el Wi‑Fi de mala calidad
Seguro que nunca compartirías la contraseña de tu Wi‑Fi, sobre todo con desconocidos, ¿verdad? Bueno, salvo algunos amigos… Vale, quizá también con la familia. Nunca sabes qué aplicaciones tienen y cuáles podrían divulgar tu SSID y contraseña al mundo.
Red doméstica vs. Proveedor público vs. Túnel
-
Aislamiento virtual/DMZ: Para servidores caseros, colócalos en una VLAN o DMZ separada si es posible. Así mantienes tus dispositivos internos fuera del alcance de una posible compromisión desde el servidor.
- Usa un router o VLAN independiente para tu servidor doméstico.
- Usa una red Wi‑Fi separada para tu servidor doméstico.
- Usa una subred distinta para tu servidor doméstico.
-
Proveedores de nube: Hetzner, Vultr, DigitalOcean, Linode, AWS, Azure y Google Cloud ofrecen distintas funcionalidades de firewall.
- Algunos proveedores y servicios bloquean puertos por defecto. Otros ofrecen opciones opt‑in o complementos. Revisa la documentación de tu proveedor.
- Muchos proveedores brindan servicios avanzados de monitoreo y detección de amenazas.
-
VPNs y túneles: Considera usar una solución tipo VPN o un servicio de túnel para conectar servicios de forma segura a través de Internet sin exponerlos al público.
- TailScale, ngrok, ZeroTier.
- WireGuard, OpenVPN.
🚀 Lista de verificación para producción
- Secretos: Todos los secretos generados aleatoriamente y almacenados de forma segura
- Actualizaciones: Estrategia de actualización de contenedores documentada y automatizada. (Vale si son solo unos comandos en un archivo de texto.)
- Red: Solo los puertos necesarios expuestos, redes internas configuradas.
- Reglas de firewall: Denegación por defecto, permitidos explícitos, bloqueos por país si es necesario.
- Proxy inverso: Nginx, Caddy o Traefik pueden añadir una capa de autenticación básica.
- Tokens canarios: Colócalos cerca de los archivos y credenciales sensibles que realmente investigarías si se manipularan.
- Monitoreo Conoce tus sistemas con
nmap,lsof,inotifywait,glances, etc. - Estrategia de copias de seguridad: Probada, preferiblemente automatizada y fuera del sitio.
- Principio de menor privilegio: Usuarios no root en contenedores, volúmenes de solo lectura.
📚 Lecturas adicionales
- Docker Security Best Practices
- OWASP Docker Security Cheat Sheet
- CIS Docker Benchmark
- Canarytokens.org for Canary Tokens
Gracias
Un agradecimiento a algunos Redditors atentos:
¡Gracias por leer! Espero que esta guía te haya sido útil. Si tienes preguntas o sugerencias, contáctame en mis redes sociales abajo, o simplemente pulsa el enlace Edit on GitHub para abrir un PR. ❤️