Create a Live Stream with Nginx and FFmpeg using HLS RTMP and Docker Compose
Learn how to create a live stream with Nginx and FFmpeg using HLS, RTMP, and Docker Compose. We will also go over what HLS is, what RTMP is, and how they work.
Table of Contents 📖
- Project Demonstration
- RTMP and HLS
- Environment Variable Setup
- Configure Nginx RTMP Server
- Configure Nginx HTTP Server
- Configure Nginx with nginx.conf
- Creating the HTML Video Element
- Building the FFmpeg Docker Image
- Building the Nginx Docker Image
- Creating the Nginx Service
- Creating the FFmpeg Service
- Running the Program
Project Demonstration
Below is what we will be building. The video on the left is being live streamed from Nginx using FFmpeg. The window on the right contains logging information from Nginx and FFmpeg about the current live stream.
RTMP and HLS
Real Time Messaging Protocol, or RTMP, is a protocol for streaming data over the internet. It is typically used to stream video and audio. For example, we can stream a large video file with RTMP by breaking it up into smaller packets and sending them to the client. The client then reassembles these packets into video. HLS, or HTTP Live Streaming, is an HTTP based version of streaming data. For example, we can use it to stream live video over HTTP. The browser then reconstructs the video with an HTML video element.
INFO: Note that HLS was created by Apple and works natively on the HTML5 video element on IOS and Android. However, currently, we need an external library to get it to run on Desktop (Chrome, Firefox, etc.).
Environment Variable Setup
To begin, lets set some environment variables to define the location of our Nginx HTTP and RTMP servers.
NGINX_HOST=nginx-c
RTMP_PORT=1935
HTTP_PORT=5555
FFMPEG_HOST=ffmpeg-c
RTMP works on port 1935 by default but we specify it anyway. We also include the locations of the Nginx and FFmpeg containers in the Docker network.
INFO: Note that we will run Nginx in one container and FFmpeg in another. It is best practice to separate any process into its own Docker container, or one service per container.
Configure Nginx RTMP Server
Now lets configure Nginx to use RTMP by using the rtmp context.
rtmp {
server {
listen ${RTMP_PORT};
chunk_size 4096;
allow publish all;
application hls {
live on;
record off;
hls on;
hls_path /tmp/hls;
hls_fragment 5;
}
}
}
- listen - Port to listen out for connections.
- chunk_size - Maximum chunk size for stream. Default is 4096. The bigger the value the lower the CPU overhead.
- allow publish - Allow publishing from specified addresses. For development we will allow anyone to publish. In production, we should restrict it to just our FFmpeg.
- application - Creates an RTMP application with the specified name. Here we call it hls.
- live on - Activate live broadcasting.
- record off - Stream can be recorded in an flv file. Here we won't record anything.
- hls on - Turn on HLS.
- hls_path - Directory to place the HLS fragments. The tmp directory in linux stands for temporary and is used to store data that is needed for a short period of time.
- hls_fragment - The HLS fragment length in time. Defaults to 5 seconds.
Configure Nginx HTTP Server
Now we need to configure an Nginx HTTP server to serve up the HLS fragments.
http {
server {
listen ${HTTP_PORT};
server_name ${NGINX_HOST};
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
}
}
}
Now Nginx will serve up the HLS fragments from /hls and an index.html file from /. We also specify the types inside the hls location. The types directive maps file name extensions to MIME types of responses. video/mp2t is a MPEG transport stream while m3u8 are playlist files used by video players to store the location of media files.
Configure Nginx with nginx.conf
Now lets create the main Nginx configuration file, nginx.conf.
load_module /etc/nginx/modules/ngx_rtmp_module.so;
user root;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
include /etc/nginx/conf.d/rtmp.conf;
include /etc/nginx/conf.d/http.conf;
The most important parts of this file are importing the Nginx RTMP module (which we will install later on) and importing the RTMP and HTTP configuration files.
Creating the HTML Video Element
Now lets create the HTML file that the Nginx HTTP server will serve up to consume from the RTMP stream.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Streaming</title>
<link href="https://vjs.zencdn.net/8.10.0/video-js.css" rel="stylesheet" />
</head>
<body>
<video id="player" class="video-js vjs-default-skin" height="360" width="640" controls autoplay muted>
<source src="http://localhost:5555/hls/my-video.m3u8" type="application/x-mpegURL" />
</video>
<script src="https://vjs.zencdn.net/8.10.0/video.min.js"></ script>
<script>
videojs('#player');
</ script>
</body>
</html>
Here we are using Video.js to display the HLS content because, in most browsers, the HTML video element cannot display RTMP streams. Video.js is a web video player that supports modern streaming formats.
Building the FFmpeg Docker Image
Now lets build the FFmpeg Docker image. For this, we will use Ubuntu version 24.04 as the base image.
FROM ubuntu:24.04
Now lets install FFmpeg from APT.
RUN apt update
RUN apt install -y ffmpeg
INFO: -y is for non-interactive mode so we don't have to enter Yes/No when installing packages.
Now we just need to copy over a media file. We'll also set the working directory as media.
WORKDIR /media
COPY <YOUR_VIDEO_FILE> .
Finally we just need to run FFmpeg when the container starts up.
ENTRYPOINT ffmpeg -re -i <YOUR_VIDEO_FILE> -c:v libx264 -c:a aac -f flv rtmp://nginx-c/hls/my-video
- -re - Tells FFmpeg to read the same number of frames per second as the framerate of the input video. In other words, read the input at its native frame rate.
- -i - The media input element.
- -c:v - Specifying the video codec. The recommended encoder for RTMP is h.264.
- -c:a - Specifying the audio codec. Here we specify it as aac which stands for advanced audio coding. It is the successor of the MP3 format and recommended for RTMP streaming.
- -f - Forces the input/output file format. Here we set it to flv, or Flash Video, a widely recognized format for RTMP.
- Send the output to the RTMP server on Nginx. It needs to match our application name which we set as hls. The name provided at the end will be the name of the output file fragments in the tmp directory.
Building the Nginx Docker Image
To use RTMP with Nginx, we need to install the RTMP module. The developers of the official Nginx image made it very easy to install third party modules. We can install modules through pkg-oss, a tool that allows us to create, install, and upgrade third party modules for Nginx. This is all handled for us inside a Dockerfile at the following URL.
https://github.com/nginxinc/docker-nginx/blob/master/modules/Dockerfile
INFO: Note that Docker BuildKit is required to use the following Dockerfile. Docker BuildKit is enabled by default in Docker version 23 but needs to be enabled in previous versions by setting the environment variable DOCKER_BUILDKIT to 1.
We can download the contents of this Dockerfile using cURL.
curl -o Dockerfile https://raw.githubusercontent.com/nginxinc/docker-nginx/master/modules/Dockerfile
The -o option tells cURL to write to a file instead of stdout. We specify the modules we want to install using build arguments. We will do this with Docker Compose.
Creating the Nginx Service
Lets start building the Nginx service with Docker Compose.
version: '3.9'
services:
nginx:
image: nginx-i
container_name: ${NGINX_HOST}
build:
context: nginx
dockerfile: Dockerfile
args:
ENABLED_MODULES: rtmp
env_file: .env
ports:
- ${HTTP_PORT}:${HTTP_PORT}
- ${RTMP_PORT}:${RTMP_PORT}
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/rtmp.conf:/etc/nginx/templates/rtmp.conf.template
- ./nginx/http.conf:/etc/nginx/templates/http.conf.template
- ./nginx/index.html:/usr/share/nginx/html/index.html
- Create the Nginx service, call the image nginx-i, and name the container after the NGINX_HOST environment variable.
- Build the image from the Nginx Dockerfile we downloaded from Github. Specify a build argument to add the RTMP module.
- Add the environment variables to the image.
- Map the HTTP and RTMP ports from the host machine to the container.
- Add the volumes. We need to copy over both the RTMP and HTTP configuration files, the main nginx.conf configuration file, and the index.html file.
INFO: Build arguments are passed to Docker at build time, allowing us to customize the build.
Creating the FFmpeg Service
Now Lets start building the FFmpeg service with Docker Compose.
ffmpeg:
image: ffmpeg-i
container_name: ${FFMPEG_HOST}
build:
context: ./ffmpeg
dockerfile: Dockerfile
env_file: .env
depends_on:
- nginx
- Create the FFmpeg service, call the image ffmpeg-i, and name the container after the FFMPEG_HOST environment variable.
- Build the image using the Dockerfile we created.
- Add the environment variables to the image.
- Make it so the FFmpeg service isn't started until after the Nginx service has started. This is so we don't stream to the RTMP server when it isn't ready.
Running the Program
To run the program, all we need to do is run the command docker compose up at the top level of the directory.
docker compose --env-file .env up
We also supply our environment variable file. This will load the environment variables into our docker-compose.yaml file. Output should be similar to the following.
ffmpeg-c | Output #0, flv, to 'rtmp://nginx-c/hls/my-video':
ffmpeg-c | frame= 0 fps=0.0 q=0.0 size= 0kB time=00:00:00.04 bitrate= 92.4kbits/s speed=0.785x
ffmpeg-c | frame= 0 fps=0.0 q=0.0 size= 0kB time=00:00:00.78 bitrate= 5.0kbits/s speed=1.42x
ffmpeg-c | frame= 15 fps= 14 q=31.0 size= 187kB time=00:00:01.17 bitrate=1305.2kbits/s speed=1.09x
ffmpeg-c | frame= 67 fps= 42 q=31.0 size= 345kB time=00:00:02.04 bitrate=1378.7kbits/s speed=1.29x
ffmpeg-c | frame= 96 fps= 46 q=31.0 size= 394kB time=00:00:02.53 bitrate=1271.4kbits/s speed=1.22x
ffmpeg-c | frame= 127 fps= 49 q=31.0 size= 446kB time=00:00:03.05 bitrate=1198.3kbits/s speed=1.18x
ffmpeg-c | frame= 157 fps= 51 q=31.0 size= 498kB time=00:00:03.54 bitrate=1151.4kbits/s speed=1.15x
ffmpeg-c | frame= 187 fps= 52 q=31.0 size= 551kB time=00:00:04.05 bitrate=1112.7kbits/s speed=1.13x
ffmpeg-c | frame= 218 fps= 53 q=31.0 size= 600kB time=00:00:04.56 bitrate=1075.7kbits/s speed=1.11x
Now all we need to do is visit localhost:5555 and enjoy the live stream.