Docker部署SRS流媒体服务器:从RTMP推流到WebRTC播放的完整实践
折腾过自建流媒体服务的朋友应该都有体会:明明只是一个“把视频流推上去、让另一端拉下来”的需求,真动起手来却总被各种编译依赖、系统库、配置项折腾得头疼。我去年想在公网分享一场线下活动的实时画面,试过直接在一台 CentOS 上编译 SRS,结果光依赖检查和 make 就花了一个晚上,第二天还发现系统自带的 OpenSSL 版本太老,WebRTC 握手直接失败。后来换成 Docker 部署 SRS,整个过程从“编译半小时”变成“拉镜像加跑容器”,推流、拉流、录像、鉴权全在配置里搞定,这才意识到: 实时音视频流媒体平台 不一定非得从零搭建,用 Docker 把 SRS 跑起来,才是绝大多数场景下的最优解。
这篇东西适合这么几类人:一是刚接触流媒体、想快速拥有一套可用的 RTMP/HTTP-FLV/WebRTC 服务的开发者;二是运维同学,想在公司内网用容器化方式交付一个轻量直播服务;三是做监控、在线教育、活动直播项目,需要一个稳定内核但又不想被编译过程劝退的团队。我会把从环境准备、镜像选择、推拉流验证到生产配置的完整链路写出来,包括我实际踩过的坑和排查思路,照着做基本能复现。
1. 为什么是 SRS:轻量级流媒体服务器的选型思路
1.1 从裸机编译到容器化部署的变化
SRS(Simple Realtime Server)是一个开源的实时流媒体服务器,作者团队一直在做音视频底层,性能、协议支持、稳定性都很能打。它原生支持的协议包括 RTMP、HTTP-FLV、HLS、WebRTC、SRT、GB28181 等,这意味着你既能接传统 OBS 推流,也能接浏览器 WebRTC 推流,还能对接海康大华那类安防摄像头。功能面上它几乎覆盖了中小团队能用到的所有场景。
但前些年大家部署 SRS 的主流方式还是下载源码、手动编译。编译本身不算难,难的是 环境的不确定性 :有的机器缺 pkg-config,有的机器的 gcc 版本太低,有的机器 OpenSSL 库里缺少 WebRTC 需要的 DTLS 支持。我那次失败就是 OpenSSL 版本问题,后来为了不折腾系统库,还得自己编译一份 OpenSSL 再指给 SRS,链路一下就复杂了。
Docker 化部署把这类问题直接消灭在镜像层。SRS 官方 Docker 镜像里包含了编译好的二进制和默认配置,你只需要关心端口映射、挂载配置、数据目录。用容器跑服务的收益不只是省去编译时间,还体现在几件事上:
- 版本切换成本极低,想从 4.0 升到 5.0 只需要换个镜像 tag;
- 多个实例可以共存,端口错开就行,互不污染系统环境;
- 日志、录像、配置文件通过 volume 挂出来,宿主机上直接管理;
- 迁移部署的时候,compose 文件拷走就是一套可复现环境。
1.2 SRS 能交付哪些实际能力
很多人第一次接触 SRS 会问:它和 Nginx-RTMP、MediaMTX、Janus 这些有什么区别?我的理解是,SRS 更像一个“全栈型”流媒体网关。它不仅能做简单的 RTMP 转发,还内置了 WebRTC SFU 能力、HLS 切片、DVR 录像、HTTP 回调鉴权、集群源站边缘模式。你要做的不是用一堆中间件拼一个系统,而是一个 SRS 实例同时承担接入、转发、存储和鉴权。
举一个我实际跑过的场景:活动现场一台电脑开 OBS,使用 RTMP 推流到 SRS。观众这边,PC 用户通过 HTTP-FLV 在网页看直播,手机用户因为网络环境复杂,我直接开了 WebRTC,让浏览器原生播放,延迟能压到一秒钟以内。同时 SRS 开启 DVR 把流录成 FLV 文件,活动结束后还能剪辑回放。整个链路里,FFmpeg 只用来推流,中间所有协议转换和分发都是 SRS 完成的。
这套能力组合放进 Docker 容器,意味着你的宿主机只需要一个 Docker 运行时,不需要装任何流媒体相关的系统库。对运维来说,交付物从“一台需要维护的服务器”变成了“一个可编排的容器”,这对后续扩容和迁移都有实际价值。
2. 环境准备与镜像选择:避免从第一步就踩坑
2.1 Docker 环境检查与端口规划
开始之前先确认宿主机 Docker 已经装好。我建议用
docker version
看一下客户端和服务端版本,不要只看客户端的输出,如果服务端没起来,后面所有操作都会报连不上 socket。Linux 下直接
systemctl status docker
确认守护进程状态,Windows/macOS 用 Docker Desktop 的同学注意,Docker Desktop 的资源设置里要给足内存,SRS 本身不算吃资源,但 WebRTC 转发并发上来后,起码留 2GB 以上内存给它,否则容器会因为 OOM 被莫名其妙杀掉。
端口规划是这个项目里最容易被忽略的部分。SRS 默认使用以下端口:
| 端口 | 用途 |
|---|---|
| 1935 | RTMP 推流/拉流 |
| 1985 | HTTP API / 后台管理 |
| 8080 | HTTP-FLV / HLS / WebRTC over HTTP |
| 8000 | WebRTC over UDP(媒体传输) |
如果只映射 1935 和 8080,你会发现 RTMP 推流正常、HTTP-FLV 播放正常,但 WebRTC 一直黑屏或者处于 connecting 状态。原因就是 UDP 8000 没有被映射出去。这是新手最容易踩的坑,后面我会单独用一节讲排查过程。
2.2 选择 SRS 镜像 tag 的正确姿势
Docker Hub 上 SRS 官方镜像名是
ossrs/srs
,但是 tag 选择有一点讲究。我见过有人在生产环境拉
latest
,结果某次重新 pull 之后版本大跳,配置格式不兼容,服务直接起不来。SRS 的问题在于不同大版本之间配置语法有差异,尤其是 WebRTC 相关的配置项,4.0 和 5.0 的写法不完全相同。
我的建议是:
不要用 latest,指定一个明确的大版本 tag
。如果你追求稳定,用
ossrs/srs:4
或者更精确的
ossrs/srs:v4.0.268
;如果你想试新版特性可以用
ossrs/srs:5
,但需要接受配置差异带来的迁移成本。我目前在用的组合是
ossrs/srs:4
配合自建的 conf 文件,因为 SRS 4 的资料最全,网上遇到问题也容易搜到解决方案。
拉镜像之前还有一件事值得做:配置容器镜像源。国内网络环境拉 Docker Hub 镜像经常很慢,甚至超时。做法是编辑
/etc/docker/daemon.json
,加一段 registry-mirrors:
{
"registry-mirrors": ["https://docker.m.daocloud.io"]
}
改完重启 Docker 再拉镜像。这一招能省下大量无谓等待。不过需要说明的是,镜像源服务的可用性随时可能变化,如果你有自建的 Harbor 或 Nexus 仓库,也可以把这台机器上的镜像转存到内网,生产环境我更推荐这种方式,毕竟公网镜像源只能解决“拉得动”的问题,不能解决“随时拉得到”的问题。
3. 十分钟拉起第一个流媒体服务:推流与拉流全流程
3.1 用 docker run 启动 SRS 容器
镜像拉好之后,第一阶段先用官方默认配置把服务跑起来,验证容器和端口是否正常工作:
docker run -d --name srs-test \
-p 1935:1935 \
-p 1985:1985 \
-p 8080:8080 \
-p 8000:8000/udp \
ossrs/srs:4
这里有个细节:8000 端口必须显式声明为 UDP,
-p 8000:8000
默认映射的是 TCP,而 WebRTC 媒体协商走的是 UDP。我最初的命令漏掉了
/udp
,结果所有 TCP 服务都正常,唯独 WebRTC 完全不通,这个坑相当隐蔽。
启动之后用
docker logs -f srs-test
看日志。正常情况下会看到 SRS 的 ASCII art logo 和一句启动成功的提示,类似
start server successfully
。看到这行,说明容器内部服务已经正常监听,接下来就可以验证外部访问了。
浏览器打开
http://你的服务器IP:8080/
,应该能看到 SRS 自带的演示页面。这个页面非常有用,它内置了播放器和一个简易的推流 Demo,可以直接在浏览器里用 WebRTC 推流测试,不用先装 FFmpeg。如果你只是想快速验证“服务能不能用”,到这里其实已经完成了 50%。
3.2 用 FFmpeg 推流验证
浏览器 Demo 能跑通,但真实场景下更多是用 OBS 或者 FFmpeg 推流。我习惯用 FFmpeg 做测试,因为它可以生成合成视频流,不需要准备真实摄像头和海量视频文件。
先生成一个测试视频源,然后用 RTMP 推出去:
ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=30 \
-f lavfi -i sine=frequency=1000:sample_rate=44100 \
-vcodec libx264 -preset veryfast -tune zerolatency \
-acodec aac -shortest \
-f flv rtmp://127.0.0.1:1935/live/test
这条命令的意图是:用 FFmpeg 内置的 testsrc 颜色测试图和 1kHz 正弦波生成一路视频流,编码成 H.264 + AAC,封装成 FLV 推给 SRS。其中的
-re
参数很关键,它让 FFmpeg 按照真实帧率读取输入,模拟直播推流,不加的话 FFmpeg 会全速推,SRS 接收没问题但无法模拟真实直播节奏。
推流成功之后,SRS 日志里会出现
rtmp: get stream
或者类似的关键词,表示收到流了。你可以再用另一个 FFmpeg 进程拉流验证:
ffmpeg -i rtmp://127.0.0.1:1935/live/test -c copy -f flv /dev/null
只要这个命令不报错,说明远端能正常拉取 RTMP 流。到这里,一个最基本的“推-拉”闭环就通了。
3.3 三种拉流方式验证
RTMP 只是其中一种拉流方式,实际项目中更常见的是 HTTP-FLV、HLS 和 WebRTC。这三种我都建议在同一路流上测试一遍,因为 SRS 会把推上来的 RTMP 流转成多种协议输出,但每一种协议对端到端链路的依赖不同。
HTTP-FLV 可以用浏览器 VLC 或者下面这个 FFmpeg 命令:
ffmpeg -i http://127.0.0.1:8080/live/test.flv -c copy -f flv /dev/null
HLS 需要 SRS 在配置中开启,默认配置可能没有,所以先用 RTMP 验证完再改。WebRTC 则可以直接打开 SRS 演示页面的播放器,地址填
webrtc://你的服务器IP/live/test
。如果一切正常,几秒内画面就会出来。
同时验证三种协议的好处是:能快速定位部署环境里哪些网络层面有问题。比如 TCP 正常、UDP 不通,RTMP 能看但 WebRTC 连不上;再比如 8080 端口被防火墙拦了,HTTP-FLV 和 HLS 都会失败,只有 RTMP 能用。这种分层排查能力在实际维护中非常有用。
4. 配置文件才是核心:常见生产场景的配置写法
4.1 用自定义 conf 代替默认配置
默认配置能跑通 Demo,但真要用于生产,还是要根据场景写 conf。SRS 的配置哲学非常简单:一个 conf 文件定义一套完整的行为,从监听端口到转发规则全部覆盖。Docker 部署下,建议把 conf 文件放到宿主机目录,然后通过 volume 挂载进容器。
我习惯在宿主机建一个
srs
目录,里面放
srs.conf
。然后这样启动容器:
docker run -d --name srs-prod \
-p 1935:1935 -p 1985:1985 -p 8080:8080 -p 8000:8000/udp \
-v /data/srs/srs.conf:/usr/local/srs/conf/srs.conf \
-v /data/srs/objs:/usr/local/srs/objs \
ossrs/srs:4 \
./objs/srs -c conf/srs.conf
注意最后一个命令覆盖了容器的默认启动命令,让 SRS 显式使用我们挂载进去的配置。
objs
目录是 SRS 用来存放日志、DVR 录像和 HLS 切片的地方,挂载出来方便查看产物。
4.2 HTTP-FLV 与 WebRTC 并存配置
下面这份配置是我目前线上在用的精简版,同时开启了 HTTP-FLV 和 WebRTC:
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000;
# 重点:UDP 端口范围,按需开放
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
rtc {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv;
dvr_plan session;
}
}
这里
http_remux
就是 HTTP-FLV 的能力开关,
mount
指定了访问路径格式。
dvr
是录像模块,
dvr_plan session
表示每录制一路流存一个文件,文件名带时间戳,适合活动录像。
rtc_server
里的
candidate
变量是给公网部署用的,后面讲 HTTPS 和公网访问时会提到。
4.3 HLS 与延时录像场景
如果你的场景是在线教育回放或者监控录像回看,HLS 比 HTTP-FLV 更合适,因为 HLS 基于切片文件,可以天然支持进度拖动、倍速播放,CMAF 切片还能被 CDN 缓存。
开启 HLS 只需要在 vhost 里加一段:
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 2;
hls_window 30;
}
hls_fragment
是每个切片的时长,单位秒,我建议设成 2 秒,兼顾延迟和文件数量;
hls_window
是时间窗口,超过这个时间的切片会被清理,只保留最近 30 秒的切片,适合直播场景,如果你想做永久回放就不能把窗口设太小,还需要把切片文件存储到持久化目录。这里有个取舍:切片太小会增加存储 IO,切片太大会提高播放启动延迟。实测下来,2 秒切片在浏览器 HLS 播放器里首屏时间大约 3 到 5 秒,对大多数场景足够。
5. 踩坑实录:端口、防火墙、镜像与版本问题的排查链路
5.1 WebRTC 黑屏:UDP 端口映射与防火墙的联合排查
这是我在最初部署时遇到的最头疼的问题:推流成功,HTTP-FLV 播放正常,但 WebRTC 播放器一直停在 connecting,偶尔转一两下然后报错。当时我第一反应是 SRS 配置不对,反复改 rtc_server 的配置,重启了十几次,全部无效。
后来我整理了一下排查链路,发现这类问题真正的排查顺序应该是:
- 先确认容器端口映射有没有把 UDP 8000 暴露出来;
- 再确认宿主机防火墙是否放行 UDP 8000;
- 然后确认云平台安全组是否放行 UDP 8000;
- 最后才是 SRS 配置里的 candidate 是否正确。
我那次卡在第一步。
docker ps
里能看到
0.0.0.0:8000->8000/tcp
,但我根本没有加
/udp
,所以容器内的 UDP 8000 完全没有外部映射,外部发来的 WebRTC 媒体包根本无法到达 SRS。这是个非常隐蔽的坑,因为
docker run
不会对 TCP/UDP 混淆做任何警告。
如果你已经加了
/udp
仍然不通,下一步在宿主机执行:
nc -uvz 127.0.0.1 8000
如果 UDP 端口测试正常,再检查安全组和防火墙。云服务器尤其是轻量服务器,默认安全组通常只放行少数几个端口,UDP 8000 很容易漏掉。
5.2 candidate 配置:公网部署必须处理的问题
WebRTC 连接不只是“端口通”就完事,还涉及信令协商中的 candidate 地址。SRS 在
rtc_server
配置里会根据网卡自动检测候选地址,这在局域网内没问题,但放在公网环境下,如果 SRS 所在服务器有内网 IP,又没有明确告诉它公网地址,客户端收到的 candidate 就是内网 IP,自然连不回来。
解决办法是在
rtc_server
中显式指定 candidate:
rtc_server {
enabled on;
listen 8000;
candidate 你的服务器公网IP;
}
如果你有多个网卡或者后续要换 IP,可以用环境变量方式:
docker run -e CANDIDATE=你的服务器公网IP ...
然后在配置里写
candidate $CANDIDATE
,SRS 支持从环境变量读取。这个做法比直接写死 IP 更灵活,我后来迁移服务器的时候深有体会,改环境变量比改 conf 要安全得多,不容易漏改。
5.3 镜像下载慢与版本配置不兼容
镜像下载慢的问题前面提过 registry-mirrors,这里再补充一个思路:如果你所在团队已经有镜像仓库,就在本地拉一次
ossrs/srs:4
,然后 push 到内网仓库,以后所有服务器都从这个内网仓库拉取。这既解决了速度问题,也保证了版本一致性。
版本不兼容这个坑同样隐蔽。SRS 3 的配置在 SRS 4 里可能直接报错,
docker run
不会因为配置错误把容器退出去,SRS 进程会尝试重启,但日志刷屏。最常见的表现是:
docker start srs
之后几秒钟容器就 Exit,
docker logs
里全部是
parse config file failed
。如果看到这个关键词,优先检查 conf 里有没有使用当前版本不支持的配置项。比如 SRS 5 对
http_hooks
、
rtc_server
都有调整,升级版本时一定要对照官方文档检查每一个大块配置,而不是只改镜像 tag。
5.4 Docker 重启策略与日志丢失
还有一个容易被忽略的问题:容器启动策略。如果服务器重启或者 Docker 守护进程重启,你的 SRS 容器默认不会自动拉起。我建议启动时加上:
--restart unless-stopped
这个策略表示:除非手动 stop,否则 Docker 会在重启后自动拉起容器。对于 7x24 小时的流媒体服务来说,这个参数比“写 systemd unit 管理 SRS”更轻量,也足够可靠。日志方面,SRS 默认打开
srs_log_tank console
,所有日志输出到 stdout,你可以通过
docker logs
查看。生产环境建议挂载
objs
目录后,同时在 conf 里启用文件日志,否则容器重建后旧日志全部丢失。
6. 生产环境化:用 docker compose 管理 SRS 与 HTTPS 接入
6.1 docker compose 编排:配置、日志、服务依赖一次定义
单容器跑通只是第一步。实际交付时,服务可能还要搭配 Nginx、Web 前端、认证服务。这种情况下用 docker compose 编排最合适,所有服务定义在一个 yml 文件里,
docker compose up -d
一键拉起。
下面是我在用的 compose 文件骨架:
services:
srs:
image: ossrs/srs:4
container_name: srs
restart: unless-stopped
ports:
- "1935:1935"
- "1985:1985"
- "8080:8080"
- "8000:8000/udp"
environment:
- CANDIDATE=your_public_ip
volumes:
- /data/srs/srs.conf:/usr/local/srs/conf/srs.conf
- /data/srs/objs:/usr/local/srs/objs
command: ["./objs/srs", "-c", "conf/srs.conf"]
这里有个小细节:
command
的写法必须是数组形式,不能写成字符串,否则 Docker 解析时可能因为 shell 转义问题导致路径拼接错误。Compose 的
restart: unless-stopped
和 docker run 的
--restart unless-stopped
完全等价,但放 yml 里更直观,团队协作时版本管理也更方便。
6.2 用 Nginx 反代 HTTPS 和 HTTP-FLV
SRS 本身不带 TLS,因为流媒体和静态文件服务不是它的主职。实际生产环境我更习惯在前面放一个 Nginx,用它终结 443 端口的 HTTPS,然后把 HTTP-FLV 请求反代到 SRS 的 8080 端口,把 API 反代到 1985 端口。这样做的好处是浏览器播放页面可以走安全的 HTTPS,避免现代浏览器对混合内容的限制,同时 Nginx 还能顺手做访问限流和静态资源缓存。
Nginx 关键配置片段:
server {
listen 443 ssl;
server_name live.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location /live/ {
proxy_pass http://127.0.0.1:8080/live/;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location /api/ {
proxy_pass http://127.0.0.1:1985/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
HTTPS 对 WebRTC 几乎是强需求,浏览器只有在 HTTPS 或者 localhost 下才允许调用 getUserMedia 采集音视频。如果你打算做纯网页端的连麦或直播,那么 HTTPS 这一层不能省。用
docker compose
把 Nginx 和 SRS 编排在同一个网络里,前端页面通过域名访问 Nginx,Nginx 再把请求转发给 SRS 容器,逻辑清晰,也方便后面扩展。
6.3 通过 API 与回调做业务联动
SRS 一个很实用的能力是 HTTP 回调,也叫 webhook。比如你想知道用户什么时候开始推流、什么时候结束推流,或者想对推流地址做鉴权,都可以通过回调来实现。
在 conf 的 vhost 里加上:
vhost __defaultVhost__ {
http_hooks {
enabled on;
on_publish http://nginx/api/srs/auth;
on_unpublish http://nginx/api/srs/unpublish;
on_play http://nginx/api/srs/play;
}
}
on_publish
回调会在客户端推流时由 SRS 发起 HTTP 请求,如果你的后端返回非 200 状态码,SRS 会拒绝推流。这就是一个非常简单的推流鉴权方案。我做过的项目里,用这个回调对接了公司内部的活动密钥系统:用户申请推流时拿到一个带有效期的 token,FFmpeg 推流的地址里带上 token 参数,后端校验通过就把请求放行,否则拒绝。整个过程不需要改 SRS 源码,只需要写一个普通的 HTTP 接口。
7. 最后的实操心得:稳定运行后的几个优化细节
容器跑稳定之后,我陆续做了几件小事,虽然不起眼,但对长期运维帮助很大。
第一件是给 SRS 容器单独限制内存。流媒体服务器最怕内存被其他容器挤占,导致 OOM,我的 compose 里会加
mem_limit: 2g
和
pids_limit: 512
,让单个容器的资源占用可控。第二件是用
docker logs --tail 100
快速定位启动问题,而不是打开完整日志找半天。第三件是把 DVR 的录像文件定期同步到对象存储,本地只保留最近三天的文件,避免磁盘被录制内容打满。
还有一点是关于版本升级的建议。SRS 发布频率不低,但我不建议一有新版就跟着升。流媒体服务一旦在线跑着,升级涉及协议兼容性、配置迁移和推流端兼容性,风险比一般 Web 服务大得多。我现在的策略是:大版本固定在 SRS 4,小版本只在发现安全漏洞或明确 bug 时升级,升级前先在测试环境完整跑一遍推拉流链路,确认无误再切流量。
如果你也计划在生产环境用 Docker 部署 SRS,我最后想强调的还是那个最朴素的原则:先把最小链路跑通,再加协议,再上鉴权,再考虑集群。很多人一上来就照着生产配置模板堆功能,结果出问题的时候根本不知道是哪一层断的。从
docker run
的默认配置起步,到自定义 conf,再到 compose 编排和 HTTPS 接入,每一步都有明确的验证方式,这才是最稳妥的落地路径。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)