---
title: "13 — Images, Calls, Tunnels, and Browser Rendering"
uid: images-calls-tunnels-rendering
tags: ["tunnels", "calls", "cloudflare", "browser-rendering", "roadmap:cloudflare", "headless", "media", "webrtc", "images"]
excerpt: "Images, Calls, Tunnels, Browser Rendering: four small products, four specific problems — image optimization, real-time media, secure origin exposure, headless rendering — each run as a managed service on Cloudflare's network."
date: 2026-08-13T03:28:17+0000
source: https://www.aveshina.my.id/en/blog/images-calls-tunnels-rendering
---

"Other stuff on the platform" was how I filed the cluster of smaller Cloudflare products — Images, Calls, Tunnels, and Browser Rendering — which meant I never knew when each earned its keep. Writing them down separately fixed that. **The unifying idea is that each takes a category of work that's painful to do well — image optimization, real-time media, secure origin exposure, headless browser execution — and runs it on Cloudflare's network as a managed service.** [1][2][3][4]

The framing that landed is that none of these are general-purpose primitives the way Workers or KV are. They're each a focused solution to a focused problem, and the decision to reach for one is a decision that the problem it solves is one I actually have.

```figure
<svg viewBox="0 0 740 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Four specialized Cloudflare products in a 2x2 grid. Top-left: Images — resize/optimize an image. Top-right: Calls — real-time audio/video between two clients. Bottom-left: Tunnels — outbound-only connection from an origin to Cloudflare, no inbound ports. Bottom-right: Browser Rendering — a headless browser producing a PDF or screenshot.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- Images -->
    <rect x="20" y="20" width="340" height="130" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="190" y="44" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">Images</text>
    <text x="190" y="62" font-size="10" fill="#475569" text-anchor="middle">resize · convert · optimize, on the fly</text>
    <rect x="50" y="78" width="60" height="50" rx="4" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="80" y="106" font-size="9" fill="#422006" text-anchor="middle">source</text>
    <path d="M120,103 L165,103" fill="none" stroke="#64748b" stroke-width="1.5" stroke-dasharray="3,2"/>
    <text x="142" y="97" font-size="8" fill="#64748b" text-anchor="middle">transform</text>
    <rect x="175" y="78" width="50" height="50" rx="4" fill="#dcfce7" stroke="#16a34a"/>
    <text x="200" y="106" font-size="9" fill="#052e16" text-anchor="middle">webp</text>
    <rect x="240" y="78" width="50" height="50" rx="4" fill="#dcfce7" stroke="#16a34a"/>
    <text x="265" y="106" font-size="9" fill="#052e16" text-anchor="middle">avif</text>
    <rect x="305" y="78" width="40" height="50" rx="4" fill="#dcfce7" stroke="#16a34a"/>
    <text x="325" y="106" font-size="9" fill="#052e16" text-anchor="middle">tiny</text>
    <text x="190" y="142" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">automatic format + size per device</text>

    <!-- Calls -->
    <rect x="380" y="20" width="340" height="130" rx="10" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="550" y="44" font-size="13" font-weight="700" fill="#500724" text-anchor="middle">Calls</text>
    <text x="550" y="62" font-size="10" fill="#500724" text-anchor="middle">real-time audio / video at the edge</text>
    <rect x="410" y="80" width="60" height="50" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="440" y="100" font-size="9" fill="#422006" text-anchor="middle">client</text>
    <text x="440" y="114" font-size="9" fill="#422006" text-anchor="middle">A</text>
    <rect x="630" y="80" width="60" height="50" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="660" y="100" font-size="9" fill="#422006" text-anchor="middle">client</text>
    <text x="660" y="114" font-size="9" fill="#422006" text-anchor="middle">B</text>
    <line x1="470" y1="105" x2="630" y2="105" stroke="#db2777" stroke-width="1.5"/>
    <text x="550" y="98" font-size="8" fill="#500724" text-anchor="middle">low-latency, via Cloudflare</text>
    <text x="550" y="142" font-size="9" font-style="italic" fill="#500724" text-anchor="middle">SFU on the edge — conferencing, live audio</text>

    <!-- Tunnels -->
    <rect x="20" y="170" width="340" height="130" rx="10" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="190" y="194" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">Tunnels</text>
    <text x="190" y="212" font-size="10" fill="#052e16" text-anchor="middle">expose an origin without opening inbound ports</text>
    <rect x="50" y="225" width="120" height="50" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="110" y="247" font-size="9" fill="#422006" text-anchor="middle">origin (home lab</text>
    <text x="110" y="261" font-size="9" fill="#422006" text-anchor="middle">or private network)</text>
    <rect x="220" y="225" width="120" height="50" rx="6" fill="#dcfce7" stroke="#16a34a"/>
    <text x="280" y="247" font-size="9" font-weight="700" fill="#052e16" text-anchor="middle">Cloudflare</text>
    <text x="280" y="261" font-size="9" fill="#052e16" text-anchor="middle">public ingress</text>
    <path d="M170,250 L218,250" fill="none" stroke="#16a34a" stroke-width="2"/>
    <text x="194" y="242" font-size="8" font-weight="700" fill="#052e16" text-anchor="middle">outbound</text>
    <text x="190" y="292" font-size="9" font-style="italic" fill="#052e16" text-anchor="middle">no inbound ports · origin never directly exposed</text>

    <!-- Browser Rendering -->
    <rect x="380" y="170" width="340" height="130" rx="10" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="550" y="194" font-size="13" font-weight="700" fill="#422006" text-anchor="middle">Browser Rendering</text>
    <text x="550" y="212" font-size="10" fill="#422006" text-anchor="middle">headless Chromium, on the edge</text>
    <rect x="410" y="225" width="120" height="50" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="470" y="247" font-size="9" fill="#422006" text-anchor="middle">headless browser</text>
    <text x="470" y="261" font-size="9" fill="#422006" text-anchor="middle">renders a URL</text>
    <rect x="580" y="225" width="120" height="50" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="640" y="240" font-size="9" fill="#1e1b4b" text-anchor="middle">→ PDF</text>
    <text x="640" y="254" font-size="9" fill="#1e1b4b" text-anchor="middle">→ screenshot</text>
    <text x="640" y="268" font-size="9" fill="#1e1b4b" text-anchor="middle">→ pre-rendered HTML</text>
    <text x="550" y="292" font-size="9" font-style="italic" fill="#422006" text-anchor="middle">PDFs, screenshots, SEO pre-rendering</text>
  </g>
</svg>
```

## Images: optimization on demand

Cloudflare Images is a managed image pipeline [1]. The source image lives somewhere (uploaded to Images, or in an R2 bucket), and the platform handles the things that are painful to do well:

- **Resizing** to the dimensions each device actually needs.
- **Format conversion** — modern formats like WebP and AVIF for browsers that support them, with graceful fallback.
- **Compression** tuned so the image is small without visible quality loss.

The appeal is that I stop hand-managing derivative images. There's no "generate thumbnail, medium, large at upload time" step; instead, a Worker (or the CDN) requests the variant it needs and Images produces it on demand, caching the result. The source is stored once; the derivatives are produced and cached as needed.

When Images fits, it fits completely. For a site that serves user-generated images, product photos, or any media where "right size, right format, per device" matters, Images replaces a chunk of custom infrastructure.

## Calls: real-time audio and video

Cloudflare Calls is the platform for real-time audio and video — video conferencing, voice chat, live interactive broadcasting [2]. It provides the infrastructure that handles the hard parts of real-time media:

- **Low-latency routing** of audio/video streams between participants, via Cloudflare's edge.
- **A Selective Forwarding Unit (SFU)** model — rather than every participant sending to every other (mesh, which doesn't scale), each participant sends once to the SFU, which forwards to the others. This is the architecture production conferencing systems use.
- **APIs to build the participant experience** — joining a session, publishing a track, subscribing to others' tracks.

The thing to internalize is that Calls is the _media routing_ layer, not a finished video-chat UI. I still build the client experience (the buttons, the participant tiles, the controls), but I don't build the network layer that gets one participant's video to the others with sub-second latency. For any application where real-time interaction is the feature — conferencing, collaborative tools, live audio rooms — Calls is the layer that makes it tractable.

## Tunnels: expose an origin without opening it

Cloudflare Tunnel (sometimes cloudflared tunnel) flips the usual direction of network exposure [3]. Normally, to expose a service to the internet, I open an inbound port on my firewall and point a domain at it. Tunnel inverts this: a process on my origin (the cloudflared daemon) makes an _outbound_ connection to Cloudflare, and that connection is what Cloudflare uses to route public traffic back to me.

The security payoff is significant. With Tunnel:

- **No inbound ports.** My origin never accepts inbound connections from the internet. The firewall can stay locked down.
- **The origin is never directly addressable.** Attackers can't scan it or hit it; the only path in is through Cloudflare.
- **Cloudflare's security stack sits in front of it.** DDoS protection, WAF rules, access policies all apply before traffic reaches my origin.

The use cases are the ones where "I have a service I want reachable, but I don't want it exposed" — a home-lab service, a self-hosted app on a private network, a staging server behind a corporate firewall. Tunnel makes the origin reachable without making it vulnerable, and the outbound-only model means it works through NATs and firewalls that would block inbound connections.

## Browser Rendering: headless Chromium at the edge

Browser Rendering is a managed headless-Chromium service [4] — "headless" meaning a real browser running without a visible window, driven from code. A Worker can drive that browser — navigate to a URL, wait for JavaScript to execute, then extract the result. The three workloads this unlocks:

- **PDF generation.** Render an HTML template in a real browser, capture the result as a PDF. Better fidelity than HTML-to-PDF libraries that don't run JS or don't have a full layout engine.
- **Screenshots.** Capture what a page actually looks like after it's rendered, including JS-driven content.
- **Pre-rendering for SEO.** Take a client-rendered SPA, execute the JavaScript in a real browser, capture the resulting HTML, and serve _that_ to crawlers that don't run JS. This is the "server-side rendering as a service" pattern without changing the app's architecture.

The reason this is a product rather than a DIY task is that running a headless browser at scale is operationally painful — memory-hungry, slow to start, awkward to pool. Browser Rendering runs the pool on Cloudflare's network and exposes it through a Worker binding, so the Worker gets a browser session without owning the infrastructure.

## How these relate to the rest of the platform

A pattern across all four: each one is accessed through the same Workers-and-bindings model as everything else. A Worker sits in front of Images to handle auth and caching; a Worker coordinates Calls sessions; a Worker terminates the public end of a Tunnel and applies logic; a Worker drives Browser Rendering. The specialized services are layers behind Workers, not separate stacks — which is why composing them (a Worker that takes a screenshot via Browser Rendering, optimizes it via Images, stores it in R2) is just code, not an integration project.

## How I use this

The decision rule per product:

- **Images** — for any site serving images where size/format/per-device matters. Replaces hand-rolled derivative generation.
- **Calls** — only when real-time audio/video is an actual feature. Don't reach for it for one-way video delivery (that's Stream).
- **Tunnels** — when I need to expose a private service without opening inbound ports. A security and networking tool, not a compute product.
- **Browser Rendering** — for PDFs, screenshots, and SEO pre-rendering where a real browser is the only thing that produces correct output.

Each earns its complexity only when the specific problem it solves is a problem I have. The temptation to use them speculatively is the thing I watch for — most applications need at most one of these, and several need none.

## References

[1] Cloudflare, "Images — Cloudflare Docs," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/images/](https://developers.cloudflare.com/images/)

[2] Cloudflare, "Cloudflare Calls," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/calls/](https://developers.cloudflare.com/calls/)

[3] Cloudflare, "Cloudflare Tunnel — Cloudflare Zero Trust," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)

[4] Cloudflare, "Browser Rendering," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/browser-rendering/](https://developers.cloudflare.com/browser-rendering/)

```quiz
Q: What does Cloudflare Images handle that an application would otherwise build manually?
- Only the source image storage
- Resize, format conversion (WebP/AVIF), and compression, on demand per device
- Only the CDN caching of images
correct: 1
explain: Images is the full optimization pipeline. The source is stored once; derivatives (right size, right format, per device) are produced on demand and cached. The application stops hand-managing thumbnails and variants.

Q: Calls uses an SFU (Selective Forwarding Unit) architecture. What does that mean?
- Every participant sends media directly to every other participant (mesh)
- Each participant sends once to the SFU, which forwards to the others — scales better than mesh
- One central participant re-broadcasts to everyone
correct: 1
explain: In a mesh, N participants mean N×(N-1) connections, which doesn't scale. An SFU receives each participant's stream once and forwards it to the others, keeping per-participant upload to one stream regardless of group size.

Q: How does Cloudflare Tunnel differ from opening an inbound port on a firewall?
- Tunnel requires more firewall configuration
- Tunnel makes an outbound connection from the origin to Cloudflare — no inbound ports are opened, and the origin is never directly addressable
- Tunnel is only for static sites
correct: 1
explain: Tunnel inverts the direction. The cloudflared daemon on the origin dials out to Cloudflare, and that outbound connection is what carries public traffic back. The origin never accepts inbound connections, so it can't be scanned or attacked directly.

Q: A client-rendered SPA isn't being indexed by search engines because crawlers don't run its JavaScript. Which product addresses this?
- Calls
- Browser Rendering — execute the JS in a real browser, capture the resulting HTML, serve that to crawlers
- Tunnels
correct: 1
explain: Browser Rendering drives a real headless Chromium. For SEO pre-rendering, it loads the SPA, lets the JS run, captures the fully-rendered HTML, and serves that to crawlers — server-side rendering without changing the app's architecture.

Q: You want to serve a recorded video for on-demand playback. Which product fits?
- Calls (it handles video)
- Stream (it's the on-demand and live video pipeline)
- Images (it handles media)
correct: 1
explain: Calls is for real-time interactive audio/video, not one-way playback. Stream is the product for recorded video delivery — encoding, adaptive bitrate, CDN, and player. Images is for still images.
```
