By default, Fly Machines are stateless — when a Machine is replaced (on deploy), all data written to the container's filesystem is lost. For persistent data, you need a Fly Volume.
A Fly Volume is a persistent block storage device attached to a single Machine in a specific region. Think of it as a hard disk for your Machine.
- Built on NVMe SSDs (fast, low-latency)
- Tied to one region
- One Volume per Machine (1:1 relationship)
- Survives Machine restarts and deploys
- Does NOT survive
fly volumes destroy
fly volumes create myapp_data \
--region ams \
--size 10 # 10 GBfly volumes create myapp_data \
--region ams \
--size 10 \
--count 2 # Creates 2 volumes in different availability zones[[mounts]]
source = "myapp_data" # Volume name (not ID)
destination = "/data" # Mount path inside containerAfter mounting, your app can read/write /data like a normal filesystem.
fly volumes list # List all volumes
fly volumes show <volume-id> # Show volume details
fly volumes extend <volume-id> --size 20 # Grow to 20GB (can't shrink)
fly volumes destroy <volume-id> # Delete permanentlyfly volumes snapshots list <volume-id> # List snapshots
fly volumes snapshots create <volume-id> # Manual snapshotFly takes automatic daily snapshots (retained for 5 days).
fly volumes create restored_data \
--from-snapshot <snapshot-id> \
--region ams \
--size 10A Volume exists in one region only. For a 3-region app:
fly volumes create myapp_data --region ams --size 10
fly volumes create myapp_data --region lhr --size 10
fly volumes create myapp_data --region syd --size 10The Machine in each region gets its own Volume. Data is NOT replicated between volumes automatically — use LiteFS or Postgres for replicated state.
| Use case | Solution |
|---|---|
| SQLite database | Volume at /data/app.db |
| User uploaded files | Volume at /data/uploads |
| Redis data (self-hosted) | Volume at /data/redis |
| Logs | Volume at /data/logs |
| App config files | Volume at /data/config |
fly.toml:
[[mounts]]
source = "sqlite_data"
destination = "/data"
[env]
DATABASE_PATH = "/data/app.db"App code (Node.js):
const Database = require('better-sqlite3');
const db = new Database(process.env.DATABASE_PATH);fly volumes create test_data --region ams --size 1 -a my-app- Add
[[mounts]]tofly.toml. - Deploy.
- SSH in:
fly ssh console - Run:
echo "hello persistent world" > /data/test.txt - Exit, redeploy, SSH back in, run:
cat /data/test.txt— data should persist.
- Run
fly volumes snapshots create <vol-id>. - Run
fly volumes snapshots list <vol-id>to see it.
→ Continue to 1200 — Private Networking (WireGuard / 6PN)