Skip to main content

Deploying Remotion Lambda with Terraformv4.0.544

You can deploy Remotion Lambda using Terraform by using the .zip files on S3 and Lambda layers that Remotion publishes.

Prerequisites​

AWS China regions are not supported because Remotion cannot publish artifacts there.

1. Build your site​

From your Remotion project, build a Remotion Bundle.
This guide assumes the site will live in the sites/remotion-project subfolder of your S3 bucket.

Build the site
bunx remotion bundle src/index.ts --out-dir=./sites/remotion-project --public-path=/sites/remotion-project/

2. Configure Terraform​

Put the following Terraform files in an infra/ directory next to sites/.

Project structure
your-project/ ├─ sites/ │ ├─ remotion-project/ # Generated by the build step ├─ infra/ │ ├─ main.tf │ ├─ variables.tf │ ├─ terraform.tfvars │ ├─ bucket.tf │ ├─ iam.tf │ ├─ function.tf │ ├─ site.tf │ ├─ outputs.tf

Configuration​

infra/main.tf
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 6.0" } http = { source = "hashicorp/http" version = "~> 3.0" } } } provider "aws" { region = var.region } data "aws_caller_identity" "current" {} data "http" "manifest" { url = "https://remotionlambda-binaries-${var.region}.s3.${var.region}.amazonaws.com/functions/v${var.remotion_version}/manifest.json" } locals { manifest = jsondecode(data.http.manifest.response_body) function_name = "remotion-render-${replace(var.remotion_version, ".", "-")}-mem${var.memory_size_mb}mb-disk${var.disk_size_mb}mb-${var.timeout_seconds}sec" function_arn = "arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:${local.function_name}" }

Configure your Terraform variables in infra/variables.tf for region, Remotion version, memory size, disk size, function timeout.

infra/variables.tf
variable "region" { type = string default = "eu-central-1" } variable "remotion_version" { type = string default = "4.0.544" } variable "memory_size_mb" { type = number default = 2048 } variable "disk_size_mb" { type = number default = 2048 } variable "timeout_seconds" { type = number default = 120 } variable "site_name" { type = string default = "remotion-project" validation { condition = can(regex("^[A-Za-z0-9_-]+$", var.site_name)) error_message = "Use letters, numbers, hyphens, or underscores for the site name." } }

You can override these defaults in a file called infra/terraform.tfvars.

infra/terraform.tfvars
memory_size_mb = 3008 disk_size_mb = 2048 timeout_seconds = 240

Bucket​

Terraform creates a uniquely named bucket with the Remotion prefix and region.
Your site is publicly readable so the Lambda browser can load your composition - use input props and environment variables to pass private data.

Object ACLs are enabled because Remotion writes private intermediate files and uses public-read ACLs for public render outputs.
Use the privacy setting to control output access.

infra/bucket.tf
resource "aws_s3_bucket" "remotion" { bucket_prefix = "remotionlambda-${replace(var.region, "-", "")}-" } resource "aws_s3_bucket_ownership_controls" "remotion" { bucket = aws_s3_bucket.remotion.id rule { object_ownership = "BucketOwnerPreferred" } } resource "aws_s3_bucket_public_access_block" "remotion" { bucket = aws_s3_bucket.remotion.id block_public_acls = false block_public_policy = false ignore_public_acls = false restrict_public_buckets = false } resource "aws_s3_bucket_acl" "remotion" { bucket = aws_s3_bucket.remotion.id acl = "private" depends_on = [ aws_s3_bucket_ownership_controls.remotion, aws_s3_bucket_public_access_block.remotion, ] } resource "aws_s3_bucket_policy" "site" { bucket = aws_s3_bucket.remotion.id policy = jsonencode({ Version = "2012-10-17" Statement = [{ Sid = "PublicSiteFiles" Effect = "Allow" Principal = "*" Action = "s3:GetObject" Resource = "${aws_s3_bucket.remotion.arn}/sites/*" }] }) depends_on = [aws_s3_bucket_public_access_block.remotion] }

IAM permissions​

The execution role lets the function access this bucket, invoke itself to distribute rendering, and write logs.

infra/iam.tf
resource "aws_iam_role" "remotion" { name_prefix = "remotion-lambda-" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Action = "sts:AssumeRole" Principal = { Service = "lambda.amazonaws.com" } }] }) } resource "aws_iam_role_policy" "execution" { name = "remotion-execution" role = aws_iam_role.remotion.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "ListBuckets" Effect = "Allow" Action = ["s3:ListAllMyBuckets"] Resource = ["*"] }, { Sid = "ReadBucket" Effect = "Allow" Action = ["s3:ListBucket", "s3:GetBucketLocation"] Resource = [aws_s3_bucket.remotion.arn] }, { Sid = "ReadWriteObjects" Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:PutObjectAcl", "s3:AbortMultipartUpload", ] Resource = ["${aws_s3_bucket.remotion.arn}/*"] }, { Sid = "InvokeRenderer" Effect = "Allow" Action = ["lambda:InvokeFunction"] Resource = [local.function_arn] }, { Sid = "WriteLogs" Effect = "Allow" Action = ["logs:CreateLogStream", "logs:PutLogEvents"] Resource = ["${aws_cloudwatch_log_group.remotion.arn}:*"] }, ] }) }

Lambda function​

Functions must follow the function naming convention and include tags to match how Remotion deploys functions.

infra/function.tf
resource "aws_cloudwatch_log_group" "remotion" { name = "/aws/lambda/${local.function_name}" retention_in_days = 14 } resource "aws_lambda_function" "remotion" { function_name = local.function_name role = aws_iam_role.remotion.arn s3_bucket = local.manifest.code.s3Bucket s3_key = local.manifest.code.s3Key source_code_hash = local.manifest.code.sha256Base64 handler = local.manifest.handler runtime = local.manifest.runtime architectures = [local.manifest.architecture] layers = local.manifest.layers["default"] memory_size = var.memory_size_mb timeout = var.timeout_seconds ephemeral_storage { size = var.disk_size_mb } tags = { "remotion-lambda" = "true" "remotion-version" = var.remotion_version "remotion-memory-in-mb" = tostring(var.memory_size_mb) "remotion-timeout-in-seconds" = tostring(var.timeout_seconds) "remotion-ephemeral-storage-in-mb" = tostring(var.disk_size_mb) } depends_on = [aws_iam_role_policy.execution] } resource "aws_lambda_function_event_invoke_config" "remotion" { function_name = aws_lambda_function.remotion.function_name maximum_retry_attempts = local.manifest.maximumRetryAttempts } resource "aws_lambda_runtime_management_config" "remotion" { function_name = aws_lambda_function.remotion.function_name update_runtime_on = "Manual" runtime_version_arn = local.manifest.runtimeVersionArn }

Site files​

Add any extra file extensions your project needs to site_content_types.

infra/site.tf
locals { site_bundle_dir = abspath("${path.module}/../sites/${var.site_name}") site_prefix = "sites/${var.site_name}" site_files = fileset(local.site_bundle_dir, "**") site_content_types = { css = "text/css" gif = "image/gif" html = "text/html" jpeg = "image/jpeg" jpg = "image/jpeg" js = "text/javascript" json = "application/json" mjs = "text/javascript" mp3 = "audio/mpeg" mp4 = "video/mp4" otf = "font/otf" png = "image/png" svg = "image/svg+xml" ttf = "font/ttf" wasm = "application/wasm" webm = "video/webm" webp = "image/webp" woff = "font/woff" woff2 = "font/woff2" } } resource "aws_s3_object" "site_asset" { for_each = setsubtract(local.site_files, toset(["index.html"])) bucket = aws_s3_bucket.remotion.id key = "${local.site_prefix}/${each.value}" source = "${local.site_bundle_dir}/${each.value}" source_hash = filemd5("${local.site_bundle_dir}/${each.value}") content_type = try( local.site_content_types[lower(regex("[^.]+$", each.value))], "application/octet-stream" ) depends_on = [aws_s3_bucket_policy.site, aws_s3_bucket_acl.remotion] } resource "aws_s3_object" "site_index" { bucket = aws_s3_bucket.remotion.id key = "${local.site_prefix}/index.html" source = "${local.site_bundle_dir}/index.html" source_hash = filemd5("${local.site_bundle_dir}/index.html") content_type = "text/html" depends_on = [ aws_s3_object.site_asset, aws_s3_bucket_policy.site, aws_s3_bucket_acl.remotion, ] }

Deployment outputs​

If your Terraform files are the source of truth for region, function name, bucket name and site name, export them as JSON. We will use it in step 4 again.

infra/outputs.tf
output "remotion" { value = { region = var.region functionName = aws_lambda_function.remotion.function_name bucketName = aws_s3_bucket.remotion.id serveUrl = "https://${aws_s3_bucket.remotion.bucket_regional_domain_name}/${aws_s3_object.site_index.key}" } }

3. Deploy​

Deploy the infrastructure and site
cd infra terraform init terraform plan -out=tfplan terraform apply tfplan terraform output -json remotion > ../remotion-deployment.json

If a function or log group with the same name already exists, import it into Terraform before applying.
Terraform creates a new bucket and execution role for this configuration.

To update your composition, rebuild ./sites/remotion-project and run the plan and apply steps again. Let Terraform exclusively manage this site's S3 objects.

4. Render​

From your Remotion project, read the exported settings and call renderMediaOnLambdaAndStream() to stream progress and wait for the render to finish:

render.ts
import {readFileSync} from 'node:fs'; import { renderMediaOnLambdaAndStream, type AwsRegion, } from '@remotion/lambda/client'; const contents = readFileSync('./remotion-deployment.json', 'utf-8'); const deployment: { region: AwsRegion; functionName: string; bucketName: string; serveUrl: string; } = JSON.parse(contents); const {region, functionName, bucketName, serveUrl} = deployment; const result = await renderMediaOnLambdaAndStream({ region, functionName, serveUrl, composition: 'HelloWorld', codec: 'h264', forceBucketName: bucketName, onProgress: ({overallProgress}) => { console.log(`${Math.round(overallProgress * 100)}%`); }, }); console.log(result.outputFile);

Replace the composition ID with your own and configure AWS authentication with the required user permissions for the script.

Upgrading​

Keep the function, site, and Remotion packages in your application on the same version.
Changing remotion_version, memory, disk space, or timeout changes the function name and makes Terraform replace the function.

Don't use create_before_destroy for zero-downtime upgrades, instead see: Zero-downtime upgrades and apply the same principles.

See also​