Skip to content

Containerizing VidGear with Docker

Vidgear Logo
Docker Logo

This guide addresses the unique challenges of containerizing VidGear applications, particularly around building OpenCV with proper GStreamer and FFmpeg support. Learn the patterns and pitfalls before you start.

Complete Reference Implementation

If you're looking for a complete implementation with:

  • Full Dockerfile: Multi-stage build with OpenCV, FFmpeg, GStreamer
  • Comprehensive Testing: pytest suite with mocks and fixtures
  • CI/CD Pipeline: GitHub Actions for automated builds
  • Multiple Examples: Different streaming scenarios
  • Troubleshooting Guide: Common issues and solutions
  • Performance Tuning: Optimization strategies

The reference implementation is available at: abhiTronix/vidgear-docker-example ➶

 

VidGear - The Dockerization Challenge

The biggest challenge in containerizing VidGear isn't VidGear itself—it's getting OpenCV with proper video backend support. Most pip-installed OpenCV builds lack GStreamer support, which limits VidGear's capabilities.

Why This Matters?

VidGear relies on OpenCV's video I/O backends:

  • FFmpeg backend: Essential for reading/writing most video formats
  • GStreamer backend: Required for network streams, pipelines, and advanced processing
  • Codec support: Proper codec libraries (libx264, libx265, etc.) must be available

Simply doing pip install opencv-python in a container gives you a minimal build that will not work properly with many VidGear features.

 

Three Approaches to Building Docker Containers

Approach 1: Use Pre-built OpenCV with Video Support (Simplest)

Use the pre-built OpenCV packages from VidGear's CI releases that include GStreamer and FFmpeg support:

Complete Reference Implementation

A complete working example demonstrating these patterns is available at: abhiTronix/vidgear-docker-example ➶

Pre-built OpenCV packages are built with full GStreamer and FFmpeg support

The pre-built OpenCV packages are available for various supported Python versions here: VidGear OpenCV CI Releases ➶

This approach simplifies the Dockerfile significantly since you don't need to build OpenCV from source. Moreover, we use the same pre-built OpenCV binaries in Vidgear's CI tests ensuring maximum compatibility.

Please note that these are non-commercial builds and are intended for development and testing purposes only. Always verify compliance with licensing terms for production use.

Pre-built OpenCV packages may not be available or work on all linux distributions or architectures. In such cases, you may need to build OpenCV from source as described in Approach 2.

Quick Start Docker commands for Reference Implementation

Here are some quick commands to build and run the Docker container using the provided Dockerfile and docker-compose setup:

# Build the image
docker build -t my-vidgear-app .

# Run with basic configuration
docker run -v "$(pwd)/output:/app/output" \
  -e VIDEO_URL="https://youtu.be/dQw4w9WgXcQ" \
  my-vidgear-app

# Run with docker-compose
docker-compose up

# Run in background and view logs
docker-compose up -d
docker-compose logs -f

# Stop and clean up
docker-compose down

# Rebuild after changes
docker-compose up --build
Essential Dependencies Breakdown for Pre-built OpenCV Packages

Ensure all these dependencies are installed in the builder stage when building OpenCV from source. Missing any of these can lead to runtime errors:

  1. FFmpeg Core:
    RUN apt-get install -y \
        ffmpeg \              # FFmpeg binary
        libavcodec-dev \        # Codec library
        libavformat-dev \     # Container format library
        libavutil-dev \       # Utility functions
        libswscale-dev \      # Scaling/color conversion
        libswresample-dev     # Audio resampling
    
  2. Video Codecs:
    RUN apt-get install -y \
        libx264-dev \         # H.264 encoder
        libx265-dev \         # H.265/HEVC encoder (if available)
        libxvidcore-dev \     # Xvid encoder
        libvpx-dev \          # VP8/VP9 encoder
        libtheora-dev         # Theora encoder
    
  3. GStreamer (For Advanced Features)
    RUN apt-get install -y \
        libgstreamer1.0-dev \                    # GStreamer core
        libgstreamer-plugins-base1.0-dev \       # Base plugins development
        gstreamer1.0-plugins-base \              # Base plugins runtime
        gstreamer1.0-plugins-good \              # Good plugins
        gstreamer1.0-plugins-bad \               # Bad plugins (useful codecs)
        gstreamer1.0-plugins-ugly \              # Ugly plugins (patent issues)
        gstreamer1.0-libav \                     # FFmpeg wrapper
        gstreamer1.0-tools                       # CLI tools for debugging
    
  4. Image Format Support
    RUN apt-get install -y \
        libjpeg-dev \         # JPEG
        libpng-dev \          # PNG
        libtiff-dev \         # TIFF
        libwebp-dev           # WebP
    
  5. Math Libraries (OpenCV Dependencies)
    RUN apt-get install -y \
        libatlas-base-dev \   # Linear algebra
        libopenblas-dev       # Optimized BLAS
    
# Stage 1: Build OpenCV with full video support 
# Supported Linux Distributions: Ubuntu 22.04 (4)
FROM ubuntu:22.04 AS opencv-installer

ENV DEBIAN_FRONTEND=noninteractive

# Install runtime dependencies first
RUN apt-get update && apt-get install -y \
    python3 python3-pip curl \
    # FFmpeg and codecs
    ffmpeg libavcodec58 libavformat-dev libavutil-dev \
    libswscale-dev libswresample-dev \
    libx264-dev libxvidcore-dev \
    # GStreamer
    libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
    gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
    gstreamer1.0-plugins-ugly gstreamer1.0-libav \
    # Image libraries
    libjpeg-dev libpng-dev libtiff-dev libwebp-dev \
    # Math libraries
    libatlas-base-dev libopenblas-dev \
    && rm -rf /var/lib/apt/lists/*

# Download and install pre-built OpenCV with GStreamer support (1)
RUN PYTHONSUFFIX=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') && \
    echo "Detecting Python $PYTHONSUFFIX" && \
    LATEST_VERSION=$(curl -sL https://api.github.com/repos/abhiTronix/OpenCV-CI-Releases/releases/latest | \
        grep "OpenCV-.*-$PYTHONSUFFIX.*.deb" | \
        grep -Eo "(http|https)://[a-zA-Z0-9./?=_%:-]*" | head -1) && \
    echo "Downloading: $LATEST_VERSION" && \
    curl -LO $LATEST_VERSION && \
    OPENCV_FILENAME=$(basename "$LATEST_VERSION") && \
    python3 -m pip install numpy && \
    dpkg -i "$OPENCV_FILENAME" && \
    # Link OpenCV to Python dist-packages
    ln -sf /usr/local/lib/python$PYTHONSUFFIX/site-packages/*.so /usr/lib/python3/dist-packages/ && \
    ldconfig && \ 
    # Verify installation
    python3 -c "import cv2; print(f'OpenCV {cv2.__version__} installed')"

# Stage 2: Runtime image
# Supported Linux Distributions: Ubuntu 22.04
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# Install ONLY runtime dependencies (no build tools)
RUN apt-get update && apt-get install -y \
    python3 python3-pip \
    ffmpeg \
    libavcodec-dev libavformat-dev libavutil-dev \
    libswscale-dev libswresample-dev \
    gstreamer1.0-tools gstreamer1.0-plugins-good \
    gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
    gstreamer1.0-libav \
    libatlas-base-dev libopenblas-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy OpenCV from builder
COPY --from=opencv-installer /usr/local/lib/ /usr/local/lib/
COPY --from=opencv-installer /usr/local/include/ /usr/local/include/
COPY --from=opencv-installer /usr/lib/python3/dist-packages/ /usr/lib/python3/dist-packages/

# Update library cache
RUN ldconfig

WORKDIR /app

# Install VidGear and dependencies (2)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Verify everything works (3)
RUN python3 -c "import cv2; print('OpenCV:', cv2.__version__, '| GStreamer:', cv2.videoio_registry.getBackendName(cv2.CAP_GSTREAMER))" && \
    python3 -c "import vidgear; print('VidGear:', vidgear.__version__)" && \
    ffmpeg -version | head -n 1

# Security: non-root user (5)
RUN useradd -r -m -u 1000 vidgear && \
    mkdir -p /app/output && \
    chown -R vidgear:vidgear /app

COPY app/ ./app/
RUN chown -R vidgear:vidgear /app

USER vidgear

CMD ["python3", "-m", "app.streamer"]
  1. ⚠️ Proper Python Library Linking: OpenCV must be accessible to Python. The linking step is crucial:

    # Detect Python version dynamically
    RUN PYTHONSUFFIX=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') && \
        # Create symlinks from OpenCV install location to Python's site-packages
        ln -sf /usr/local/lib/python$PYTHONSUFFIX/site-packages/*.so /usr/lib/python3/dist-packages/ && \
        # Update dynamic linker cache
        ldconfig
    
  2. ⚠️ Dependency Installation Order: Install dependencies in the correct order to avoid conflicts with shared libraries:

    # 1. System dependencies first
    RUN apt-get update && apt-get install -y ffmpeg gstreamer1.0-tools ...
    
    # 2. Python build dependencies (if building packages)
    RUN pip install --upgrade pip setuptools wheel
    
    # 3. VidGear and its dependencies
    RUN pip install vidgear[asyncio]
    
    # 4. Optional: yt-dlp for streaming from YouTube/Twitch
    RUN pip install --upgrade "yt-dlp[default]"
    
  3. ⚠️ Verification Steps: Always verify installations work before copying your application:

    RUN python3 -c "import cv2; print(f'OpenCV: {cv2.__version__}')" && \
        python3 -c "import cv2; print(f'GStreamer available: {cv2.videoio_registry.getBackendName(cv2.CAP_GSTREAMER)}')" && \
        python3 -c "import vidgear; print(f'VidGear: {vidgear.__version__}')" && \
        ffmpeg -version | head -n 1
    

    If any of these fail, your container won't work properly with VidGear.

  4. ⚠️ Supported Linux Distributions: The current pre-built OpenCV packages are built against Ubuntu 22.04 (Jammy Jellyfish). If you're using a different distribution or version, you may need to build OpenCV from source as described in Approach 2.

  5. 🗝️ Security: Running as non-root user to minimize security risks.

Why Multi-Stage Build?

  • Builder stage: Includes build tools and downloads OpenCV with full support
  • Runtime stage: Contains only runtime libraries, reducing final image size by ~500MB
  • OpenCV binaries are copied between stages, not rebuilt

Approach 2: Build your Own OpenCV from Source (Most Flexible)

If pre-built OpenCV packages are not available for your distribution or architecture, you can build OpenCV from source with the required video backend support.

Checkout this detailed script that automates building OpenCV with GStreamer and FFmpeg support: create_opencv.sh

You could integrate this script into a multi-stage Dockerfile similar to Approach 1, ensuring all dependencies are installed in the builder stage before running the script.

You could also find useful resources for building OpenCV from source with GStreamer support in our FAQ section: Building OpenCV with GStreamer Support ➶

Approach 3: Minimal Build (Limited Functionality)

If you only need basic video file I/O and don't require GStreamer:

Limitations of Minimal Build

This approach uses pip's opencv-python which lacks GStreamer support. You cannot access:

  • Network streams via GStreamer pipelines
  • Youtube/Twitch live streams
  • Advanced camera controls
  • Some specialized VidGear features
FROM python:3.10-slim

RUN apt-get update && apt-get install -y \
    ffmpeg \
    libsm6 libxext6 libxrender-dev libgomp1 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# ... rest of your app

 

Application Configuration Pattern ( Environment-Based)

Structure your VidGear application to read all configuration from environment variables:

Environment File Template (Basics)

The following is a sample .env.example file from abhiTronix/vidgear-docker-example illustrating common configuration options for a VidGear streaming application:

# ==========================================
# Input Source Configuration
# ==========================================
VIDEO_URL=https://youtu.be/xvFZjo5PgG0
VIDEO_STREAM_QUALITY=best        # Options: best, 1080p, 720p, 480p
AUDIO_STREAM_QUALITY=bestaudio   # Options: bestaudio, 192, 128

# ==========================================
# Output Configuration
# ==========================================
OUTPUT_FILE=/app/output/vidgear_output.mp4
OUTPUT_CODEC=libx264             # Options: libx264, libx265, libvpx
AUDIO_CODEC=aac                  # Options: aac, mp3, libopus

# ==========================================
# Processing Options
# ==========================================
FRAME_LIMIT=0                    # 0 = process entire video
VERBOSE=false                    # Enable debug logging

It depicts how to configure input source, output parameters, and processing options using environment variables. This pattern allows easy customization without modifying the code.

Complete Reference Implementation

A complete working example demonstrating these patterns is available at: abhiTronix/vidgear-docker-example ➶

# app/streamer.py
import os
from pathlib import Path
from yt_dlp import YoutubeDL
from vidgear.gears import CamGear, WriteGear

class VideoStreamer:
  def __init__(self):
    # Input configuration
    self.video_url = os.getenv('VIDEO_URL')
    self.video_quality = os.getenv('VIDEO_STREAM_QUALITY', 'best')
    self.audio_quality = os.getenv('AUDIO_STREAM_QUALITY', 'bestaudio')

    # Output configuration
    self.output_file = Path(os.getenv('OUTPUT_FILE', '/app/output/output.mp4'))
    self.output_video = self.output_file.with_suffix('.tmp.mp4')
    self.output_audio = self.output_file.with_suffix('.tmp.aac')
    self.output_codec = os.getenv('OUTPUT_CODEC', 'libx264')
    self.audio_codec = os.getenv('AUDIO_CODEC', 'aac')

    # Processing options
    self.frame_limit = int(os.getenv('FRAME_LIMIT', '0'))
    self.verbose = os.getenv('VERBOSE', 'false').lower() == 'true'
    self.framerate = 30  # Default framerate

    # Ensure output directory exists
    self.output_file.parent.mkdir(parents=True, exist_ok=True)

  def download_audio(self):
    """Download audio stream using yt-dlp"""
    ydl_opts = {
      "format": self.audio_quality,
      "quiet": not self.verbose,
      "no_warnings": True,
      "outtmpl": str(self.output_audio),
    }
    with YoutubeDL(ydl_opts) as ydl:
      ydl.download([self.video_url])

  def start_stream(self):
    """Initialize CamGear with options"""
    stream_options = {
      "STREAM_RESOLUTION": self.video_quality,
    }

    self.stream = CamGear(
      source=self.video_url,
      stream_mode=True,
      logging=self.verbose,
      **stream_options
    ).start()

    # Get video metadata
    video_metadata = self.stream.ytv_metadata
    self.framerate = video_metadata.get("fps", 30)

  def start_writer(self):
    """Initialize WriteGear with output parameters"""
    output_params = {
      "-input_framerate": self.framerate,
      "-c:v": self.output_codec,
    }

    self.writer = WriteGear(
      output=str(self.output_video),
      compression_mode=True,
      logging=self.verbose,
      **output_params
    )

  def combine_audio_video(self):
    """Combine audio and video into final output"""
    ffmpeg_command = [
      "-y",
      "-i", str(self.output_video),
      "-i", str(self.output_audio),
      "-c:v", "copy",
      "-c:a", "copy",
      "-map", "0:v:0",
      "-map", "1:a:0",
      "-shortest",
      str(self.output_file),
    ]
    self.writer.execute_ffmpeg_cmd(ffmpeg_command)

  def process(self):
    """Main processing loop"""
    self.download_audio()
    self.start_stream()
    self.start_writer()

    frame_count = 0

    try:
      while True:
        frame = self.stream.read()

        if frame is None:
          break

        self.writer.write(frame)

        frame_count += 1
        if self.frame_limit > 0 and frame_count >= self.frame_limit:
          break

    finally:
      self.cleanup()

  def cleanup(self):
    """Clean up resources"""
    if hasattr(self, 'stream'):
      self.stream.stop()
    if hasattr(self, 'writer'):
      self.writer.close()

    # Combine audio and video
    if self.output_audio.exists() and self.output_video.exists():
      self.combine_audio_video()

    # Remove temporary files
    if self.output_audio.exists():
      self.output_audio.unlink()
    if self.output_video.exists():
      self.output_video.unlink()

if __name__ == "__main__":
  streamer = VideoStreamer()
  streamer.process()

 

Common Pitfalls and Solutions

Issue 1: "cv2 module not found" After Building

  • Cause: OpenCV shared libraries not linked to Python's site-packages or missing

  • Solution: Verify linking and ldconfig was run:

RUN PYTHONSUFFIX=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') && \
    ln -sf /usr/local/lib/python$PYTHONSUFFIX/site-packages/*.so /usr/lib/python3/dist-packages/ && \
    ldconfig && \
    python3 -c "import cv2"  # This should not fail

Issue 2: "GStreamer backend not available"

  • Cause: OpenCV built without GStreamer support or GStreamer plugins missing

  • Solution: Use the pre-built OpenCV from VidGear CI releases (Approach 1) or ensure GStreamer dev packages are installed during OpenCV compilation (Approach 2).

Issue 3: "Permission denied" Writing Output Files

  • Cause: Container running as non-root user but mounted volume has incorrect permissions

  • Solution:

# On host
mkdir -p output
chmod 755 output

# In Dockerfile, match the UID
RUN useradd -m -u 1000 vidgear
USER vidgear

Issue 4: "codec not found" Errors

  • Cause: Missing codec libraries in runtime stage

  • Solution: Ensure all codec libraries from builder are available in runtime:

    # Runtime stage must include codec libraries
    RUN apt-get install -y \
        libx264-dev \
        libavcodec-dev \
        ffmpeg
    

Issue 5: Container Size Too Large (>2GB)

  • Cause: Including build tools in final image

  • Solution: Use multi-stage build and only copy necessary binaries:

    # Don't include in runtime:
    # build-essential, cmake, git, *-dev packages (except runtime libs)
    

 

Debugging Your Container

You could exec into the running container to test GStreamer, FFmpeg, and VidGear functionality directly.

Test GStreamer Support

The following commands check if GStreamer is properly installed and if OpenCV can access the GStreamer backend:

# Enter running container
docker exec -it vidgear-streamer bash

# Check GStreamer plugins
gst-inspect-1.0 | grep -i video

# Test GStreamer pipeline
gst-launch-1.0 videotestsrc ! autovideosink

# Check OpenCV GStreamer backend
python3 -c "import cv2; print(cv2.getBuildInformation())" | grep -i gstreamer

Test FFmpeg Codecs

The following commands check if FFmpeg has the necessary encoders installed:

# Check available encoders
ffmpeg -encoders | grep -i h264

# Test encoding
ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 \
  -c:v libx264 -preset medium test.mp4

Test VidGear Functionality

The following command tests if VidGear can read from a YouTube URL:

python3 -c "from vidgear.gears import CamGear; stream = CamGear(source='https://youtu.be/xvFZjo5PgG0', stream_mode=True).start(); frame = stream.read(); print(frame.shape); stream.stop()"