Skip to content

Why Lucimark runs entirely on Cloudflare

Workers, D1, R2 and one Durable Object per customer. What we gained, what hurt, and why there is no Docker anywhere in our stack.

Photo of Paulo R.

by3 min read

Illustration of a distributed screen network around abstract layers of compute, database, media, and state.

Every DOOH platform solves the same basic problem: a screen somewhere needs to know what to play, right now, even when that location's internet drops three times a day. From there, the decisions diverge sharply.

Ours was to run everything on Cloudflare's edge. No application server, no third-party managed queue, no Docker anywhere — not even in development.

The shape of it

Four pieces do the heavy lifting:

  • Workers serve the whole API (Hono). No player request travels to a fixed datacenter: it is answered at the point of presence nearest the screen.
  • D1 holds the global catalogue — tenants, users, the player directory.
  • R2 holds the media. No egress cost, which matters when every screen pulls the same video files.
  • Durable Objects hold each customer's state. One TenantDO per organization, with SQLite embedded.

The fourth is the one that shapes everything else.

One Durable Object per customer

The normal instinct is a playlists table with a tenant_id column and a WHERE clause on every query. It works, and it is where almost every SaaS starts.

We went the other way: each organization gets its own Durable Object, with its own database. That customer's playlists, schedules, proof-of-play records and assets live inside it, and nowhere else.

That buys three things at once:

  1. Isolation by construction. No query can leak data across customers, because there is no shared table to leak from. The classic bug of forgetting WHERE tenant_id stops being possible.
  2. Coordination without a queue. A DO is single-threaded by definition. When ten screens belonging to the same customer reconnect at once after a power cut, they talk to the same object, in order. We need no distributed lock for any of it.
  3. Locality. The object migrates toward whoever uses it most.

The cost is real and worth stating: you lose the global query. "How many screens are active across the whole platform?" stops being a SELECT COUNT(*) and becomes a fan-out, or a projection you maintain deliberately. Every question that crosses customers now takes explicit work.

For our case, it was worth it. The questions that matter day to day — did this playlist change, is this screen alive, what played on it yesterday — all sit inside a single customer.

What hurt

Migrations inside the DO. Migrating one central database is an operation; migrating N databases, each waking up whenever its customer appears, is a different one. Our rule today is strict: SQL running at TenantDO boot is DDL only, and it must be cheap. Any heavy backfill goes to an alarm or waitUntil, off the critical path. We learned that the usual way — a boot that got too slow.

Per-request CPU limits. Workers are not the place to transcode video. Nothing CPU-heavy runs inline; it either gets its own service or becomes a Workflow.

Observability. State spread across thousands of objects does not show up on a dashboard by itself. Projecting what matters outward takes deliberate effort.

What we never had to build

The easiest part to underestimate is what disappears from the backlog.

We have no server to scale, no cluster to patch, no TLS certificate to renew, no CDN to configure in front of the media. wrangler dev brings the whole stack up on a developer's machine — no docker compose, no local Postgres, no six containers to remember to tear down.

For a small team running a screen network that has to stay up, that is the decision that pays the most.

Start with one screen, free

Start on the free plan with 1 screen. Upgrade when your network grows.

Photo of Paulo R.

Paulo R.

Founder and CTO

Founder of Lucimark. Builds the platform end to end — from the Android player on the screen to the Workers serving the API.

View Paulo R.'s full profile

Get the next posts by email

No spam — just a note when we publish something new about DOOH, product or engineering.

By subscribing, you agree to receive emails from Lucimark. You can unsubscribe anytime.

We use analytics cookies to understand which articles are read and how to improve the blog. You can accept, reject non-essential cookies, or choose your preferences.