One bucket, many businesses: how Kanvo isolates customer files
Every business running on Kanvo generates files. Before-and-after job photos. Signed service agreements. Equipment condition records at rental checkout. Damage documentation when something comes back wrong.
None of that is optional paperwork. In this industry, a photo timestamped before you touched the property is the difference between a resolved dispute and an unpaid invoice. So file storage is not a side feature — it is evidence handling.
Kanvo's file storage now runs on Cloudflare R2. Here is how it is structured and why.
One bucket, not one per customer
The instinctive answer to multi-tenant storage is a bucket per tenant. It feels safer. Isolation is physical, a misconfiguration blast radius is one customer, and deleting an account is deleting a bucket.
Kanvo does not do that. Everything lives in a single kanvo-files bucket, with tenant separation enforced by path prefix.
The reason is operational, not technical. A bucket per tenant means every new signup provisions infrastructure. That is a step that can fail, that needs monitoring, that needs a cleanup path when it half-fails, and that will eventually run into an account-level limit nobody thought about at ten customers. It turns onboarding from a database insert into a distributed transaction.
With one bucket, a new customer is a row. Nothing gets provisioned. Nothing can be half-provisioned.
Where the isolation actually lives
If separation is not physical, it has to be enforced in code — and enforced in exactly one place, or it will eventually not be enforced at all.
Every object key is namespaced by tenant, and no code path anywhere in the application constructs a storage key by hand. Keys come from a single function that takes the tenant context and the logical path and returns the physical key. That function is the entire security boundary for file storage.
Concentrating it that way is deliberate. Scattered authorization checks fail silently — somebody adds a new upload endpoint six months later, forgets the prefix, and there is no error, just a file in the wrong place. One chokepoint means there is exactly one thing to audit and exactly one thing to get right.
Signed URLs, not proxied downloads
Files are served through short-lived signed URLs rather than streamed through the application.
This was partly a performance decision — proxying every job photo through the app means the app is now in the path of every image load on every screen, which is a lot of bandwidth for no benefit. But mostly it was about the field.
Crews load work orders on phones over cell connections at job sites. A proxied download adds a hop and a dependency: if the app is slow, the photos are slow. Signed URLs let the file come straight from the edge. The app's job is to decide whether you are allowed to see the file, not to hand you the bytes.
The tradeoff is that a signed URL, once issued, is valid until it expires regardless of what happens to the underlying permissions. Someone removed from a company mid-session can still load a file they had already been granted a URL for. Expiry windows are short for that reason, and it is a genuine limitation rather than something the design solves.
The part I am still not happy with
Deletion.
Path-based namespacing makes "delete everything for this customer" a prefix listing and a batch delete, which is fine at current scale and gets slower in a way I have not stress-tested. A bucket-per-tenant design would make it one call.
That is the honest cost of the choice above: onboarding got simple, offboarding got less simple. Given that businesses sign up far more often than they leave, and that leaving is a process with a human in the loop anyway, that felt like the right side to be on. But it is a real tradeoff, not a free win, and if Kanvo ever serves customers with regulatory deletion deadlines it will need revisiting.
Why write this down
If you are evaluating field service software, you are being asked to put your job photos, your signed agreements, and your damage documentation into someone else's system. Almost none of the platforms in this category will tell you how any of that is stored or separated.
I would rather publish the design, including the parts I would still change, than ask anyone to take it on faith.
Tell us what your operation needs.
Kanvo gets built against real jobs. If there's a workflow we haven't solved yet, that's the most useful thing you can send us.
Submit Feedback →