Rails 3.1 Assets on S3 with HTTPS

So your Rails app is deployed on Heroku which is pretty sweet and makes things easy. The Rails 3.1 asset pipeline is pretty awesome and packages your files up for you. You're using carrierwave to store Image attachments on Amazon S3, and everything is going great. Then, it's time to scale. Heroku isn't going to like serving all your assets all the time, CDNs are the right place to do that. Oh, and you need to be able to use SSL on the registration and login pages. Seems pretty easy, right? There are plenty of very easily findable docs on each of those topics, but gluing them together took some figuring. First up, Make sure your Amazon S3 buckets are in the format 'media-example-com' and _NOT_ 'media.example.com'. Due to the way HTTPS works, if you try to access a https://media.example.com.s3.amazonaws.com/ URL you're going to get an invalid certificate warning. Doh! If you haven't done this, it's pretty annoying because there is no concept of 'move' or 'rename', so you'll have to copy files from the old bucket to the new one. Go ahead and start on this, it might take you a while. Once that's done, it's pretty much just gems and configuration. Make sure you have these guys installed:
gem 'fog' # Storage on S3 for carrierwave
gem 'rmagick' # Resizes images and makes thumbnails
gem 'carrierwave' # Image attachments, newer version doesn't work with local files preview hack
gem 'asset_sync' # Syncs assets to S3
view raw Gemfile hosted with ❤ by GitHub
Then make a rake task that depends on 'assets:precompile' (which with asset_sync will take care of uploading your compiled assets to heroku):
ENV['AWS_ACCESS_KEY'] = 'XXX'
ENV['AWS_ACCESS_SECRET'] = 'XXXXXX'
ENV['AWS_BUCKET'] = 'bucket-example-com'
desc "deploys to heroku after uploading assets to S3"
task deploy: [:environment, 'assets:precompile'] do
puts `git push heroku`
puts `rake assets:clean`
end
view raw deploy.rake hosted with ❤ by GitHub
That `assets:clean` part? That makes sure to wipe out your assets locally so that when you make changes to CSS/JS locally, they will show up in your browser as expected. That one took a few of us a while to figure out. ("WHY WONT MY CSS UPDATE!!!111!!!") Then configure carrierwave, paying extra special attention to the fog_host URL, starting it with "//" which makes sure the resources will be loaded via http when the page is http, and https when the page is https:
if Rails.env.production?
CarrierWave.configure do |config|
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['AWS_ACCESS_KEY'],
aws_secret_access_key: ENV['AWS_ACCESS_SECRET'],
region: 'us-east-1'
}
config.fog_host = "//#{ENV['MEDIA_BUCKET']}.s3.amazonaws.com"
config.fog_directory = ENV['MEDIA_BUCKET']
config.fog_attributes = {'Cache-Control' => 'max-age=315576000'}
config.storage = :fog
end
elsif Rails.env.development?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
elsif Rails.env.test? or Rails.env.cucumber?
CarrierWave.configure do |config|
config.storage = :file
config.enable_processing = false
end
end
view raw carrierwave.rb hosted with ❤ by GitHub
Last up is configuring your asset_host for your production environment. All the docs suggest using a Proc here, but precompiling assets (needed for Rails 3.1 -> S3 on Heroku) doesn't have a 'request' to detect the hostname from so this completely blows up and you end up with no CSS or JS on your asset hosts. There are several related issues in the Rails issue tracker, but there's a super simple solution that works better than all of them:
config.action_controller.asset_host = "//#{ENV['AWS_BUCKET']}.s3.amazonaws.com"
view raw production.rb hosted with ❤ by GitHub
And you're all set! `rake deploy` will compile your assets, upload them to your bucket on S3, and deploy your code to Heroku. When a visitor visits http://example.com/ they'll get all the assets (JS/CSS) and all your media (item images) from the HTTP version of your Amazon S3 buckets, and when they visit https://example.com/, they'll get the HTTPS version. It should be super easy to plug in CloudFront if you need to scale up even further.