>FFmpegLab Sign In
FFmpegLab Guide

GitOps for Video – Version & Trigger FFmpegLab Pipelines with GitHub

Store every pipeline as code, review edits in pull requests, and let GitHub Actions render videos automatically on every merge.

Ask any video team where their edit lives and you'll get a shrug: a project file on someone's laptop, an export buried in a shared drive, a timeline nobody can diff. Application teams solved this years ago with Git — every change reviewed, versioned, and reproducible. With FFmpegLab, video pipelines get the same treatment.

This guide shows you how to run your entire media operation as GitOps: YAML pipelines committed to a repo, filter graph changes reviewed in pull requests, and GitHub Actions triggering renders on merge.

Key takeaways

The Gap: Video Edits Live Nowhere

Traditional NLE workflows have no source of truth. The "current version" of a video is whatever is open in someone's editor. Changes aren't reviewable, history doesn't exist, and reverting means hoping someone kept a backup copy of the project file.

What if instead:

That's exactly what FFmpegLab's YAML + SQL interfaces enable — and GitHub is the natural home for them.

Architecture Overview

Edit pipeline.yml git push / Pull Request CI Validation Merge to main
GitHub Actions FFmpegLab API Render Runners Zero-Egress Storage

The flow has four moving parts:

Repository Layout

A minimal GitOps repo looks like this:

ffmpeglab-pipelines/
├── .github/workflows/
│   ├── validate.yml  # CI check on PRs
│   └── render.yml    # render trigger on merge
├── pipelines/
│   ├── trailer.yml
│   ├── intro.yml
│   └── podcast-audio.yml
└── README.md

Note what's not here: no media files. A typical pipeline YAML is under 2 KB, so the repo stays fast to clone and trivially reviewable. Media is referenced by URL (https://cdn.example.com/footage.mp4) or by a storage path that the runner resolves at render time.

The Pipeline YAML

Here's a simple example — a trailer with a Ken Burns intro, a crossfade transition, and a music bed. This is the file your team will edit, diff, and review:

pipelines/trailer.yml
project:
  id: "trailer"
  title: "Product Trailer"
  editor:
    code: "-i MEDIA1−iMEDIA_1 -iMEDIA1​−iMEDIA_2 -filter_complex \"[0:v]zoompan=z='min(zoom+0.0015,1.5)':d=125[v0];[v0][1:v]xfade=transition=fade:duration=1:offset=4[v]\" -map \"[v]\" -movflags +faststart -y $OUTPUT_PATH"
    selectedCode: "custom"

layers:
  - id: "layer1"
    media:
      - id: "media1"
        url: "https://cdn.example.com/footage/intro.mp4"
        filename: "intro.mp4"
        encoding: {}
      - id: "media2"
        url: "https://cdn.example.com/footage/demo.mp4"
        filename: "demo.mp4"
        encoding: {}

output:
  path: "storage://renders/trailer.mp4"

Now imagine a teammate wants a slower zoom. Their pull request diff is literally:

--- a/pipelines/trailer.yml
+++ b/pipelines/trailer.yml
@@ -4,7 +4,7 @@
-  code: "...zoompan=z='min(zoom+0.0015,1.5)':d=125..."
+  code: "...zoompan=z='min(zoom+0.0010,1.5)':d=180..."

One line. Reviewable, commentable, revertible. That's the whole point.

The GitHub Actions Workflow

Create .github/workflows/render.yml. The critical detail is the paths filter — renders only trigger when pipeline files actually change, not on every commit:

.github/workflows/render.yml
name: Render on merge
on:
  push:
    branches: [main]
    paths: ['pipelines/**']   # only re-render when YAML changes

jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Detect changed pipelines
        id: changed
        run: |
          FILES=(gitdiff−−name−only(git diff --name-only(gitdiff−−name−only{{ github.event.before }} ${{ github.sha }} -- 'pipelines/*.yml' | tr '\n' ' ')
          echo "files=FILES">>"FILES" >> "FILES">>"GITHUB_OUTPUT"

      - name: Trigger FFmpegLab renders
        run: |
          for f in ${{ steps.changed.outputs.files }}; do
            echo "Rendering $f ..."
            curl -sS -X POST https://api.ffmpeglab.com/renders \
              -H "Authorization: Bearer ${{ secrets.FFMPEGLAB_API_KEY }}" \
              -H "Content-Type: application/json" \
              -d @"$f" | jq -r '.id'
          done

      - name: Wait for completion
        run: |
          # Poll the render status until it finishes (or times out)
          RENDER_ID=$(curl -sS https://api.ffmpeglab.com/renders/latest \
            -H "Authorization: Bearer ${{ secrets.FFMPEGLAB_API_KEY }}" | jq -r '.id')
          for i in $(seq 1 60); do
            STATUS=(curl−sShttps://api.ffmpeglab.com/renders/(curl -sS https://api.ffmpeglab.com/renders/(curl−sShttps://api.ffmpeglab.com/renders/RENDER_ID \
              -H "Authorization: Bearer ${{ secrets.FFMPEGLAB_API_KEY }}" | jq -r '.status')
            [ "$STATUS" = "done" ] && exit 0
            [ "$STATUS" = "error" ] && exit 1
            sleep 10
          done
          echo "Render timed out"; exit 1

With this in place, the loop is closed: edit YAML → push → merge → video appears in storage. No editor opened, no export button pressed.

Reviewing Renders in Pull Requests

Because pipelines are plain YAML, GitHub's diff view becomes your media review tool:

For visual confirmation, add a preview bot: a CI job that renders a low-res proxy of the changed pipeline and posts the result URL as a PR comment. Reviewers watch the draft before approving the real render.

Validating Pipelines in CI

Add a second workflow that runs on every pull request. It uses the FFmpegLab transpiler to prove each changed YAML parses and produces valid SQL/migration output — catching typos and malformed filter graphs before merge:

.github/workflows/validate.yml
name: Validate pipelines
on:
  pull_request:
    paths: ['pipelines/**']

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: denoland/setup-deno@v2
        with:
          deno-version: v1.x

      - name: Transpile all pipelines
        run: |
          curl -O https://raw.githubusercontent.com/ffmpeglab/server/main/sdk/yaml/transpiler.ts
          mkdir -p /tmp/out
          for f in pipelines/*.yml; do
            deno run --allow-read --allow-write transpiler.ts "$f" /tmp/out || exit 1
          done
          echo "✅ All pipelines are valid"

If any YAML is broken, this job fails red, the PR can't merge, and no runner time is wasted on a doomed render.

Rolling Back a Bad Render

This is where GitOps quietly beats every traditional workflow. If a merge produces a bad video:

# Revert the bad commit
git revert <bad-commit-sha>
git push origin main

The push matches the paths filter, the workflow fires, and the previous pipeline definition is re-rendered. The output returns to its prior state — deterministically, because the input was identical. No backups to hunt for, no "which version was final?" archaeology.

Secrets & Security

Step 1
Store the API key as a repository secret
In your repo: Settings → Secrets and variables → Actions → New repository secret. Name it FFMPEGLAB_API_KEY.
Step 2
Scope the key to rendering only
Issue a dedicated API key on your FFmpegLab server limited to the renders endpoints. Never reuse an admin key in CI.
Step 3
Protect main with required checks
Enable branch protection requiring both the validate check and at least one approval. This makes "no unreviewed pipeline reaches production" an enforced rule, not a convention.

Frequently Asked Questions (FAQ)

How does GitHub trigger a video render?

A GitHub Actions workflow watches the pipelines/ directory. When a push to main modifies any YAML pipeline file, the workflow calls the FFmpegLab API with the updated pipeline definition, which queues the render on your runners.

Can I review video edits in a pull request?

Yes. Because every project is stored as YAML and SQL, filter graph changes appear as readable diffs. Teammates can comment, suggest edits, and approve before anything renders.

How do I roll back a bad render?

Use git revert on the offending commit and push. The workflow re-triggers with the previous pipeline definition, regenerating the output exactly as it was.

Do I need to store media files in the repository?

No. Pipelines reference media by URL or storage path. Only the small YAML definitions live in Git — keeping the repo lightweight while outputs go to zero-egress storage.

Can I validate pipelines before merging?

Yes. Add a CI job that runs the FFmpegLab transpiler against changed YAML files. If the pipeline is invalid, the check fails and the pull request cannot merge.

Final Word

You now have a complete GitOps loop for video:

Your media pipeline stops being "whatever is on the editing machine" and becomes what it should have been all along: versioned infrastructure.