You want file attachments on Active Record models (has_one_attached,
has_many_attached) stored in Cloudinary. This gem ships an Active Storage service, so
Cloudinary becomes a storage backend like S3 or GCS.
Choose this over CarrierWave for new Rails applications — Active Storage is part of Rails and needs no extra dependency.
Declare the service in config/storage.yml. The service name is Cloudinary:
cloudinary:
service: Cloudinary
secure: truePoint the environment at it:
# config/environments/production.rb
config.active_storage.service = :cloudinaryCredentials come from CLOUDINARY_URL as usual — see
Configure Cloudinary.
Any additional keys in the storage.yml entry are passed through as upload options, so
you can set defaults for every attachment:
cloudinary:
service: Cloudinary
secure: true
folder: my-app-uploads # prefix every public_id
tags:
- productionclass User < ApplicationRecord
has_one_attached :avatar
has_many_attached :photos
enduser = User.create!(name: "Ada")
user.avatar.attach(
io: File.open("avatar.jpg"),
filename: "avatar.jpg",
content_type: "image/jpeg"
)
user.avatar.attached? # => true
user.avatar.key # the Active Storage key; also the Cloudinary public_idIn a view, the Cloudinary helpers accept the attachment directly and apply transformations:
<%= cl_image_tag(user.avatar.key, width: 200, height: 200, crop: "fill", gravity: "face") %>- The Active Storage key becomes the Cloudinary
public_id, prefixed byfolderif you configured one. Keys are random, so public IDs are not human-readable — that is Active Storage's model, not a Cloudinary limitation. resource_typeis derived from the content type: images asimage, video asvideo, everything else asraw.- Raw files keep their extension in the public ID, because that is how Cloudinary addresses raw assets.
- Uploads go through
upload_large, so large attachments are chunked automatically.
The service converts Cloudinary failures into
ActiveStorage::IntegrityError, matching what other Active Storage services raise:
begin
user.avatar.attach(io: io, filename: "big.mp4", content_type: "video/mp4")
rescue ActiveStorage::IntegrityError => e
warn "Attachment rejected: #{e.message}"
endDo not use Active Storage variant processing (avatar.variant(resize_to_limit:))
with this service — that pipeline expects local image processing via ImageMagick/Vips and
defeats the point of a transformation CDN. Build a Cloudinary URL instead:
<%= cl_image_tag(user.avatar.key, width: 400, crop: "fill", fetch_format: "auto", quality: "auto") %>The service extends ActiveStorage::Blob, so it can only load after the Active
Storage engine has initialized. In a standard application this is automatic: Rails loads
the engine, then reads config.active_storage.service from
config/environments/*.rb.
It only breaks if you resolve the service during application boot — for example setting
config.active_storage.service inside the Rails::Application class body in a script.
That raises uninitialized constant ActiveStorage::Blob. Keep the assignment in an
environment file, as Rails generates it.
Cannot load service :cloudinary—service: Cloudinarymust be capitalised exactly; it names the service class, not a lowercase adapter key.uninitialized constant ActiveStorage::Blob— the service was resolved before the engine finished loading. See Load order.- Attachments upload but URLs 404 — the environment's
config.active_storage.servicepoints at a different entry than the one you configured. - Raw file URL 404s without the extension — expected; raw public IDs include the extension. Use the key the service returns.
- Attachment succeeds but the asset is
rawwhen you expectedimage— thecontent_typepassed toattachwas generic (for exampleapplication/octet-stream). Pass the real content type.
- Runnable example:
examples/upload-with-activestorage.rb - Use with Rails
- Upload with CarrierWave
- Active Storage guide