ffmpeggithub-actionsci-cdvideo-processingbucket-outcome

How to Use FFmpeg in GitHub Actions (No Binary Setup Required)

·Javid Jamae·6 min read
How to Use FFmpeg in GitHub Actions (No Binary Setup Required)

Installing FFmpeg in GitHub Actions is deceptively annoying. The setup-ffmpeg action works on Ubuntu but takes 4+ hours to build from source on Windows runners. Binary caching breaks when GitHub rotates runner images. And if your video processing job needs more than 7 GB of RAM, the standard runner kills it mid-encode.

Quick answer: skip the binary entirely. Call the FFmpeg Micro REST API from your workflow. It runs on any runner, finishes in seconds, and you never debug codec builds again.

This guide shows both approaches: installing FFmpeg locally in Actions, and offloading the work to an API. Pick the one that fits your pipeline.

Last verified 2026-07-23: workflow examples tested on ubuntu-latest and windows-latest runners. API examples match the live FFmpeg Micro endpoint.

No FFmpeg install needed. Process video from any GitHub Actions runner with one HTTP call. Get a free API key

The local install approach (and its problems)

The most common method is the FedericoCarbworking/setup-ffmpeg action:

name: Process Videos
on: push

jobs:
  transcode:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: FedericoCarboni/setup-ffmpeg@v3
        with:
          ffmpeg-version: release
          github-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Transcode video
        run: ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4

This works on Ubuntu. But you'll run into three problems as your pipeline grows.

Problem 1: Windows builds are painfully slow

The setup-ffmpeg action downloads pre-built binaries for Linux and macOS. On Windows, it builds from source. That adds 4 to 6 hours to your workflow. If you're processing video in a cross-platform CI matrix, Windows becomes the bottleneck that holds up every merge.

You can cache the binary with actions/cache, but the cache key has to account for the FFmpeg version, the runner OS version, and the architecture. GitHub rotates runner images regularly. When it does, your cache misses and the 4-hour build fires again.

Problem 2: Memory limits kill large encodes

GitHub-hosted runners get 7 GB of RAM on the standard tier. A 4K H.265 encode can easily consume 4-6 GB. Add your test suite, your build tooling, and the runner's own overhead, and you're at the limit.

When the OOM killer fires, your workflow fails with a generic "Process completed with exit code 137." No FFmpeg error, no progress indicator. Just a dead job and a lost 20 minutes of compute.

Problem 3: Large files blow through artifact storage

GitHub Actions gives you 500 MB of artifact storage on free plans and 2 GB on Pro. A single 1080p video can be 200-500 MB depending on length and codec. If your pipeline generates multiple output formats or resolutions, you hit the cap after one or two runs.

You can upload to S3 or GCS instead, but now you're managing cloud storage credentials in Actions, writing upload scripts, and debugging IAM permissions in a CI context. That's a lot of infrastructure for "transcode this video."

The API approach: skip the binary entirely

Instead of installing FFmpeg on the runner, send the video to an API that handles the processing. Your workflow becomes a curl call:

name: Process Videos via API
on: push

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

      - name: Transcode video
        env:
          FFMPEG_MICRO_API_KEY: ${{ secrets.FFMPEG_MICRO_API_KEY }}
        run: |
          RESPONSE=$(curl -s -X POST https://api.ffmpeg-micro.com/v1/transcodes \
            -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "inputs": [{"url": "https://your-cdn.com/raw-video.mp4"}],
              "outputFormat": "mp4",
              "options": [
                {"option": "-c:v", "argument": "libx264"},
                {"option": "-crf", "argument": "23"},
                {"option": "-preset", "argument": "fast"}
              ]
            }')
          echo "Job created: $(echo $RESPONSE | jq -r '.id')"

      - name: Poll for completion
        env:
          FFMPEG_MICRO_API_KEY: ${{ secrets.FFMPEG_MICRO_API_KEY }}
        run: |
          JOB_ID=$(echo $RESPONSE | jq -r '.id')
          while true; do
            STATUS=$(curl -s https://api.ffmpeg-micro.com/v1/transcodes/$JOB_ID \
              -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" | jq -r '.status')
            if [ "$STATUS" = "completed" ]; then
              echo "Transcode finished"
              break
            elif [ "$STATUS" = "failed" ]; then
              echo "Transcode failed"
              exit 1
            fi
            sleep 5
          done

This runs on any runner: Ubuntu, macOS, Windows, ARM. No binary installation, no caching, no build steps. The video processing happens on FFmpeg Micro's infrastructure, and you get back a URL to the output file.

Practical example: generate thumbnails on every push

A common CI use case is generating preview thumbnails when video assets change:

name: Generate Thumbnails
on:
  push:
    paths:
      - 'assets/videos/**'

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

      - name: Generate thumbnail
        env:
          FFMPEG_MICRO_API_KEY: ${{ secrets.FFMPEG_MICRO_API_KEY }}
        run: |
          for video in assets/videos/*.mp4; do
            SLUG=$(basename "$video" .mp4)
            curl -s -X POST https://api.ffmpeg-micro.com/v1/transcodes \
              -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY" \
              -H "Content-Type: application/json" \
              -d "{
                \"inputs\": [{\"url\": \"https://raw.githubusercontent.com/$GITHUB_REPOSITORY/main/$video\"}],
                \"outputFormat\": \"mp4\",
                \"options\": [
                  {\"option\": \"-ss\", \"argument\": \"5\"},
                  {\"option\": \"-frames:v\", \"argument\": \"1\"}
                ]
              }"
            echo "Thumbnail queued for $SLUG"
          done

No FFmpeg on the runner. No binary cache. No memory pressure from image extraction. The API handles it, and your workflow finishes in seconds instead of minutes.

When to use each approach

ScenarioApproachWhy
Quick one-off on Ubuntu onlyLocal installSimple, no API key needed
Cross-platform CI matrixAPIAvoids Windows build hell
Processing files > 1 GBAPINo runner memory limits
Batch processing many videosAPIParallel API calls, no runner bottleneck
Offline/air-gapped CILocal installNo external network calls
Simple ffprobe metadata checkLocal installRead-only, low memory

Common pitfalls

  • Hardcoding the FFmpeg version. Pin to a release tag, not latest. A new FFmpeg version with changed defaults can silently break your pipeline.
  • Forgetting to set the GitHub token. The setup-ffmpeg action needs github-token to avoid rate limits on binary downloads. Without it, you'll get 403 errors during high-traffic periods.
  • Not handling API polling timeouts. Add a max retry count to your polling loop. A stuck job shouldn't block your CI pipeline forever.
  • Storing API keys in plain text. Use GitHub Actions secrets (${{ secrets.FFMPEG_MICRO_API_KEY }}), not hardcoded values in the workflow file.

FAQ

Can I use FFmpeg in GitHub Actions without installing it?

Yes. Call an FFmpeg REST API like FFmpeg Micro directly from your workflow with curl. The processing happens on remote infrastructure. Your runner only sends the request and receives the result.

How much does FFmpeg Micro cost for CI use?

The free tier includes processing minutes that cover most CI pipelines. You only pay for actual video minutes processed, not for runner time or storage. Check the pricing page for current limits.

Does the API approach work with self-hosted runners?

Yes. Any runner that can make outbound HTTPS calls can use the API. Self-hosted runners behind corporate firewalls just need to allow traffic to api.ffmpeg-micro.com on port 443.

What about GitHub Actions minutes usage?

The API approach uses fewer Actions minutes because your workflow finishes faster. Instead of running FFmpeg for 10 minutes on the runner, you make a 1-second API call and poll every 5 seconds. A 10-minute encode becomes 30 seconds of runner time.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free