containerflare
containerflare lets you run Axum inside Cloudflare Containers without re-implementing the platform glue, and now auto-detects when it is executing on Google Cloud Run. It exposes a tiny runtime that:
- boots an Axum router on the container’s loopback listener (or Cloud Run’s
PORTbinding) - forwards Cloudflare/Cloud Run request metadata into your handlers
- keeps a command channel open so you can reach host-managed capabilities (KV, D1, Queues, etc.) whenever the platform exposes one
The result feels like developing any other Axum app—only now it runs next to your Worker.
Highlights
- Axum-first runtime – bring your own router, tower layers, extractors, etc.
- Cloudflare metadata bridge – request ID, colo/region/country, client IP, worker name, and
URLs are injected via
ContainerContext. - Cloud Run aware – automatically binds to the Cloud Run
PORT, populates service / revision / project / trace fields, and disables the host command channel when unavailable. - Command channel client – talk JSON-over-STDIO (default), TCP, or Unix sockets to the host;
the IPC layer now ships as the standalone
containerflare-commandcrate for direct use. - Production-ready example –
examples/basictargets both Cloudflare Containers and Google Cloud Run with the same codebase and Dockerfile.
Installation
The crate targets Rust 1.90+ (edition 2024).
Quick start
use ;
use ;
async
async
ContainerContextis injected via Axum’s extractor system and surfacesContainerContext::platform()so you can differentiate between Cloudflare and Cloud Run.RequestMetadatacontains everything Cloudflare knows about the request (worker name, colo, region,cf-ray, client IP, method/path/url, etc.) plus Cloud Run service/revision/ configuration/project information and the parsedx-cloud-trace-contextheader when present.ContainerContext::command_client()provides the low-level JSON command channel; callinvokewhenever Cloudflare documents a capability. On Cloud Run the channel is disabled and the client reportsCommandError::Unavailableso you can log or fall back gracefully.
Run the binary inside your container image. Cloudflare will proxy HTTP traffic from the
Worker/Durable Object to the listener bound by containerflare (binds to PORT when set, otherwise
CF_CONTAINER_PORT, falling back to 0.0.0.0:8787 for the Cloudflare sidecar). Override
CF_CONTAINER_ADDR for a custom interface. Use CF_CMD_ENDPOINT when pointing the command client
at a TCP or Unix socket shim.
Standalone command crate
If you only need access to the host-managed command bus (KV, R2, Queues, etc.), depend on
containerflare-command directly. Cloud Run
does not expose this bus so commands immediately return CommandError::Unavailable, but the same
API works on Cloudflare Containers:
It exposes CommandClient, CommandRequest, CommandResponse, and the CommandEndpoint
parsers without pulling in the runtime/router pieces.
Running locally
# build and run the example container (amd64)
# curl echoes the RequestMetadata JSON – easy proof the bridge works
# customize the listener
Deploying to Cloudflare Containers
From examples/basic, run:
The example’s wrangler.toml sets image_build_context = "../..", so the Docker build sees
the entire workspace (the example crate depends on this repo via path = "../.."). After
deploy Wrangler prints a workers.dev URL that proxies into your container:
Deploying to Google Cloud Run
The same example crate can target Cloud Run. From examples/basic:
It uses your gcloud defaults for project/region unless overridden (PROJECT_ID, REGION,
SERVICE_NAME, TAG, RUST_LOG). By default the script deploys without allowing
unauthenticated traffic; pass --allow-unauthenticated (or ALLOW_UNAUTH=true) to opt in. When
containerflare detects Cloud Run it binds to the injected PORT, captures
K_SERVICE/K_REVISION/K_CONFIGURATION/GOOGLE_CLOUD_PROJECT,
parses x-cloud-trace-context, and disables the host command channel. Handlers can inspect that
state via ContainerContext::platform() and the new Cloud Run fields on RequestMetadata.
Metadata bridge
The Worker shim (see examples/basic/worker/index.js) adds an x-containerflare-metadata
header before proxying every request into the container. That JSON payload includes:
- request identifier (
cf-ray) - colo / region / country codes
- client IP
- worker name (derived from the
CONTAINERFLARE_WORKERWrangler variable) - HTTP method, path, and full URL
On the Rust side you can read all of those fields via ContainerContext::metadata() (see
RequestMetadata in src/context.rs). If you customize the Worker, keep writing this header
so your Axum handlers continue to receive Cloudflare context.
On Cloud Run the runtime infers metadata directly from HTTP headers + environment variables. It
records the service, revision, configuration, project ID, region, trace/span IDs, and whether the
request is sampled based on the x-cloud-trace-context header. These new fields appear on
RequestMetadata alongside the existing Cloudflare values. Geo fields like country/colo are
only populated on Cloudflare because Cloud Run does not provide them.
Example project
examples/basic is a real Cargo crate that depends on containerflare via path = "../..".
It ships with:
- a Dockerfile that builds for
x86_64-unknown-linux-musl - a Worker/Durable Object that forwards metadata and proxies requests
- deployment scripts and docs for Wrangler v4
- deploy scripts for Cloud Run that reuse the same Dockerfile
- first-class support for both Cloudflare and Cloud Run from the same codebase/Dockerfile
Use it as a template for your own containerized Workers.
Platform expectations
- Cloudflare currently expects Containers to be built for the
linux/amd64architecture, so we targetx86_64-unknown-linux-muslby default. You could just as easily use a debian/ubuntu based image, however alpine/musl is great for small container sizes. - The runtime binds to
PORTwhen provided (Cloud Run injects it), otherwise falls back toCF_CONTAINER_PORTor0.0.0.0:8787so the Cloudflare sidecar (which connects from10.0.0.1) can reach your Axum listener. OverrideCF_CONTAINER_ADDRfor custom setups. - The
CommandClientspeaks JSON-over-STDIO for now. When Cloudflare documents additional transports we can add typed helpers on top of it. Cloud Run disables the channel, so the client immediately returnsCommandError::Unavailable.
Contributions are welcome—file issues or PRs with ideas!