# Rails Active Storage in 2026: File Uploads, S3 Integration and Interview Questions > Master Rails Active Storage for file uploads with S3 and direct uploads. Complete tutorial with code examples and common interview questions about file handling in Ruby on Rails. - Published: 2026-09-09 - Updated: 2026-09-09 - Author: Anthony Fillion-Maillet - Tags: ruby-on-rails, active-storage, file-upload, s3, aws - Reading time: 11 min --- Rails Active Storage provides a unified API for attaching files to Active Record models, supporting local disk storage and cloud services like Amazon S3, Google Cloud Storage, and Azure Storage. Released with Rails 5.2 and refined through Rails 8.1, Active Storage handles the complexity of file uploads, transformations, and cloud integration behind a simple interface. > **Direct Uploads Save Server Resources** > > With direct uploads enabled, browsers upload files straight to S3 using presigned URLs. The Rails server handles two small JSON requests instead of proxying large files, reducing memory usage and avoiding request timeouts on platforms like Heroku. ## Setting Up Active Storage with S3 in Rails 8 Active Storage ships with Rails. The installation creates three tables: `active_storage_blobs` for file metadata, `active_storage_attachments` for polymorphic associations, and `active_storage_variant_records` for tracking generated variants. ```bash # Terminal commands bin/rails active_storage:install bin/rails db:migrate ``` For S3 integration, add the AWS SDK gem to the Gemfile. The `require: false` option delays loading until Active Storage needs it. ```ruby # Gemfile gem "aws-sdk-s3", require: false ``` Configure the storage service in `config/storage.yml`. Store credentials in Rails credentials or environment variables, never in version control. ```yaml # config/storage.yml amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: us-east-1 bucket: myapp-production-uploads ``` Set the storage service per environment. Development typically uses local disk, production uses S3. ```ruby # config/environments/production.rb config.active_storage.service = :amazon ``` ## Attaching Files to Models with has_one_attached and has_many_attached Active Storage uses two macros for attachments: `has_one_attached` for single files like avatars, and `has_many_attached` for collections like photo galleries. ```ruby # app/models/user.rb class User < ApplicationRecord has_one_attached :avatar has_many_attached :documents end ``` Attaching files happens through assignment. Active Storage accepts `ActionDispatch::Http::UploadedFile` from form submissions, `File` objects, or blobs. ```ruby # app/controllers/users_controller.rb class UsersController < ApplicationController def update @user = User.find(params[:id]) @user.avatar.attach(params[:avatar]) if params[:avatar].present? @user.update(user_params) redirect_to @user end private def user_params params.require(:user).permit(:name, :email, documents: []) end end ``` Querying attachment presence uses `attached?`. For eager loading attachments to avoid N+1 queries, use the `with_attached_*` scope that Active Storage generates automatically. ```ruby # Checking attachment presence if @user.avatar.attached? # Process avatar end # Eager loading to prevent N+1 queries User.with_attached_avatar.where(active: true) ``` ## Direct Uploads: Browser-to-S3 File Transfer Direct uploads bypass the Rails server entirely. The browser requests a presigned URL from Rails, then uploads the file directly to S3. This approach handles large files without server timeouts and reduces memory consumption. Enable direct uploads by adding the `direct_upload: true` option to file fields. ```erb <%# app/views/users/_form.html.erb %> <%= form_with model: @user do |form| %> <%= form.file_field :avatar, direct_upload: true %> <%= form.file_field :documents, multiple: true, direct_upload: true %> <%= form.submit %> <% end %> ``` The `@rails/activestorage` JavaScript library handles the upload flow. Rails 8 applications using import maps get this automatically. ```javascript // app/javascript/application.js import * as ActiveStorage from "@rails/activestorage" ActiveStorage.start() ``` S3 buckets require CORS configuration to accept direct uploads from browsers. This policy allows uploads from any origin during development; restrict it in production. ```json [ { "AllowedHeaders": ["*"], "AllowedMethods": ["PUT"], "AllowedOrigins": ["https://myapp.com"], "ExposeHeaders": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"], "MaxAgeSeconds": 3600 } ] ``` ## Image Variants with libvips Processing Active Storage generates image variants on-demand using the [image_processing](https://github.com/janko/image_processing) gem. Rails 8 defaults to libvips, which processes images faster than ImageMagick while using less memory. ```ruby # Gemfile gem "image_processing", "~> 1.13" ``` Define variants inline or as named methods. Variants are generated lazily when first requested and cached for subsequent requests. ```ruby # Inline variant in view <%= image_tag @user.avatar.variant(resize_to_limit: [200, 200]) %> # Named variant for reuse # app/models/user.rb class User < ApplicationRecord has_one_attached :avatar do |attachable| attachable.variant :thumbnail, resize_to_limit: [100, 100] attachable.variant :medium, resize_to_limit: [300, 300] end end # Using named variant <%= image_tag @user.avatar.variant(:thumbnail) %> ``` The July 2026 security update ([CVE-2026-66066](https://rubyonrails.org/2026/7/29/Rails-Versions-7-2-3-2-8-0-5-1-and-8-1-3-1-have-been-released)) blocks untrusted libvips loaders by default. Applications processing BMP, ICO, or PSD files need explicit configuration, and libvips must be version 8.13 or higher. ## Validating File Uploads in Rails 8 Active Storage does not include built-in validations. Use the [active_storage_validations](https://github.com/igorkasyanchuk/active_storage_validations) gem or implement custom validators. ```ruby # Gemfile gem "active_storage_validations" # app/models/user.rb class User < ApplicationRecord has_one_attached :avatar validates :avatar, content_type: [:png, :jpg, :jpeg, :webp], size: { less_than: 5.megabytes } end ``` For custom validation without external gems, implement an Active Model validator. ```ruby # app/models/user.rb class User < ApplicationRecord has_one_attached :avatar validate :acceptable_avatar private def acceptable_avatar return unless avatar.attached? allowed_types = ["image/png", "image/jpeg", "image/webp"] unless allowed_types.include?(avatar.content_type) errors.add(:avatar, "must be PNG, JPEG, or WebP") end if avatar.byte_size > 5.megabytes errors.add(:avatar, "must be less than 5MB") end end end ``` ## S3 Alternatives: Cloudflare R2 and Backblaze B2 S3-compatible services like Cloudflare R2 offer significant cost savings. R2 provides 10GB free storage with zero egress fees. Configure these services using the S3 adapter with a custom endpoint. ```yaml # config/storage.yml cloudflare_r2: service: S3 access_key_id: <%= ENV["R2_ACCESS_KEY_ID"] %> secret_access_key: <%= ENV["R2_SECRET_ACCESS_KEY"] %> region: auto bucket: myapp-uploads endpoint: https://.r2.cloudflarestorage.com ``` The [Rails documentation on Active Storage](https://guides.rubyonrails.org/active_storage_overview.html) covers additional configuration options for different storage providers. ## Common Interview Questions About Active Storage Technical interviews for Rails positions frequently include questions about file handling. These questions assess understanding of storage patterns, performance considerations, and security practices. **How does Active Storage differ from CarrierWave or Shrine?** Active Storage ships with Rails and requires no external dependencies beyond the cloud SDK gems. CarrierWave and Shrine are standalone gems with different philosophies. CarrierWave uses uploader classes for processing logic. Shrine emphasizes a plugin architecture with explicit configuration. Active Storage favors convention and integrates tightly with Action View helpers and Active Record associations. For more foundational concepts, the [Active Storage module questions](/technologies/ruby-on-rails/interview-questions/active-storage) on SharpSkill cover the full range of interview topics. **What happens to attachments when a record is destroyed?** By default, Active Storage purges attachments asynchronously when the parent record is destroyed. The `dependent` option controls this behavior. ```ruby # Purge attachments synchronously on destroy has_one_attached :avatar, dependent: :purge_later # default has_one_attached :avatar, dependent: :purge # synchronous has_one_attached :avatar, dependent: false # keep files ``` **How do direct uploads handle network failures?** The JavaScript library emits events during the upload lifecycle. Applications can listen for `direct-upload:error` to handle failures and provide user feedback. ```javascript // app/javascript/application.js addEventListener("direct-upload:error", (event) => { const { id, error } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.add("upload-error") element.setAttribute("title", error) }) ``` **How would you prevent N+1 queries when displaying multiple users with avatars?** Active Storage generates a `with_attached_*` scope for each attachment. This scope eager-loads the blob and attachment records. ```ruby # N+1 problem: separate query per avatar User.all.each { |u| u.avatar.filename } # Bad # Solution: eager load attachments User.with_attached_avatar.each { |u| u.avatar.filename } # Good ``` For more [Ruby on Rails interview questions](/blog/ruby-on-rails/ruby-on-rails-interview-questions), including ActiveRecord optimization and testing strategies, SharpSkill provides focused preparation materials. ## Mirroring for Redundancy and Migration Active Storage supports mirroring uploads to multiple services simultaneously. This pattern works for redundancy or when migrating between storage providers. ```yaml # config/storage.yml production: service: Mirror primary: amazon mirrors: - cloudflare_r2 amazon: service: S3 # ... S3 config cloudflare_r2: service: S3 # ... R2 config ``` With mirroring, uploads go to all services, but downloads come from the primary. After migration completes, switch the primary and remove the mirror. ## Performance Optimization for Production Deployments Several techniques improve Active Storage performance in production environments. **Proxy mode for private files:** Active Storage can proxy downloads through Rails or redirect to signed URLs. Redirects are faster but require public files or signed URL support. ```ruby # config/environments/production.rb config.active_storage.resolve_model_to_route = :rails_storage_proxy # proxy config.active_storage.resolve_model_to_route = :rails_storage_redirect # redirect ``` **Variant tracking:** Rails 8.1 tracks generated variants in `active_storage_variant_records`. This table prevents regenerating the same variant repeatedly. **Background purging:** Configure Active Job to process `ActiveStorage::PurgeJob` in a dedicated queue to avoid blocking other jobs. ```ruby # config/application.rb config.active_storage.queues.purge = :low_priority ``` ## Key Takeaways for Rails Active Storage Implementation - Install Active Storage with `bin/rails active_storage:install` and configure S3 credentials in `config/storage.yml` using Rails credentials - Enable direct uploads with `direct_upload: true` on file fields to bypass server file handling and reduce memory pressure - Use `with_attached_*` scopes when loading multiple records with attachments to prevent N+1 queries - Add the `active_storage_validations` gem or implement custom validators since Active Storage has no built-in file validation - Consider S3-compatible alternatives like Cloudflare R2 for reduced egress costs - Update to Rails 8.1.3.1 or later for the [CVE-2026-66066 security patch](https://rubyonrails.org/2026/7/29/Rails-Versions-7-2-3-2-8-0-5-1-and-8-1-3-1-have-been-released) affecting libvips image processing - Use mirroring when migrating between storage providers to maintain file availability during transition --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/ruby-on-rails/rails-active-storage-file-uploads-s3-interview-questions