Back to Articles
Engineering
April 6, 2026

Building DoubleSpace: 7 Cloudflare Services, Zero Servers

GR

George Rios

Edge Architect & Cloudflare Specialist

I set myself a constraint that sounded simple but turned out to be one of the most rewarding engineering decisions I've made: build a complete cloud sync platform using only Cloudflare services. No AWS. No GCP. No third-party databases. No traditional servers of any kind.

The result is DoubleSpace — a file sync and storage platform that runs entirely at the edge. Every request is handled by Cloudflare's network, every byte of storage lives on their infrastructure, and the total monthly bill for the free tier is exactly $0 in egress fees.

Here's how seven Cloudflare services come together to make it work, and what I learned along the way.

The constraint that shaped everything

Most cloud sync products are built on a foundation of EC2 instances, RDS databases, S3 buckets, and a tangled web of IAM policies. They work, but they come with operational overhead that scales linearly with your user count. You need capacity planning, auto-scaling groups, database replicas, and an ops team to keep it all running.

I wanted to see what happens when you remove all of that. Not as a thought experiment — as an actual product. Could you build something competitive with Dropbox's core feature set using nothing but edge infrastructure?

The answer is yes. But the path there required rethinking almost every architectural assumption I'd built up over 25 years of enterprise development.

Seven services, one platform

1. Workers (Hono framework) — the API layer

Cloudflare Workers is the compute layer. Every API request — authentication, file metadata operations, presigned upload URLs, share link generation, search queries, sync coordination — is handled by a Worker running the Hono framework.

Authentication was the first real test. I implemented two paths: PBKDF2-SHA256 for email/password auth (using the Web Crypto API available in Workers) and Google OAuth via the Arctic library. The OAuth flow was straightforward, but PBKDF2 was interesting — Workers gives you access to crypto.subtle, which means you can do proper password hashing without any native modules.

The Worker handles everything stateless: file metadata CRUD, generating presigned upload URLs for direct-to-R2 uploads, creating and validating share links with expiry, full-text search queries against D1, and the sync protocol that keeps clients in lock-step.

2. R2 — file storage with zero egress

R2 is the backbone. Every file uploaded to DoubleSpace lands in R2, keyed with a structure that looks like this:

{workspaceId}/files/{fileId}/v1/original

This key structure is deliberate. It partitions by workspace for future multi-tenant isolation, includes a version segment for eventual versioning support, and keeps the original filename out of the key entirely (that lives in D1 metadata).

For large files, I use R2's multipart upload API. The client requests an upload session, gets presigned URLs for each part, uploads directly to R2 (bypassing the Worker entirely for the heavy data transfer), and then the Worker completes the multipart assembly. This keeps Worker CPU time minimal even for multi-gigabyte files.

The killer feature: $0 egress. When users download their files, Cloudflare doesn't charge for bandwidth out. For a sync product where users constantly pull files to their devices, this changes the unit economics entirely. The same workload on S3 would cost hundreds in egress fees at scale.

3. D1 — SQLite at the edge

D1 is Cloudflare's serverless SQLite database. DoubleSpace uses it as the primary metadata store with 14 tables managed through Drizzle ORM. Users, workspaces, files, folders, shares, sessions, sync cursors — everything relational lives in D1.

The most interesting D1 feature I used is FTS5 — SQLite's full-text search extension. I created a virtual table that indexes file names, extracted text content, and tags. When a user searches "quarterly report," it's not doing a LIKE query — it's using FTS5's inverted index, which is genuinely fast even at tens of thousands of documents.

CREATE VIRTUAL TABLE file_search USING fts5(
  filename, content, tags,
  content='files', content_rowid='id'
);

Drizzle ORM made the migration story painless. Schema changes are written in TypeScript, generate SQL migrations, and deploy through wrangler d1 migrations apply. It's not as feature-rich as something like Prisma, but for D1 specifically, Drizzle is the right tool.

4. KV — session caching and rate limiting

Cloudflare KV is the fast-read, eventually-consistent store. I use it for two things:

  • Session token caching. When a user authenticates, their session token is written to KV with a TTL. Subsequent requests validate the token against KV instead of hitting D1. This keeps auth checks under 1ms at the edge.
  • Sliding window rate limiting. Each API key gets a KV entry tracking request counts within a time window. The sliding window approach is more fair than fixed windows — it prevents the burst-at-boundary problem where a user could double their rate limit by timing requests at a window boundary.

KV's eventual consistency is fine for both use cases. If a session invalidation takes 60 seconds to propagate globally, that's acceptable. And rate limiting doesn't need to be perfectly precise — it just needs to be close enough to prevent abuse.

5. Durable Objects — real-time sync and upload tracking

This is where it gets interesting. Durable Objects give you single-threaded, stateful compute at the edge. I use two types:

SyncRoom: One Durable Object per workspace, managing WebSocket connections for real-time sync. When a client uploads a file, the SyncRoom broadcasts the metadata change to all connected clients in that workspace. This is how DoubleSpace achieves near-instant sync without polling — the second you save a file on your laptop, your phone's file list updates.

UploadSession: One Durable Object per active multipart upload, tracking which parts have been received and managing the lifecycle. The critical feature here is the alarm API. Each UploadSession sets a 24-hour alarm. If the upload hasn't completed by then, the alarm fires and the Durable Object aborts the multipart upload, cleaning up orphaned parts in R2. Without this, abandoned uploads would leak storage forever.

6. Queues — background processing

Cloudflare Queues handle everything that doesn't need to happen in the request path. Three main jobs:

  • Thumbnail generation. When an image is uploaded, a message hits the queue. A consumer Worker fetches the image from R2, generates thumbnails at standard sizes, and writes them back to R2. The client sees a placeholder until the thumbnails are ready.
  • Text indexing. For documents (PDF, DOCX, TXT), the queue consumer extracts text content and updates the FTS5 index in D1. This keeps the upload path fast — the user's request returns immediately, and search becomes available within seconds as the queue processes.
  • Cleanup tasks. Expired share links, files in the 30-day trash past their retention period, orphaned R2 objects — all cleaned up by queue consumers on a schedule.

7. Pages — the web interface

The web UI is built with Vinext and deployed to Cloudflare Pages. It's a terminal-inspired dark interface that feels fast because it is fast — Pages serves static assets from Cloudflare's edge, and the app communicates with the Workers API for all dynamic operations.

Pages gives you automatic preview deployments for branches, instant rollbacks, and zero-config CDN. For a frontend that's fundamentally a single-page app making API calls, it's the simplest possible deployment story.

The CLI: a Rust binary

DoubleSpace isn't just a web app. The companion CLI, dblspc, is a Rust binary built with Clap for argument parsing. It handles:

  • Device auth flow. Instead of asking users to paste API keys, the CLI initiates a device authorization flow. You run dblspc login, it opens a browser tab, you approve the device, and the CLI receives a token. Clean and secure.
  • Bidirectional sync. The CLI watches local directories for changes (using OS file system events) and reconciles them with the server state. Conflicts are handled with a last-writer-wins strategy at the file level, with conflicting versions preserved as separate files.
  • FUSE/WinFSP mounting. On macOS and Linux, the CLI can mount your DoubleSpace workspace as a FUSE filesystem. On Windows, it uses WinFSP. This means your cloud files show up as a native drive — you can cd into them, grep across them, and every file operation transparently syncs.

Rust was the right choice here. The CLI needs to be fast (file watchers can't introduce latency), memory-efficient (it runs as a background daemon), and cross-platform. Rust delivers on all three.

What I learned

Building DoubleSpace taught me that Cloudflare is a credible full-product platform, not just an edge cache or a CDN. You can build complex, stateful, real-time applications without ever provisioning a server.

The mental model shift is significant. You stop thinking about servers and start thinking about services. Instead of "which EC2 instance handles this request," it's "which Durable Object owns this state." Instead of "how do I scale my database," it's "how do I structure my D1 queries to stay within SQLite's strengths."

The constraints are real — D1 has size limits, Workers have CPU time limits, Durable Objects have storage limits. But constraints breed creativity. Every limit I hit forced a better architectural decision than I would have made with unlimited resources.

If you're building a new product today and your instinct is to reach for AWS, consider this: DoubleSpace handles file sync, real-time collaboration, full-text search, background processing, multipart uploads, and native drive mounting. It does all of this across seven Cloudflare services with a single wrangler deploy command. No Terraform. No Kubernetes. No 3 AM pager alerts.

Zero servers. And it works.

Related articles