Cómo configurar Cloudflare R2 para tu comunidad Discourse

Los buckets de Cloudflare R2 se pueden usar para almacenar activos estáticos como imágenes y GIFs para la comunidad de Discourse, ¡pero no se pueden usar para almacenar copias de seguridad de la comunidad!

Introducción:

El almacenamiento de objetos Cloudflare R2 se puede usar como una alternativa a Amazon S3 para almacenar cargas para tu foro de Discourse. Los siguientes pasos describen cómo configurarlo.

Pasos de configuración:

  1. Habilitar cargas S3: Marca la casilla para habilitar las cargas S3 en la configuración de tu Discourse.
  2. ID de clave de acceso S3: Ingresa el ID de clave de API para tu bucket de almacenamiento R2. Este es el ID proporcionado cuando creaste un token de API para tu bucket.
  3. Clave de acceso secreta: Ingresa la clave secreta que se proporcionó cuando creaste el token de API que otorga acceso a tu bucket de almacenamiento. Importante: Esta clave secreta solo se muestra una vez, así que asegúrate de hacer una copia de seguridad segura.
  4. Región S3: Puedes ingresar cualquier región, no importa para R2.
  5. Bucket de cargas S3: Ingresa el nombre de tu bucket de almacenamiento R2.
  6. Punto final S3: Ingresa el enlace de la API S3 para tu bucket R2, que tiene el formato https://xxxxxx.com. Consulta el panel de control de Cloudflare R2 para encontrar este enlace.
  7. URL de CDN S3: Ingresa la URL pública del bucket de almacenamiento R2.dev para tu bucket. Esto también se encontrará en tu panel de control de Cloudflare R2.

Finalización:

Una vez que se configuren estos ajustes, tu foro de Discourse estará configurado para usar Cloudflare R2 para el almacenamiento.

Información del nivel gratuito:

El servicio R2 de Cloudflare proporciona un nivel gratuito que incluye 10 GB de almacenamiento, 1 millón de cargas y 1 millón de operaciones de lectura por mes.

5 Me gusta

Te recomiendo que sigas los ejemplos de Configurar un proveedor de almacenamiento de objetos compatible con S3 para subidas y pongas la configuración en tu archivo yml en lugar de en la base de datos.

Gracias por tus comentarios. He leído atentamente la guía anteriormente y creo que el consejo sobre Cloudflare R2 es incorrecto. El artículo sugiere que la comunidad Discourse no admite los buckets de Cloudflare R2. Sin embargo, en realidad, Cloudflare R2 es muy compatible con S3 y puede manejar perfectamente las cargas y descargas de imágenes y archivos para la comunidad Discourse. Esto ha sido verificado a través de la aplicación práctica en mi comunidad (starorigin.net).

1 me gusta

Y sospecho que eso era cierto cuando se escribió.

Es mucho mejor poner la configuración de S3 en el archivo yml que configurarla a través de la UX y almacenarla en la base de datos. ¿Has intentado restaurar tu base de datos en un nuevo servidor?

Una vez que hayas configurado las cosas de la manera recomendada, puedes editar ese tema o hacer un comentario y pedirle a alguien más que lo haga.

1 me gusta

Tienes razón, utilizo un bucket de almacenamiento Cloudflare R2 para almacenar las imágenes, GIFs y otros recursos de mi comunidad. Esto reduce en gran medida la carga del servidor de la comunidad y acelera la carga de las páginas.

No he configurado copias de seguridad automáticas para que se almacenen en el bucket de almacenamiento Cloudflare R2 porque los buckets de Cloudflare R2 no admiten el almacenamiento de archivos comprimidos. Sin embargo, el almacenamiento Cloudflare R2 puede almacenar los PDFs, imágenes, GIFs y otros recursos estáticos de la comunidad, lo cual también es muy bueno.

Gracioso. Pensé que había usado R2 para copias de seguridad antes. Pero quizás no lo recuerdo correctamente.

Aún puedes seguir las instrucciones recomendadas y tomar nota de no poner copias de seguridad allí.

Gracias por el recordatorio, resaltaré esta parte.

Los buckets de Cloudflare R2 se pueden usar para almacenar activos estáticos como imágenes y GIFs para la comunidad de Discourse, ¡pero no se pueden usar para almacenar copias de seguridad de la comunidad!

Solo para actualizar esta publicación, tuve algunos problemas que debían incluirse antes de que Cloudflare funcionara para mí.


1. Región


Esto no era cierto, tuve que usar “auto” o la región que seleccioné, auto es más fácil, así que usa auto.
si necesitas saber qué opciones puedes usar, prueba con cualquier cadena aleatoria en tu región y:

sudo -E -u discourse bundle exec rake s3:upload_assets

Si usas nixos

sudo discourse-rake s3:upload_assets

Esto generará un error para tus opciones válidas.


2. Permisos de API


También es importante saber que los tokens de API restrictivos no funcionan. Tienes que usar Admin Read & Write
Object Read & Write no funcionó.

3 Me gusta

Error al ejecutar sudo -E -u discourse bundle exec rake s3:upload_assets @Eviepayne

Establecer región en automático.
También es posible que tengas que establecer:
DISCOURSE_S3_INSTALL_CORS_RULE: false

Hice ambas cosas y reconstruí app.yml:

  ## Configuración de S3
  DISCOURSE_USE_S3: true
  DISCOURSE_S3_REGION: auto
  DISCOURSE_S3_ACCESS_KEY_ID: XXX
  DISCOURSE_S3_SECRET_ACCESS_KEY: XXX
  DISCOURSE_S3_CDN_URL: https://pub-XXX.r2.dev
  DISCOURSE_S3_ENDPOINT: https://XXX.r2.cloudflarestorage.com/XXX
  DISCOURSE_S3_BUCKET: XXX
  DISCOURSE_S3_INSTALL_CORS_RULE: false

También confirmé que las claves de API son claves de API de cuenta en lugar de claves específicas del bucket (como se menciona en la publicación). Además, mi instancia de Discourse muestra esto:

Y después de ejecutar sudo -E -u discourse bundle exec rake s3:upload_assets, muestra:

`/root` no es escribible.
Bundler usará `/tmp/bundler20250410-2363-zj2g6x2363' como tu directorio de inicio temporalmente.
Instalando reglas CORS...
saltando
rake abortado!
Seahorse::Client::NetworkingError: Cuerpo de respuesta vacío o incompleto (Seahorse::Client::NetworkingError)
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/sse_cpk.rb:24:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/dualstack.rb:21:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/plugins/accelerate.rb:43:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/checksum_algorithm.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:16:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/invocation_id.rb:16:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/request_callback.rb:89:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/plugins/response_target.rb:24:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:39:in `block in call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/telemetry/no_op.rb:29:in `in_span'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:53:in `span_wrapper'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/telemetry.rb:39:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/seahorse/client/request.rb:72:in `send_request'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/client.rb:12654:in `list_objects_v2'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/bucket.rb:1513:in `block (2 levels) in objects'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/plugins/user_agent.rb:69:in `metric'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-s3-1.182.0/lib/aws-sdk-s3/bucket.rb:1512:in `block in objects'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:101:in `block in non_empty_batches'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:52:in `block in each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/aws-sdk-core-3.219.0/lib/aws-sdk-core/resources/collection.rb:58:in `each'
/var/www/discourse/lib/tasks/s3.rake:14:in `map'
/var/www/discourse/lib/tasks/s3.rake:14:in `existing_assets'
/var/www/discourse/lib/tasks/s3.rake:24:in `should_skip?'
/var/www/discourse/lib/tasks/s3.rake:36:in `upload'
/var/www/discourse/lib/tasks/s3.rake:197:in `block (2 levels) in <main>'
/var/www/discourse/lib/tasks/s3.rake:197:in `each'
/var/www/discourse/lib/tasks/s3.rake:197:in `block in <main>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
/usr/local/bin/bundle:25:in `load'
/usr/local/bin/bundle:25:in `<main>'
Tasks: TOP => s3:upload_assets
(Ver el rastro completo ejecutando la tarea con --trace)

Creo que podrías tener que eliminar el nombre del bucket del endpoint.
Se debe eliminar el /xxx final para que solo sea .com.

Reconstruyendo y volveré a ejecutar el comando, ¡gracias por ayudarme con esto!

Mi app.yml se ve así ahora:

  ## Configuración de S3
  DISCOURSE_USE_S3: true
  DISCOURSE_S3_REGION: auto
  DISCOURSE_S3_ACCESS_KEY_ID: XXX
  DISCOURSE_S3_SECRET_ACCESS_KEY: XXX
  DISCOURSE_S3_CDN_URL: https://pub-XXX.r2.dev
  DISCOURSE_S3_ENDPOINT: https://XXX.r2.cloudflarestorage.com
  DISCOURSE_S3_BUCKET: XXX
  DISCOURSE_S3_INSTALL_CORS_RULE: false

Creo que todo eso es correcto.
Asegúrate de que la CDN_URL (https://pub-xxx.r2.dev)
tenga acceso de lectura pública para que los usuarios anónimos puedan ver los recursos.
Puedes ver lo que está sucediendo en las herramientas de desarrollador del navegador. Obtendrás un montón de 403 y solicitudes en rojo en la pestaña de red si los permisos son incorrectos.

Sí, creo que sí:

¿Es esta la configuración correcta?

Esa es una forma de hacerlo, pero no es la forma recomendada y experimentarás problemas.
Suponiendo que ya tienes tu dominio y Cloudflare es tu DNS:

Cloudflare proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy proxy

¿Oh, necesito conectar el dominio personalizado al bucket?

dentro de la configuración del bucket de S3, hay una configuración de acceso público.
establece un subdominio único para él. (Cloudflare creará automáticamente el registro DNS por ti, además de proxy y caché)

¿Creo que lo tengo?

¿Has conseguido que las copias de seguridad también funcionen en Cloudflare R2 y es posible (suponiendo que las copias de seguridad en Cloudflare R2 sean posibles) hacer que haga copias de seguridad tanto localmente como en Cloudflare R2?

Además, ¿el script que sube todos los activos significa que los eliminará localmente (para liberar espacio)? ¿O hay un procedimiento separado que debo seguir para eso?

Gracias por tomarte el tiempo de ayudarme con esto :slight_smile:

Personalmente no lo he intentado.
Mi foro entra en la categoría de “no compatible” porque mi base de datos es externa y tengo una estrategia de copia de seguridad diferente a la de los pg_dumps que utiliza el foro.
Por lo que he oído, las copias de seguridad no funcionan en Cloudflare, pero nada te impide intentarlo.

1 me gusta