Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Rails 7: Switch Webpacker with esbuild While Using Tailwind and Docker


This set up gives you a flexible JavaScript and CSS environment that's lightning fast. I decided to use Node too but that's ok!

Quick Jump: What Bugged Me about Webpacker | The Perks of the New Set Up with esbuild | It's Docker / Docker Compose Friendly | Before / After Code Review on Video

If you prefer video I recorded a video going over all of these points as well as going over the code in a before / after state. The before and after is only covered on video since it’s more natural for that type of comparison.

Over the years I’ve been maintaining an example Rails app that’s set up to use Docker / Docker Compose. It pulls together Rails, Sidekiq, Postgres, Redis, Action Cable, Hotwire Turbo / Stimulus and TailwindCSS.

Originally it used Webpacker and it worked but I was never super happy about it. After moving to esbuild and using the TailwindCSS CLI tool along with Node (mainly for yarn installing packages) I couldn’t be happier.

This is something I wanted to do since early 2020 but switching back then required making too many compromises where it didn’t feel like a direct upgrade but it feels very much ready nowadays! Especially with nearly all major browsers fully supporting ES6.

This post focuses on Rails 7+ but it applies to any web framework if you’re looking to make the switch too.

What Bugged Me about Webpacker

I have nothing against Webpack(er) and think it has done wonders for us over the years but the main things that bugged me about using it with Rails was:

  • It didn’t use the default Webpack config, it was a very Rails opinionated way of handling Webpack which made it hard to apply general Webpack knowledge you’ve picked up
  • So many Node packages had to be installed to get a baseline set up working and this created long yarn install times
  • It took quite some time to generate Webpacker “packs” which was something you had to generate before running tests in CI
  • Even if you have an app with sprinkles of JS, it still took a bit to build things in dev and required enabling file caching with Webpack to make the experience feel decent

The first bullet was really painful because when I use Webpack with Flask, Django, Phoenix, Express and Play they all use the same exact Webpack set up. Webpack independently processes assets, outputs them to a desired path and the web framework picks them up.

The above web frameworks didn’t care about using Webpack or not, they only cared that static files existed in a specific directory which were then served.

The Perks of the New Set Up with esbuild

Every bullet point listed above has been addressed:

  • Rails 7 will look for built assets in a specific directory allowing you to use native configs and features from esbuild, TailwindCSS or whatever you decide to use
  • Running a yarn install on a fresh box only takes 30 seconds to go from never being run to ready to go even with 6 year old hardware (SSD included)
  • Building production esbuild JS and TailwindCSS bundles take ~250ms
  • In development the esbuild and TailwindCSS watchers will incrementally produce changes in 20-50ms for each update and there’s no sign it’ll get much slower since esbuild is a beast

As for using Node, here’s why I’m deciding to stick with it – at least for now:

  • While TailwindCSS does have a standalone CLI that doesn’t require installing Node, it only comes bundled with official and “blessed” plugins
  • Dropping packages into a package.json file and using yarn isn’t too bad in my opinion, neither is installing Node – especially with Docker
  • If you didn’t want to use Hotwire you’re free to install whatever JavaScript packages you want in a natural way that you’re used to (personally I like Hotwire)

The first bullet ends up being kind of a tease. If you want to use something like PostCSS imports you need to install the postcss-import package but since it’s not available in the standalone CLI (as of this post) you’re back to needing Node.

Now, the creator of Tailwind did say he’ll most likely add that package but that’s only 1 package. PostCSS has a lot of useful packages and unless he decides to bundle everything (making the standalone executable quite large) it just feels more natural to use Node.

Progress is being made but I’m not sure I feel good about dropping Node yet, one day!

It’s Docker / Docker Compose Friendly

It was painless to get the esbuild and tailwindcss watchers working in development. There’s an example of that in the Rails repo, but here’s a snippet:

# docker-compose.override.yml

x-assets: &default-assets
    context: "."
    target: "assets"
      - "RAILS_ENV=${RAILS_ENV:-production}"
      - "NODE_ENV=${NODE_ENV:-production}"
    - ".env"
  restart: "${DOCKER_RESTART_POLICY:-unless-stopped}"
  stop_grace_period: "3s"
  tty: true
    - ".:/app"

    <<: *default-assets
    command: "yarn build --watch"

    <<: *default-assets
    command: "yarn build:css --watch"

This uses a combination of multi-stage builds and the override pattern which I covered in my DockerCon talk from 2021. It lets you use the same Dockerfile and docker-compose.yml file in both development and production.

The really nice thing about this is it’s cross web framework compatible. When I switch all of my example apps to using esbuild I’ll be able to copy / paste almost everything directly over with no adjustments.

Now let’s dive into the before / after code, specifically for Rails 7.

Before / After Code Review on Video


  • 0:19 – The example Rails app we’ll be going over
  • 1:02 – About the app and browser support for ES6 compatibility
  • 3:09 – What bugged me about using Webpack(er)
  • 6:37 – The perks of the new esbuild / tailwindcss CLI set up
  • 8:36 – Why I’m going to continue using Node, at least for now
  • 12:09 – The esbuild / tailwindcss CLI set up is very Docker friendly
  • 14:18 – Going over the before / after in code
  • 15:08 – Starting with the list of packages from the Webpack version
  • 17:15 – The different sets of watchers we need to run
  • 20:16 – Running rails new with the -j esbuild and -c tailwind flags
  • 24:00 – Going over the Tailwind config and modifying the Rails generated code
  • 26:43 – How JavaScript is handled
  • 27:11 – Live debugging a silly mistake around why assets were md5 tagged
  • 31:25 – A few less files are required when we’re not using Webpacker
  • 33:24 – That’s it, any questions?

Have you switched to esbuild? How did it go? Let us know below.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per month (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.