Storage Backends
Backend objects are content-addressed by SHA-256 - the storage engine doesn't care where the bytes physically live, only that the backend can put, get, delete, and exists by hash. Switch backends by changing one line in anvil.toml.
Available backends
| Backend | Use | Status |
|---|---|---|
| local | Files under data/storage/{hash[0:2]}/{hash[2:4]}/{hash} | Production |
| s3 | AWS S3 (or any S3-compatible: R2, MinIO, Wasabi, B2) | Stub - Phase 25.4 deferred |
| gcs | Google Cloud Storage | Stub |
| azure | Azure Blob Storage | Stub |
Local backend
[file_storage] backend = "local" file_storage_local_root = "./data/storage" staging_dir = "./data/staging" # temp uploads go here first
Files are content-addressed: identical bytes are stored once and shared via the in-memory refcounter. The backend never deletes a file while its refcount > 0.
S3-compatible (when enabled)
[file_storage] backend = "s3" # Credentials and endpoint pulled from environment: # ANVIL_S3_KEY=<access-key> # ANVIL_S3_SECRET=<secret> # ANVIL_S3_REGION=us-east-1 # ANVIL_S3_BUCKET=my-anvil-bucket # ANVIL_S3_ENDPOINT=https://<r2-account-id>.r2.cloudflarestorage.com # R2 / MinIO
Migration path
Switching from local to s3 later is a streaming migration: walk storage.objects, read each blob from the local backend, write it to S3 by the same hash, then flip the config. Because the database holds the canonical metadata, the path-level URLs stay identical for every client.