On this page · 11 sections
- What actually shipped
- The gap that will bite you in CI, not on your laptop
- The R2 mount example installs an unpinned, stale dependency
- What the mount costs, which the example does not say
- Sizing, because a mount does not remove the disk limit
- India-specific considerations
- What to do
- What is still unknown
- FAQ
- How eCorpIT can help
- References
Summary. On 20 August 2026 Cloudflare made Miniflare grant local Containers the Docker privileges needed for Filesystem in Userspace, covering wrangler dev, the Cloudflare Vite plugin and direct Miniflare use. The grant is conditional. It applies when the Docker daemon runs inside a virtual machine, which covers macOS and Windows Subsystem for Linux, and on Linux only for rootless Docker where /dev/fuse exists. Cloudflare states plainly that "rootful Docker on Linux does not support FUSE by default during local development" - the exact configuration most Linux CI runners use. Separately, the official R2 FUSE example, last updated 21 August 2026, installs tigrisfs by querying the GitHub releases API at build time with no version pin. That project's latest release is v1.2.1, published 6 May 2025, and its last repository push was 1 October 2025. Every directory listing across that mount bills as an R2 Class A operation at $4.50 per million requests, 12.5 times the Class B read rate of $0.36 per million.
What actually shipped
The changelog entry of 20 August 2026 is four sentences long and every one of them is a condition. Miniflare grants FUSE privileges "when the local Docker daemon runs inside a virtual machine (VM)", and on Linux "for local rootless Docker when /dev/fuse is available". The local development documentation repeats the same wording and adds the failure mode: Miniflare "does not grant FUSE privileges when the Docker daemon does not meet these conditions or cannot be inspected."
Read that last clause carefully. The grant depends on Miniflare being able to inspect the Docker daemon. A daemon reachable over a non-default socket, a remote daemon, or a rootless setup where the inspection call is refused all land in the same bucket as an unsupported daemon, and the failure is a silent absence of a privilege rather than an error.
| Environment | Docker daemon | FUSE granted in wrangler dev? |
|---|---|---|
| macOS with Docker Desktop | Runs in a VM | Yes |
| Windows with WSL 2 | Runs in a VM | Yes |
Linux, rootless Docker, /dev/fuse present |
Rootless | Yes |
Linux, rootless Docker, no /dev/fuse |
Rootless | No |
| Linux, rootful Docker | Rootful | No, stated explicitly |
| Any host where the daemon cannot be inspected | Unknown | No |
The gap that will bite you in CI, not on your laptop
The split in that table runs along exactly the wrong line. Developer machines are overwhelmingly macOS and Windows, where the Docker daemon lives in a VM and FUSE now works. Continuous integration runners are overwhelmingly Linux with rootful Docker, where Cloudflare says it does not.
So a container test that mounts a bucket and reads a fixture passes on every laptop in the team and fails on the first push. Worse, it may not fail loudly. A FUSE adapter that cannot obtain the device typically fails at mount time and leaves an empty directory behind, so a test asserting on file contents fails with a confusing "file not found" rather than "FUSE unavailable". Teams running self-hosted GitHub Actions runners with version enforcement should check the daemon mode on the runner image before assuming parity with local development.
There is no documented flag to force the grant, and the Containers limits page, last updated 3 July 2026, does not mention FUSE at all. Neither does the Containers pricing page, last updated 21 April 2026.
The R2 mount example installs an unpinned, stale dependency
Cloudflare's Mount R2 buckets with FUSE example, last updated 21 August 2026, gives a working Dockerfile built on alpine:3.20. It installs tigrisfs, an Apache-2.0 licensed S3-compatible FUSE adapter, using this pattern inside the image build:
VERSION=$(curl -s https://api.github.com/repos/tigrisdata/tigrisfs/releases/latest | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4) && \
curl -L "https://github.com/tigrisdata/tigrisfs/releases/download/${VERSION}/tigrisfs_${VERSION#v}_linux_${ARCH}.tar.gz" -o /tmp/tigrisfs.tar.gz
Three problems sit in those two lines.
The version is resolved at build time from releases/latest, so two builds of the same Dockerfile on different days can produce different binaries. That is the definition of a non-reproducible image, and it is being demonstrated in a vendor's own documentation.
The GitHub REST API is being called unauthenticated from inside a Docker build. Unauthenticated requests are rate limited by source IP. On a shared CI network address that limit is reached routinely, at which point curl -s returns a rate-limit JSON body, the grep finds no tag_name, VERSION becomes an empty string, and the download URL degrades to a path with two consecutive slashes and an empty version. The build then fails inside tar, several steps away from the actual cause.
The dependency itself is not moving. As of 23 August 2026 the tigrisfs repository shows a latest release of v1.2.1 published on 6 May 2025, a last push of 1 October 2025, 231 stars and 9 open issues. Nothing about that is disqualifying for a utility binary, but it is a fifteen-month-old release being pulled unpinned into a container that will hold R2 credentials. If you copy this example, pin the tag and verify the published checksums.txt asset shipped alongside the release. This is ordinary software supply chain hardening work, and the same discipline that stops hallucinated package names reaching a build applies here.
One more detail in that Dockerfile: the Alpine package install passes an explicit repository over plain HTTP, http://dl-cdn.alpinelinux.org/alpine/v3.20/main. Alpine signs its package indexes, so this is not an open door, but it is not the default and it is worth a conscious decision rather than a copy-paste.
What the mount costs, which the example does not say
The example page discusses performance and warns that "object storage is not a POSIX-compatible filesystem, nor is it local storage" and that you "should not expect native SSD-like performance". It says nothing about billing, and billing is where a FUSE mount surprises people.
A FUSE adapter turns filesystem syscalls into S3 API calls. R2 prices those in two classes, and the classification is not the one intuition suggests.
| Filesystem action on the mount | R2 operation | Rate, standard storage |
|---|---|---|
| Listing a directory | ListObjects, Class A |
$4.50 per million requests |
| Reading a file | GetObject, Class B |
$0.36 per million requests |
| Stat on a file | HeadObject, Class B |
$0.36 per million requests |
| Writing a file | PutObject, Class A |
$4.50 per million requests |
| Multipart write parts | UploadPart, Class A |
$4.50 per million requests |
| Deleting a file | DeleteObject, free |
No charge |
ls is the expensive one. The startup script in Cloudflare's own example ends with ls -lah /mnt/r2, so every container cold start performs at least one Class A listing before it does any work. The free tier covers 1 million Class A and 10 million Class B requests per month, and Cloudflare rounds usage up to the next billing unit, so "one million and one operations" bills as two million. A build tool or a language runtime walking a mounted tree at startup, which is precisely the "bootstrapping containers with assets" use case the page recommends, can turn a few hundred container starts a day into a Class A line item.
Container compute is billed separately. The pricing page sets Workers Paid at $5 USD per month with 25 GiB-hours of memory, 375 vCPU-minutes and 200 GB-hours of disk included, then $0.0000025 per additional GiB-second, $0.000020 per additional vCPU-second and $0.00000007 per additional GB-second. A FUSE mount does not change those rates, but a slow mount extends wall-clock time on an instance whose memory and disk bill on provisioned resources rather than active use.
Sizing, because a mount does not remove the disk limit
Instance disk runs from 2 GB on lite to 20 GB on standard-4, and custom instance types cap at 4 vCPU, 12 GiB memory and 20 GB disk, with a maximum of 2 GB of disk per 1 GiB of memory. Image size is capped at the instance disk space, and total image storage per account is 50 GB.
Mounting R2 sidesteps the image-size ceiling for data, which is the genuine win here and the reason "avoid bloating container images" appears in the use-case list. It does not sidestep local disk pressure from an adapter's cache, and lite at 256 MiB of memory and 2 GB of disk is a thin place to run a caching FUSE client. Teams already sizing Cloudflare Containers alongside Durable Objects should treat the mount as another resident process, not as free storage.
India-specific considerations
Container egress is priced by region, at $0.025 per GB for North America and Europe with 1 TB included, $0.05 per GB for Oceania, Korea and Taiwan with 500 GB included, and $0.04 per GB for everywhere else with 500 GB included. India falls in the third band, so a workload that mounts a bucket and streams data out to Internet clients carries a higher egress rate and a smaller allotment than the same design run for a European audience. R2 itself charges no egress. Where the bucket holds personal data, remember that R2 jurisdictions are set at bucket creation and cannot be changed afterwards, which matters for anything with a Digital Personal Data Protection Act 2023 residency commitment attached, as covered in our note on the R2 us jurisdiction endpoint lock-in.
What to do
Check the daemon mode on your CI image first. If it is rootful Docker on Linux, either move the runner to rootless with /dev/fuse exposed or mark FUSE-dependent tests as skipped rather than letting them fail with a misleading error.
Pin tigrisfs to a tag and verify the checksum rather than resolving releases/latest inside the build. Move the download to a build stage that can be cached, so a GitHub API rate limit does not break an unrelated deploy.
Instrument R2 operations by class before you widen the mount's use, and assume every directory walk is a Class A charge. If your access pattern is mostly whole-file reads, the mount is cheap. If it is a tree walk, it is not.
Size the instance for the adapter, not just the application, and keep the mount out of anything on the request path until you have measured it. The docs are explicit that this is not local storage, and treating it as though it were is how a 2 GB lite instance becomes a latency incident.
What is still unknown
Cloudflare has not published a supported way to force the FUSE grant on rootful Docker, has not said whether the limits or pricing pages will be updated to cover FUSE, and has not stated whether tigrisfs is a recommendation or merely one working example. Nor has it published guidance on which R2 operation classes a typical mount generates. Until it does, measure your own.
The real cost here is rarely the mount. It is the test suite that only fails where nobody is watching.
FAQ
How eCorpIT can help
A vendor example that pulls an unpinned binary into an image holding storage credentials is the sort of thing that passes review because it came from the vendor. Our engineering teams pin, checksum and rebuild these paths as part of software supply chain hardening, and align CI runner images so local and pipeline behaviour match. If your container tests pass locally and fail in the pipeline, ask our platform team to review your runner and Dockerfile setup.
References
Last updated 23 August 2026.