Using AWS S3 with Rails ActiveStorage for File Uploads

Sachin Kabadi's avatar

Sachin Kabadi

How to setup AWS S3 Bucket for Rails Active Storage

Active Storage in Rails simplifies file uploads to cloud storage services like Amazon S3. Here’s a comprehensive guide to setting up an AWS S3 bucket for Rails Active Storage.

Step 1: Create an S3 Bucket in AWS

  1. Log in to your AWS Management Console and navigate to the S3 service.
  2. Click on the Create bucket button.
  3. Enter a unique name for your bucket (e.g., rails-active-storage) and select a default region.
  4. In the Object Ownership section, select ACIs enabled.
  5. In the Block Public Access settings for this bucket section, uncheck the following options and acknowledge the current settings:
    • Block all public access
    • Block public access to buckets and objects granted through new access control lists (ACLs)
    • Block public access to buckets and objects granted through any access control lists (ACLs)
  6. Click on Create bucket.

Step 2: Create an IAM Policy in AWS

  1. Navigate to the IAM section and select Policies.
  2. Click on Create policy and configure:
    • Service: s3
    • Actions: Set access levels:
      • List: ListBucket
      • Read: GetObject
      • Write: PutObject, DeleteObject
      • Permissions management: PutObjectAcl
    • Resources: Select Specific, then add two ARNs for your bucket (e.g., rails-active-storage and rails-active-storage/*).
  3. Proceed to the next page, specify a name for the policy (e.g., rails-active-storage-s3), and click on Create policy.

Step 3: Create an IAM User in AWS

  1. In the IAM section, navigate to Users and click on Add user.
  2. Enter a user name (e.g., rails-active-storage) and under Permissions options, select Attach policies directly.
  3. Find and select the policy you've just created, then continue to create the user.
  4. Open the user's Security credentials tab, click on Create access key, and note down the access key ID and secret access key.

Step 4: Configure CORS in AWS Bucket

  1. Go to your AWS bucket, select Permissions, and navigate to Cross-origin resource sharing (CORS).
  2. Edit and paste the following configuration:
    [
        {
            "AllowedHeaders": ["Content-Type", "Content-MD5", "Content-Disposition"],
            "AllowedMethods": ["PUT", "POST"],
            "AllowedOrigins": ["*"],
            "ExposeHeaders": [],
            "MaxAgeSeconds": 3600
        }
    ]

Step 5: Configure Rails Application

  1. Add the access key and secret key to your Rails credentials:

    EDITOR="nano" bin/rails credentials:edit
    

    Paste the keys as follows:

    aws:
      access_key_id: <your_access_key_id>
      secret_access_key: <your_secret_access_key>
    

    To exit nano, press Ctrl+X, then Y, and hit Enter.

  2. Edit config/storage.yml to include your S3 configuration:

    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: <your_bucket_region>
      bucket: <your_bucket_name>
      public: true
  3. In config/environments/development.rb, add:

    config.active_storage.service = :amazon
    
  4. Add the AWS SDK S3 gem to your Gemfile:

    gem "aws-sdk-s3", require: false

    Then run bundle install.

Step 6: Setup Active Storage Tables

Generate and migrate Active Storage tables:

rails active_storage:install
rails db:migrate

Step 7: Attach files to records

Active Storage provides macros for attaching files:

  • Single File Attachments: Use has_one_attached for a one-to-one relationship. Example for an Employee model:

    class Employee < ApplicationRecord
      has_one_attached :photo
    end
  • Attach a photo: employee.photo.attach(params[:photo]) and check attachment: employee.photo.attached?

  • Multiple File Attachments: Use has_many_attached for a one-to-many relationship.

Example for a Product model:

class Product < ApplicationRecord
  has_many_attached :images
end
  • Attach images: @product.images.attach(params[:images]) and check attachments: @product.images.attached?

Step 8: Attaching and Removing Files locally

  • Attach locally stored files: @product.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf').
  • Remove attached files: @employee.photo.purge.

Step 9: Create and Download Links

  • Generate a download link: rails_blob_path(@employee.photo, disposition: "attachment").
  • Download a file: file_content = @employee.photo.download.

Another method for configuring AWS credentials is by employing the following options:

  1. Using environment variables:

    With this approach, we don't have to explicitly pass the credentials as aws-sdk gem will handle by itself.

    Add following lines in your .env file.

    AWS_ACCESS_KEY_ID = your_access_key_id
    AWS_SECRET_ACCESS_KEY = your_secret_access_key

    Edit config/storage.yml to include your S3 configuration:

    amazon:
      service: S3
      region: <your_bucket_region>
      bucket: <your_bucket_name>
      public: true
  2. Using (~/.aws/credentials) file:

    Create .aws folder in the root of you rails applications and create a credentials file (.aws/credentials)

    Add your credentials as default:

    default
       aws_access_key_id = YOUR_ACCESS_KEY_ID
       aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
    

    Create/Edit the /config/initializers/aws-sdk.rb

    Aws.config.update(
       credentials: Aws::SharedCredentials.new,
       region: <your_bucket_region>
       public: true
    )
    

Security Note Never commit the ~/.aws/credentials or .env or master.key file to version control to avoid exposing your credentials, instead add it to .gitignore.

Conclusion

With these steps, your Rails app is now configured to use AWS S3 for storage with Active Storage. This setup allows for efficient management of file uploads in your application.