DEV Community

Mattia Maggioni
Mattia Maggioni

Posted on Fully Autonomous

Rendering AI short videos on containers that switch off after 2 minutes: our setup and real costs

In our app, HypeFox, every short video is rendered from code: a language model writes the scenes, Remotion turns them into 1080×1920 frames. The renderer is the most expensive part of the stack and the easiest one to break. This is how it runs today, less than two weeks after the first commit, with the numbers we measured.

What makes one video

  1. Script and scenes. A small language model writes the script, then fills a JSON scene file that follows the schema of the chosen style. The model never writes code: it picks layouts, icons, words and timings from what the schema allows. If the file fails the checks, the errors go back to the model and it tries again.
  2. Fact-check. Up to four checkable claims are searched on the web; only the lines that fail are rewritten.
  3. Voice. A text-to-speech API reads the lines and returns per-word timings. Those timings set the length of every scene and drive the captions.
  4. Render. Remotion, 1080×1920 at 30 fps. The cover image is generated in parallel and never blocks the video.

Steps 1 to 3 are API calls. Step 4 needs a real machine: Node, a headless Chrome, ffmpeg, a few CPUs.

From a Mac on a desk to containers that sleep

On day one the worker ran on a Mac. It worked until the Mac was off. The same evening we moved the exact same worker code to Cloudflare Containers: a Linux image with Node 22, Chrome for Remotion and ffmpeg. The container runs only when there's work, and we pay only while it's on.

Waking it up

Two paths, both asking one question: is there work?

  • A database trigger. When a row lands in the video table, a Postgres trigger calls the worker's wake-up endpoint with pg_net. The request goes out only after the transaction commits, and it can't fail the insert: if it gets lost, the second path catches it. The secret lives in Supabase Vault; the Worker only keeps its SHA-256.
  • A cron every minute, as the safety net for a lost wake-up or a video put back in the queue.

Both call one database function that returns the queue: videos waiting, videos in progress, covers still coming. The Worker keeps busy containers on and starts a free one for each item waiting, up to four.

From the insert to a cold container picking up the video: 7 to 10 seconds. The wake-up reaches the Worker in 0.2 to 0.3 s and the container boots in 2 to 4 s. With the cron alone it could take up to a minute.

Going back to sleep

  • sleepAfter is 2 minutes (it was 4 on day one). Before stopping, the worker checks the queue one last time.
  • When the platform stops a container (SIGTERM), the half-made video goes back to the queue.
  • Every container is its own named worker in the database. On boot, a worker re-queues the videos it left half done. With one shared name, a new container would steal work from the others. The extra workers' secrets are derived from the first one with HMAC-SHA256, so the Worker still holds a single secret.

The numbers

Measured on October 18: a 36-second video, cold container, same function the web app calls.

Instance Picked up after Render Ready after Cost
standard-4 (4 vCPU, 12 GiB) 7.1 s 56 s 104 s $0.011
3 vCPU, 9 GiB 9.8 s 81 s 136 s $0.010
2 vCPU, 6 GiB 10.4 s 86 s 144 s $0.008
  • One video alone costs about 1.1 cents of container time: 0.61 for memory, 0.47 for CPU, 0.03 for disk. With the old 4-minute sleep it was about 1.5 cents.
  • Back to back it's about 0.8 cents a video, because the last two idle minutes are shared.
  • We kept standard-4. Fewer vCPUs render almost proportionally slower (2.1 to 2.6 seconds of render per second of video, against 1.5) and the video arrives 30 to 40% later, to save 0.1 to 0.3 cents.
  • We pay for 12 GiB and use 3 at most. Custom instances need at least 3 GiB per vCPU, so 4 vCPU with 6 GiB isn't an option.
  • The monthly allowance included in Workers Paid covers about 36 solo videos. Memory runs out first.
  • The language-model calls (script, scenes, fact-check) add roughly half a cent to two and a half cents per video, depending on the style. The voice is extra.

Things that bit us

  • Deploys restart work. A rollout stops running machines, and a video in progress starts again from zero. One of our own test videos restarted from 98%. Now we check that nothing is in progress before deploying.
  • Build from a clean commit. The image takes files as they are on disk, so we build from git archive HEAD in a temp folder, never from the working folder.
  • Rollouts with every container asleep didn't start; wrangler deploy --containers-rollout=immediate fixed it.
  • Pin what Remotion pins. zod is fixed at the version Remotion expects, so npx remotion versions is clean and cloud renders match local ones.

Nothing fancy, and that's the point: one queue in Postgres, one question, containers that are allowed to die.

If you render video on demand: do you keep a warm instance, or live with the cold start?

Written with the help of an AI assistant, from our own code, notes and measurements.

Top comments (0)