Configuración de despliegue de Discourse con opiniones de MKJ

Tal vez respondiendo a mi propia pregunta, aquí hay algunas notas de intentar usar Garage con tu guía de implementación:


1. Garage no admite “etiquetas de permisos” en archivos (esta es la verdadera limitación de Garage)

Amazon te permite etiquetar cada archivo como “público” o “privado” de forma individual. Garage no implementa eso.

Discourse intenta usarlo por defecto. La parte engañosa: cuando Discourse decía “guarda este archivo, márcalo como público”, Garage aceptaba la solicitud e ignoraba silenciosamente la etiqueta. Así que las cargas básicas parecían funcionar bien, y solo se habría roto mucho después, la primera vez que alguien hiciera una carga privada.

Solución: decirle a Discourse que deje de usar etiquetas (un solo ajuste). El R2 de Cloudflare tiene la misma limitación y recibe la misma solución. Garage maneja los permisos a su manera, a nivel de cubo (bucket), que es todo lo que necesitamos.


2. Discourse insiste en nombrar los cubos de una manera específica (no es un defecto de Garage)

Discourse no dirá “el servidor en garage, cubo uploads”. Insiste en uploads.garage — el nombre del cubo pegado al frente, como un subdominio. Y no hay opción para desactivar eso.

Nada en nuestra red conocía ese nombre, así que Discourse no podía conectarse en absoluto — la instalación murió a mitad de camino.

Solución: di a Garage ese estilo de nomenclatura y registré los nombres. Dos líneas de configuración.

Este es un quirk conocido de Discourse, no de Garage — es por eso que el almacenamiento de Oracle está en la lista oficial de “no funcionará” de Discourse.


3. Las imágenes necesitaban una dirección web pública (nada que ver con Garage)

Discourse escribe la dirección de cada imagen en la publicación permanentemente. Sin decirle la dirección pública de antemano, guardó direcciones internas que solo nuestro servidor puede alcanzar — así que cada imagen estaría rota para los visitantes, para siempre, a menos que reconstruyéramos cada publicación.

Solución: establecer la dirección del CDN antes de la primera carga. Habría sido idéntico en Amazon, R2, cualquier cosa.


Nota: cada uno de estos fue silencioso. Nada dijo “no compatible”. Uno aceptó la solicitud e la ignoró, otro pareció un error de red, otro pareció funcionar bien.

Así que, solo algunas cosas iniciales a tener en cuenta para un nuevo usuario que intenta esto por primera vez, y/o alguien que intenta seguir la guía de Michael con Garage.

Aquí están las notas completas de IA de lo que experimentamos ahora que el foro ha sido desplegado:


Ejecutando Discourse en Garage autoalojado (S3) detrás de un túnel de Cloudflare — notas

Garage no está en la tabla de compatibilidad “Configurar un proveedor de almacenamiento de objetos compatible con S3”, así que aquí hay un punto de datos. Configuración: Garage v2.3.0, Discourse de dos contenedores, Debian 13,
nodo único, túnel de Cloudflare en lugar de un nginx externo. Advertencia de antemano: esta es una
pequeña implementación sin tráfico de producción aún, así que trátala como “funciona” y no como “probada
a escala de Maker Forums”.

  1. Funciona. Verificado contra las propias rutas de código de Discourse, no una CLI: UploadCreator,
    OptimizedImage, ListObjectsV2, HeadObject/GetObject con viaje de ida y vuelta byte-exacto,
    multipart, delete, remove_upload, más BackupRestore::Backuper escribiendo en el cubo de copias de seguridad
    y BackupStore#files / #download_file leyéndolo de vuelta. La configuración de ciclo de vida
    funciona, así que s3_configure_tombstone_policy realmente tiene efecto en lugar de ser
    ignorada silenciosamente. PutBucketCors funciona, así que el CORS manual está bien.

  2. Debes configurar el direccionamiento estilo host virtual, y romperá db:migrate si no lo haces.
    Discourse convierte el endpoint http://garage:3900 + cubo “uploads” en el host
    uploads.garage:3900, y no hay opción de estilo de ruta en ningún lugar de s3_helper.rb o
    site_settings.yml. Se requieren dos mitades: root_domain bajo [s3_api] en garage.toml,
    Y un nombre DNS resoluble por cubo (un alias de red Docker por cubo, ya que el DNS de Docker
    no tiene comodines). El síntoma si lo omites es un error Aws::Waiters sobre
    getaddrinfo durante SiteIconManager.ensure_optimized! — parece un fallo de red, no
    uno de almacenamiento. Cada nuevo cubo necesita un nuevo nombre.

  3. Garage no implementa PutObjectAcl / GetObjectAcl, así que establece s3_use_acls en falso —
    igual que R2. Dos trampas aquí. Primero, PutObject con --acl public-read es silenciosamente
    ACEPTADO y el encabezado ignorado, así que las pruebas de carga básicas pasan y solo se rompe después
    en una transición de carga segura. Segundo, DISCOURSE_S3_USE_ACLS no es una variable global oculta —
    ponerla en app.yml no hace nada. Apareció como verdadero en mi primer arranque a pesar de estar en
    el entorno. Tiene que ser una configuración del sitio, y necesita ser revisada después de cada reconstrucción
    porque nada te advierte.

  4. Si pones un CDN al frente, apúntalo al endpoint WEB de Garage (:3902), no al API S3
    (:3900). Las lecturas anónimas solo funcionan en el endpoint web; el API S3 correctamente devuelve 403
    a solicitudes no autenticadas, así que un CDN apuntando a :3900 falla en cada imagen mientras tus
    credenciales son perfectamente válidas. También necesitas garage bucket website --allow en el
    cubo de cargas, y un root_domain bajo [s3_web]. Actívalo solo en cargas — el
    cubo de copias de seguridad nunca debe ser legible anónimamente.

  5. ListObjectVersions devuelve NotImplemented en Garage. No importa: buscar
    s3_helper.rb y file_store/s3_store.rb para list_object_versions / object_versions
    devuelve cero coincidencias. Discourse no usa versionado de objetos S3; la expiración de marcadores de posición es
    reglas de ciclo de vida más un prefijo.

  6. Probar con aws-cli o mc NO prueba compatibilidad. Ambos usan por defecto
    direccionamiento estilo ruta contra un endpoint personalizado; Discourse solo hace estilo host virtual. Mi paso de CLI
    volvió 15/16 y pareció una luz verde, luego la instalación real falló
    inmediatamente en la diferencia de direccionamiento. Mismo almacén, mismas credenciales, mismas
    operaciones. Si estás evaluando un backend S3 no probado, ejecuta Discourse real — una
    instancia de contenedor único desechable es suficiente, y hacerlo antes de que exista cualquier contenido
    es todo el punto, ya que S3 → local es una puerta de un solo sentido.

  7. En un túnel de Cloudflare específicamente: la sección external-nginx-for-SSL no aplica
    (TLS termina en el borde, no hay certbot, no hay 80/443 entrante), pero la configuración de salida real-IP
    sigue siendo esencial y argumentablemente más aún. Usa real_ip_header CF-Connecting-IP
    en lugar de X-Forwarded-For — cuando el túnel es la única ruta de entrada es un único
    valor de conjunto de borde inequívoco. Verifícalo publicando desde una IP pública conocida y revisando
    lo que registró nginx; sin esto Discourse registra la dirección Docker del conector para
    cada solicitud y limita la tasa de toda internet como un solo cliente. Lo que pierdes versus
    el enfoque external-nginx es la página de mantenimiento durante las reconstrucciones — cloudflared
    no puede servir una.

  8. Corrección menor a algo ampliamente (mal)declarado, incluido por mí: DISCOURSE_S3_CDN_URL
    no está horneado irreversible en URLs almacenadas. upload.url sí almacena la URL S3 cruda con
    el host interno, pero Discourse sustituye el host CDN en tiempo de renderizado vía
    Discourse.store.cdn_url / UrlHelper.cook_url. Establecerlo tarde es recuperable — el
    costo es rake posts:rebake, porque posts.cooked almacena en caché el HTML desde el tiempo de horneado. Aún así
    establécelo antes de la primera carga; solo no entres en pánico si no lo hiciste.

  9. Dos notas operativas no relacionadas con Garage. ./launcher eco la línea completa docker run
    incluyendo DISCOURSE_S3_SECRET_ACCESS_KEY en texto plano, así que los registros de bootstrap son
    portadores de secretos — vale la pena rotar claves después de una instalación ruidosa. Y un Garage de nodo único
    aún requiere layout assign + layout apply antes de que almacene algo, con
    replication_factor = 1 significando ninguna replicación en absoluto, así que tu trabajo de copia de seguridad es la única
    durabilidad que tienes.