Elements vs Fly.io
Fly.io is a cloud hosting company that runs apps in lightweight virtual machines, called Fly Machines, in 17 regions (regions). fly launch detects a project's type, generates its configuration in a fly.toml file, and provisions resources such as databases (Fly Launch). fly deploy builds a Docker image for the app, by default on a remote builder, and rolls it out to the app's Machines (deploy). Fly.io also runs Managed Postgres (Managed Postgres). It bills as you go, by the second for each Machine while it runs, with Managed Postgres and support on monthly plans (pricing).
Elements is not a hosting company. It replaces the Dockerfile, the image registry and the release command with tools of its own: a project server, a default app framework, a test runner, a Postgres database bundled in, and a deploy that speaks SSH to any Ubuntu server you rent, wherever you rent it. Elements is paid for per machine, by the month, and none of it is metered (pricing).
The difference is what a deploy is. On Fly.io, every deploy builds a container image and restarts the app's Machines on it. With Elements there is no image: a deploy sends only the files that changed, from the project server on your machine to the one on your server, and the new release goes live after your tests pass, usually in under a second.
At a Glance
| Elements | Fly.io | |
|---|---|---|
| What it is | Tools that build, test and ship an app to servers you choose | A cloud hosting company |
| What you pay for | Tooling, per machine, per month (pricing) | Each Machine by the second, plus storage, network and monthly plans (pricing) |
| Where your app runs | An Ubuntu server you rent from any provider | Fly Machines, in Fly.io's 17 regions |
| A deploy | Changed source files only, usually under a second, into the running server | A Docker image built on a remote builder, then each Machine restarted on it |
| Tests and migrations | Built into every deploy: a migrated test database first, your data last | Smoke checks, plus a release command you write that hits your real database |
| Background jobs and cron | Shipped in the framework, running beside the app, queued in Postgres | Processes and Machines you define and pay for |
| Database | Bundled Postgres, or any Postgres host | Managed Postgres on a monthly plan |
| Moving away | Change a server address and deploy | Rebuild fly.toml, volumes, secrets and Managed Postgres on another platform |
Fly.io's Bill Changes With Every Machine You Run
Fly.io bills each Machine by the second while it runs, at a rate set by its CPU type, memory and region, and adds volumes, network and monthly plans for Managed Postgres and support (pricing). The bill changes with how many Machines run, for how long, and in which regions. A worker process, a second region or a staging copy is another set of Machines on the invoice.
With Elements, a worker or a cron schedule is code in the app, not another Machine. One rented server runs the app, its job worker and cron, Postgres, and a load balancer that issues and renews TLS certificates (deploy), at the rate its provider publishes. The Elements license on that server is a monthly price per machine, and machine seconds, builds and background work are never counted toward it (pricing, licenses).
No Dockerfile, No Image, No Remote Builder
On Fly.io the unit of deployment is a Docker image. fly deploy builds one from your Dockerfile or a buildpack, by default on a remote builder, and then updates each Machine, one after another by default, to run the new image (deploy). Every change, however small, is a new image and a restart of every Machine.
An Elements project has no Dockerfile. On the first deploy to a new server, Elements installs itself, creates the service users, brings up Postgres, and sets up systemd units for the app, the job worker and the load balancer (what deploy does). After that setup, the server keeps a project server of its own running, with the build state from the release before. Where Fly.io ships an image, elements deploy ships a diff: the two project servers compare their files over SSH and exchange only what changed, usually a few KB. No server restarts. The new release takes over through an atomic rename of its directory, with no boot, no restart pause and no warm-up, and once that first few-second setup is done, deploys usually finish in under a second.
Tests Run on Every Machine Before the Release Goes Live
Fly.io checks a release by watching it start. Smoke checks confirm each Machine keeps running for about 10 seconds after it boots, and a release command can run in a temporary VM before the release is deployed, which is where Fly.io suggests running migrations (deploy). That command runs against your real database. Testing a migration before it touches your data would mean provisioning a second database, migrating it first, and running your tests against it inside the release command.
An Elements deploy carries that second database with it. Before your data is involved, every machine compiles the new code, the first machine migrates a separate test database, and every machine runs the test suite against it. Passing that gate is what lets the first machine migrate the app database, applying all pending migrations as a single transaction. Any failure along the way, whether in the compiler, a test or a migration, halts the deploy while the previous release goes on serving (deploy, tests, migrations). A smoke check tells you a Machine started; an Elements deploy tells you the code does what its tests say before any user sees it.
Your Agent Reads One Build, Not a Remote Builder's Log
What an agent gains most from is correction, each mistake reported while it is still the last thing the agent did. Fly.io gives it host concepts to learn first: fly.toml, a Dockerfile or buildpack, Machines, volumes and secrets, each with its own settings to look up and its own way to fail apart from the code. A deploy does need some machinery between the code and the server, and past that machinery each extra concept costs an agent more to get right than it saves.
When a Fly.io deploy fails, the error is in the output of a Docker build on a remote builder, or in the logs of a Machine that did not stay up. Your agent reads those, changes the code, and builds a new image.
Elements has no remote builder to wait on: the build already passed where you work. Your local project server holds the build graph and build state in memory, and on every save the build state is updated almost instantly: it builds in milliseconds and answers agents and humans in microseconds (build). One elements build -json after an edit hands your agent every build error, from failing tests and migration problems to what type checking and program analysis turn up, as a single structured reply, without waiting on any Docker layer. Elements is one coherent system on standards the agent already knows, SSH, Ubuntu and Postgres, so the same build that answers on your laptop decides each release. elements deploy -json then reports every result from every server in the same shape, tagged with the machine it came from (what deploy does).
Pick the Region by Picking the Server
Fly.io offers 17 regions. Ubuntu servers are rented from many providers, in many more places: AWS alone lists 39 regions (AWS), and DigitalOcean and Hetzner add their own data centers. Elements deploys to a server from any of them (setup). Choose the provider and the city for each environment, and list its servers in config.jsoc.
Several servers in different places can serve one app if they share a database. Set DB_HOST to a hosted Postgres, such as Amazon RDS or DigitalOcean Managed PostgreSQL, and the servers split the traffic, each one balancing requests across the group (load balancer). For a prototype, or anything that fits on one server, the Postgres Elements bundles is enough. Asset URLs carry a content hash and are served as immutable, so any CDN can cache them close to your users.
Changing Servers Is a Config Change
Moving a Fly.io app means rebuilding its fly.toml, volumes, secrets and database on another platform. An Elements app holds nothing host-specific: its code is TypeScript, HTML, CSS and SQL, run on Node with standard Postgres, and its build, tests, job worker and deploy belong to Elements, not to the host. To switch providers, rent a server, swap its address into config.jsoc and deploy again (walkthrough). Bundled Postgres data comes across as SQL from elements db dump (database cli). Runtime packages like @elements/app are published under the MIT license (licenses).
Questions
What is the difference between Elements and Fly.io?
Fly.io is a hosting company: it builds your app into a Docker image and runs it on its own virtual machines, billed by the second. Elements is not a host. It builds and tests your app, then copies the changed source files to a server you rent elsewhere, with no image in between.
Is Elements a Fly.io alternative?
Yes, for a web app, and without Docker. Rent a server from any provider and let Elements build, test and deploy to it. That server then hosts what you would spread across Fly Machines: the app, Postgres, the job worker, cron, and a load balancer that issues and renews TLS certificates.
Do I need Docker to deploy an Elements app?
No. Elements deploys your source files over SSH, from the project server on your machine to the one on the server, and sets up systemd units on the first deploy. There is no Dockerfile, image or registry.
How much does Elements cost compared to Fly.io?
Fly.io bills each Machine by the second while it runs, plus storage, network, and monthly plans for Managed Postgres and support (Fly.io pricing). Elements charges a price per machine, per month, with nothing metered (Elements pricing), and the server is rented from the provider you choose, at its published rate.
Where do I host an Elements app?
On any Ubuntu server with SSH, from DigitalOcean, Hetzner, AWS or any other provider, in whichever of its data centers you choose. A single machine is enough to start: the app, its Postgres and the load balancer share it.