Run yt-dlp in Docker (Compose, Volumes and Permissions)
Quick answer
A container keeps yt-dlp, ffmpeg and Python off your host and makes updating a rebuild. There is no official image, and a six-line Dockerfile is better than trusting a third party's update schedule for a tool whose main failure mode is being out of date:
FROM python:3.13-alpine
RUN apk add --no-cache ffmpeg ca-certificates \
&& pip install --no-cache-dir -U "yt-dlp[default]"
WORKDIR /downloads
ENTRYPOINT ["yt-dlp"]
docker build -t yt-dlp .
docker run --rm -v "$PWD:/downloads" --user "$(id -u):$(id -g)" yt-dlp "URL"
The two flags that matter
-v "$PWD:/downloads" is what makes files appear on your machine rather than
vanishing with the container. Without it the download succeeds and the result is deleted when
the container exits — a confusing five minutes for everyone the first time.
--user "$(id -u):$(id -g)" is what stops every downloaded file being owned by root.
Skip it and you will be running chown afterwards forever.
Compose, for something scheduled
services:
yt-dlp:
build: .
user: "1000:1000"
volumes:
- ./downloads:/downloads
- ./config:/config:ro
- ./cookies.txt:/config/cookies.txt:ro
environment:
- TZ=Europe/London
command: >
--config-location /config/yt-dlp.conf
--download-archive /downloads/archive.txt
-i
"CHANNEL_URL"
Three things worth noticing in that file. The archive lives in the mounted volume, so it survives the container and a rerun continues rather than repeating. The config is mounted read-only, so a mistake inside the container cannot rewrite it. And the cookies file is mounted as a single file rather than a directory, which keeps it out of anything else's reach.
Cookies
There is no browser inside the container and no desktop keyring, so
--cookies-from-browser cannot work. Export on the host, mount the file:
chmod 600 cookies.txt
docker run --rm \
-v "$PWD:/downloads" \
-v "$PWD/cookies.txt:/cookies.txt:ro" \
--user "$(id -u):$(id -g)" \
yt-dlp --cookies /cookies.txt "URL"
Read-only matters: yt-dlp does not need to write to it, and a live session in a container is a credential you want as little exposed as possible. The cookies guide covers exporting one.
Staleness is the real cost
A container is a frozen filesystem, and freezing yt-dlp is exactly the wrong thing to do to it. An image built two months ago has a two-month-old yt-dlp inside, with all the extractor breakage that implies.
Rebuild without cache, on a schedule:
docker build --no-cache -t yt-dlp .
Or update at container start, which trades a few seconds per run for never thinking about it:
#!/bin/sh
pip install --no-cache-dir -U --pre "yt-dlp[default]" >/dev/null 2>&1 || true
exec yt-dlp "$@"
|| true so a network hiccup during the update does not abort the download that was
the point of the run.
A nightly archive job
0 3 * * * cd /srv/ytdlp && docker compose run --rm yt-dlp >> /var/log/ytdlp.log 2>&1
--rm so containers do not accumulate, and a log file because a job that fails
silently at 3am is one you discover in a month.
The playlist guide covers the archive-file
pattern that makes this safe to rerun.
Alpine or Debian
Alpine is around 120 MB with ffmpeg; Debian slim is closer to 400 MB. Alpine's ffmpeg is occasionally missing an encoder that a niche postprocessing option wants. If you hit ffmpeg exited with code 1 on a container that works fine on the host, switch the base image before debugging further:
FROM python:3.13-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U "yt-dlp[default]"
WORKDIR /downloads
ENTRYPOINT ["yt-dlp"]
If it still fails
-
Files owned by root.
--userwas omitted. Fix the existing ones withsudo chown -R "$(id -u):$(id -g)" downloads. -
Certificate errors on everything.
ca-certificatesmissing from the image. - Downloads vanish. No volume mount, or the working directory inside the container is not the mounted one.
- Rate limited immediately. If this is a VPS, the address range is the reason — HTTP 429. Pace the job.
- Works on the host, not in the container. Rebuild without cache. It is almost always a stale layer holding an old yt-dlp.
Frequently asked
Why are my downloads owned by root?
Is there an official yt-dlp image?
Can the container read my browser cookies?
Do I need to rebuild often?
Related
- Install yt-dlp on Linux Without Your Distro's Stale Package Almost every distro ships a yt-dlp too old to download from YouTube. Why that happens, how to install a build...
- yt-dlp Cookies: --cookies-from-browser vs cookies.txt Cookies are how yt-dlp proves it is you — for private videos, members-only content and bot checks. Both method...
- Download a Playlist or Whole Channel with yt-dlp Playlists are where yt-dlp goes from a command to a workflow: archives so reruns skip what you have, ranges so...