Se você já trabalhou com uploads em Rails antes do Active Storage, sabe que era quase uma gincana: Paperclip daqui, CarrierWave dali, Refile ou Shrine de lá... cada projeto com uma solução diferente, combinação de gems e integração manual com S3 ou outro serviço qualquer. Com o Active Storage, isso acabou! Tudo deixou de ser um drama, simplificando uploads, variantes de imagem e integração com nuvem do jeito Rails de ser: simples, direto e eficiente.
Quando trabalhamos com aplicações Rails, é muito comum começarmos com o armazenamento local dos arquivos. Mas conforme o projeto ganha tração, essa abordagem rapidamente se torna inadequada. É aí que entram os serviços de armazenamento em nuvem, como o Amazon S3 (Simple Storage Service).
O S3 se tornou praticamente o padrão da indústria para hospedagem de assets: é confiável, escalável e tem alta disponibilidade. Entretanto, o modelo de preços do S3 possui uma característica que pode se transformar em dor de cabeça para projetos em crescimento.
À medida que a aplicação cresce, surge um fantasma que todos tentam evitar: as taxas de egress. Não basta pagar para guardar seus arquivos. No S3, você também paga — e caro — para entregar cada imagem ou vídeo ao seu usuário. Isso pode inviabilizar apps que trabalham com mídia pesada, especialmente quando começam a ganhar tração.
Foi então que comecei a buscar alternativas mais inteligentes para resolver esse problema, sem abrir mão da facilidade do Active Storage. E assim cheguei ao Cloudflare R2 — um serviço que bebe da mesma fonte que o S3, é fácil de configurar no Rails e, ainda, não cobra taxa de egress. Isso mesmo: sem susto na fatura.
Neste artigo, vou te mostrar, em detalhes, como sair de uma solução tradicional de uploads para uma arquitetura mais moderna e econômica usando Rails, Active Storage e Cloudflare R2.
Por Que Cloudflare R2 Pode Ser Melhor Que S3?
Antes de colocar a mão no código, vale entender o porquê de pensar além do S3 tradicional quando o assunto é armazenamento de arquivos em produção.
O Problema: Custos e Performance
No S3, você paga para armazenar arquivos e também paga por cada GB baixado (egress). Só que tem um detalhe: o S3 não entrega arquivos via CDN automaticamente. Isso quer dizer que cada download é servido direto do bucket, sem cache global e sem otimização de rota, o que afeta tanto custo quanto a velocidade para quem está longe da AWS.
Se você quiser resolver o problema da entrega lenta, precisa configurar um CDN (como o CloudFront), o que adiciona mais uma camada de custo e complexidade. Além disso, tanto S3 quanto CloudFront também cobram por quantidade de requisições (GET, PUT, etc.).
O Cloudflare R2 simplifica esse cenário:
Não cobra nada por egress (download), independentemente do volume de saída de dados.
Já entrega arquivos usando a rede global da Cloudflare (CDN).
Também cobra por armazenamento e por requisição, como o S3, mas normalmente com preços mais baixos para operações típicas de leitura.
Comparativo de Custos
Serviço | Armazenamento | Download (egress) | Requisições | CDN Incluso? | Observações |
|---|---|---|---|---|---|
AWS S3 | $$ | $$$ (por GB baixado) | $$ | Não | Entrega direta do bucket, sem cache global |
AWS S3 + CloudFront | $$ | $$ (CloudFront + S3) | $$ (ambos) | Sim | Cobrança dupla em alguns casos, configuração extra necessária |
Cloudflare R2 | $ | $0 (sem egress) | $(por operação) | Sim | Cobra por GET/PUT/LIST/DELETE, CDN nativa, download livre |
Os valores variam conforme região, volume e tipo de operação. Sempre consulte as tabelas oficiais de preços: AWS S3 Pricing, AWS CloudFront Pricing, Cloudflare R2 Pricing.
Por que S3 não entrega arquivos via CDN automaticamente?
Por padrão, quando você salva arquivos no S3, eles ficam disponíveis apenas no data center da AWS onde o bucket foi criado. Ou seja, cada download é uma requisição direta ao bucket — e se seu usuário está em outra região do mundo, a latência aumenta e o download pode ficar lento.
Para resolver, a AWS recomenda usar o CloudFront (o CDN deles), mas:
Isso exige configuração extra (DNS, regras de cache, permissões, HTTPS).
Há custos adicionais de egress e de operação do CloudFront.
A configuração errada pode gerar downloads duplicados, cache “velho” ou falhas de acesso.
No Cloudflare R2, a CDN já faz parte da solução, sem passos a mais. É subir o arquivo e ele já está espalhado pela rede global.
Preparando o Projeto Rails
Considerando que você já tem um projeto Rails funcionando, vamos avançar direto para a configuração.
Setup Inicial
Se você ainda não tem uma aplicação Rails, basta criar com o comando abaixo e prosseguir para as próximas etapas:
rails new seu_projeto
cd seu_projetoAdicione as Gems Necessárias
Mesmo usando Cloudflare, a integração é feita via protocolo S3, então usamos a gem oficial da AWS.
No seu Gemfile, adicione as seguintes gems:
gem "aws-sdk-s3"
gem "image_processing"A gem aws-sdk-s3 permite o uso da API S3, enquanto a image_processing é essencial para o processamento de variantes das imagens no Active Storage.
Depois, rode:
bundle installConfigure o Active Storage
Se ainda não tiver configurado, execute os comandos para instalar o Active Storage e criar as tabelas necessárias no banco de dados:
rails active_storage:install
rails db:migrateEssas migrations criarão as tabelas active_storage_blobs e active_storage_attachments, que são responsáveis por associar arquivos aos seus modelos.
Crie o Model e o Formulário
Vamos supor que você tenha um modelo Post:
rails g scaffold Post title:string content:textNo model:
class Post < ApplicationRecord
has_one_attached :cover_image
endNo controller (posts_controller.rb), permita o novo campo nos parâmetros:
def post_params
params.require(:post).permit(:title, :content, :cover_image)
endNo form (app/views/posts/_form.html.erb):
<%= form.label :cover_image %>
<%= form.file_field :cover_image, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
Dica: Se quiser múltiplos arquivos, use has_many_attached e file_field :cover_images, multiple: true.
Configure o Cloudflare R2
O Cloudflare R2 é fácil de configurar, mas tem uma interface diferente do S3. Para começar, acesse o painel da Cloudflare e navegue até a opção Armazenamento de objetos do R2 (R2 Object Storage), na barra lateral. Lá você verá todos os buckets associados à sua conta - se você ainda não criou nenhum, a lista estará vazia.
A seguir, clique em Criar Bucket e defina um nome único, como cms-blog-uploads. Você verá a seguinte tela:
Deixe a localização como Automática e mantenha as opções padrão de classe de armazenamento para obter melhor performance. Não é necessário fazer alterações adicionais nessa etapa.
Depois de clicar no botão "Criar bucket", você será redirecionado de volta para a visão geral dos buckets, onde verá o bucket recém-criado na lista. O próximo passo é criar um token de API para usar com o Active Storage ao fazer upload de arquivos. Para criar um token, clique no botão "{} API", que mostrará um menu suspenso:
Em seguida, clique em Gerenciar tokens de API (Manage API tokens), que nos levará para a seguinte tela:
Em seguida, clique no botão "Criar token de API Account" para criar uma chave de API associada à sua conta, e você verá a seguinte tela:
Nesse formulário, podemos configurar o seguinte:
Nome do Token: podemos dar uma identificação ao token se for pertinente.
Permissões: como queremos gerenciar uploads a partir da nossa aplicação Rails, precisamos dar permissões de "Administrador com Permissão de Leitura/gravação" para este token.
Especificar bucket(s): é possível limitar quais buckets essa credencial poderá acessar.
TTL: definido como "Sempre" (Forever) por padrão, mas podemos alterar se precisarmos que o token expire antes.
Filtragem de endereço IP do cliente: podemos limitar o endereço IP que pode usar o token. A menos que você queira filtrar o acesso ao token, é seguro deixar este campo vazio.
Em seguida, após confirmar os valores de configuração, devemos ver algo como isto:
Esta tela fornece quatro valores importantes, que vamos utilizar:
Token de API do Cloudflare: usado para autenticar requisições à API do Cloudflare.
Access Key ID e Secret Access Key: estas credenciais substituem as credenciais da AWS quando usamos clientes compatíveis com S3, como faremos.
Endpoint específico de jurisdição: usado quando temos restrições jurisdicionais necessárias para atender aos requisitos de dados.
Por fim, precisamos recuperar o ID associado à nossa conta Cloudflare.
Existem duas maneiras de obtê-lo:
ele está na URL da maioria das seções do painel:
https://dash.cloudflare.com/#{ACCOUNT_ID}/r2/overview, por exemplo.Alternativamente, você pode navegar até as configurações dos seus buckets e verificar o cabeçalho da seção Geral, onde você verá algo como isto:
Na imagem acima, o account_id corresponde à seção borrada (um código alfanumérico longo que identifica sua conta Cloudflare).
Com todas as credenciais em mãos, podemos configurar o Active Storage para usar o R2 como serviço de armazenamento. Vamos avançar para a implementação prática.
Configurando o Rails para Usar o Cloudflare R2
Chegou o momento de integrar o R2 à sua aplicação Rails.
Use o sistema de credentials do Rails (mais seguro que arquivos .env ou variáveis de ambiente isoladas):
bin/rails credentials:editAdicione:
cloudflare:
account_id: SEU_ACCOUNT_ID
r2:
account_api_token: SEU_ACCOUNT_LEVEL_TOKEN
access_key_id: SEU_ID_DE_CHAVE_DE_ACESSO
secret_access_key: SUA_CHAVE_DE_ACESSO_SECRETA
endpoint: ENDPOINT_ESPECIFICO_PARA_SUA_JURISDICAO
bucket: NOME_DO_SEU_BUCKETAgora, podemos acessar estes valores com segurança no arquivo storage.yml, que já foi criado em nosso repositório:
# config/storage.yml
cloudflare:
service: S3
endpoint: https://<%= Rails.application.credentials.dig(:cloudflare, :account_id) %>.r2.cloudflarestorage.com
access_key_id: <%= Rails.application.credentials.dig(:cloudflare, :r2, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:cloudflare, :r2, :secret_access_key) %>
bucket: <%= Rails.application.credentials.dig(:cloudflare, :r2, :bucket) %>
region: autoNo seu ambiente (por exemplo, config/environments/production.rb) altere o serviço, conforme a seguir:
config.active_storage.service = :cloudflareSe quiser testar localmente, faça o mesmo em development.rb.
No entanto, se tentarmos fazer upload de um arquivo com nossa configuração atual, receberemos o seguinte erro: Aws::S3::Errors::InvalidRequest com a mensagem You can only specify one checksum at a time.
Isso ocorre porque o Cloudflare R2 e o Active Storage têm implementações ligeiramente diferentes do protocolo S3 quando se trata de verificação de checksums.
O erro se origina do método ActiveStorage::Blob.create_and_upload!, que envia dois checksums simultaneamente, algo que é permitido pela AWS, mas rejeitado pelo R2 neste caso.
Este problema parece ocorrer em versões mais recentes do SDK da AWS, então definir uma versão anterior seria uma alternativa. No entanto, a solução mais simples é modificar a configuração no storage.yml adicionando o seguinte:
request_checksum_calculation: "when_required"
response_checksum_validation: "when_required"Agora, após reiniciar o servidor, podemos fazer upload de arquivos diretamente para o Cloudflare R2:
Ao criar um novo post com uma imagem anexada, o arquivo será automaticamente enviado para o seu bucket no R2:
Agora tudo está funcionando! Mas com essa configuração, não conseguiremos realizar uploads diretos com o Active Storage. Então, vamos ajudar o R2 para adicionar esse suporte.
Habilitando os Direct Uploads
Às vezes, especialmente em aplicações voltadas para o usuário final, faz sentido permitir que os próprios usuários enviem arquivos direto do navegador para o storage na nuvem — principalmente arquivos grandes. Assim, ganhamos em performance, reduzimos a carga no nosso servidor e melhoramos a experiência do usuário.
O processo funciona assim:
O navegador solicita ao nosso servidor uma URL assinada para upload (usando nossas chaves do Cloudflare R2 e informações sobre o arquivo).
O servidor responde com essa URL temporária (assinada), autorizando o upload direto.
O navegador então faz o upload do arquivo diretamente para o Cloudflare R2 usando essa URL.
Quando o upload termina, o Cloudflare retorna informações do arquivo, e o navegador pode informar a aplicação, que salva os dados necessários no banco.
O fluxo, resumindo, fica assim:
Para liberar o upload direto do navegador para o Cloudflare R2, é essencial configurar corretamente as permissões de CORS (Cross-Origin Resource Sharing) no painel do Cloudflare. É isso que permite que o navegador se comunique diretamente com o storage na nuvem sem esbarrar em bloqueios de segurança.
O caminho é simples:
Acesse o seu bucket no painel do Cloudflare R2.
Vá para a seção de configurações e procure por CORS.
Adicione uma nova política, informando quais domínios podem realizar upload, além dos métodos e headers permitidos.
Veja um exemplo de configuração prática:
[
{
"AllowedOrigins": [
"http://localhost:3000",
"https://meusite.com"
],
"AllowedMethods": ["GET", "POST", "PUT"],
"AllowedHeaders": ["Content-Type", "Content-Md5", "Content-Disposition"],
"ExposeHeaders": ["Origin", "Content-Type", "Content-Md5", "Content-Disposition"],
"MaxAgeSeconds": 3600
}
]No exemplo acima, adicionamos os métodos POST e PUT, que são essenciais para uploads, além do GET para leitura. Definimos os AllowedHeaders, que são os cabeçalhos que nossa aplicação pode enviar ao fazer o upload, e também os ExposeHeaders, que controlam quais cabeçalhos estarão disponíveis na resposta do R2 para o navegador. O campo MaxAgeSeconds determina por quanto tempo o navegador pode manter essa configuração de CORS em cache, otimizando as próximas requisições.
Se você utiliza um domínio personalizado durante o desenvolvimento ou já está colocando o app em produção, lembre-se de incluir todos esses domínios, igual fizemos no exemplo, na lista de AllowedOrigins. Assim, você garante que apenas as origens realmente necessárias terão permissão para acessar o bucket, evitando liberar acesso para domínios que não serão utilizados.
URLs Públicas com CDN no Cloudflare R2
Uma das grandes vantagens do Cloudflare R2 é a possibilidade de disponibilizar seus arquivos com URLs amigáveis e entregues globalmente pela CDN. Ao configurar um domínio próprio para o bucket, você deixa os links dos seus assets muito mais profissionais, além de garantir performance máxima para quem acessa de qualquer lugar do mundo.
Se você ainda não configurou, vale a pena fazer: em poucos passos você conecta um subdomínio seu (exemplo: assets.meusite.com) ao R2. Assim, todos os arquivos públicos armazenados ali passam a ser acessados por uma URL como:
https://assets.meusite.com/alguma_pasta/arquivo.jpgAlém de valorizar a identidade do seu produto, essa configuração garante que seus arquivos serão entregues via CDN da Cloudflare, aproveitando o cache global e reduzindo a latência.
Abaixo, vou explicar como realizar esse processo direto no painel do Cloudflare. Simples e fácil.
Como configurar seu bucket no Cloudflare R2
Veja como é simples conectar o seu subdomínio ao Cloudflare R2 para deixar as URLs dos seus arquivos mais profissionais:
No menu do bucket, nas configurações, localize a seção “Domínios personalizados” ou “Custom Domains”:
Clique em “+ Adicionar” ou “+ Add”. Insira o subdomínio que você quer usar, como assets.meusite.com. Digite o subdomínio que deseja conectar (por exemplo, assets.meusite.com) e clique em Continuar (Continue).
O Cloudflare vai mostrar o registro DNS que precisa ser criado — normalmente um CNAME apontando do seu subdomínio para o endpoint do R2. Clique em Conectar Domínio (Connect Domain).
Aguarde alguns minutos para que o status do domínio mude. Às vezes, é necessário atualizar a página. Se o status não mudar, clique nos três pontinhos ao lado do bucket e escolha Tentar novamente (Retry connection).
Pronto! Agora seu bucket está acessível por um endereço próprio e todos os arquivos públicos já podem ser acessados por essa URL personalizada e entregues via CDN, com toda a performance da Cloudflare.
Ative o Proxy Mode no Rails
Agora que você já conectou seu domínio próprio ao bucket no Cloudflare R2, é importante ativar o Proxy Mode no Active Storage. Essa configuração garante que os arquivos sejam entregues usando rotas estáveis e apropriadas para cache na CDN, aproveitando todo o potencial de performance do seu domínio customizado.
Por padrão, o Active Storage usa o Redirect Mode: ele gera uma URL temporária assinada, que redireciona o usuário para o storage (R2/S3) e expira rapidamente. Esse comportamento impede o cache eficiente na CDN, pois cada URL é diferente e dura poucos minutos.
Com o Proxy Mode, o Rails serve os arquivos por meio de rotas próprias (como /rails/active_storage/blobs/:signed_id). Assim:
O arquivo é baixado do bucket apenas na primeira requisição.
O Rails adiciona cabeçalhos de cache corretos para a CDN.
Nas próximas vezes, o arquivo é servido direto do cache da Cloudflare, reduzindo a carga no servidor e acelerando o acesso para usuários de qualquer lugar do mundo.
Para que todas as URLs de blobs e variantes sejam servidas pelo proxy, basta adicionar essa linha em um initializer do seu projeto:
# config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxySe preferir ativar só em situações específicas, use os helpers rails_storage_proxy_path e rails_storage_proxy_url nas suas views:
<%= image_tag rails_storage_proxy_path(@post.cover_image) %>Depois disso, os links dos seus arquivos já vão usar as rotas proxy, ficando prontos para serem cacheados pela CDN do seu domínio customizado.
Você encontra detalhes e outros exemplos diretamente na documentação oficial do Rails (Proxy Mode).
(Opcional) Rota Direta
Se você quiser levar a integração ao próximo nível e ter ainda mais controle sobre as URLs dos arquivos (por exemplo, usando helpers personalizados ou integrando com rotas “bonitinhas” no seu domínio CDN), o Rails permite criar uma rota direta (direct route) para o Active Storage.
Isso é especialmente útil se você quer garantir que todos os links de assets já venham usando o seu domínio customizado — por exemplo, para SEO, organização de assets, integração com ferramentas de terceiros ou simplesmente para deixar as URLs ainda mais profissionais.
Exemplo de configuração de rota direta
No seu config/routes.rb, adicione:
# config/routes.rb
direct :cdn_blob do |blob|
File.join("https://assets.meusite.com", blob.key)
endDepois, basta usar nos seus views:
<%= image_tag cdn_blob_url(@post.cover_image.blob) %>Pronto: suas imagens e arquivos já estarão com URLs limpas, padronizadas e sempre servidas via CDN!
Conclusão
Ao integrar o Cloudflare R2 com o Active Storage no seu projeto Rails, você ganha o melhor dos dois mundos: uma solução robusta, econômica e pronta para escalar, sem abrir mão da simplicidade e dos padrões Rails que facilitam a vida de quem desenvolve.
Com o domínio customizado seus arquivos são entregues de forma profissional e rápida para usuários de qualquer lugar do mundo, aproveitando todo o potencial da CDN da Cloudflare — e sem surpresas na fatura do final do mês.
Se você está cansado das limitações e dos custos crescentes do S3, migrar para o R2 é um passo natural para tornar sua aplicação mais enxuta, eficiente e preparada para crescer.
Ficou com dúvidas ou tem alguma dica de implementação? Compartilha aqui nos comentários ou me chama para trocar ideia! Vou curtir saber como ficou a integração do R2 no seu projeto Rails.
Ainda não há comentários. Seja o primeiro a comentar!