Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Shipping It

Deployment is where architectures confess. A stack that needs a container orchestra, a managed database, a queue, and four YAML dialects is telling you something about how many moving pieces it failed to consolidate. Mar’s confession is short: one artifact per app.

The fullstack artifact: a single binary

mar build compiles a fullstack app into one self-contained executable. Inside it: your compiled program, the runtime, the SQLite engine, the web frontend it serves, and every static asset. Not a folder with a binary in it; one file.

Copy that file to any Linux server and run it. It opens its port, creates or migrates its database (a single SQLite file beside it), and serves the app, the API, the admin panel, and the PWA manifest. There is no Dockerfile to write, no runtime to install on the host, no “works on my machine” gap, because the machine’s only job is to execute one static program.

mar deploy automates the common case, shipping that binary to a Fly.io VM: it generates the ephemeral packaging, provisions, checks health after rollout, and prints where your app lives. Secrets travel as environment variables (env: fields in mar.json), never inside the artifact.

The reasoning behind the single-binary bet mirrors the language: fewer moving parts, stronger promises. Every operational seam removed (app-to-database network hop, container-to-host contract, asset-CDN-to-app versioning) is a failure mode deleted rather than monitored. SQLite-in-process is the enabling choice: the database is not a second service to operate; it is a file the app owns. Backups, accordingly, are a framework feature (snapshot bundles of that file plus config), not an ops project.

The frontend artifact: static files

An App.frontend project (no backend of its own, like the games) compiles to a folder: index.html, the runtime, your program, assets. Any static host serves it; mar deploy targets Cloudflare Pages. Every frontend build is an installable PWA out of the box: manifest, icons, home-screen behavior, generated from mar.json.

Configuring the deploy

mar deploy will not guess where your app lives, so the first run stops and prints the block to add. A fullstack app needs Fly:

"deploy": {
  "fly": {
    "app": "my-app",
    "region": "gru",
    "memory": "256mb"
  }
}

A frontend-only app needs Cloudflare Pages, and reads its credentials from the environment rather than the file:

"deploy": {
  "cloudflare-pages": {
    "app": "my-app",
    "account": "env:CLOUDFLARE_ACCOUNT_ID",
    "apiToken": "env:CLOUDFLARE_API_TOKEN"
  }
}

app is a globally-unique name on the platform; it becomes your URL. Every other missing piece is reported the same way, one at a time, with the valid values listed: run, read, paste, run again. Secrets are the exception to pasting — an interactive run asks for them and stores them on the platform, because a session key or an SMTP password does not belong in a file you commit.

Over-the-air updates for iOS

Still being built. What follows is what the architecture makes possible, not something you can lean on yet.

The iOS app that mar scaffolds is a native shell around your compiled program, and the program itself is data the shell can refresh. That is the opening the design leaves: a fullstack deployment could serve the current program at a well-known path, and installed apps could pick up new logic and UI on next launch, with no App Store review cycle for ordinary iteration. (Frontend-only deployments on static hosting would not serve such an endpoint, so this would be a fullstack feature.)

That it is possible at all is a direct dividend of the interpreter architecture from chapter 1: because the program is a value the runtime executes, “update the app” can mean “fetch the new value”. The architecture is real today. The plumbing that would use it is not finished, which is the honest distinction between a design that permits something and a product that does it.

Development is the same loop, faster

mar dev is the same machinery in a tighter cycle: hot reload on save, migrations on restart, dev sign-in codes printed to the console instead of emailed, the time-travel debugger a keypress away. The artifact you ship and the loop you develop in are the same program in two moods, which is why “it worked in dev” carries unusual weight in Mar.

What is deliberately missing

No Kubernetes story, no multi-region database replication, no horizontal autoscaling of stateless pods. A Mar app is one process with one database file, scaled by making the VM bigger. For the products Mar targets, tools, small SaaS, team apps, games, this covers a startlingly large range (SQLite on modern hardware reads faster than most teams’ managed Postgres round-trips). For products beyond it, Mar is the wrong tool, and says so in its own docs, which is the subject of the final chapter.