Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All ideas and experiments

Idea

Can Campfire's Rust port run on Laravel Cloud?

· Ed Grosvenor

We have just been given early access to the Rust runtime on Laravel Cloud. The timing is hard to ignore: a few days ago 37signals released once-campfire-rust, a port of Campfire from Rails to Rust.

The numbers in its README are remarkable. It ships as one binary that replaces Ruby, Puma, Redis, Resque and Thruster. It serves the room page 95 times faster than Rails, sits at 15 MB of memory when idle, and holds 10,000 connected clients in a fifth of the memory Rails needs. It was built to be impossible to tell apart from the Rails app, down to the pixel, and it reads the Rails app's existing database and files so an install can switch over without anyone signing in again.

We have also just finished getting the Rails version running on Cloud. So this is a rare chance to run the same experiment twice, on the same product, in two languages, and see what actually carries over.

What is most likely to trip us up

We've read the source, but we haven't deployed anything. These are our best guesses, roughly in order of how much we expect them to hurt.

SQLite, and this time there's no adapter to swap

This was the known problem last time, and it is bigger here. Cloud doesn't offer SQLite, because the filesystem doesn't survive a deploy. In Rails, moving to Postgres was mostly configuration plus a search rewrite, because Active Record already speaks Postgres.

The Rust port has no such layer. Its data code is a reimplementation of Active Record written directly against SQLite: WAL checkpoints on their own thread, full-text search on SQLite's FTS5, and SQL written for SQLite throughout. Much of the speed comes from exactly that closeness. Moving it to Postgres means rewriting the data layer, not changing a setting. It would also give up the port's promise that Rails can boot on the database it writes. This is the item most likely to decide whether the experiment is a weekend or a month.

Storage that has never heard of S3

The Rails app at least had an S3 service built into Active Storage. The Rust port's storage is disk-only: the same blob keys and folder layout as Rails, with no object-storage code at all. We would be writing an S3 backend, and variants make it awkward, because thumbnails and video posters are generated from a local file and written back.

libvips, but not ffmpeg

Thumbnails come from libvips and video posters from ffmpeg. The port's Dockerfile builds trimmed versions of both from source.

ffmpeg is a solved problem for us. The port runs ffmpeg and ffprobe as separate programs, and loading a pinned ffmpeg build onto Cloud during the build is exactly what our video renderer Slate already does.

libvips is the one to watch. The port doesn't call it as a program. It links against it directly, along with the GLib libraries it depends on, so the library has to be there when the binary is compiled and again when it runs, along with the image-format libraries libvips loads (JPEG, PNG, WebP, HEIF and the rest). That is more than dropping a binary on the path. We expect it to be manageable, whether as a prebuilt libvips for Cloud's platform or a build of our own, but it is the part of the build most likely to fail in a way the logs explain badly.

The build itself

The repository expects its reference/ submodule, the pinned Rails app it tests against, to be checked out for a build. It also compiles with full link-time optimisation and a single codegen unit, which is slow by design. We will find out whether Cloud checks out submodules, and how it feels about a long release build.

Realtime, again

This is the lesson most likely to repeat exactly. The Rust port runs its own Action Cable server inside the binary, and last time Cloud's edge didn't forward websocket upgrades to the app at all. If that holds for the Rust runtime, we'll need the same detour through Cloud's managed Reverb. The good news is that the port keeps Campfire's frontend JavaScript as it is, so the browser half of our Rails workaround, the pusher-js shim and the channel auth flow, should carry over. The server half would have to be rewritten in Rust.

One process by design

Jobs such as pushes and bot webhooks run inside the process, and so does the pub/sub that fans messages out to websockets. That's part of why it's fast, and it also means it's built to be exactly one instance. On Cloud that means no autoscaling. It also leaves open questions about what happens to queued jobs, and to connected clients, when a deploy briefly runs the old instance and the new one side by side.

What we expect to carry over from the Rails run

  • Read the dashboard deploy log first. Last time, seventeen deploys failed with the same vague status before we found the real reason in the one place that showed it. That will be step one.
  • IPv6 probably won't bite twice. Cloud's network is IPv6-only, and that cost us most of the Rails attempt. The Rust port's public listener already binds every address, IPv6 included. Its internal app port binds loopback by default, which is worth checking once we know how Cloud routes traffic to a Rust app.
  • The Redis trap disappears. There is no Redis in this stack at all, so the managed Valkey restriction that broke Action Cable last time can't happen.
  • TLS needs thought, not the same fix. Last time, disabling SSL behind Cloud's proxy broke every form. The Rust port does forgery protection differently, checking the browser's Sec-Fetch-Site header instead of Rails' CSRF tokens, so that exact failure may not happen. The right setting still has to be worked out, and the port also has its own Let's Encrypt handling, which must stay off.
  • The S3 checksum clash will probably be back. If we write the storage backend with the AWS SDK for Rust, it also adds default checksums to uploads, and Cloud's S3-compatible storage rejected exactly that from the Ruby SDK. The same when_required setting exists in Rust.
  • Keep the secrets stable. SECRET_KEY_BASE and the VAPID keys work the same way, and rotating either one signs everyone out or breaks push.

What success looks like

The same bar as last time: two people chatting in real time, attachments in a bucket, search working, and everything surviving a redeploy. This time we also want the port's headline number checked on Cloud: how much less the Rust version costs to run for the same room.

Experiments on this idea

  1. Experiment

    Go as a Universal Runtime

    The Campfire-Rust port got onto Cloud through the Go runtime, and we wanted to know whether that was a Rust trick or a general one. It is general. Thirty-eight environments later we have one line that kills any VM on Cloud, one framework manifest that refuses a deploy outright, and a packaging shape that two separate teams invented on the same night.

    Read the experiment
  2. Experiment

    Campfire's Rust port on Laravel Cloud: the long way round

    It runs, it costs about a fifth of what the Rails version costs, and it has its own working websockets. Getting there took a wrong premise, a Go launcher, a byte-level rewrite in front of our HTTP parser, and then doing the whole thing again properly on the runtime that had been there the whole time.

    Read the experiment

Support the Lab

The Lab runs on our supporters

Every idea here gets tried for real: deployed, broken, fixed and written up. Right now that is one person doing the work between everything else, and supporters are what buy the time. As support grows, it will also pay freelancers to run experiments of their own, so more of these ideas get tested, sooner.

Our supporters

  • Sputnik Intelligence

    Spain

    We transcribe the podcasts and read the newsletters, and make them searchable. Set up alerts, or hand it all to your AI agent over MCP or API.

    Supporter Visit
    Visit Sputnik Intelligence