# How to run FFmpeg in a cloud sandbox Install Ubuntu's ffmpeg package in a microVM, upload the media, run ffmpeg or ffprobe, and download the output file. **On Runtime you pay for the CPU an encode burns, and nothing for the minutes around it.** A 2 vCPU, 4 GiB sandbox with both CPUs encoding costs $0.08 an hour, so ten minutes of full-load x264 work costs about $0.013, and the same sandbox waiting for the next upload costs $0.03125 an hour ([pricing](/docs/pricing)). Each job runs in its own Firecracker microVM, which matters for media: a malformed file uploaded by a stranger is parsed on a machine that holds nothing else. Ubuntu 24.04 shipped FFmpeg 6.1.1 on 25 September 2026. ## Transcode an upload to web-ready MP4 ```ts check import { writeFile } from "node:fs/promises"; import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 3600 }); await sbx.exec("sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg", { check: true, timeoutMs: 600_000, }); await sbx.files.upload("./talk.mov", "/workspace/in.mov"); await sbx.exec( [ "ffmpeg", "-hide_banner", "-y", "-i", "in.mov", "-c:v", "libx264", "-preset", "medium", "-crf", "23", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", "out.mp4", ], { check: true, timeoutMs: 3_000_000, onStderr: (text) => process.stderr.write(text) }, ); await sbx.exec(["ffmpeg", "-y", "-ss", "5", "-i", "out.mp4", "-frames:v", "1", "poster.jpg"], { check: true, }); await writeFile("out.mp4", await sbx.files.read("/workspace/out.mp4")); await writeFile("poster.jpg", await sbx.files.read("/workspace/poster.jpg")); ``` ```python check import sys from withruntime import Sandbox with Sandbox.create(disk_mib=8192, timeout_seconds=3600) as sbx: sbx.exec("sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg", check=True, timeout_ms=600_000) sbx.files.upload("talk.mov", "/workspace/in.mov") sbx.exec(["ffmpeg", "-hide_banner", "-y", "-i", "in.mov", "-c:v", "libx264", "-preset", "medium", "-crf", "23", "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", "out.mp4"], check=True, timeout_ms=3_000_000, on_stderr=sys.stderr.write) sbx.exec(["ffmpeg", "-y", "-ss", "5", "-i", "out.mp4", "-frames:v", "1", "poster.jpg"], check=True) sbx.files.download("/workspace/out.mp4", "out.mp4") sbx.files.download("/workspace/poster.jpg", "poster.jpg") ``` - The command is an array, so it runs without a shell: a file name a user chose cannot inject shell syntax. - FFmpeg writes progress to stderr, which `onStderr` streams as it happens. - `+faststart` moves the MP4's index to the front of the file, per FFmpeg's format documentation, so a browser can start playing before the download ends. - Uploads and downloads move in parallel 1 MiB chunks, each checked by SHA-256, and resume after a dropped connection. There is no file size limit beyond the sandbox's disk. ## Read a file's streams as JSON `ffprobe` comes with the package. Its JSON writer gives an agent the codec, duration, resolution and bitrate without parsing text: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ image: "ffmpeg" }); await sbx.files.upload("./clip.mkv", "/workspace/clip.mkv"); const probe = await sbx.exec( ["ffprobe", "-v", "error", "-of", "json", "-show_format", "-show_streams", "clip.mkv"], { check: true }, ); const info = JSON.parse(probe.stdout) as { format: { duration: string }; streams: { codec_type: string; codec_name: string; width?: number }[]; }; console.log( info.format.duration, info.streams.map((s) => `${s.codec_type}:${s.codec_name}`), ); ``` ## Encoders in Ubuntu's build Ubuntu 24.04's `libavcodec60`, the library behind its `ffmpeg`, depends on these codec libraries, per packages.ubuntu.com on 25 September 2026: | Library | Use it for | Name in ffmpeg | | --------------- | -------------------------------- | ----------------- | | `libx264-164` | H.264 video, the widest playback | `-c:v libx264` | | `libx265-199` | H.265/HEVC video | `-c:v libx265` | | `libvpx9` | VP8 and VP9 video for WebM | `-c:v libvpx-vp9` | | `libsvtav1enc1` | AV1 encoding | `-c:v libsvtav1` | | `libdav1d7` | AV1 decoding | (decoder) | | `libopus0` | Opus audio | `-c:a libopus` | | `libmp3lame0` | MP3 audio | `-c:a libmp3lame` | Run `ffmpeg -hide_banner -encoders` in a sandbox for the full list. ## Encodes run on CPUs Runtime sandboxes have no GPUs, so NVENC and other hardware encoders are not available; every encoder above runs on the CPU. Two ways to go faster: - **Paid sandboxes** can ask for more `vcpu` than the trial's two; x264 spreads its work across threads. The bill follows the CPU actually used, not the cores reserved. - **Many files at once:** give each its own sandbox. A paid account runs 100 at once to start with, and the trial runs eight. ```ts check import { Sandbox } from "withruntime"; const files = ["a.mov", "b.mov", "c.mov"]; await Promise.all( files.map(async (name) => { await using sbx = await Sandbox.create({ image: "ffmpeg", diskMiB: 8192 }); await sbx.files.upload(`./${name}`, `/workspace/${name}`); await sbx.exec(["ffmpeg", "-y", "-i", name, "-c:v", "libx264", "-crf", "23", "out.mp4"], { check: true, timeoutMs: 3_000_000, }); await sbx.files.download("/workspace/out.mp4", `./${name}.mp4`); }), ); ``` ## Start every sandbox with FFmpeg installed The samples above that say `image: "ffmpeg"` use this [custom image](/docs/images): ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.images.build({ name: "ffmpeg", recipe: { apt: ["ffmpeg"] } }); ``` ```python check from withruntime import Runtime Runtime().images.build(name="ffmpeg", recipe={"apt": ["ffmpeg"]}) ``` Building is free and uses no trial hours; the free trial stores three images free. ## Disk and time | Limit | What to set | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | Disk | Input, output and FFmpeg itself share it; the default 4 GiB had about 2.5 GiB free on 24 September 2026, the trial allows 10 GiB | | Disk speed | Bursts to about 250 MB/s for up to 30 seconds, then about 40 MB/s | | One command | 60 seconds by default, up to 24 hours with `timeoutMs` | | Sandbox lifetime | `timeoutSeconds`, at most an hour ahead; `extend` moves it, or `keepAlive` holds it while your process runs | For media that arrives from users, turn the sandbox's internet off after the install so a crafted file cannot call out ([turn off sandbox internet](/how-to/turn-off-sandbox-internet)). To generate images or thumbnails in other formats, see [ImageMagick in a sandbox](/integrations/imagemagick). Document and PDF pipelines are in [document conversion](/use-cases/document-conversion), and batch jobs across many sandboxes in [AI data pipelines](/use-cases/ai-data-pipelines). ## Sources - FFmpeg formats documentation (`movflags` and `faststart`), https://ffmpeg.org/ffmpeg-formats.html, read 25 September 2026 - Ubuntu 24.04 ffmpeg and libavcodec60 packages, https://packages.ubuntu.com/noble/ffmpeg and https://packages.ubuntu.com/noble/libavcodec60, read 25 September 2026 Facts on this page were checked on 25 September 2026.