> ## Documentation Index
> Fetch the complete documentation index at: https://docs.run.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Render Remotion videos

> Run single-machine or distributed Remotion renders on run.cloud with customer-owned S3.

run.cloud replaces the compute used by a Remotion Lambda render. Your Remotion
bundle and S3-compatible bucket stay under your control: run.cloud creates the
sandboxes, renders the video, and uploads directly to the signed URLs you
provide.

<Note>
  run.cloud does not create or host an S3 bucket. Create the final and temporary
  objects in your own S3-compatible storage and provide presigned PUT and GET
  URLs for them.
</Note>

## Before you start

You need:

* Node.js 20 or newer and `@run-cloud/sdk`
* a run.cloud API key in `RUN_CLOUD_API_KEY`
* a deployed Remotion bundle URL
* an exact Remotion version, an organization-owned runtime snapshot, or a compatible custom image
* a presigned PUT URL and a downloadable URL for the final output object

```bash theme={null}
npm install @run-cloud/sdk
```

## Render in one sandbox

`cloud.remotion.render()` waits for the render, returns its result, and destroys
the sandbox when it finishes or fails.

```ts theme={null}
import { Client } from "@run-cloud/sdk";

const cloud = new Client();
const result = await cloud.remotion.render({
  serveUrl: "https://example.com/remotion-bundle",
  composition: "ProductDemo",
  remotionVersion: "4.0.507",
  inputProps: { customer: "Acme" },
  codec: "h264",
  output: {
    uploadUrl: presignedPutUrl,
    downloadUrl: finalObjectUrl,
    contentType: "video/mp4",
  },
});

console.log(result.outputFile);
```

Keep every `remotion` package in your project on the same version. The SDK uses
`remotionVersion` to select the matching public runtime image and install that
exact renderer during sandbox startup. Use `image` instead when you maintain a
compatible image that already includes Remotion, Chrome, and FFmpeg.

## Cache dependencies in a runtime snapshot

The zero-setup path installs the exact renderer when a sandbox starts. For
repeated renders, install it once into a snapshot owned by your run.cloud
organization:

```ts theme={null}
const runtime = await cloud.remotion.createRuntimeSnapshot({
  remotionVersion: "4.0.507",
});

// Persist this ID with your deployment configuration.
console.log(runtime.id);
```

Restore every render worker from that snapshot by passing `snapshotId` instead
of `remotionVersion`:

```ts theme={null}
const result = await cloud.remotion.render({
  serveUrl: "https://example.com/remotion-bundle",
  composition: "ProductDemo",
  snapshotId: runtime.id,
  codec: "h264",
  output: {
    uploadUrl: presignedPutUrl,
    downloadUrl: finalObjectUrl,
  },
});
```

Create one snapshot for each Remotion version you use. Snapshots are private to
your organization; delete an obsolete one with
`cloud.snapshots.delete(snapshotId)`.

## Move from Remotion Lambda

For code that already uses `@remotion/lambda/client`, switch the import to the
compatibility adapter and add `s3Output`. Existing `region` and `functionName`
fields may remain while the compute moves to run.cloud.

```ts theme={null}
import {
  getRenderProgress,
  renderMediaOnLambda,
} from "@run-cloud/sdk/compat/remotion-lambda";

const render = await renderMediaOnLambda({
  region: "us-east-1",
  functionName: "remotion-render-4-0-507-mem2048mb-disk2048mb-240sec",
  serveUrl: "https://example.com/remotion-bundle",
  composition: "ProductDemo",
  codec: "h264",
  s3Output: {
    bucketName: "customer-video-output",
    uploadUrl: presignedPutUrl,
    downloadUrl: finalObjectUrl,
  },
});

const progress = await getRenderProgress({ renderId: render.renderId });
console.log(progress.done, progress.overallProgress);
```

The adapter reads the Remotion version from a standard Lambda function name.
If your function name uses another format, set `runCloud.remotionVersion`,
`runCloud.snapshotId`, or `runCloud.image` explicitly.

## Add distributed concurrency

Without distributed rendering, one render request creates one sandbox.
`concurrencyPerLambda` controls the Remotion renderer concurrency inside that
sandbox; it does not create more sandboxes.

To split one video across multiple sandboxes, set `concurrency` and provide a
temporary customer-owned object for each video and audio chunk:

```ts theme={null}
const render = await renderMediaOnLambda({
  functionName: "remotion-render-4-0-507-mem2048mb-disk2048mb-240sec",
  serveUrl: "https://example.com/remotion-bundle",
  composition: "ProductDemo",
  codec: "h264",
  concurrency: 8,
  concurrencyPerLambda: 1,
  s3Output: {
    bucketName: "customer-video-output",
    uploadUrl: finalPresignedPutUrl,
    downloadUrl: finalObjectUrl,
  },
  runCloud: {
    distributed: {
      createChunkOutput: async ({ renderId, index }) => {
        const prefix = `remotion-chunks/${renderId}/${index}`;
        return {
          video: await presignTemporaryObject(`${prefix}.ts`),
          audio: await presignTemporaryObject(`${prefix}.aac`),
        };
      },
    },
  },
});

console.log(render.sandboxCount);
```

`presignTemporaryObject()` is your storage helper. It must return
`{ uploadUrl, downloadUrl, headers?, contentType? }`, with the PUT and GET URLs
pointing to the same object. Add `downloadHeaders` when the signed GET requires
headers.

`concurrency` is the target maximum sandbox count. You can use
`framesPerLambda` instead and let the SDK calculate the count from the selected
composition. Chunks must be equal-sized except for the last chunk, so a short
composition can use fewer sandboxes than requested. Read `render.sandboxCount`
or `progress.runCloud.sandboxCount` for the exact number allocated.

Each allocated sandbox renders one frame range. Temporary chunks and the final
artifact stay in storage you provide, and `sandboxCount` is the total compute
allocated for the render.

## Progress and cleanup

The Lambda-compatible methods keep the render detached so you can poll it from
another process. Destroy every render after completion, failure, or
cancellation:

```ts theme={null}
import {
  deleteRender,
  getRenderProgress,
} from "@run-cloud/sdk/compat/remotion-lambda";

const progress = await getRenderProgress({ renderId });

if (progress.done || progress.fatalErrorEncountered) {
  await deleteRender({ renderId });
}
```

With the native SDK, use `cloud.remotion.start()`,
`cloud.remotion.getProgress()`, and `cloud.remotion.cancel()` for the same
detached lifecycle. Finite sandbox timeouts are a cleanup backstop, not a
replacement for cancelling completed or abandoned renders.

Configure an S3 lifecycle rule for the temporary chunk prefix. Use short-lived
signed URLs that remain valid for the expected render duration; signed URLs are
written to private files inside the sandbox and are not included in command
logs.

See [Remotion's distributed rendering guide](https://www.remotion.dev/docs/distributed-rendering)
for the underlying chunking model.
