Rails provides a smooth
assets:precompile task to prepare application assets but keeps all required gems for assets generation as a standard part of the generated Gemfile. Let’s see if we can avoid these dependencies for runtime.
A new Rails application comes with various gems concerning assets compilation and minification:
We might see other gems in older versions of Rails, like
It makes sense since the Rails’s
assets:precompile tasks is usually run within the
PRODUCTION environment, where the CSS concerns are defined:
$ cat config/environment/production.rb ... # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false
# example of older applications config.assets.css_compressor = :sass config.assets.js_compressor = :uglifier
All of this works but includes extra gems and might have implications about system dependencies. For example, both
assets:precompile and possibly for starting the Rails server (Webpacker shouldn’t complain, but
But what if we want to handle assets outside the Rails application’s deployment, thus removing these dependencies? What if we’re going to build an optimized Docker container using the multi-stage build and not provide Node.js in the final image?
Well, we can do something we already do with development and test dependencies – omit these gems for production. We can move them into a new assets group in the Gemfile:
gem 'webpacker', '~> 5.0' group :assets do gem 'sass-rails', '>= 6' ... end # or group :assets do gem 'sass-rails', '>= 6' gem 'uglifier' ... end
We could be ommiting Webpacker for production only if we don’t depend on
Rails determines which groups to require by calling
Rails.groups. By default, that’s the
:default group (anything ungrouped), the environment group coming from
Rails.env, and anything added to the
RAILS_GROUPS environment variable. With
RAILS_GROUPS we can add the
assets group to our development and testing environments.
If you are building a Docker image, set
ARG during build (while avoiding it in the final image):
Once that’s done, let’s also instruct Bundler to load the configuration to set the right groups for the task at hand:
$ export RAILS_ENV=production $ export RAILS_GROUPS=assets $ bundle config set --local without development:test $ rails assets:precompile
assets:precompile task needs to include assets, but a production start later doesn’t:
$ export RAILS_ENV=production $ export RAILS_GROUPS= $ bundle config set --local without development:test:assets $ rails s
without option won’t load these assets gems but will fail whenever you try to use them in the configuration directly. This shouldn’t be an issue for a brand new Rails 6.1 application with Webpacker, but if your
js_compressor is set to
:uglifier, then omitting the gem ends up not starting the Rails server:
That happens because
uglifier relies on
Gem.loaded_specs, we can check if we are loading a specific gem and not set these configuration options:
... if Gem.loaded_specs.has_key?('uglifier') config.assets.js_compressor = :uglifier end ...
uglifier is used only if we are loading it – and we only load it while running
With the new assets group we could omit certain gems while running the Rails application server and leave out Node.js from the production server or a final container image. That saves memory and removes a possible attack vector. Not bad.
← IT'S OUT NOW
I wrote a complete guide on web application deployment. Ruby with Puma, Python with Gunicorn, NGINX, PostgreSQL, Redis, networking, processes, systemd, backups, and all your usual suspects.