-
Notifications
You must be signed in to change notification settings - Fork 0
Encoding
ffmpeg cannot run in a Worker: no subprocesses, 128 MB memory, CPU limits per
request. Cloudflare Containers can run it, since they run an arbitrary OCI
image, and the existing apps/worker image is one.
Status: not built. This page records what the platform allows and what would have to change.
| Type | vCPU | Memory | Disk |
|---|---|---|---|
| lite | 1/16 | 256 MiB | 2 GB |
| basic | 1/4 | 1 GiB | 4 GB |
| standard-1 | 1/2 | 4 GiB | 8 GB |
| standard-2 | 1 | 6 GiB | 12 GB |
| standard-3 | 2 | 8 GiB | 16 GB |
| standard-4 | 4 | 12 GiB | 20 GB |
Custom instances allow at most 4 vCPU and 12 GiB, minimum 3 GiB per vCPU. Account limits are 1,500 concurrent vCPU and 30 TB concurrent disk, so many containers can run at once even though each one is small.
20 GB maximum disk. The worker downloads the whole source before decoding, because ffmpeg wants a seekable file and a proxy ladder reads the source repeatedly. A 40 GB camera master does not fit on any instance type. This is the blocker, and it is the same design point that makes object-storage encoding expensive in transfer terms generally.
No GPU. Software encoding only. nyx currently does real VAAPI
(vaapi:/dev/dri/renderD128:QVBR:low-power). Software 4K is substantially
slower, and the difference is unmeasured.
4 vCPU ceiling per container. Per-job latency is bounded by 4 cores. Throughput scales by running more containers, which the account limits allow.
The 20 GB cap forces the fix already wanted for cost reasons: stop downloading whole sources.
Options, none implemented:
- Range reads against R2, so ffmpeg reads only the parts it needs. Requires the worker to present the source to ffmpeg as a seekable stream rather than a file.
- Segmented encoding: split the source by byte or time range, encode segments in separate containers, concatenate. Fits the instance sizes well and parallelises, but is a real change to the job model.
- Keep whole-file download and accept a source size limit of roughly 15 GB after leaving room for renditions. Simplest, and rules out camera masters, which is most of the intended use.
Rates from Pricing. A standard-4 costs roughly:
- vCPU: 4 x $0.000020/s = $0.288/hour at full utilisation, billed on actual use
- Memory: 12 GiB x $0.0000025/s = $0.108/hour, billed as provisioned
- Disk: 20 GB x $0.00000007/s = $0.005/hour, billed as provisioned
About $0.40/hour with the CPU busy. A comparable spot VM is a few cents an hour, so this trades roughly an order of magnitude of compute cost for having no machine to operate.
Container egress is not free (NA/EU $0.025/GB after 1 TB), unlike R2 egress. Whether traffic between a container and an R2 bucket counts as billable egress is not confirmed here and should be checked before relying on it.
Stream is a managed encode and delivery product. It is not a fit for the review pipeline: onelight needs specific artefacts that Stream does not produce, including frame-accurate proxies carrying tmcd timecode, sprite sheets, waveform data, and reference HDR renditions. Playback delivery is already free through R2.
Containers are viable and would make the deployment fully Cloudflare-native, but only after sources stop being downloaded whole. That change is worth making regardless, because it is also the fix for the transfer cost measured in Benchmarks.
Order: measure transfer versus encode first, then decide between range reads and segmented encoding, then run it on Containers.
Measured across acceptance runs on 2026-08-03. A freshly deployed
*.workers.dev URL does not answer consistently for some minutes, and not in a
way that settles:
claim: 401
claim: 401
claim: 401
claim: 404, body "error code: 1042" (milliseconds later)
Three consecutive correct answers followed immediately by a miss. Individual edge locations appear to learn the route independently, so any single request can fail regardless of how many succeeded before it. Waiting longer reduces the odds; it does not remove them.
This matters beyond CI. A media worker pointed at a deployment makes a long sequence of requests -- claim, fetch source, upload each rendition, report -- and a single miss fails a job. Any client of a workers.dev deployment therefore needs to retry, and a production deployment should sit behind a custom domain rather than the workers.dev subdomain.