What to do when your Rails app is too big for Heroku
Let's say that for some strange reason, you end up working with a Rails application that's more than 300mb in file size. It's probably because it has a lot of really high-resolution images, a bunch of PSD's, a variety of binary files and some other junk that you really shouldn't be putting inside of your application. If you're on Heroku, that's going to be a problem because they only allow up to 300mb for an app. Here are some things that you can do to be able to reduce your application's file size before Heroku compiles it into a slug:
.slugignore
Everytime you do a deploy, Heroku compiles your app into a slug before it finishes deploying. This slug is what Heroku is actually pointing to when you're using your application and (correct me if I'm wrong) it's probably the reason why Heroku doesn't allow writing files on the fly inside your application.
The good thing about this is that you can tell Heroku to ignore some files that your repo has before it compiles it to a slug. That way, you can remove any binary files that you're not actually using during production.
What if your app is still too big for Heroku because of the assets?
If excluding all your binaries is still not enough to make your application small enough to deploy to Heroku, it's probably because of your assets. If that's the case, you need to do two things:
asset_sync
A lot of you probably know this gem already, but for the uninitiated, asset_sync is a gem that was built to synchronize assets inside the asset pipeline to an external source like S3. Once you've setup your app to use asset_sync, all of your assets should be copied to S3 (or whatever source you've chosen) whenever your app does a rake assets:precompile
.
I feel like I shouldn't even mention this, but just in case you forgot, your app should actually use the asset pipeline for this to work. That means the following:
- Use
javascript_include_tag 'somefile'
instead of<script type="text/javascript" src="somefile.js"></script>
- Use
stylesheet_link_tag 'somefile'
instead of<link rel="stylesheet" type="text/css" href="somefile.css" />
- Use
image_tag 'somefile'
instead of<img src="somefile.jpg" alt="some_text">
Delete app/assets
Yup, you read that right. It sounds crazy, but it's really not. Deleting app/assets before slug compilation will probably reduce your app's file size ten-fold. This won't be an issue since your assets are already in S3 through asset_sync. I found this solution on StackOverflow a few weeks ago, but I can't seem to find that specific answer again (I'll edit this post again once I found the link). Anyway, what you need to do is to make a rake task that deletes app/assets after it finishes syncing all of your assets to S3:
Rake::Task["assets:precompile:nondigest"].enhance do
return "can't run in dev" if Rails.env.development?
puts 'my assets:precompile hook is started!'
["#{Dir.pwd}/public/", "#{Dir.pwd}/app/assets/"].each do |dir_path|
records = Dir.glob("#{dir_path}**/*")
records.each do |f|
if f =~ /.*.png$/ or
f =~ /.*.jpg$/ or
f =~ /.*.gif$/ or
f =~ /.*.ico$/ or
f =~ /.*.eot$/ or
f =~ /.*.svg$/ or
f =~ /.*.woff$/ or
f =~ /.*.ttf$/ or
f =~ /.*.otf$/ or
f =~ /.*.css$/ or
f =~ /.*.js$/ or
f =~ /.*.sass$/ or
f =~ /.*.css$/ or
f =~ /.*.scss$/ or
f =~ /.*.coffee$/ or
f =~ /.*.wav$/ then
File.delete(f) if File.file?(f)
puts "removing #{f}"
end
end
puts Dir.glob("#{dir_path}**/*")
end
puts 'my assets:precompile hook is finished!'
end
Doing this reduce an app that I was working on from 650mb to 40mb (it had a lot of high-res images). I hope this helps.
Written by Terence Ponce
Related protips
3 Responses
Thanks for the tip!
Or you could just spin up your own Dokku instance with digital ocean for example.
http://vimeo.com/68631325
https://github.com/progrium/dokku
That's interesting. I'll probably give that a try in my VPS some time. The app that I was describing in this article is actually from a client, so I don't really have any say regarding deployment.
I good thing to remember is that you need the manifest.json stored in public/assets. So you can't just add a rake assets:clobber to the rake assets:clean
hook or rm -rf public/assets
Which I learned the hard way. Without the manifest, rails wont be able to find the assets. It tries to find the fingerprinted filepath stored in the manifest, and uses the asset_host to find the file on S3.
I say assets:clean
because that is run just before the slug compress during the build heroku does. Normally assets:clean removes old versions of your assets. But since it's a fresh slug it doesnt delete anything (sorry if Im wrong).
You could refactor your script by
Rake::Task["assets:clean"].enhance do
#You probally don't need to loop the app/assets folder, just remove it. I need to test this
["#{Dir.pwd}/public/", "#{Dir.pwd}/app/assets/"].each do |dir_path|
records = Dir.glob("#{dir_path}**/*")
records.each do |f|
if !(f =~ /manifest.*.json$/)
File.delete(f) if File.file?(f)
puts "removing #{f}"
end
end
puts Dir.glob("#{dir_path}**/*")
end
end