Axum — one binary that depends on nothing

Axum is a web framework written in Rust, built by the same people who wrote
Tokio — the async engine a large share of Rust's networked software runs on.
You write your endpoints, compile once, and get a single self-contained
executable. It starts in milliseconds, holds only what you tell it to hold, and
because Rust checks the work before it ever runs, whole categories of 3am
failures aren't possible.

The two are not separate additions. Axum sits on Tokio: Tokio holds the
timers, tasks and state; Axum is the door the browser knocks on. One binary,
both jobs.

Why it came up

Building an audio pipeline — a sermon or an episode goes in, a transcript and a
published post come out — across several sites. The existing backends are plain
PHP, which works, but hits two walls: it cannot process audio, and it cannot
hold state between requests. Both are things this pipeline needs.

What it actually replaces, and what it doesn't

The honest version, measured rather than assumed:

PHP Axum
Idle memory (real, PSS) ~200MB for a pool serving 70 domains ~15MB for one service
Marginal cost per extra site ~3MB — the pool already exists ~15MB, a new process
Dependencies interpreter + 52 loaded extensions + 57 packages that get security updates, plus Apache and suexec the Linux kernel
Isolation fresh process per request; a fatal error kills one request one process; a panicking task dies, the process lives
Can it process audio no yes
Can it hold a timer for Tuesday no yes

Memory is the weakest argument for Rust, which is the opposite of what most
people lead with. Meilisearch is written in Rust and sits at 165MB on the same
box — because it deliberately holds search indexes in memory. Rust isn't
magically small; footprint follows what you keep. Language sets the floor,
architecture sets the number.

The real arguments are the last three rows: nothing to patch, it can do
audio
, and it can hold time.

Two things people say loosely

"Static binary." Not automatically. A default Linux Rust build links against
glibc — ldd on it will list libgcc, libpthread, libm, and those must
exist at the right version on the machine. To get a genuinely dependency-free
file you target musl, a small C library that gets compiled inside the
binary. glibc is "use the oven in that kitchen"; musl is "bring your own oven."
Same file then runs on CentOS, Ubuntu, a container, a fresh VPS, unchanged.

"PHP has no daemon." It does. php-fpm is a long-running service — two of
them on this box, one per PHP version. The real difference isn't daemon versus
no daemon, it's where the isolation sits: PHP resets state between requests
and recycles a worker on a crash; Axum shares one process and isolates per task.
Both have a single point of failure at the top. PHP's is just hardened by twenty
years of everyone else's crashes.

Where it earns its place

Not as a rewrite of working code. Put it exactly where the old tool is doing
gymnastics:

  • Audio. If trim, fade and re-encode currently happen in the browser
    because the server can't, that's not an optimisation gap — it's a missing
    capability, and it means the user's device is doing the work.
  • Time. "Publish Tuesday 6am." "Delete the offcuts two days after it went
    live." A cron can fire a job but cannot remember what's supposed to be true.
    A long-running process with a database can, and can raise the alarm twelve
    hours before
    a deadline instead of after it.
  • Order that can't be bypassed. Rust lets a delete function require proof
    of a verified backup as an argument. "Delete something unbacked" then fails to
    compile, rather than failing at 2am.

The cost, stated plainly

A daemon has one availability number. If the service that publishes on a
schedule is down through the day it should have published, the episode doesn't
go out and nobody is told. That's mitigated with systemd Restart=always, a
heartbeat, and an external watchdog — but the mitigation is required, not
optional, and it doesn't exist by default.

The containment that makes it safe: keep it out of the request path of the
public site. If the media service is down, every page still serves — only new
publishing pauses.

Rules of thumb

  1. Don't rewrite working code. Add the new tool where the old one physically can't go.
  2. Target x86_64-unknown-linux-musl if you want "depends on nothing" to be literally true.
  3. Bind it to localhost and let the existing auth layer stay the gate — the new service shouldn't learn to authenticate browsers.
  4. State goes in SQLite, not in memory, so a restart resumes instead of forgetting.
  5. Measure with PSS, not RSS. RSS double-counts shared pages and will flatter or damn the wrong thing.