In Rails versions prior to 6.1, Rails Active Storage only served files from an expiring URL that redirected to the selected service. This made it impossible to serve the files via a CDN. There were many workarounds to this problem, and fortunately Rails introduced the new proxy features for serving files from Active Storage in Rails 6.1.
The proxy feature provides a permanent URL to an asset through your Rails application instead of an expiring URL. This enables you to put a CDN between your application and the browser. When a user requests an asset from your site the architecture looks like this:
The first time an asset is requested, it will be served by your application. On all subsequent requests, it will be served from the CDN. To test this, access a file served by Active Storage. Here is one as an example: View this link to see a proxied URL.
Open the “Network” tab in developer tools and request the resource again (i.e. refresh the page). You can see HIT in the cache headers, showing the file was served from a CDN, not from your application.
A lot of the workarounds the community was doing before Rails 6.1 involved creating a direct URL using the object key and the desired CDN domain. There were a few problems with this.
First of all, if you were serving public files from S3, the storage providers usually required the domain name and the bucket name to be identical. This really became a problem if you were using wildcard subdomains.
Also, it made it tricker to switch storage providers. It was possible, but there were necessary DNS changes and the buckets still had to conform to the specified naming conventions.
With the new proxy feature switching storage providers is as easy as updating `storage.yml` in your Rails application. You can also now easily use wildcard subdomains and have more freedom in naming your buckets.
Assuming you have Active Storage set up and you’re trying to add a CDN, all you need to do is update your routing and serving of files.
In your `storage.yml`, add the `public:true` setting to your configuration if you haven’t already done so:
In a standard Active Storage configuration, you serve the file using
`<%= image_tag(@user.avatar) %>` (for example).
This provides you an expiring URL that redirects to your storage service. You’ll notice the URL typically has the word “redirect” in the path.
Using the default Active Storage serving service, the URL will look like the one below:
Notice the blobs/redirect string in the URL. If you click an expiring URL it will redirect to your bucket and eventually expire. Which means it can’t be cached by a CDN.
To use a CDN, you need to change the URL to be permanent. One way to do this is to modify the `routes.rb` file and use the route directly.
In your view you can now use `<%= image_tag cdn_image_url(@user.avatar) %>`. If you look at the generated URL, you will see it now contains the string `blob/proxy` and when you click it you are not redirected to the bucket/key endpoint:
If you don’t have your CDN set up at this point, you can use your app host in the `CDN_HOST` variable, and the assets will still resolve correctly.
With the setup just described, you can still use the redirect URL alongside the `cdn_url` as needed. The following routes both work. The first produces the redirect URL, and the second produces the proxy URL.
If you want to proxy all your files, you can add an initializer to your application. Create an `active_storage.rb` file in `config/initializers` with the following:
`Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy`
You can then remove the `cdn_routes` created in the previous step, and all files will use the proxy URL. So in this case with one additional line in the configuration a standard `<%= image_tag (@user.avatar), width: 500, height: 500 %>` will produce a proxy URL.
Another option is to use route helpers directly. With no `active_storage.rb` configuration and no `cdn_image` routes defined, you can use `<%= image_tag rails_storage_proxy_path(@user.avatar), height: 100, width: 100 %>` directly in your view to generate the proxy URL.
This helper accepts parameters, so you can specify your CDN host, if desired.
The architecture for the complete DNS and CDN setup for the application will look something like this:
That's it! Once all the DNS changes have propagated, the setup is complete. The very first time a file is requested, the request will go through your Rails application, and subsequent requests will be served via the CDN.