Docker image for live streaming with nginx and nginx-rtmp-module: RTMP ingest and HLS playback.
Security note: Publish requires a shared secret (PUBLISH_SECRET). HLS playback over HTTP is still unauthenticated—do not expose this on the public internet without TLS and additional access controls.
| Port (container) | Protocol | Purpose |
|---|---|---|
1935 |
RTMP | Publish / play |
8080 |
HTTP | HLS (/hls), health (/healthz), publish auth (/auth) |
| Path | Purpose |
|---|---|
/opt/data/hls |
HLS fragments and playlists (persist via volume) |
/usr/local/nginx/conf/templates/nginx.conf.template |
Config template (envsubst at start) |
Application name: live (not show).
docker run -d \
-p 80:8080 -p 1935:1935 \
-e PUBLISH_SECRET='your-strong-secret' \
--name rtmp-server \
mparvin/nginx-rtmp-server:latestHealth check: http://YOUR_HOST/healthz
export PUBLISH_SECRET='your-strong-secret'
docker compose up -d --buildSee docker-compose.yml. HLS data is stored in the hls-data volume.
The image includes a production template. Select it with:
docker run -d -p 80:8080 -p 1935:1935 \
-e PUBLISH_SECRET='your-strong-secret' \
-e CORS_ORIGIN='https://player.example.com' \
-e NGINX_CONF_TEMPLATE=/usr/local/nginx/conf/templates/nginx.production.conf.template \
--name rtmp-server \
mparvin/nginx-rtmp-server:latestOr mount your own template over /usr/local/nginx/conf/templates/nginx.conf.template.
- Server:
rtmp://YOUR_SERVER_IP-OR-DOMAIN/live - Stream key:
STREAM_NAME?secret=YOUR_PUBLISH_SECRET
ffmpeg -re -i /path/to/your/video.mp4 -c copy -f flv \
"rtmp://YOUR_SERVER_IP-OR-DOMAIN/live/STREAM_NAME?secret=YOUR_PUBLISH_SECRET"- Service: Custom
- Server:
rtmp://YOUR_SERVER_IP-OR-DOMAIN/live - Stream key:
STREAM_NAME?secret=YOUR_PUBLISH_SECRET
RTMP (optional):
rtmp://YOUR_SERVER_IP-OR-DOMAIN/live/STREAM_NAME
HLS:
http://YOUR_SERVER_IP-OR-DOMAIN/hls/STREAM_NAME.m3u8
| Variable | Default | Description |
|---|---|---|
PUBLISH_SECRET |
changeme |
Required query secret for RTMP publish |
CORS_ORIGIN |
* |
Value for Access-Control-Allow-Origin on HLS |
SKIP_TEMPLATE |
0 |
Set to 1 to skip envsubst (use a pre-rendered nginx.conf) |
Mount a template (still processed by envsubst for ${PUBLISH_SECRET} and ${CORS_ORIGIN}):
docker run -d -p 80:8080 -p 1935:1935 \
-e PUBLISH_SECRET='your-strong-secret' \
-v ./nginx_rtmp_hls.conf:/usr/local/nginx/conf/templates/nginx.conf.template:ro \
--name rtmp-server \
mparvin/nginx-rtmp-server:latestOr mount a fully rendered config and skip templating:
docker run -d -p 80:8080 -p 1935:1935 \
-e SKIP_TEMPLATE=1 \
-v ./my-nginx.conf:/usr/local/nginx/conf/nginx.conf:ro \
--name rtmp-server \
mparvin/nginx-rtmp-server:latestgit clone https://github.com/MParvin/Nginx-RTMP-Docker.git
cd Nginx-RTMP-Docker
docker build -t nginx-rtmp-server .
docker run -d -p 80:8080 -p 1935:1935 \
-e PUBLISH_SECRET='your-strong-secret' \
--name rtmp-server nginx-rtmp-serverBase image is pinned to alpine:3.22 by digest in the Dockerfile. Optional build args:
NGINX_VERSION(defaultrelease-1.28.0)RTMP_MODULE_VERSION(defaultv1.2.2)
This image serves plain HTTP on 8080. Terminate TLS on a reverse proxy (Caddy, Traefik, or nginx) and proxy to http://rtmp-server:8080. Keep RTMP (1935) on a private network or VPN when possible.
Example Caddyfile: examples/Caddyfile
Full compose stack (build + Caddy HTTPS):
export PUBLISH_SECRET='your-strong-secret'
docker compose -f examples/docker-compose.https.yml up -d --buildFor production CORS, set CORS_ORIGIN to your player origin (not *).
Live HLS fragments accumulate under /opt/data/hls. nginx-rtmp removes fragments for ended streams according to playlist length, but long-running or crashed publishers can leave files behind. Monitor volume usage and prune stale files under /opt/data/hls as needed.
./test/smoke.sh- This project: MIT
- nginx: BSD-2-Clause
- nginx-rtmp-module: BSD-style license
Telegram: @MMPARVIN