🚀 Guía: Subdominios Dinámicos en Rails 8 con Kamal 2 y Cloudflare

Emmanuel Serna Sandoval
💻 Código
09 de marzo de 2026 • 3 min de lectura

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
end

4. 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"
end

5. 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 = true

6. El Despliegue con Kamal 2

Kamal 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áfico

7. Resumen de comandos útiles

Acció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 Esenciales

Para 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.