Title: 🚀 Guía: Subdominios Dinámicos en Rails 8 con Kamal 2 y Cloudflare Author: Emmanuel Serna Sandoval Date: 2026-03-09 URL: https://eserna27.com/posts/guia-subdominios-dinamicos-en-rails-8-con-kamal-2-y-cloudflare Category: 💻 Código Esta arquitectura permite que cada usuario tenga su propia URL (ej: usuario.tuapp.com) compartiendo la misma base de datos y servidor. 1. Configuración de DNS (El "Embudo")Para que cualquier subdominio llegue a tu servidor, necesitas un comodín (Wildcard). • En Cloudflare: Crea un registro A con el nombre * apuntando a la IP de tu servidor. • Estado de Proxy: Activa la nube naranja (Proxied). Esto nos da SSL gratuito sin configurar certificados complejos en el servidor. 2. Compartir la Sesión (Login Universal)Por defecto, Rails trata a cada subdominio como un sitio diferente. Para que el usuario no se desloguee al saltar del dominio principal al subdominio, debemos configurar la cookie. config/initializers/session_store.rb: Ruby # El punto inicial permite que la cookie sea válida para todos los subdominios domain = Rails.env.production? ? ".tuapp.blog" : ".lvh.me" Rails.application.config.session_store :cookie_store, key: '_tu_app_session', domain: domain, tld_length: 2 # Ajusta según tu dominio (ej: .com = 2, .com.mx = 3)3. La "Aduana" de Rutas (Constraint)Necesitamos una clase que decida si una petición debe ir a la "Landing Page" o al "Blog del Usuario". app/constraints/subdomain_required.rb: Ruby class SubdomainRequired def self.matches?(request) # Filtramos para no atrapar el dominio principal o subdominios técnicos request.subdomain.present? && !["www", "admin", "api"].include?(request.subdomain) end end4. El Mapa de Rutas (routes.rb)El orden es vital: Rails lee de arriba hacia abajo. El bloque del subdominio debe ir primero. Ruby Rails.application.routes.draw do constraints(SubdomainRequired) do root "profiles#show", as: :user_blog resources :posts end # Rutas del dominio principal devise_for :users root "homes#index" end5. Configuración de Producción (Rails 8)Rails 8 es muy estricto con la seguridad. Hay que configurar el TLD Length (para dominios como .blog) y el Host Authorization. config/environments/production.rb: Ruby # 1. TLD Length: Segmentos del dominio base menos uno (ej: tuapp.blog = 1) config.action_dispatch.tld_length = 1 # 2. Host Authorization: Permitir subdominios y excluir el Health Check de Kamal config.hosts << ".tuapp.blog" config.host_authorization = { exclude: ->(request) { request.path == "/up" } } # 3. SSL Detrás de Cloudflare (Modo Flexible) config.assume_ssl = true config.force_ssl = true6. El Despliegue con Kamal 2Kamal Proxy debe saber que tiene permitido dejar pasar los subdominios hacia el contenedor de la app. config/deploy.yml: YAML proxy: # Lista de hosts permitidos host: - tuapp.blog - "*.tuapp.blog" app_port: 3000 # Puerto interno de Rails 8 ssl: false # Porque Cloudflare ya cifra el tráfico7. Resumen de comandos útilesAcción Comando Actualizar infraestructura kamal deploy Solo actualizar el Proxy kamal proxy reboot Ver logs del Proxy kamal proxy logs -f Ver logs de la App kamal app logs -f Consola de producción kamal app exec -i "bin/rails c" 🛠️ Configuración en Cloudflare (El "Cerebro" de la Red)Cloudflare no solo protege tu sitio, sino que actúa como el director de orquesta para tus subdominios dinámicos. 1. DNS: El Comodín (Wildcard)Para que cualquier-cosa.tuapp.blog funcione sin que tengas que crear registros manuales para cada usuario: • Tipo: A (o CNAME si usas un alias). • Nombre (Name): * (el asterisco es el secreto, captura todos los subdominios). • Contenido (Content): La IP de tu servidor Hetzner. • Proxy Status: Proxied (Nube Naranja). • • ¿Por qué? Esto permite que Cloudflare maneje el certificado SSL por ti y oculte la IP real de tu servidor. “[!TIP] No olvides crear también un registro A para el dominio raíz (@) apuntando a la misma IP.” 2. SSL/TLS: Modo "Flexible"Como en nuestra configuración de Kamal Proxy pusimos ssl: false, necesitamos que Cloudflare se encargue de la "cara pública" (HTTPS) mientras habla con nuestro servidor por un canal privado (HTTP). • Configuración: Ve a la pestaña SSL/TLS -> Overview. • Modo: Selecciona Flexible. • Resultado: El usuario ve https://usuario.tuapp.blog (con candado), pero Cloudflare le pide la información a tu servidor por el puerto 80 (HTTP). Es la forma más rápida de evitar errores de "Handshake" o certificados expirados. 3. Ajustes de Seguridad EsencialesPara garantizar que nadie entre por conexiones no seguras, activa estos dos interruptores en SSL/TLS -> Edge Certificates: 1. Always Use HTTPS: Redirige automáticamente cualquier intento de http:// a https://. 2. Automatic HTTPS Rewrites: Ayuda a solucionar errores de "contenido mixto" si accidentalmente cargas una imagen por HTTP.