FFmpeg is the part that reads your media and sends it to YouTube. It is not the playlist downloader: use yt-dlp to fetch the files, then give FFmpeg a local file or playlist input inside the Docker container.
The reliable pattern is to keep media and yt-dlp's download archive on a known persistent path on the VPS, mount that path into the container when needed, and check storage and permissions before starting a long run. Docker can restart an exited process, but it cannot fix a missing file, an invalid stream key, or a full disk.
Separate downloading from streaming
There are two different jobs in this setup. yt-dlp retrieves videos from a supported source and writes them to disk. FFmpeg reads media inputs, optionally decodes and changes them, and writes an output to YouTube's ingest URL.
Keeping the jobs separate makes failures easier to identify. If yt-dlp stops, check the source, playlist access, filenames, or disk. If FFmpeg stops, inspect its input, output URL, codecs, logs, and connection to YouTube. Calling FFmpeg as though it downloads the playlist combines two unrelated problems and makes recovery harder.
A simple flow looks like this:
playlist source
|
v
yt-dlp downloads files and updates an archive
|
v
persistent VPS path
|
v
Docker bind mount
|
v
FFmpeg reads the mounted media and sends RTMPS to YouTube
For a pre-recorded channel, first decide whether you want one long prepared file, a directory of downloaded files, or a playlist file that FFmpeg can read. A directory is often easier to inspect and update incrementally. It also lets you retain files that have already been downloaded instead of repeating the whole job.
The pre-recorded video guide for YouTube Live from India covers the wider broadcast choices. Here, the focus is the path from a playlist to a mounted directory and then to FFmpeg.
Download the playlist with yt-dlp
yt-dlp should run on the VPS, or in a separate short-lived container, and write to the storage location you have checked. It is not necessary to make the downloader part of the long-running FFmpeg container. A separate command keeps the streaming process concerned only with streaming.
Create a directory for the media and another location for the archive, or keep both beneath one persistent media directory:
mkdir -p /srv/youtube-media/videos
mkdir -p /srv/youtube-media/state
The exact host path is your choice. The important part is that it is deliberate, writable by the account running yt-dlp, and located on storage that will still be present after a container is recreated or a VPS is restarted.
A typical playlist download command might look like this:
yt-dlp \
--download-archive /srv/youtube-media/state/downloaded.txt \
-o '/srv/youtube-media/videos/%(playlist_index)03d-%(title)s.%(ext)s' \
'PLAYLIST_URL'
Replace PLAYLIST_URL with the source address. The output template gives each file a playlist position and title. Titles can contain characters that are inconvenient in shell commands, so do not build later commands by copying filenames into an unquoted shell line. Let FFmpeg or a generated playlist file handle the names instead.
The --download-archive option records items yt-dlp has already processed. On a later run, yt-dlp can consult that file rather than treating every playlist item as new. The archive is operational state, not a substitute for checking that the corresponding media file still exists. If somebody removes a downloaded file while leaving its entry in the archive, a later incremental run may not restore it automatically.
Before using downloaded media publicly, check that you have the necessary rights and permissions for the source and the music or footage. A technical download completing successfully does not establish permission to rebroadcast it on YouTube.
You may prefer to download only first and test the files before involving Docker. That is usually easier than debugging a downloader, a mount, FFmpeg, and YouTube at the same time. Once the directory contains usable media, record the host path and keep it unchanged unless you also update the Docker mount and every related command.
Find the VPS mount point before the large run
A VPS can expose several disks, volumes, or filesystem paths. The path you first notice is not necessarily the path with the capacity or persistence you intended. Confirm where /srv/youtube-media lives before downloading a large playlist.
Useful checks include:
findmnt -T /srv/youtube-media
df -h /srv/youtube-media
df -T /srv/youtube-media
findmnt helps show the filesystem associated with a path. df -h shows the available space for that filesystem, while df -T also shows its filesystem type. You are checking the target path, not merely the root directory, because two paths on the same VPS may be backed by different mounts.
If your provider gave you a separately attached volume, verify that it is mounted where you expect. A missing mount can leave an ordinary directory at the same location. The command may still succeed, but the files will be written to the wrong filesystem. After a reboot, that can result in a different view of the directory or no media at all, depending on how the volume is configured.
Do not assume that a directory is persistent simply because its name is familiar. Confirm the VPS provider's storage arrangement and your own mount configuration. This article does not identify a universal VPS plan or storage size because the requirement depends on the number, duration, resolution, and format of the files you intend to retain.
If you want a single prepared loop rather than many downloaded items, the guidance on how large a loop file should be is useful when estimating what you will actually keep on disk.
Confirm free space and write permissions
A playlist download can fail late if you check storage only after it has begun. Check the destination before the run and again if the source contains large files. df reports available filesystem space, but it does not predict the final size of media whose format or quality varies.
Start with:
df -h /srv/youtube-media
Then test that the intended account can create and remove a small file:
touch /srv/youtube-media/state/write-test
rm /srv/youtube-media/state/write-test
If yt-dlp will run as a different user, perform the test as that user rather than as an administrator. A directory can be writable by your login account but not by the service account or container user. Similarly, a Docker bind mount can expose a path successfully while the process inside the container receives a permission error when it opens a file.
Inspect ownership and permissions when the test fails:
ls -ld /srv/youtube-media /srv/youtube-media/videos /srv/youtube-media/state
Correct the ownership or permissions according to your VPS's account model. Avoid making the whole media tree world-writable merely to get past an error. A read-only media mount for FFmpeg is preferable once downloading is complete, because the streaming process only needs to read the input and archive.
Also check inodes if you are storing many small files:
df -i /srv/youtube-media
A filesystem can have apparent free space while running short of file entries. The useful check depends on the shape of your library, so do not rely on one disk reading alone.
Put downloads and the archive on the mounted path
The output template and archive path should point to the storage you have just verified. If the files go to a temporary container directory, they may disappear when that container is removed. If the archive goes elsewhere, yt-dlp may lose its record of completed items even when the media remains.
For example:
yt-dlp \
--download-archive /srv/youtube-media/state/downloaded.txt \
--no-overwrites \
-o '/srv/youtube-media/videos/%(playlist_index)03d-%(title)s.%(ext)s' \
'PLAYLIST_URL'
The archive and media files now share the same deliberate storage area. --no-overwrites adds another guard against replacing existing files, but it does not replace the archive. Keep both mechanisms where they match your workflow.
After the run, inspect the result rather than trusting the command's exit status alone:
find /srv/youtube-media/videos -maxdepth 1 -type f -print | sort | head
wc -l /srv/youtube-media/state/downloaded.txt
The archive line count is not a count of valid playable files. It is only a quick indication that entries were recorded. Open or probe representative files with the tools you normally use, and check that the file extensions, audio tracks, and video properties fit the FFmpeg command you plan to run.
The media can be streamed directly from the host, but mounting it into a container gives the FFmpeg process a stable path inside its own environment. Keep the host and container paths simple. For example, use /media/videos inside Docker even though the host path is /srv/youtube-media/videos.
Bind-mount the host path into Docker
A Docker bind mount shares a host file or directory with a container. Docker documents that bind mounts are read-write by default, so explicitly use a read-only mount for a container that only consumes prepared media. The Docker bind mount documentation explains the behaviour and the distinction between host and container paths.
A read-only mount using --mount looks like this:
docker run --name youtube-ffmpeg \
--restart unless-stopped \
--mount type=bind,src=/srv/youtube-media,dst=/media,readonly \
your-ffmpeg-image \
ffmpeg -re -i /media/videos/input.mp4 ...
This is a pattern, not a universal final command. The image name, input, codecs, output options, and credentials depend on your media. The important mapping is src=/srv/youtube-media on the VPS to dst=/media inside the container. The FFmpeg command must use /media/videos/input.mp4, not the host path.
If you are using a directory of files rather than one file, create the input list in a location that is also mounted. For example, a list generated on the host could be available as /media/state/playlist.txt in the container. Check the syntax required by your chosen FFmpeg input format and ensure that every referenced path is visible from inside the container.
Docker's --mount form is recommended in the Docker container run reference. It is more explicit than relying on an abbreviated volume expression, which helps when you later inspect or recreate the container.
Do not publish a port simply because the container is running a stream. A process that makes an outbound connection to YouTube normally needs no inbound port. Docker says that a published port without a host IP is exposed on all host interfaces by default. If you add a web control panel or monitoring service, publish only the port and interface that service genuinely needs, then account for both Docker's firewall behaviour and the VPS provider's network firewall.
Configure FFmpeg and YouTube's ingest destination
Obtain the current stream URL and stream key from YouTube Live Control Room. YouTube documents RTMPS ingestion in its RTMPS guide. Treat the stream key as a credential: keep it out of a public Docker image, repository, screenshot, and routine log output.
FFmpeg's general command model is inputs followed by outputs and their options. Its -c copy documentation describes stream copying, where packets are copied without decoding, filtering, or encoding. That can avoid transcoding and preserve the existing streams, but only when the source and YouTube output requirements are compatible.
A stream-copy shape might look like this:
ffmpeg -re -i /media/videos/input.mp4 \
-c copy \
-f flv 'rtmps://YOUR_YOUTUBE_INGEST_URL/YOUR_STREAM_KEY'
Do not copy this command unchanged and assume it suits every file. -c copy cannot resize, add an overlay, alter audio, or convert incompatible codecs. It can also fail where the target container requires information that the source does not provide. If the channel needs a different resolution, an overlay, audio changes, or codec conversion, FFmpeg must decode and encode instead, which changes the CPU requirement and the settings you must validate.
YouTube also documents HLS ingestion for supported cases such as HDR or codecs not supported by RTMP. That does not make HLS the default for an ordinary RTMP-compatible stream. Follow the current instructions in the YouTube HLS guide when your source and delivery requirement specifically calls for it.
If your stream is a lesson, devotional video, music-free ambience loop, or local information channel, test one representative file first. A file that plays on your desktop can still have a codec, frame-rate, audio, or container combination that is unsuitable for the selected output. Validate the result in YouTube Live Control Room rather than using FFmpeg's lack of an immediate error as proof that the broadcast is healthy.
Keep recovery and persistence separate
Keep FFmpeg as the container's foreground process. Docker can then observe whether it exits and apply a restart policy. The policy does not explain why the process stopped and does not repair the cause.
Docker provides no, on-failure with an optional retry limit, always, and unless-stopped restart policies. unless-stopped is useful when an operator's explicit stop should remain in effect across a Docker daemon restart. always is appropriate for a container that should be brought back after it stops, subject to Docker's documented manual-stop behaviour. Choose on-failure when the recovery rule should be tied to a failing exit rather than every exit.
For a long-running stream, a common starting point is:
--restart unless-stopped
Read the current Docker restart policy documentation before choosing a policy for your operating procedure. Docker recommends restart policies rather than using a separate process manager to start containers.
A restart loop may be caused by an invalid key, a missing mount, an unreadable file, unsupported media, a full disk, or a network problem. Inspect the logs and container state:
docker logs --tail 200 youtube-ffmpeg
docker inspect youtube-ffmpeg
If the problem is a bad input, automatic restarts only repeat the same failure. Stop the container deliberately, correct the input or command, then start it again. The media directory and archive must remain outside the disposable container layer, or the container lifecycle can remove the state you meant to preserve.
If keeping a home computer on overnight is the part that causes trouble, StreamNeo removes that particular operational burden by accepting your prepared video and YouTube stream key and running the YouTube broadcast with the computer switched off. It is a YouTube-only route, so it does not replace this VPS pattern when you need your own Docker process or broader infrastructure control.
Rerun incrementally and verify the files
After adding new playlist items, rerun yt-dlp with the same archive and output template. Do not create a new archive in a temporary directory for every run. The point of incremental downloading is that the downloader can recognise what it has already recorded while leaving the media in the same persistent location.
Before the rerun, check the mount and available space again:
findmnt -T /srv/youtube-media
df -h /srv/youtube-media
Then run the downloader and inspect both new files and the archive. If a file was manually removed, do not assume the archive will notice. Compare the archive with the actual directory and decide whether a missing item should be restored through the downloader's appropriate options.
For FFmpeg, verify the container sees the same files the host sees:
docker run --rm \
--mount type=bind,src=/srv/youtube-media,dst=/media,readonly \
your-ffmpeg-image \
ls -lah /media/videos
This check isolates the mount. If the file appears on the host but not in the container, fix the source path, destination path, or container command before investigating YouTube.
Watch the running container for a while after a change. Check docker logs, confirm that YouTube Live Control Room reports the expected input, and look for repeated reconnects or input-read errors. The 10-minute bitrate checklist can help structure a short preflight, while the guide on restarting a 24/7 YouTube stream after disconnecting covers the wider recovery question.
Do not size the VPS from the stream title alone. A stream-copy workload, a re-encoded stream with filters, and several simultaneous outputs have different CPU, memory, storage, and transfer needs. Choose a plan only after identifying the media properties, output settings, expected retention, and provider terms, then observe the actual process and disk behaviour during a representative test.
Before committing, compare the operating options on the pricing page. When the file and channel are ready, start free — 24-hour trial, no card.
FAQ
Is FFmpeg a replacement for yt-dlp?
No. yt-dlp downloads supported online media and can maintain a download archive. FFmpeg processes media and sends an input to an output such as YouTube, so use each tool for the job it performs.
Will Docker preserve my downloaded files?
Only if the files and the yt-dlp archive are stored on persistent host storage or another deliberately persistent location. Files written only inside the container's writable layer can be lost when the container is removed or recreated.
Should I always use -c copy?
No. Stream copying is suitable only when the existing streams meet the output requirements. If you need resizing, overlays, audio changes, or codec conversion, FFmpeg must process and encode the media instead.
Do I need to publish a Docker port for YouTube Live?
Usually not for a container that only makes an outbound connection to YouTube. Avoid unnecessary published ports; if you add a monitoring or control service, expose only its required port and interface and review the relevant firewall settings.