4.8 KiB
NGINX HLS VOD
Docker Compose deployment for local MP4 to HLS VOD packaging using Kaltura
nginx-vod-module. The service maps a media tree read-only into the container
and preserves its hierarchy in HLS URLs.
Scope
- ARM64 Linux Docker host
- Trusted LAN HTTP service on port
8124 - One H.264 video track and one AAC audio track per MP4
- One configured browser origin for CORS
- No authentication, TLS, ABR, external subtitles, or multiple audio/video tracks
An attached-picture video stream such as MP4 cover art is ignored by the media validator.
Configuration
Copy the environment template and replace the example player origin:
cp .env.example .env
Required values:
PLAYER_ORIGIN=http://192.168.1.20:3000
MEDIA_DIR=/srv/media
CATALOG_CACHE_DIR=/srv/nginx-hls/catalog-cache
HLS_PORT=8124
MEDIA_DIR=./data is appropriate only for local development. Production uses
an absolute path on the Docker host. The container user must be able to read
the mounted files and directories. CATALOG_CACHE_DIR stores catalog snapshots
and generated covers. Create it before startup, for example
install -d -m 0700 /srv/nginx-hls/catalog-cache.
CATALOG_REFRESH_INTERVAL_SECONDS defaults to 21600 (6 hours).
The Dockerfile deliberately fixes NGINX 1.30.5 and Kaltura module commit
26f06877b0f2a2336e59cda93a3de18d7b23a3e2. Change either only as a deliberate
upgrade, then rebuild and run the smoke test.
Run
Start the stack after creating the configured cache directory:
set -a
. ./.env
set +a
install -d -m 0700 "$CATALOG_CACHE_DIR"
docker compose --env-file .env up -d --build
docker compose ps
The catalog validates files during its background refresh and omits invalid
MP4s from the published snapshot. Use ./scripts/validate-media.sh "$MEDIA_DIR"
before publishing only when an update must contain no invalid files at all.
For a media file at:
/srv/media/movies/example.mp4
request this HLS master playlist:
http://<vod-host>:8124/hls/movies/example.mp4/master.m3u8
Path segments must be URL encoded by clients. There is intentionally no directory listing or progressive-download route.
Media Catalog
Clients discover validated MP4 files through the same origin:
GET /api/media
The response is a flat array from the latest completed catalog snapshot:
[
{
"path": "movies/example.mp4",
"name": "example",
"playlistUrl": "/hls/movies/example.mp4/master.m3u8",
"coverUrl": "/api/media/covers/<cache-key>.jpg"
}
]
Catalog requests never scan media or run FFmpeg. A background worker scans the
tree at startup and then every six hours, validates one H.264 video track plus
one AAC audio track, and pre-generates covers for every valid file. It publishes
the completed JSON and JPEGs atomically, so clients continue to read the last
successful snapshot while a refresh runs or fails. Without any completed
snapshot, media and cover requests return 503.
Use the coverUrl from the media item to request a pre-generated cover:
GET /api/media/covers/<cache-key>.jpg
GET /api/media/status reports the refresh state, latest successful timestamp,
valid and skipped file counts, and the latest build error. Trigger an immediate
asynchronous rebuild with POST /api/media/rescan; concurrent requests do not
start duplicate builds. The service is intended for a trusted LAN, so this
endpoint has no additional authentication. Browser caching remains disabled.
curl -sS http://<vod-host>:8124/api/media/status
curl -i -X POST http://<vod-host>:8124/api/media/rescan
Run the smoke test after the container becomes healthy. Its default test media
is data/aa/1170193193-1-192.mp4; override it with a relative, URL-encoded
TEST_MEDIA_PATH when needed.
export PLAYER_ORIGIN=http://192.168.1.20:3000
./scripts/smoke-test.sh
Publishing Updates
Use a staging directory on the same filesystem as the media library, validate the staged MP4, and atomically rename it into place. Restart the container only after the rename completes:
./scripts/validate-media.sh /srv/media/.staging
mv -T /srv/media/.staging/example.mp4 /srv/media/movies/example.mp4
./scripts/refresh-vod.sh
The service sends Cache-Control: no-store and the restart clears VOD caches.
This makes a replacement visible promptly, but intentionally interrupts active
playback for a short period. Do not overwrite MP4 files in place.
Operations
- Health endpoint:
GET /healthz - Logs:
docker compose logs -f vod - Stop:
docker compose down - The host firewall should limit TCP
8124to the trusted LAN. This service does not provide TLS or authentication.
The first build downloads the fixed source inputs and base image. The ARM64
target host therefore needs access to Docker registries, nginx.org, and
GitHub during docker compose build.