The Images service, built in Rust on Workers , runs on every machine in Cloudflare’s edge network. To handle client connections, we use hyper , an open-source HTTP library for Rust. Last year, we introduced the Images binding to enable custom, programmatic workflows for processing remote images in Workers.
At the end of 2025, we rearchitected the binding to provide a more direct, local connection between the Workers runtime and the Images service. Shortly after rollout, we received reports that transformation requests from the binding were failing — but only intermittently and only for larger images. Even stranger, the responses for these requests returned a 200 status without any errors logged.
The image data was simply cut short: A response that should have been two megabytes might arrive with a few hundred kilobytes instead. We spent six weeks chasing a nearly invisible bug — a race condition that occurred only under specific conditions — in the hyper library that impacted how the Images binding returned processed image data back to the client. In the end, it took four lines of code to fix it.
Hops, handoffs, and hyper When developers build on Cloudflare, they compose full-stack applications from a set of platform services that are accessible to Workers through bindings. Bindings provide direct APIs to resources on the Developer Platform like compute , storage , AI inference , and media processing . The Images binding decouples image optimization from delivery; you can transcode, composite, or manipulate images without needing to return the output as an HTTP response.
It also lets you apply optimization parameters in any order, rather than following the fixed sequence imposed by the URL interface . Here, a worker can pass image data directly to the Images API, chain operations together, and get the processed result back as a stream: const result = await env. IMAGES .
input(image) . transform({ width: 800, rotate: 90 }) . output({ format: "image/avif" }); return result.
response(); At a high level, this is how image data moves through our various services: The pipe represents a socket connection between the intermediary and Images, where data is handed off from one process to the next through the kernel’s buffer. The binding communicates with Images through a socket connection managed by the Workers runtime. A socket connection is a communication channel between two processes.
Each end of the socket has buffers that are managed by the operating system’s kernel; these buffers are temporary holding areas where data sits after one side writes it but before the other side reads it. Hyper manages the connection on the Images service’s side, reading incoming requests from the socket and writing responses back to it. When a request uses the Images binding, the Images service reads the input, performs the requested optimization operations, and encodes the result.
It then passes the entire encoded image to hyper as a single in-memory block. Hyper writes this response data into its own internal buffer. At this point, hyper considers the encoding work as complete, since it has all the bytes that it needs to send.
The next step is to flush its internal buffer to the socket’s outbound buffer, moving the data from the Images service to the intermediary on the other end. If the reader on the other end is fast, then hyper can flush everything in one pass — the outbound buffer will have room because the reader is consuming data as quickly as it arrives. Once all data is sent, hyper issues a shutdown on the socket, signaling that the connection is finished and no more data will be written.
But if the reader is slower (even by a few milliseconds), then the outbound buffer fills up, and hyper needs to wait until there’s room to continue writing. Taking the local All incoming traffic on Cloudflare's network passes through FL, an internal intermediary service that runs security and performance features and routes requests to the appropriate backend.
When we first launched the binding, image data flowed from the Workers runtime, through FL, to the Images service. This path was a natural fit for our initial release and follows the same architecture as our URL interface. Over time, though, this coupling with FL became a constraint: Every change to the binding had to follow FL’s release cycle.
In December 2025, the Images team replaced FL with a new intermediary service, an internal worker binding that runs on the same machine. In the original architecture, data moved through FL over network sockets; this path carried the overhead of FL’s full processing pipeline, such as DNS lookups and routing. The internal binding replaced these with Unix sockets to directly connect the services on the same machine, bypassing FL and the overhead of the network stack.
This made the request path to Images faster and gave the team independent control over binding releases. Within days of the rollout, we received our first customer report. 200 OK (not OK) The first sign of trouble came from a customer with a non-standard setup: two layers of image processing, where one pipeline was nested inside another.
First, their worker used the Images binding to composite multiple large source images from R2 — a JPEG background plus PNG overlay layers — into a single combined JPEG. Second, they further compressed, transcoded, and resized the result through the URL interface. The bug originated in the inner pipeline’s return path, where the response was truncated before reaching the outer pipeline.
The inner pipeline (transformation binding) handled compositing. The outer pipeline (transformation URL) handled delivery optimizations like scaling and format conversion.
Originally published at blog.cloudflare.com

