Ways for reducing Heroku slug size


If you see this warning regularly and worry that your application may soon fail to deploy, then this article is for you.

Warning: You slug size (443 MB) exceeds our soft limit (300 MB) which may affect boot time

Slug

First things first, what is a slug?

A slug is essentially the compressed copy of your application comprising of your git repo along with packages that are installed at deploy time like gems, node_modules, etc. The maximum allowed slug size (after compression) is 500MB. Applications using multiple buildpacks especially can strain this limit.

Using slugignore

The first and foremost thing we need to do is determine which files/folders are taking up the maximum space on the dyno. Heroku has documented the command for doing just that but it doesn’t display hidden files.

It is crucial that you take hidden files into account due to the following reasons:

  • You could have hidden files/folder as part of your repository.
  • apt-based dependencies installed using heroku-buildpack-apt are stored in the hidden folder .apt
  • The buildpacks- ruby, python are installed in the hidden folder .heroku

If we are using multiple buildpacks, then it’s possible that the buildpacks themselves are consuming a major chunk of the slug size.

Let’s check who is consuming the maximum space from one of our applications-

# du -sh .[^.]* * | sort -hr

>>
216M   node_modules
188M   vendor
76M    tmp
38M    bin
16M    public
1.5M   app
372K   yarn.lock
236K   test
196K   config
108K   db
32K    lib
16K    .heroku

The immediate steps we can take is to start by ignoring files from the repository that are not required to run the app. We do this by specifying the name of the top-level folders inside the file .slugignore

# cat .slugignore

/test
/spec
/docs

Next, we can ignore any bundler groups that are not needed for the environment running our application. The development and test group are excluded by default, but if we are using any custom groups like docker, we can exclude it by adding it to the environment variable BUNDLE_WITHOUT.

heroku config:set BUNDLE_WITHOUT="development:docker:test"

node_modules and other artifacts

Even though the first two steps helped in reducing the slug size, they didn’t make much of a difference. What really helps is removing the node_modules folder after compilation but right before the slug is generated.

All that is needed is to add the below file to enhance the assets:clean rake task to run the rake task for removing the node_modules folder.

# cat lib/tasks/assets.rake
# Adapted from https://github.com/heroku/heroku-buildpack-ruby/issues/792

namespace :assets do
  desc "Remove 'node_modules' folder"
  task rm_node_modules: :environment do
    Rails.logger.info "Removing node_modules folder"
    FileUtils.remove_dir("node_modules", true)
  end
end

skip_clean = %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"])

unless skip_clean
  if Rake::Task.task_defined?("assets:clean")
    Rake::Task["assets:clean"].enhance do
      Rake::Task["assets:rm_node_modules"].invoke
    end
  else
    Rake::Task.define_task("assets:clean" => "assets:rm_node_modules")
  end
end

heroku-buildpack-ruby autoruns assets:clean on every deploy so we don’t need to manually do anything on top of this.

This simple technique resulted in following reduction in one of our smaller Apps-

# du -sh node_modules
247M	node_modules

# Slug size before

-----> Compressing...
       Done: 104M

# Slug size after

-----> Compressing...
       Done: 79.4M