Persistent volumes on Fly.io Machines are a bit of a mind-bender: they’re not "attached" in the traditional sense of a block device being mounted directly to a running VM. Instead, they’re a separate, independently managed storage resource that your Machine’s process references via a mount path.

Let’s see this in action. Imagine you have a PostgreSQL database running on a Machine.

First, you need to create a volume. This is a one-time operation per volume.

flyctl volumes create my-db-data --size 10 --region lhr

This command spins up a new storage volume named my-db-data in the lhr (London) region with 10GB of capacity. It doesn’t do anything with your Machines yet.

Next, you define how this volume should be presented to your Machine in your fly.toml file. This is where the "referencing" happens.

[mounts]
  source = "my-db-data"
  destination = "/data"

This tells Fly.io: "When you launch a Machine configured with this fly.toml, make sure the volume named my-db-data is available at the /data path inside the Machine’s filesystem."

Now, when your application (e.g., PostgreSQL) starts up on the Machine, it’s configured to use /data as its data directory. The flyctl agent on the Machine handles the magic of making the actual storage volume appear at that mount point.

The real power here is that the volume is decoupled from the Machine’s lifecycle. If your Machine restarts, crashes, or is even replaced with a new one (e.g., during a deployment), the volume persists. When the new Machine starts, Fly.io ensures that the my-db-data volume is once again mounted at /data. Your database process simply resumes from where it left off, with all its data intact.

This persistence is crucial for stateful applications. Without it, any restart would wipe out your database, cache, or user uploads. The source in the [mounts] section of fly.toml is the key identifier that links your Machine’s filesystem to a specific, durable storage volume.

The most surprising thing about Fly.io volumes is that they are entirely managed by the Fly.io platform and not directly accessible or manipulable by the underlying VM’s operating system in the way you might expect with cloud provider block storage. You can’t lsblk and see a raw disk device that you then need to mkfs and mount. The mount operation is entirely handled by the Fly.io agent. Your application just sees a directory, and that directory is backed by persistent, replicated storage.

The key takeaway is that you create volumes independently and then declare how they should be made available to your Machines in your fly.toml. The platform handles the rest, ensuring your data survives Machine restarts and deployments.

The next conceptual leap is understanding how to manage multiple volumes for different purposes within a single application or how to migrate data between volumes.

Want structured learning?

Take the full Fly-io course →