What melee is
A place to put the small Ruby apps that are not worth a server: compiled to native binaries, kept asleep, woken on demand, and isolated from each other by the Linux kernel.
Most of the software a person writes is small. A page that shows the family calendar on a tablet. A form that records something. A thing that checks a feed every ten minutes and tells you when it changes. These are an afternoon’s work and then they need somewhere to live, and everywhere they can live is built for software a hundred times their size — a container that is always running, a database that is always running, a bill that arrives whether or not anyone visited.
melee is for that shape of app. The unit is a directory. You push it, and it becomes a native binary that the server starts when a request arrives and stops when nobody has asked for a while. An idle app costs a directory on a disk.
What an app is
notes/
melee.toml name, which server, what URL
app.rb routes
lib/ your own Ruby, loaded with require_relative
views/*.erb templates, compiled at build time
db/migrations/*.sql applied in name order, never edited once applied
public/ files served straight from disk
app.rb is the whole configuration:
# frozen_string_literal: true
title "Notes"
get "/" do
render :index, notes: db.query("SELECT id, body FROM notes ORDER BY id DESC")
end
post "/notes" do
halt 400, "Empty note" if params.fetch(:body).empty?
db.run "INSERT INTO notes (body) VALUES (?)", params.fetch(:body)
redirect "/"
end
If that looks like Sinatra, it is meant to. The shape was chosen because it is the one people and models already know — see If you know Rails or Sinatra for where the resemblance stops.
The four ideas
One request is one process. A request is handled by a process that exists for that request and then exits. Nothing leaks from one request into the next, because there is no next request in that process. This is why every helper can be a plain top-level method with no request object threaded through it: there is only ever one request in scope. It is also why an app cannot have a global cache or a background thread — see What melee cannot do.
The app is a binary. Spinel compiles your Ruby, the melee library and
SQLite into one native executable ahead of time. That is what makes activation cost milliseconds instead of
seconds, and it is why the Ruby you write is real Ruby minus anything that decides what to call at run time:
no eval, no method_missing, no gems. The Ruby that compiles is the rule list.
Isolation comes from the kernel. Each app runs in its own mount and user namespaces, with a Landlock allow-list of the paths its binary actually needs and a seccomp allow-list of 85 syscalls — and, when the server runs as root with a uid range configured, under a Unix user of its own. The compiler is not the security boundary. This means an app can be handed Ruby written by someone you do not know and the blast radius is a process that can read its own release directory and write its own data directory.
State that outlives a request is an object. A request child cannot hold anything — it exits. So anything long-lived is a durable object: a named instance of a class you write, living in a separate long-running process, with its own SQLite database and one timer.
class Household < Durable
def setup = timer(after: 0)
def on_timer
refresh
timer after: 600
end
def refresh = storage.run("UPDATE ...")
end
Household.get("home").refresh
That timer is also melee’s answer to cron and to background jobs, because there are no threads to run them in. Background jobs and scheduled work is the whole story.
What it is not
melee does not run Rails, Sinatra, or RubyGems, and it is not trying to. It is not full CRuby: it is Ruby syntax and core classes on a different runtime, with a closed standard library. It is not a global edge network, and it is not a system of record. If your app needs a background worker fleet, a connection pool to Postgres, or a gem, it wants a real server and Rails is a fine answer.
It is worth being blunt about the stage as well: melee runs, and two real apps are deployed on it, but it has only ever been operated by the person who built it. The list of what is missing is in What melee cannot do, and you should read it before you commit to anything.
Next
- How a request reaches your app — the machinery, in order, with diagrams.
- The tutorial — an app from
melee newto deployed.