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.

Over-the-air updates for iOS

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

This 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”.

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.