intertwingly

It’s just data

Re-Retired


Three years ago, I unretired to join Fly.io as a Rails Specialist. As of last month, I've re-retired. What's changed for me? Not much.

My dance showcase app is still hosted at Fly.io, and I'm still working to make it easier to host apps like mine, not just at Fly.io but with Kamal. I've always been self-directed and I'm working on the things apps like mine need next even if that means that what I'm working on goes beyond what Fly.io needs at the moment.

What I'm a Fan Of

I'm a fan of the products Fly.io and 37signals produces. In a nutshell, 37signals produces apps that lots of people will want to run, and Fly.io is a platform which can be used to host apps that lots of people will want to run. Technically, the types of apps that I'm talking about are multi-tenant apps, and there are various ways to address such apps. I'm a fan of the approach 37signals has selected, which amounts to one database per (group of) users. It is the approach I've been using for over three years.

I'm a fan of 37 signals because they are looking to address the whole problem of End-to-End Freedom. An example of addressing the whole problem is asset bridging. If you deploy a new version of an app, it may have different assets (CSS, JS, images, etc) than the previous version. There will be a period of time where clients will still fetch the old version. This is clearly an app specific problem, but the solution as a whole needs to have a "hook" where this can be done. Kamal has asset bridging. Kamal can also do things like true blue/green deployment of an app with volumes with no down time.

I'm a fan of Fly.io because of features like fly-replay and stop/suspend. Stop/suspend in particular enables you to create machines that are only running (and therefore charged for) when there is activity. This turns out to be more than appealing to the broke college students who can't afford to pay for a VPS 24/7; this enables you to effortlessly create machines worldwide and only pay for them when they are in use. As an example, I use it to have a full, multi-region, staging version of my app that is only active when I am testing a fix.

The Gaps

That's not to say that both are perfect. Starting with 37 signals, a default Rails application will be configured for SQLite and Solid Cable, which are reasonable choices, but if you want to use both you need to have a way to start multiple processes. The Action Cable docs tells you that you should run it standalone in production. Solid Queue is yet another process. These processes will need direct access to your SQLite database, which means that they need access to the volume on which the database resides. You can run Solid Queue as a Puma plugin, but I'm not aware of any such feature for Action Cable, besides Action Cable needs to be run on either a different port or a different DNS host. This rules out an accessory, but a second application might work. Taken together, this one example is some place between some assembly required and you are on your own. And we are not far off the beaten path here: a default Rails application with a web socket.

Nor is Fly.io much better. Stop and suspend are wonderful features, but if you want either a different amount of time before the stop happens, or the ability to run a custom process at the time of your app goes idle or when it is first restarted/resumed, you need to drop down to the machine API. Which isn't so bad, but you need to determine when to call the API, and that means tracking each request start and stop and have a timer. And deal with things like websockets that "complete" with a HTTP 101 switching protocols and then continue. Things normally handled for you by your framework. Again, some place between some assembly required and you are on your own. All because you wanted a hook or a different timeout value.

There is a lot of prior art for solving these problems. And even current art. Dockerfiles generated with new Rails applications insert Thruster, which is a reverse proxy that can handle static assets and more. And mentioned previously, Puma is now a process manager in addition to being a web server. A single tool that reverse proxy all requests can also be a process manager. It can reverse proxy cable requests to a different application without requiring a second DNS address or a non-standard port. And it doesn't have to stop there, it can do authentication, multi-tenant hosting, routing, sticky sessions, and stream logs to a remote server.

Enter Navigator

Once upon a time it would have taken a team to pull this off. These days, we have Claude Code. It effectively is a team, creating agents as needed when asked to do larger tasks. The result is Navigator, a Go program that does all of the above. Think of it as the middleware that you didn't know you needed.

If a vibe coded reverse proxy gives you pause, perhaps some of the following will help: Go is a strongly typed language, has both a vet and a lint tool, the codebase has unit and integration tests, and I'm running it in production with 75+ users in 8 countries on 4 continents.

Usage is as simple as replacing the CMD you have in your Dockerfile with:

COPY --from=samruby/navigator:latest /navigator /usr/local/bin/navigator
CMD ["navigator", "config/navigator.yml"]

And then providing a config file. See Use Cases and Reference.

I don't know about you, but given a choice, I'd rather modify two lines in a Dockerfile and provide a configuration file that declaratively lets me run action cable as a process and rewrite the /cable URL, or identify a duration and state what scripts are to be run after that much idle time after which the server is to be suspended or stopped. Particularly if the alternative is searching blog articles, stack overflow, blueprints, docs, or even architecture sessions.


Navigator is MIT licensed. If you find it useful, start a discussion. I'll continue working on the tools I need for my apps. Perhaps others might find value in them too.