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
- Pipelines as code — every project is a small YAML file in Git, not a binary project file.
- Readable diffs — changing a transition duration shows up as a one-line diff in a PR.
- Render on merge — a GitHub Action calls the FFmpegLab API when pipeline files change.
- Instant rollback —
git revertrestores the previous output exactly. - CI validation — broken filter graphs fail the build before they ever hit a runner.
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:
- The entire edit were a text file you could read, diff, and grep?
- Every change went through a pull request with comments and approvals?
- Merging automatically re-rendered the final video?
- Any previous version could be restored with one command?
That's exactly what FFmpegLab's YAML + SQL interfaces enable — and GitHub is the natural home for them.
Architecture Overview
GitHub Actions → FFmpegLab API → Render Runners → Zero-Egress Storage
The flow has four moving parts:
- Your repository — holds only small YAML pipeline definitions. Media stays out of Git, referenced by URL or storage path.
- GitHub Actions — two jobs: one validates changed pipelines on every PR, one triggers renders on merge to
main. - FFmpegLab server — receives the pipeline via its REST API and queues the render.
- Render runners — execute the FFmpeg commands and write outputs to your storage bucket.
Repository Layout
A minimal GitOps repo looks like this:
├── .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:
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:
+++ 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:
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:
- Filter graph changes show as readable diffs — durations, scales, transitions, overlays.
- Line comments let reviewers point at the exact flag they want changed ("make this crossfade 2s").
- Suggestions can be applied with one click, just like code.
- Branch protection ensures no pipeline reaches
mainwithout approval.
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:
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:
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
FFMPEGLAB_API_KEY.renders endpoints. Never reuse an admin key in CI.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:
- Pipelines as YAML — small, readable, greppable files in Git
- PR-based review — diffs, comments, suggestions, and enforced approvals
- CI validation — broken pipelines fail before merge
- Render-on-merge — GitHub Actions calls the FFmpegLab API automatically
- Deterministic rollback —
git revertrestores any previous output - Full audit history — every render traces back to a commit, an author, and a review
Your media pipeline stops being "whatever is on the editing machine" and becomes what it should have been all along: versioned infrastructure.