Rails On-Premise... At RailsConf!

Jon Sully headshot

Jon Sully

@jon-sully

The Judoscale team just arrived back from RailsConf 2025 and boy did we have fun! In case you missed Adam’s posts on LinkedIn (and elsewhere), Judoscale brought the fun and music to RailsConf this year… with kazoos. Free Kazoos for all!

Adam pouring out a bag of Judoscale kazoos into a display at Judoscale’s RailsConf 2025 booth
We had so many kazoos..

Now, you might be asking yourself, “Why kazoos? What does that have to do with autoscaling?” And the official answer is… nothing! We didn’t sponsor RailsConf to try to sell Judoscale or win customers, we just wanted to have some fun and bring some silliness to the traditional sponsor-booth. So… kazoos! A bit of whimsy, a touch of joy, and a whole lot of noise. It was awesome.

While musing over this harebrained idea a couple of months ago, we sparked another: what if we had a gag-video titled “How to Kazoo” right beside the kazoos, which actually wasn’t a guide at all… what if we recorded several clips of us kazooing to classic party songs and played it really loud in the middle of RailsConf any time someone tapped the screen?

And that’s exactly what we did… in the style of a 90’s Beastie Boys music video:

So… beyond embarrassing ourselves for the sake of bringing joy to RailsConf’ers (which we did), there’s actually an interesting technical challenge here! We had a TV, which we wanted to display a demo of the Judoscale app most of the time, an iPad, which sat static, waiting for someone to tap ‘play’, a goal that hitting play on the iPad would cause both devices to simultaneously play a video in sync, and some kind of need for loud audio to also play synchronously. That’s a mouthful.

Here’s a diagram. Start with two devices showing different things:

Simple diagram showing an iPad with a green screen and a TV with a blue screen

Then, once tapped, both synchronously play the same video and audio comes from… somewhere!

A slightly more complex diagram now showing both displays with the same content on it, Adam and Jon stick figures, and a speaker in the middle somewhere indicating very loud volume

Did I mention that I wanted it to be loud? It’s a dance party, people!

Oh, one other constraint we wanted to account for (and a general rule of thumb) — don’t rely on conference wifi. So we had to get everything working without a network connection. Caching, preloading, etc. is fine, but there should be no network traffic required once the play button was tapped! We really wanted to prioritize both devices playing the videos in near-perfect synchronization.

Let’s round up our bill of needs here:

  • Two devices
  • A static screen for one device, with a button to trigger ‘playing’
  • A demo of Judoscale on the other device, but able to play videos on top at any given time
  • A speaker…somehow, somewhere
  • A tap on the iPad triggering the video on both devices
    • As close to perfectly synchronous as possible

The good news? All of this is possible with a modern Rails setup. And we built it in just a handful of hours. Let me show you how.

Feel free to skip to Step 2 if you’re only interested in the technical bits

Step 1: Make some Videos

Sometimes it’s easier to shape an idea based around media if you already have the media done. But there were a few steps involved here and I’m going to give you a little look behind the curtain. Our videos weren’t actually audio-dubbed in real time. That would be much harder (and fancier) than we wanted to tackle. Instead, Adam prerecorded all of the kazoo-cover tracks in GarageBand, like this:

And this:

Then we did the video separate.

👀 Note

Sadly we have no behind-the-scenes footage of Adam playing kazoos, alone, at his desk with headphones on. Come on, man!!

Here’s the thing about doing the video separate. It requires you to show up and act like an absolute doofus in the middle of a public park with essentially no audio and only the sound of your out-of-breath partner as you jump around a camera and hope that the final product is worth the foolery. Seriously, look at this nonsense (headphones recommended):

Anyway, once we had the audio and the video clips, we simply applied some effects, chopped them up, and lined up the timing as best we could in ScreenFlow. We ended up with fifteen video clips ready to be played for RailsConf’ers’ enjoyment. Here’s the supercut of all fifteen. Enjoy 😜

Step 2: Figure Out An Architecture

With some great videos in hand, we sketched out a quick setup of how this could work. If our setup was just an iPad it’d be a tiny Javascript task: have a webpage with a button and a bunch of hidden videos, then when the user clicks the button, Javascript intercepts the clicks, shows one of the videos, and hides it once the video finishes. But since we had two separate devices to coordinate, there had to be some server action.

Essentially we landed on having a separate webpage for the iPad and the TV, both served from a central Rails server, and both having all of the videos preloaded and hidden. The iPad’s page would incorporate a simple ‘play’ button (hold that thought) and the TV’s page would actually be an iframe to our demo app. Something like this:

Diagram showing both the iPad and the TV being served webpages from the Rails server, each getting a unique page but with both having hidden preloaded videos on them

That leaves two hurdles: audio and triggering. Let’s start with triggering since audio access and permissions may depend on how we trigger!

✅ Tip

Before we get into the actual pipeline of triggering, it’s important to call out that we specifically ensured that our videos were all loaded at page load, fully cached by the browser, and that playing them didn’t stream them from the server again. For both devices to play synchronously, neither could be re-pulling or streaming the video chunks from the server!

The preload attribute (<video ... preload="auto" ...>) in Safari ended up working for us both on desktop and mobile (iPadOS) —  all the videos would load at initial page-load and subsequent JS API calls to play() ran them from that cached file rather than re-streaming. Nice! Notably, and for reasons we didn’t debug all that far, we did not find similar behavior in Chrome.

The triggering magic here is ActionCable. Each of our two webpages (iPad and TV) subscribes to an ActionCable channel, VideoTriggerChannel. Once the iPad button is tapped, the POST is received by our VideoTriggersController:

class VideoTriggersController < ApplicationController
   VIDEO_COUNT = KazooVideo.all.count

   def create
     ActionCable.server.broadcast("video_trigger", {
       videoIndex: rand(VIDEO_COUNT)
     })

     head :created
   end
 end

Which sends a 201 back to the browser (so the button can be tapped again later) and triggers an instant ActionCable message to all listeners — simply an integer of which video to activate and play!

The Javascript on the front end is nearly as simple:

received({ videoIndex }) {
 const video = this.videos[videoIndex]
 video.show()
 video.play()
}

👀 Note

Keep in mind that our videos are all pre-cached and hidden, but they’re above the backing content in terms of Z-index. They’re also absolute and full-screen to ensure that the backing content fully ‘disappears’ during playback!

We have a couple of other JS functions to hide the video once it ends and prevent tap events in the meantime, but the idea is pretty simple. Since both devices are listening on the same ActionCable channel and we’re broadcasting a message to that ‘shared’ channel, both observe the message within a couple of (non-perceptible) milliseconds of one-another 🎉. Badda-bing badda-boom, the same video plays synchronously on both devices!

The last component here is audio! A synced up video with no audio is no fun. It’s a kazoo booth! Our early preference was that the audio would be played, one way or another, from the iPad. We knew we’d want the audio to be as loud as reasonable for the sponsor hall / area, but we also wanted to deliver that audio as close to the tapper as possible. Luckily we had a bottle-sized bluetooth speaker that hid right behind the iPad perfectly. Those things are louder than you may think!

The bigger concern with audio is whether or not iPadOS Safari would actually allow us to play audio at all! It’s one thing to trigger an audio’d video to play when you call .play() in direct response to a tap event. It’s something else entirely when an asynchronous websocket message triggers .play()! For reasons we chose not to question, iPadOS did let the audio play, and the speaker worked great. Say no more!

Here’s what the final architecture looks like, then:

Diagram showing the workflow of (1) iPad and TV each getting a webpage, (2) someone tapping ‘play’ on the iPad, and (3) a broadcast message to both that triggers the video playing

  1. Each device navigates their browser to a specified page on the Rails app, the iPad’s being a simple play button and the TV’s being essentially just an iframe for real demo content
  2. Someone taps the play button on the iPad when they want to see “How to Kazoo”
  3. The server receives that trigger and broadcasts an ActionCable message to play a video
    • Both devices / webpages are listening on that channel and immediately play the selected (random) video!

Step 3: In Practice…

There’s always a difference between planned architecture and actual reality! Luckily Adam and I set aside some time before RailsConf to fully test our rig and system. Turns out, we had some problems! And the whole thing immediately loses its charm when the screens fall out of sync… especially since the audio is only driven by one 😬:

✅ Tip

The specific issue we worked through there 👆 was that the iPad was connected to a VPN! That’s a huge network step and should absolutely be avoided!

So here are a few tips for little road blocks we ironed out along the way. It’s always the small things.

Don’t Trust Conference Wifi

Say it again with me now, don’t trust conference wifi. Especially not for a booth with two machines attempting to be in sync via websockets! That’s crazy-talk! But we did need some kind of wireless action going on. While I’ve been describing the TV as a stand-alone client in most of this post, it was actually setup as a monitor for our host laptop, so its full-screen browser was actually local to the Rails server.

The iPad, on the other hand, was fully wireless. We didn’t want to risk running any cables out to the table at the corner of our booth to avoid any trip hazards, so we need some kind of wifi here.

A picture of the Judoscale booth at RailsConf 2025, specifically showcasing the table on which the iPad stood, about 10 feet away from any other hardware, cables, or power
No trip hazards, please!

The solution? We brought our own!

Taking a page out of the early 2000’s, we simply brought our own router, spun up our own LAN (without WAN / internet access) and ran Rails from the host laptop in our booth. If you zoom in, you can actually see that laptop on the ground beneath the TV. That’s our server!

That is, we ran Rails on-premise at RailsConf to power our sponsor booth 😎.

Run Rails in Production Mode

It’s a small detail, but we’re mostly all used to running our Rails servers locally in development mode for development! That makes sense. Rails has lots of auto-reloading and re-connecting tooling in development mode that makes our lives easier when building these features.

Here’s the thing, though: Rails’ auto reloading often breaks websocket connections every time that reloading occurs. That should only really happen when you change the source code, but it’s not worth the risk. Once our Rails app / code was proven, tested, and working, we switched over to running it in Production mode. Again, it’s a small detail, but we had zero issues with our websockets and triggering across three days of conference fun.

For reference, here’s how we actually invoked our app (after manually compiling assets):

SECRET_KEY_BASE=random-value RAILS_ENV=production PORT=5006 bundle exec rails s -b 0.0.0.0

👀 Note

For reasons we didn’t dig into all that far, we need the -b 0.0.0.0 in order for the app to be accessible from another device on the local network. YMMV!

Use Guided Access

Did you know that all Apple devices ship with a Kiosk Mode feature? I sure didn’t! Adam discovered the accessibility setup, “Guided Access”, somewhere along the way and it essentially is kiosk mode. It allows you to fully lock down the device so that the screen stays on, users remain locked into the current application, and the iPad behaves like a simple kiosk interface!

We saved the iPad webpage off as a “Home Screen App” (a little Safari web container) then activated Guided Access once that app was open. From then on, it was simply a big play button, some fun videos, and some booming audio tracks 😁. Thanks, Apple!

Bring a Battery Bank (or two!)

Probably the briefest of our tips here, but just make sure that you have ample backup battery juice to power an iPad staying on all day! We didn’t run any cables to the Kazoo table and kept the iPad screen on all day. That eats up an iPad battery pretty quick! Do yourself a favor and keep some lithium on hand 🔋.

Have Fun at Conferences

Our goal in sponsoring RailsConf was to have some fun, bring some silliness to the conference, and encourage everyone to make a little music. We love supporting the Ruby and Rails communities (since Judoscale was built on both!) and prefer keeping things light and fun.

We hope our antics at the conference inspired a little bit of joy and maybe even sparked some creativity in how technology can be used to entertain and engage. Rails should be fun!

Here’s to many more conference adventures filled with laughter, creativity, and, who knows, maybe some other small plastic music-makers 👀.

Finally, a great big thank you to the Ruby Central team and the RailsConf 2025 organizers. It’s bittersweet to be the last RailsConf, but we treasured our time there. We’re looking forward to the resurgence of smaller, regional conferences as the Ruby/Rails community thrives onward!

With that, thanks for reading! You’re an all-star:

FREE KAZOOS! GET ‘YA FREE KAZOOS HERE!

A picture of Jon standing in the booth wearing a sign that says “Free Kazoos” and holding a bin of kazoos, ready to give them away