Render on a server
This content is for v0.4. Switch to the latest version for up-to-date documentation.
After this recipe you can render projects on a Linux server, in a container, on a GPU or on CPUs only, and work through a queue of render jobs from Node.js.
The server runs alpha2-host, the native renderer. It draws the same frames as the browser preview, because both run the same rendering code. See Determinism.
What the server needs
Section titled “What the server needs”- Linux x64 or arm64 with glibc. The release archives are built for glibc, not musl, so they do not run on Alpine and other musl images.
- Node.js 20 or later for
@alpha2-sh/node, or Python 3.11 or later for thealpha2Python package. - For GPU rendering, a Vulkan driver for the GPU. Hardware decoding uses Vulkan Video. NVENC on NVIDIA GPUs needs driver 530.41.03 or newer, and inside a container it needs the driver’s
videocapability. - For CPU rendering, nothing else: the CPU renderer opens no graphics driver.
The bundled archive, which install fetches by default, carries its own FFmpeg 8 libraries in lib/, so the image needs no FFmpeg package. install --thin fetches the thin archive instead, which loads an FFmpeg 8 installed on the system.
Choose the renderer
Section titled “Choose the renderer”alpha2-host picks its renderer when it starts, from --renderer on export or the ALPHA2_RENDERER environment variable (which serve and the Node.js and Python clients read, along with their own renderer option):
| Mode | Renders on | Fails when |
|---|---|---|
auto (default) |
a hardware GPU if there is one, else the CPU | never for lack of a GPU |
gpu |
any GPU adapter, software ones included | there is no adapter: GPU_INIT_FAILED |
cpu |
the CPU renderer | never for lack of a GPU |
auto never renders on a software adapter (lavapipe, llvmpipe, WARP, SwiftShader), which draws the same pictures many times slower than the CPU renderer. A cloud VM with only a software Vulkan driver therefore renders on the CPU under auto.
The CPU renderer draws the same render graph as the GPU, and its pictures are the same bytes on every machine and for any number of threads. It uses every thread the process may run on; ALPHA2_RENDER_THREADS sets the number. It draws every built-in effect and transition, but a runtime WGSL effect needs a GPU and fails with EFFECT_ERROR on the CPU.
Every export result says which renderer drew it: renderer (gpu or cpu), rendererReason when auto chose the CPU, and rendererThreads on the CPU.
H.264 without a hardware encoder
Section titled “H.264 without a hardware encoder”The FFmpeg in the archives is LGPL and has no software H.264 encoder. A server with no hardware H.264 encoder (a CPU-only machine, an arm64 server, an NVIDIA datacenter GPU such as A100 or H100) needs Cisco’s OpenH264, or another codec:
- Install OpenH264 with
npx @alpha2-sh/node install --openh264(orpython -m alpha2 install --openh264). It downloads Cisco’s binary from Cisco, as Cisco’s licence requires. - Or export a codec the runtime encodes on the CPU: VP9 or AV1 (
--video-codec vp9in an.mp4,.mkvor.webm), ormpeg4-part2.
getCapabilities() reports encode.h264.status (available, gpu-only or unavailable) with a reason and a remedy, before you render anything.
A container image for CPU rendering
Section titled “A container image for CPU rendering”This image installs @alpha2-sh/node, the host archive of the same version, and OpenH264.
FROM node:22-slimWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci# One cache for the build user and the run user.ENV ALPHA2_HOST_CACHE_DIR=/opt/alpha2RUN npx @alpha2-sh/node install && npx @alpha2-sh/node install --openh264ENV ALPHA2_RENDERER=cpuCOPY . .CMD ["node", "worker.mjs"]npx @alpha2-sh/node installdownloads the release archive for this package’s version and checks it against the release’schecksums.sha256.- The installs write to the cache directory.
ALPHA2_HOST_CACHE_DIRmakes it the same at build time and at run time, whatever user the container runs as. - Running the OpenH264 install in the image build is the download Cisco’s terms ask for. Do not copy the library into an image you publish.
For GPU rendering, start from an image with the GPU’s Vulkan driver, pass the GPU into the container, and leave ALPHA2_RENDERER unset (auto) or set it to gpu to fail fast without one.
Render one job from the shell
Section titled “Render one job from the shell”alpha2-host export --renderer cpu \ --project job.json \ --asset clip-1=/data/in/clip-1.mp4 \ --font /data/fonts/Inter.ttf \ --out /data/out/job.mp4The command prints the result as JSON on standard output and progress on standard error. It exits 0 on success, 1 on a failure, and 70 when the GPU device cannot be created. See the CLI reference.
alpha2-host conform checks the job’s media without rendering and without a GPU, and exits 1 when something blocks the export: a cheap check before a job takes a render slot.
Render a queue with Node.js
Section titled “Render a queue with Node.js”Alpha2NodeClient runs each export in its own alpha2-host process. concurrency is how many run at once and so how many share the machine; more requests wait for a slot.
import { Alpha2HostError, Alpha2NodeClient } from "@alpha2-sh/node";import { isRetryableFailure } from "alpha2/export-retry";
const client = new Alpha2NodeClient({ concurrency: 2 });
// The end of everything the timeline shows.function projectEnd(project) { let end = 0; for (const track of project.timeline.tracks) { for (const clip of track.clips) end = Math.max(end, clip.timeline_range.end); for (const cue of track.captions?.cues ?? []) end = Math.max(end, cue.timeline_range.end); } return end;}
async function render(job) { const { project } = job; for (let attempt = 1; ; attempt++) { try { return await client.exportProject({ project, assets: job.assets, // { "<source-id>": "/data/in/..." } fonts: job.fonts, startTicks: 0, endTicks: projectEnd(project), frameRate: project.settings.frame_rate, output: job.output, overwrite: true, onProgress: ({ phase, completed, total }) => job.report?.(phase, completed, total), }); } catch (error) { if (!(error instanceof Alpha2HostError)) throw error; if (attempt < 3 && isRetryableFailure(error.failure)) continue; throw error; // invalid input, missing media, cancelled... retrying will not help } }}
// Take jobs from your queue; here, an async iterable.for await (const job of queue) { render(job).then(job.done, job.fail);}- The project is validated before a process starts. An invalid one is refused with
Alpha2ProjectValidationErrorand itsproblems. Media that is missing or offline is refused withAlpha2ConformErrorand the conformreport, before anything renders. error.failureclassifies every failure (lost device, out of memory, bad input…).isRetryableFailurefromalpha2/export-retryis false for failures a retry cannot mend;decideRetryalso says when to wait and when to retry with less in flight. Installalpha2beside@alpha2-sh/nodeto import it.- A host that exits before it answers (the kernel’s out-of-memory killer, for one) fails with
HOST_EXITED. If memory is short, lowerconcurrencyor give the export smallermemorybudgets. signal(anAbortSignal) cancels an export: the host stops, removes its partial file, and the call rejects withAlpha2AbortError.- An output is written as
<stem>.alpha2-partial.<ext>beside the final path and renamed into place when complete, so a reader never sees half a file.
See also
Section titled “See also”- Node.js: every export option and result field.
- CLI reference: every
alpha2-hostflag. - Host protocol: drive
alpha2-host servefrom any language. - Platform support: targets, GPU APIs and encoders.
