Claude
Skills
Sign in
Back

ffmpeg-cloudflare-containers

Included with Lifetime
$97 forever

Complete Cloudflare Container FFmpeg system. PROACTIVELY activate for: (1) Cloudflare Containers setup, (2) Native FFmpeg at edge, (3) GPU-accelerated containers, (4) Durable Objects integration, (5) R2 storage for video files, (6) Container autoscaling, (7) Streaming large files, (8) Workers + Containers architecture, (9) Live streaming relay at edge, (10) Container vs Workers comparison. Provides: Dockerfile examples, Worker code, container configuration, GPU setup, R2 integration, production patterns. Ensures: Native FFmpeg performance at Cloudflare edge with full GPU support.

Image & Video

What this skill does

## Quick Reference

| Component | Configuration |
|-----------|---------------|
| Container Class | `export class FFmpegContainer extends Container` |
| Dockerfile | `FROM jrottenberg/ffmpeg:7.1-alpine` |
| wrangler.toml | `[[containers]]` with `class_name` and `image` |

| Feature | Workers (ffmpeg.wasm) | Containers (Native) |
|---------|----------------------|---------------------|
| Size Limit | 10MB | Unlimited |
| Performance | 10-100x slower | Native speed |
| GPU Support | None | NVIDIA available |
| Cold Start | Instant | 2-3 seconds |

## When to Use This Skill

Use for **edge video processing at scale**:
- Native FFmpeg performance at Cloudflare edge
- GPU-accelerated transcoding in containers
- Large file processing (>10MB)
- Complex FFmpeg pipelines
- R2 storage integration for video files

---

# FFmpeg in Cloudflare Containers (2025)

## Overview

Cloudflare Containers (launched June 2025) enable running FFmpeg natively in a full Linux container environment at the edge. This overcomes all limitations of Workers-based approaches (ffmpeg.wasm size limits, SharedArrayBuffer restrictions).

## Key Benefits

| Feature | Workers (ffmpeg.wasm) | Containers (Native) |
|---------|----------------------|---------------------|
| Binary Size | Limited to 10MB | Unlimited (disk space) |
| Performance | WebAssembly overhead | Native speed |
| GPU Support | None | NVIDIA GPUs available |
| Filesystem | Virtual/memory only | Full Linux FS |
| Dependencies | Must compile to WASM | Any Linux package |
| Cold Start | Instant | 2-3 seconds |

## Getting Started

### Prerequisites

```bash
# Docker must be running locally
docker info

# Verify wrangler installation
npx wrangler --version
```

### Create Container Project

```bash
# Create from template
npm create cloudflare@latest -- --template=cloudflare/templates/containers-template

# Or manually configure existing project
```

### wrangler.toml Configuration

```toml
name = "ffmpeg-processor"
main = "src/index.ts"
compatibility_date = "2025-12-19"

# Container configuration
[[containers]]
class_name = "FFmpegContainer"
image = "./Dockerfile"
max_instances = 10

# Durable Object binding for container
[[durable_objects.bindings]]
name = "FFMPEG_CONTAINER"
class_name = "FFmpegContainer"

# Required migration for SQLite-backed containers
[[migrations]]
tag = "v1"
new_sqlite_classes = ["FFmpegContainer"]
```

### Dockerfile for FFmpeg

```dockerfile
# Use official FFmpeg image
FROM jrottenberg/ffmpeg:7.1-alpine AS ffmpeg

# Production image
FROM node:22-alpine

# Install FFmpeg from the official image
COPY --from=ffmpeg /usr/local /usr/local

# Install additional dependencies
RUN apk add --no-cache \
    libva \
    libva-utils \
    mesa-va-gallium

# Set up application
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Container must listen on a port
EXPOSE 8080

CMD ["node", "server.js"]
```

### Worker Code

```typescript
// src/index.ts
import { Container, DurableObject } from "cloudflare:workers";

interface Env {
  FFMPEG_CONTAINER: DurableObjectNamespace<FFmpegContainer>;
}

export class FFmpegContainer extends Container {
  // Default port the container listens on
  defaultPort = 8080;

  // Sleep after 5 minutes of inactivity
  sleepAfter = "5m";

  // Environment variables for container
  envVars = {
    NODE_ENV: "production",
    FFMPEG_PATH: "/usr/local/bin/ffmpeg"
  };

  async onStart() {
    console.log("FFmpeg container started");
  }

  async onStop() {
    console.log("FFmpeg container stopped");
  }

  async onError(error: Error) {
    console.error("Container error:", error);
  }
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Route to container for video processing
    if (url.pathname.startsWith("/process")) {
      // Create unique container instance per job
      const id = env.FFMPEG_CONTAINER.idFromName(crypto.randomUUID());
      const container = env.FFMPEG_CONTAINER.get(id);

      // Forward request to container
      return container.fetch(request);
    }

    // Load balance across containers
    if (url.pathname === "/lb") {
      const id = env.FFMPEG_CONTAINER.newUniqueId();
      const container = env.FFMPEG_CONTAINER.get(id);
      return container.fetch(request);
    }

    return new Response("FFmpeg Container Service", { status: 200 });
  }
};
```

### Container Server (Node.js)

```javascript
// server.js - Runs inside the container
import express from 'express';
import { spawn } from 'child_process';
import fs from 'fs/promises';
import path from 'path';

const app = express();
app.use(express.raw({ type: '*/*', limit: '100mb' }));

const TEMP_DIR = '/tmp/ffmpeg-work';

app.post('/transcode', async (req, res) => {
  const jobId = Date.now().toString();
  const workDir = path.join(TEMP_DIR, jobId);

  await fs.mkdir(workDir, { recursive: true });

  try {
    // Write input file
    const inputPath = path.join(workDir, 'input');
    await fs.writeFile(inputPath, req.body);

    // Get output format from query
    const format = req.query.format || 'mp4';
    const outputPath = path.join(workDir, `output.${format}`);

    // Run FFmpeg
    const result = await runFFmpeg([
      '-i', inputPath,
      '-c:v', 'libx264',
      '-preset', 'veryfast',
      '-crf', '23',
      '-c:a', 'aac',
      '-b:a', '128k',
      '-movflags', '+faststart',
      outputPath
    ]);

    if (result.exitCode !== 0) {
      throw new Error(result.stderr);
    }

    // Return processed file
    const output = await fs.readFile(outputPath);
    res.set('Content-Type', `video/${format}`);
    res.send(output);

  } finally {
    // Cleanup
    await fs.rm(workDir, { recursive: true, force: true });
  }
});

app.post('/gif', async (req, res) => {
  const jobId = Date.now().toString();
  const workDir = path.join(TEMP_DIR, jobId);

  await fs.mkdir(workDir, { recursive: true });

  try {
    const inputPath = path.join(workDir, 'input');
    const palettePath = path.join(workDir, 'palette.png');
    const outputPath = path.join(workDir, 'output.gif');

    await fs.writeFile(inputPath, req.body);

    // Two-pass GIF for better quality
    // Pass 1: Generate palette
    await runFFmpeg([
      '-i', inputPath,
      '-vf', 'fps=15,scale=480:-1:flags=lanczos,palettegen',
      '-y', palettePath
    ]);

    // Pass 2: Generate GIF with palette
    await runFFmpeg([
      '-i', inputPath,
      '-i', palettePath,
      '-lavfi', 'fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse',
      '-y', outputPath
    ]);

    const output = await fs.readFile(outputPath);
    res.set('Content-Type', 'image/gif');
    res.send(output);

  } finally {
    await fs.rm(workDir, { recursive: true, force: true });
  }
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy', ffmpeg: process.env.FFMPEG_PATH });
});

function runFFmpeg(args) {
  return new Promise((resolve) => {
    const proc = spawn('ffmpeg', args, { stdio: ['pipe', 'pipe', 'pipe'] });

    let stdout = '';
    let stderr = '';

    proc.stdout.on('data', (data) => { stdout += data; });
    proc.stderr.on('data', (data) => { stderr += data; });

    proc.on('close', (exitCode) => {
      resolve({ exitCode, stdout, stderr });
    });
  });
}

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`FFmpeg server listening on port ${PORT}`);
});
```

## GPU-Accelerated Containers

Cloudflare Containers support NVIDIA GPUs for hardware-accelerated encoding:

### GPU Container Configuration

```toml
[[containers]]
class_name = "FFmpegGPU"
image = "./Dockerfile.gpu"
max_instances = 5
# G

Related in Image & Video