Builds a single .deb that turns a stock vLLM Docker container into a
GPU-accelerated video-decode-capable host.
The build script downloads the NVIDIA DeepStream SDK from NGC, extracts
the minimal subset of .so files needed, bundles a pure-Python
decoder wheel, and produces a single .deb.
After apt install ./deepstream-decode_<DS_VERSION>-1_amd64.deb, the
container has:
- ~14 NVIDIA DeepStream runtime libraries at
/opt/nvidia/deepstream/deepstream-<X.Y>/ - All required system symlinks (
libv4l2.so.0,libv4lplugin alias, GStreamer plugin alias, unversioneddeepstreamsymlink) - The full GStreamer 1.x runtime + plugin sets (pulled via apt
Depends:) - A pure-Python
deepstream_decodepackage installed into the container's active Python interpreter (pip-installed by the postinst) pygobjectandpymediainfoPyPI deps installed alongside it
Inside that container, from deepstream_decode import DecodePool works
out of the box, and vLLM's modified multimodal/video.py can call into
it via VLLM_VIDEO_LOADER_BACKEND=deepstream.
# 1. Clone the recipe
git clone https://github.com/<your-org>/deepstream-decode-deb.git
cd deepstream-decode-deb
# 2. Build the .deb (downloads SDK from NGC; ~5 min)
./build.sh
# 3. Install it inside your vLLM container
sudo apt install --no-install-recommends \
./dist/deepstream-decode_*.deb
# 4. Verify
deepstream-decode-selftest- NVIDIA GPU with NVDEC support (H100, H200, A100, L40, RTX 30xx/40xx, etc.)
- Host NVIDIA driver R570+ (CUDA 13.0+ compatible)
- For Docker use: NVIDIA Container Toolkit
- x86_64 Linux
.
├── build.sh ← entry-point: download + build orchestrator
├── README.md
├── .gitignore ← excludes downloaded tbz2, extract dir, build/
├── python_pkg/ ← pure-Python wheel source
│ ├── pyproject.toml
│ ├── README.md
│ ├── LICENSE
│ ├── MANIFEST.in
│ └── src/deepstream_decode/
│ ├── __init__.py
│ ├── _ds_dec.py ← decoder (pipeline, ctypes, NVDEC capture)
│ ├── _runtime.py ← version-agnostic lib_dir locator
│ ├── _selftest.py ← `deepstream-decode-selftest` CLI
│ └── py.typed
├── deb/ ← Debian package recipe
│ ├── control ← apt Depends + metadata template
│ ├── postinst.in ← postinst template (DS_PREFIX placeholder)
│ └── postrm.in ← postrm template
└── dist/ ← build output (gitignored)
└── deepstream-decode_<DS_VERSION>-1_amd64.deb
On the build host (Ubuntu 22.04+ recommended):
sudo apt install -y dpkg-dev python3 python3-pip python3-venv tar wget binutils
# Optionally: install uv for faster wheel builds
curl -LsSf https://astral.sh/uv/install.sh | shbinutils is required so build.sh can run objdump / readelf to
auto-detect the bundled CUDA major version.
Network access to api.ngc.nvidia.com is required for the initial
tarball download (one-time per DS version).
build.sh downloads the SDK tarball automatically the first time it
runs. The URL it hits:
https://api.ngc.nvidia.com/v2/resources/nvidia/deepstream/versions/${DS_MAJOR_MINOR}/files/deepstream_sdk_v${DS_VERSION}_x86_64.tbz2
For DS 9.0.0 specifically:
https://api.ngc.nvidia.com/v2/resources/nvidia/deepstream/versions/9.0/files/deepstream_sdk_v9.0.0_x86_64.tbz2
If you want to pre-download (behind a proxy, air-gapped build host, or to inspect/cache the tarball before building):
wget --content-disposition \
"https://api.ngc.nvidia.com/v2/resources/nvidia/deepstream/versions/9.0/files/deepstream_sdk_v9.0.0_x86_64.tbz2" \
-O deepstream_sdk_v9.0.0_x86_64.tbz2Drop the resulting .tbz2 in the recipe root (same dir as build.sh)
and run ./build.sh --no-download to skip the network step and use
your local copy.
Browse the SDK and accept the license at:
https://catalog.ngc.nvidia.com/orgs/nvidia/resources/deepstream
By downloading the tarball, you accept NVIDIA's DeepStream SDK License.
This recipe does not redistribute the SDK — it only orchestrates
the download from NGC and packages a minimal subset of .so files into
a local .deb for installation on your own machines.
./build.sh # DS_VERSION=9.0.0 (default)
./build.sh --ds-version=9.1.0 # any other major.minor.patch
DS_VERSION=9.1.0 ./build.sh # same, via env varWhat build.sh does:
- Downloads
deepstream_sdk_v<DS_VERSION>_x86_64.tbz2from NGC if not already present locally (skipped on subsequent runs). - Extracts it into
deepstream_sdk_v<DS_VERSION>_x86_64/. - Probes for
libnvbufsurface.soto locate the DS source root inside the extracted tree (handles flat or rooted layouts). - Reads the NPP SONAME from
libnvbufsurftransform.soto detect the bundled CUDA major version. The detected value is injected into the.debfilename (+cuda<MAJOR>) and the aptDepends:line (so apt auto-pulls a matchinglibnpp-X-Y). - Builds the pure-Python wheel from
python_pkg/(usinguv buildif available, elsepython -m build). - Stages the
.sofiles + GStreamer plugins + libv4l plugin + C headers + the built wheel into aDEBIAN/tree atopt/nvidia/deepstream/deepstream-<DS_MAJOR_MINOR>/. - Substitutes
@DS_PREFIX@and@CUDA_MAJOR@placeholders in thedeb/templates. - Runs
dpkg-deb --buildto producedist/deepstream-decode_<DS_VERSION>+cuda<MAJOR>-1_amd64.deb.
Build flags:
./build.sh --no-download # don't download even if tbz2 missing (fail instead)
./build.sh --no-extract # reuse existing extracted directory
./build.sh --keep-stage # don't delete the staging tree (debugging)In the stock vLLM container (vllm/vllm-openai or your local equivalent):
# One command — pulls GStreamer + glib + libmediainfo + NPP via apt,
# unpacks DS libs, creates symlinks, pip-installs the Python wheel.
#
# --no-install-recommends keeps the install minimal (~250 MB instead
# of ~1.3 GB; the difference is mostly compiler toolchain and X11 stack
# that the package's Recommends pull in but doesn't need at runtime).
sudo apt install --no-install-recommends \
./dist/deepstream-decode_9.0.0+cuda13-1_amd64.deb
# Verify
deepstream-decode-selftest
# Use
python3 -c "from deepstream_decode import DecodePool; print('ok')"Note about paths: apt interprets a
/in the argument aspackage/release. To pass a local.deb, prefix the path with./(or use an absolute path). Without that, you'll seePackage 'dist' is not available.
| Path | Origin |
|---|---|
/opt/nvidia/deepstream/deepstream-<X.Y>/lib/*.so (11 files) |
bundled |
/opt/nvidia/deepstream/deepstream-<X.Y>/lib/gst-plugins/*.so (2 files) |
bundled |
/opt/nvidia/deepstream/deepstream-<X.Y>/lib/libv4l/plugins/libcuvidv4l2_plugin.so |
bundled |
/opt/nvidia/deepstream/deepstream-<X.Y>/sources/includes/* |
bundled (DS C headers) |
/usr/share/deepstream-decode/deepstream_decode-*.whl |
bundled (postinst installs from here) |
<active python's site-packages>/deepstream_decode/ |
created by postinst pip install |
| Link | Target |
|---|---|
/opt/nvidia/deepstream/deepstream |
/opt/nvidia/deepstream/deepstream-<X.Y> |
/usr/lib/x86_64-linux-gnu/libv4l2.so.0.0.99999 |
…/lib/libnvv4l2.so |
/usr/lib/x86_64-linux-gnu/libv4l2.so.0 |
…/libv4l2.so.0.0.99999 |
/usr/lib/x86_64-linux-gnu/libv4l/plugins/libcuvidv4l2_plugin.so |
…/lib/libv4l/plugins/libcuvidv4l2_plugin.so |
/usr/lib/x86_64-linux-gnu/gstreamer-1.0/deepstream |
…/lib/gst-plugins |
/etc/ld.so.conf.d/deepstream.confregisters the lib dir with the dynamic linker.ldconfigis run.
| Package | Reason |
|---|---|
gstreamer1.0-tools + 5 plugin sets + gstreamer1.0-rtsp |
GStreamer runtime |
libglib2.0-0t64 / libglib2.0-0 |
GLib C runtime |
libgirepository-1.0-1 |
GObject introspection (required by PyGObject) |
libcairo2 |
required by PyGObject's pycairo binding |
libmediainfo0v5 |
the C library pymediainfo wraps |
libnpp-<CUDA_MAJOR>-X (alternatives: cuda-libraries-<CUDA_MAJOR>-X) |
NVIDIA NPP — required by DS plugins |
python3 (>= 3.10), python3-pip |
Python toolchain |
| Package | Reason |
|---|---|
pygobject |
Python bindings for GStreamer/GObject |
pymediainfo |
Video metadata probe |
torch is intentionally not declared as a hard pip dep — the vLLM
container already provides it.
The postinst runs python3 -m pip install ... (or uv pip install --system ... if uv is on PATH). This uses whichever Python is first on
PATH at install time — in a stock vLLM container that's the container's
active Python (typically the venv that vLLM itself lives in). The wheel
lands in the same site-packages/ as vllm/, so from deepstream_decode import DecodePool resolves through standard sys.path lookup with no
PYTHONPATH manipulation.
build.shtakesDS_VERSION=X.Y.Z, derivesDS_MAJOR_MINOR=X.Y, and uses both in the URL, filenames, install paths, and.debversion.- CUDA major is auto-detected from the bundled
libnvbufsurftransform.soby reading itslibnppig.so.<N>DT_NEEDEDentry. Injected into the.debfilename (+cuda<N>) and into the aptDepends:(alternatives listlibnpp-<N>-0 | libnpp-<N>-1 | libnpp-<N>-2 | cuda-libraries-<N>-0 | cuda-libraries-<N>-1). deb/postinst.inis a template with an@DS_PREFIX@placeholder that build.sh substitutes to/opt/nvidia/deepstream/deepstream-<X.Y>at .deb build time._runtime.pyin the wheel probes for the unversioned/opt/nvidia/deepstream/deepstream/libsymlink first (created by every version's postinst), then falls back to globbing/opt/nvidia/deepstream/deepstream-*/libfor highest version. So the same wheel works against any DS major.minor the .deb installs.
# Change DS version:
./build.sh --ds-version=9.1.0
# Source code change to _ds_dec.py / _runtime.py / _selftest.py:
./build.sh --no-extract # skip slow tar extract (reuse extracted dir)
# Inspect the produced .deb before installing:
dpkg-deb -c dist/deepstream-decode_<version>-1_amd64.deb
# Test in a throwaway container:
docker run --rm -it --gpus all -v "$PWD/dist:/dist" vllm/vllm-openai bash
# inside the container:
apt update && apt install --no-install-recommends /dist/deepstream-decode_*.deb
deepstream-decode-selftestsudo apt remove deepstream-decode # removes libs, symlinks, ldconfig entry
# + uninstalls the Python wheel
sudo apt autoremove # remove transitively-pulled apt deps
# (GStreamer plugin sets, glib, etc.)apt is interpreting dist/... as a package/release argument, not a
local file path. Prefix with ./:
sudo apt install ./dist/deepstream-decode_*.debThe vLLM container is based on a stripped CUDA image (nvidia/cuda:*-base-*)
that lacks NPP. The .deb declares NPP as a dependency, but apt install
may have missed it if --no-install-recommends was used without
apt-get update first. Force it:
sudo apt-get update
sudo apt install -y --no-install-recommends libnpp-13-1 # or libnpp-13-0
sudo ldconfig
deepstream-decode-selftestThe vLLM container's Python has PEP 668 enabled (Ubuntu 24.04+). The
postinst is designed to handle this via --break-system-packages. If you
see this error, the postinst was interrupted; finish it manually:
sudo python3 -m pip install --break-system-packages --no-deps \
/usr/share/deepstream-decode/deepstream_decode-*.whl
sudo python3 -m pip install --break-system-packages pygobject pymediainfo
sudo dpkg --configure deepstream-decodeEither the tarball name doesn't exist for the requested DS_VERSION on
NGC, or NGC has rate-limited you. The script prints the exact URL it
tries. Hit it directly with wget to see what NGC returns. If NGC ever
requires authentication for DeepStream downloads, pre-download the
tarball manually (see "Manual download" above) and re-run with
./build.sh --no-download.
The wheel was pip-installed into a different Python than your shell is
using. Check which python3 matches the interpreter that ran the
postinst's pip install. The simplest fix is to rerun the postinst:
sudo dpkg --configure deepstream-decodeThis is a vLLM / CUDA compatibility issue, not a deepstream-decode one.
Your vLLM image was likely compiled for a different CUDA major than the
one the .deb was built against. Confirm both use the same CUDA major:
# Inside the container
ldd /usr/local/lib/python3.12/dist-packages/vllm/_C.abi3.so | grep cudart
# vs
ldd /opt/nvidia/deepstream/deepstream/lib/libnvbufsurface.so | grep cudartBoth should point at the same libcudart.so.<MAJOR>.
- Recipe + Python decoder code: Apache-2.0 (see
python_pkg/LICENSE). - Bundled DeepStream
.sofiles (assembled into the.debat build time): NVIDIA proprietary, distribution restricted by NVIDIA's DeepStream SDK License. The.debproduced by this recipe should be distributed only within the scope NVIDIA's license permits. This repo does not redistribute any NVIDIA-proprietary files.