Zoneminder 1.38.6 Docker Compose 部署指南

ZoneMinder 1.38.6 官方镜像 Docker Compose 部署实录:MariaDB+MediaMTX+ffmpeg 模拟摄像头四服务编排与数据持久化,首启 privacy 同意页、API 改密与开启认证全流程,Monitor 创建(Device/Recording 两个 API 坑)与事件录制端到端验证,环境变量与三层配置说明,附首启日志与 Zone API 几何三坑等 FAQ(2026-10-30 实测)。

ZoneminderDockerDocker Compose视频监控RTSPMariaDB2026/10/30

Zoneminder 1.38.6 Docker Compose 部署指南

目标:在一台 Ubuntu Linux 服务器上,用 Docker Compose 从零到一跑起一套完整的 ZoneMinder 1.38.6 视频监控环境,包括官方镜像配套的 MariaDB 数据库、RTSP 流媒体服务器(MediaMTX),并用 ffmpeg 循环推流一段测试视频充当「模拟摄像头」,端到端完成首启初始化、Web 登录、添加监控(Monitor)与事件录制验证全流程。本篇实测的测试源为本地推流模拟(Intel 开源样例视频循环推 RTSP),无真实摄像头硬件,读者的真实摄像头只需把模拟源地址替换为自己的 RTSP 地址即可。

一、环境信息

本篇全部命令与回显在下列实测环境中逐条跑出(2026-10-30):

项目值
操作系统Ubuntu 22.04(Linux x86_64)
服务器配置6 核 CPU / 19GB 内存 / 无独立显卡
DockerDocker Engine 28 + Docker Compose v2
ZoneMinderzoneminderhq/zoneminder:1.38.6(官方镜像,Web 与守护进程同栈)
数据库mariadb:11.8(Zoneminder 官方 compose 配套)
RTSP 服务器bluenviron/mediamtx:1.21.3-ffmpeg
模拟源视频person-bicycle-car-detection.mp4(Intel intel-iot-devkit/sample-videos 开源样例,H.264 768x432@12fps + AAC,时长 53.9 秒)
Compose 项目名zoneminder-1.38.6-stack
Docker 网络zoneminder-1386-stack_zm-net,10.89.66.0/24
宿主端口8140 → ZoneMinder Web UI(80);容器内 8555(Janus/RTSP2Web)未映射
实测目录/opt/zoneminder(文档内路径,读者按需自定)

二、镜像选型说明

四个服务均选用官方/上游镜像,版本与选型理由如下:

服务镜像与 tag选型理由
zoneminderzoneminderhq/zoneminder:1.38.6官方镜像(GitHub README「Run with Docker」一节指明 Docker Hub 与 ghcr.io 双发布、每日 CI 重建、amd64/arm64);1.38.6 为 2026-10-05 发布的最新稳定版,tag 固定精确版本保证可复现
mariadbmariadb:11.8ZoneMinder 强依赖 MySQL/MariaDB 存配置与事件索引;官方 zmdockerfiles 的 compose 方案即以 MariaDB 为配套数据库
mediamtxbluenviron/mediamtx:1.21.3-ffmpegRTSP/RTMP/HLS/WebRTC 全协议流服务器;选 -ffmpeg 变体因为镜像内置 ffmpeg,同一镜像可同时充当「RTSP 服务器」与「模拟摄像头推流器」,省一个镜像
模拟摄像头bluenviron/mediamtx:1.21.3-ffmpeg(复用)覆盖 entrypoint 直接跑 ffmpeg 循环推流,模拟一台持续在线的摄像头;生产环境去掉该服务、换成真实摄像头 RTSP 地址

⚠️ 注意:镜像 tag 一律固定到具体版本号(如 1.38.6、11.8、1.21.3),不要用 latest,保证任何时候重建环境结果可复现。

三、前置条件

  1. 一台 Ubuntu 22.04 服务器,已安装 Docker Engine 与 Docker Compose v2(docker compose version 能正常输出版本)。
  2. 端口 8140 未被占用(ss -ltn 检查;8128-8152 是本文约定端口段,若被占用可自行替换为空闲端口)。
  3. 服务器可访问外网拉取镜像(实验环境 Docker 已配置镜像加速与代理;直连 Docker Hub 缓慢时可为 Docker daemon 配置代理)。
  4. 约 10GB 磁盘(zoneminder 镜像约 1.5GB + mariadb 约 0.4GB + 录像数据预留)。
  5. 约 2GB 可用内存(Zoneminder 1 路 768x432 监控 + MariaDB 的实测常驻占用远低于该值,该值为余量建议)。

四、部署步骤

4.1 创建目录结构

所有配置与数据统一收敛到一个部署目录(下文以 /opt/zoneminder 为例),便于持久化与备份:

mkdir -p /opt/zoneminder/{config,db,events,log,source}
cd /opt/zoneminder
tree -L 1 .

实测回显:

.
├── config
├── db
├── events
├── log
└── source

4.2 准备模拟摄像头测试视频

下载 Intel 开源的检测测试样例视频(BSD-3 许可,包含行人、自行车、汽车三类目标,正好用于演示运动检测产生事件):

cd /opt/zoneminder/source
curl -L -o person-bicycle-car-detection.mp4 \
  "https://github.com/intel-iot-devkit/sample-videos/raw/master/person-bicycle-car-detection.mp4"
ls -la person-bicycle-car-detection.mp4

⚠️ 网络说明:本篇实测时 GitHub 官方源直连与代理通道均超时不可用(curl 返回 000),该视频文件在实测环境中为离线预先备妥后放入 source 目录(实测文件 6031199 字节)。读者可从上述官方仓库地址下载,或直接使用任意一段 H.264 编码的测试视频(文件名与 4.3 中 camsource 推流命令对应即可)。

用 ffprobe 核对素材参数(mediamtx 镜像自带 ffprobe,宿主机未装 ffmpeg 时可借用):

docker run --rm -v /opt/zoneminder/source:/s:ro --entrypoint ffprobe \
  bluenviron/mediamtx:1.21.3-ffmpeg \
  -v error -show_entries format=duration:stream=codec_name,width,height,avg_frame_rate \
  -of default=noprint_wrappers=1 /s/person-bicycle-car-detection.mp4

实测回显:

codec_name=h264
width=768
height=432
avg_frame_rate=12/1
codec_name=aac
avg_frame_rate=0/0
duration=53.916667

💡 技术拓展:视频监控里常说的「拉流」与「推流」——

方式含义谁发起典型场景
拉流(pull)服务端主动去取流ZoneMinder 连接摄像头摄像头提供 RTSP 服务(最常见)
推流(push)流主动送到服务端摄像头/编码器推送摄像头内网隔离、公网推 RTMP

ZoneMinder 属于「拉流」架构:Monitor 源地址里写 RTSP 地址,zmc 进程主动连接。本篇的模拟摄像头即用 ffmpeg 把测试视频推到 MediaMTX,MediaMTX 对 ZoneMinder 扮演一台「提供 RTSP 服务的摄像头」。

4.3 编写 docker-compose.yml

在 /opt/zoneminder 下创建 docker-compose.yml(本篇实测文件全文如下):

name: zoneminder-1.38.6-stack

networks:
  zm-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.66.0/24

services:
  mariadb:
    image: mariadb:11.8
    container_name: zoneminder-1.38.6-mariadb
    restart: unless-stopped
    command:
      - --transaction-isolation=READ-COMMITTED
      - --skip-log-bin
    environment:
      TZ: Asia/Shanghai
      MARIADB_DATABASE: zm
      MARIADB_USER: zmuser
      MARIADB_PASSWORD: q1w2e3r4
      MARIADB_ROOT_PASSWORD: q1w2e3r4
    volumes:
      - ./db:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      start_period: 30s
      retries: 5
    networks:
      zm-net:

  zoneminder:
    image: zoneminderhq/zoneminder:1.38.6
    container_name: zoneminder-1.38.6-app
    restart: unless-stopped
    depends_on:
      mariadb:
        condition: service_healthy
    stop_grace_period: 60s
    environment:
      TZ: Asia/Shanghai
      ZM_DB_HOST: mariadb
      ZM_DB_NAME: zm
      ZM_DB_USER: zmuser
      ZM_DB_PASS: q1w2e3r4
    ports:
      - "8140:80"
    volumes:
      - ./events:/var/cache/zoneminder
      - ./log:/var/log/zm
      - ./config:/config
    tmpfs:
      - /run:exec,mode=0755
      - /tmp
    networks:
      zm-net:
        aliases:
          - zoneminder

  mediamtx:
    image: bluenviron/mediamtx:1.21.3-ffmpeg
    container_name: zoneminder-1.38.6-mediamtx
    restart: unless-stopped
    networks:
      zm-net:
        aliases:
          - mediamtx

  camsource:
    image: bluenviron/mediamtx:1.21.3-ffmpeg
    container_name: zoneminder-1.38.6-camsource
    restart: unless-stopped
    entrypoint: ["ffmpeg"]
    command:
      - -re
      - -stream_loop
      - "-1"
      - -i
      - /source/person-bicycle-car-detection.mp4
      - -c:v
      - libx264
      - -preset
      - veryfast
      - -tune
      - zerolatency
      - -g
      - "25"
      - -b:v
      - 1500k
      - -maxrate
      - 1500k
      - -bufsize
      - 3000k
      - -pix_fmt
      - yuv420p
      - -f
      - rtsp
      - rtsp://mediamtx:8554/cam1
    volumes:
      - ./source:/source:ro
    depends_on:
      - mediamtx
    networks:
      zm-net:

配置要点逐项说明:

  • mariadb:MARIADB_DATABASE: zm 预建 ZoneMinder 专用库 zm;--transaction-isolation=READ-COMMITTED 与 --skip-log-bin 是 ZoneMinder 官方推荐的数据库参数(降低高并发写入下的锁等待并关闭二进制日志节省磁盘);healthcheck 让 zoneminder 服务等数据库真正就绪后再启动(depends_on 的 condition: service_healthy)。
  • zoneminder:ZM_DB_* 四个环境变量是数据库连接的唯一切入口,镜像启动脚本会把它们写入 /etc/zm/conf.d/03-docker.conf(见「七、配置参数说明」);数据目录 /var/cache/zoneminder(事件录像)、日志目录 /var/log/zm 分别挂载持久化;stop_grace_period: 60s 给 zmdc 优雅停止全部守护进程留足时间;tmpfs 挂载 /run 与 /tmp。
  • mediamtx + camsource:前者提供 RTSP 服务(rtsp://mediamtx:8554/cam1),后者用 ffmpeg 把测试视频无限循环推流到该地址,模拟一台持续在线的摄像头。生产环境把这两段换成真实摄像头即可。
  • 网络:显式指定 subnet,避免与其他 compose 项目的网段冲突。

4.4 启动与状态确认

cd /opt/zoneminder
docker compose up -d

实测回显(首次启动):

 Container zoneminder-1.38.6-camsource Created 
 Container zoneminder-1.38.6-app Created 
 Container zoneminder-1.38.6-mariadb Starting 
 Container zoneminder-1.38.6-mediamtx Starting 
 Container zoneminder-1.38.6-mariadb Started 
 Container zoneminder-1.38.6-mariadb Waiting 
 Container zoneminder-1.38.6-mediamtx Started 
 Container zoneminder-1.38.6-camsource Starting 
 Container zoneminder-1.38.6-camsource Started 
 Container zoneminder-1.38.6-mariadb Healthy 
 Container zoneminder-1.38.6-app Starting 
 Container zoneminder-1.38.6-app Started 

确认状态(zoneminder 与 mariadb 应显示 healthy,healthy 需等启动脚本完成数据库初始化,实测约 30-40 秒):

docker compose ps

实测回显:

NAME                          IMAGE                               COMMAND                  SERVICE      CREATED             STATUS                       PORTS
zoneminder-1.38.6-app         zoneminderhq/zoneminder:1.38.6      "/init"                  zoneminder   About an hour ago   Up 46 minutes (healthy)      8555/tcp, 0.0.0.0:8140->80/tcp, [::]:8140->80/tcp
zoneminder-1.38.6-camsource   bluenviron/mediamtx:1.21.3-ffmpeg   "ffmpeg -re -stream_…"   camsource    About an hour ago   Up About an hour             
zoneminder-1.38.6-mariadb     mariadb:11.8                        "docker-entrypoint.s…"   mariadb      About an hour ago   Up About an hour (healthy)   3306/tcp
zoneminder-1.38.6-mediamtx    bluenviron/mediamtx:1.21.3-ffmpeg   "/mediamtx"              mediamtx     About an hour ago   Up About an hour             

再核对模拟摄像头流已就绪(拉一次流信息,报 200 即在播):

curl -s -o /dev/null -w 'RTSP-over-HTTP探测 HTTP %{http_code}\n' \
  -X OPTIONS http://127.0.0.1:8554/cam1

💡 说明:mediamtx 在本 compose 中未映射宿主端口,该探测需在部署目录所在宿主机通过临时映射或直接在后续 4.6 中由 ZoneMinder 侧验证拉流结果。ZoneMinder 能否拉到流以 4.6 添加监控后的帧率读数为准。

4.5 首启初始化、改密与开启认证

ZoneMinder 镜像首次启动时会自动完成数据库初始化。查看启动日志确认([init-db]/[init-config] 行为镜像启动脚本输出):

docker compose logs zoneminder 2>&1 | grep -E "init-db|init-config"

实测回显(fresh 空库首启,节选):

[init-db] empty database, loading zm_create.sql into 'zm'
[init-config] WARNING: /dev/shm is only 64 MB

empty database 行是启动脚本检测到空库后自动装载 zm_create.sql 建表,属正常流程;/dev/shm 容量告警的影响与调大方法见「十、常见问题」Q8。

首次访问没有登录页:官方镜像默认认证关闭(Config 项 ZM_OPT_USE_AUTH=0),浏览器打开 http://<服务器IP>:8140/zm/(直接访问根路径会 302 跳转到 /zm/)先进入隐私协议同意页(view=privacy),点击 Accept 后直达 Console 控制台——此时超级管理员为 admin,初始口令 admin(数据库口令哈希 password_verify 实证)。命令行等价流程:

# 1) 首访返回隐私同意页,页面内含 __csrf_magic 令牌(格式 key:HEX串,时间戳,需完整回传)
curl -s -c /tmp/zmcookies.txt http://127.0.0.1:8140/zm/ \
  | grep -oE 'name="__csrf_magic" value="[^"]*"' | head -1
# 2) 提交同意(Accept,option=1)+ __csrf_magic → 进入 Console
curl -s -b /tmp/zmcookies.txt -c /tmp/zmcookies.txt -o /dev/null -w 'POST privacy -> HTTP %{http_code}\n' \
  --data-urlencode "__csrf_magic=key:<HEX串>,<时间戳>" \
  -d "view=privacy&option=1" http://127.0.0.1:8140/zm/index.php
# 3) 带 Session 访问即到控制台
curl -s -b /tmp/zmcookies.txt "http://127.0.0.1:8140/zm/?view=console" | grep -oE '<title>[^<]*</title>'

⚠️ 首启后先改密、再开认证:认证关闭期间,任何能访问 8140 端口的人都可用 admin/admin 进入控制台,公网或办公网暴露前务必完成下面两步。本篇统一用 REST API 操作(认证关闭状态下 API 无需 token),演示口令统一为 q1w2e3r4(仅作演示,生产环境请使用更长更复杂的口令)。

第一步:API 改密。 先说实测踩坑:Web 界面 Users 页改密(表单提交按钮 Save)实测返回 HTTP 200 但页面出现 SQL-ERR '00000',口令并未变更;改走 REST API 一次成功:

curl -s -X PUT http://127.0.0.1:8140/zm/api/users/1.json \
  -H "Content-Type: application/json" \
  -d '{"User":{"Username":"admin","Password":"q1w2e3r4","Enabled":1}}'

实测回显:

{"message":"Saved"}

第二步:API 开启认证(同样无需 token):

curl -s -X POST -d "Config[Value]=1" \
  "http://127.0.0.1:8140/zm/api/configs/edit/ZM_OPT_USE_AUTH.json"

保存后认证立即生效:浏览器刷新出现登录页(<title>ZM - Login</title>),API 不带 token 一律 401。验证新旧口令:

# 旧口令 admin 已失效
curl -s -X POST "http://127.0.0.1:8140/zm/api/host/login.json?username=admin&password=admin"

实测回显(HTTP 401):

{"success":false,"data":{"name":"Invalid username or password",...,"code":401}}
# 新口令成功(ZoneMinder 内建 REST API,OPT_USE_API 官方镜像默认开启)
curl -s -X POST "http://127.0.0.1:8140/zm/api/host/login.json?username=admin&password=q1w2e3r4"

实测回显(access_token 为 JWT,长串已节选):

{"version":"1.38.6","apiversion":"2.0","access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJab25lTWluZGVyIiwiaWF0IjoxNzkzMzMwMTMyLCJleHAiOjE3OTMzMzczMzIsInVzZXIiOiJhZG1pbiIsInR5cGUiOiJhY2Nlc3MifQ.kEnueuJdVCPDguzkyZKVBeUNxJBz5z6it-GrapNZ1-A","access_token_expires":7200,"refresh_token":"...","refresh_token_expires":86400,"credentials":"auth=66caaae9316b3a94b09d453d10838474","append_password":0}

Web 表单登录同样验证通过(等价于浏览器登录,curl 复现):

curl -s -c /tmp/zmcookies.txt -o /dev/null -w 'GET /zm/ -> HTTP %{http_code}\n' http://127.0.0.1:8140/zm/
curl -s -b /tmp/zmcookies.txt -c /tmp/zmcookies.txt -o /dev/null -w 'POST login -> HTTP %{http_code}\n' \
  -d "username=admin&password=q1w2e3r4&action=login&view=login" http://127.0.0.1:8140/zm/index.php
curl -s -b /tmp/zmcookies.txt "http://127.0.0.1:8140/zm/?view=console" | grep -oE '<title>[^<]*</title>'

实测回显:

GET /zm/ -> HTTP 302
POST login -> HTTP 200
<title>ZM - Console</title>

返回中的 access_token 有效期 7200 秒(2 小时),后续 API 调用以 ?token=<access_token> 查询参数携带(实测直接放 Authorization: Bearer 请求头会被拒绝,返回 401 Not Authenticated——这是 ZoneMinder API 的实际行为,token 一律走查询参数或表单字段)。

4.6 添加监控(Monitor)

登录控制台后,Web 界面路径为:Console 页 → Add New Monitor(或点击已有监控行进入编辑)。关键字段:General 页的 Name/Source Type/Function,Source 页的 Source Path(RTSP 地址)与分辨率。本篇以 API 等效调用实测(与 Web 表单提交等价,便于留档验证):

TOK=$(curl -s -X POST http://127.0.0.1:8140/zm/api/host/login.json \
  -d "user=admin&pass=q1w2e3r4" | php -r '$d=json_decode(stream_get_contents(STDIN),true); echo $d["access_token"];')

curl -s -X POST -d "Monitor[Name]=Cam1&Monitor[Type]=Ffmpeg&Monitor[Function]=Modect&Monitor[Enabled]=1&Monitor[MaxFPS]=10&Monitor[AlarmMaxFPS]=10&Monitor[Path]=rtsp://mediamtx:8554/cam1&Monitor[Width]=768&Monitor[Height]=432&Monitor[Colours]=3&Monitor[Device]=" \
  "http://127.0.0.1:8140/zm/api/monitors.json?token=$TOK"

实测回显:

{"message":"Saved"}

⚠️ 实测坑一(Device):POST 创建 Type=Ffmpeg 的监控时,请求必须携带 Monitor[Device] 参数(空值即可),否则会被误校验为本地采集卡而拒绝(实测报错 {"message":{"Device":["Invalid device path. Must be a valid /dev/ path (e.g. /dev/video0).",...]}})。Web 表单同样填写 RTSP 源时无此问题(表单按源类型分支校验)。

⚠️ 实测坑二(Recording):POST 带 Monitor[Function]=Modect 保存后,GET 复查实存 Capturing=Always / Analysing=Always / Recording=Always——即连续录像而非「运动才录」,事件 Cause 也为 Continuous。需再补一次 PUT 修正为标准 Modect 形态(保存后 zmdc 自动 stop+start 该监控的 zmc 进程,zmdc.log 可见完整 stop→start 序列,无需手动重启):

curl -s -X PUT -d "Monitor[Recording]=OnMotion" \
  "http://127.0.0.1:8140/zm/api/monitors/1.json?token=$TOK"

创建后 ZoneMinder 会自动拉起该监控的采集进程 zmc,无需手动重启(用 zmdc 确认;注意 zmdc 的守护进程名要按参数逐段传,整体加引号会报 Invalid daemon):

docker compose exec zoneminder zmdc.pl status zmc -m 1

实测回显:

'zmc -m 1' running since 26/10/30 12:33:44, pid = 1502

用 zmu(ZoneMinder 的监控状态查询工具)确认拉流正常(Capturing 为 3 表示持续采集;Recording 为 2 即 On motion,说明坑二修正已生效):

docker compose exec zoneminder zmu -l -U admin -P q1w2e3r4

实测回显:

  Id Capturing Analysing Recording State TrgState LastImageTime RdIdx WrIdx LastEvent FrameRate
   1         3         2         2     1        0 1793334969.91   739     1         0      5.00

FrameRate 与源推流速率、CPU 转码负载相关(本环境一路 768x432 实测在 2.00~10.00 间波动,属正常)。另外本镜像中 zmu 复跑偶发 glibc free(): double free detected in tcache 2 崩溃(容器构建 quirk,重跑即可,不影响采集与录像)。

4.7 事件录制验证

Monitor 为 Modect(运动检测并记录)时,画面中出现移动物体(测试视频中的行人/车辆)即触发事件;也可用 zmu -a 强制告警。三种方式逐层验证:

方式一:CLI 查事件序号(-e 输出最近事件 ID):

docker compose exec zoneminder zmu -m 1 -e -U admin -P q1w2e3r4

实测回显:

1

方式二:API 查事件列表:

curl -s "http://127.0.0.1:8140/zm/api/events.json?token=$TOK" \
  | php -r '$d=json_decode(stream_get_contents(STDIN),true); foreach(array_slice($d["events"],0,3) as $e){$m=$e["Event"]; echo $m["Id"]." | ".$m["Name"]." | ".$m["StartTime"]." | ".$m["Frames"]."f | AlarmFrames=".$m["AlarmFrames"]." | ".$m["Cause"]."\n";}'

实测回显:

1 | Event-1 | 2026-10-30 12:24:00 | 1886f | AlarmFrames=3 | Continuous
2 | Event-2 | 2026-10-30 12:31:22 | 610f | AlarmFrames=0 | Continuous
3 | Event-3 | 2026-10-30 12:47:53 | 64f | AlarmFrames=44 | Forced Web

说明:Event-1/2 是 4.6 坑二修正前(Recording=Always)产生的连续录像事件;Event-3 的 Cause=Forced Web 来自强制告警 zmu -m 1 -a(端到端验证:告警 → 事件创建 → 帧图落盘,64 帧中 44 帧为告警帧)。Recording 修正为 OnMotion 且 Zone 配置正确后,运动检测触发的事件 Cause 为 Motion:All(All 指默认全屏 Zone "All");API 创建 Zone 的三个实测坑见「十、常见问题」Q10。

方式三:直接查事件落盘(每事件一个目录,路径规律 events/<监控ID>/<日期>/<事件ID>/):

ls /opt/zoneminder/events/events/1/2026-10-30/3/

实测回显(Event-3:64 个 capture 帧图 + 告警/快照图,节选):

00001-capture.jpg
00002-capture.jpg
00003-capture.jpg
00004-capture.jpg
alarm.jpg
snapshot.jpg

在 Console → Events(或 Web 界面事件回放页)可直接回放事件;事件的视频封装与导出用 zmvideo.pl 完成(见本专栏命令篇)。

五、数据持久化说明

挂载宿主目录(本文)容器内路径内容
事件录像/opt/zoneminder/events/var/cache/zoneminder全部事件帧图与视频(events/1/<日期>/<事件ID>/)
日志/opt/zoneminder/log/var/log/zmzmdc/zmc/zmaudit 等全部组件日志
数据库/opt/zoneminder/db/var/lib/mysqlMariaDB 数据目录(监控配置、事件索引)
配置/opt/zoneminder/config/config预留配置挂载(ZM_WEB_* 等 PHP 级配置可放此处)

实测重启持久化验证:docker compose down 后 docker compose up -d 重新拉起,监控配置(Cam1)与已有事件目录全部保留,ZoneMinder 启动后自动恢复 Cam1 的采集进程并继续产生新事件,重启后 docker compose ps 各服务回到 healthy。

六、环境变量表

ZoneMinder 官方镜像识别的 ZM_* 环境变量(启动脚本读取后写入配置或作用到运行时,实测自镜像启动脚本 init-config):

变量必填说明
ZM_DB_HOST是数据库地址,可含端口(host:port)或 Unix socket 路径;compose 中填服务名 mariadb
ZM_DB_NAME是数据库名(默认 zm)
ZM_DB_USER是数据库用户
ZM_DB_PASS(或 ZM_DB_PASS_FILE)是数据库口令;ZM_DB_PASS_FILE 指向挂载的口令文件(内容即口令),适合 secrets 场景
TZ是时区(如 Asia/Shanghai),同时作用于系统、PHP 与守护进程
ZM_DB_SSL_CA_CERT否数据库 TLS CA 证书路径
ZM_DB_SSL_CLIENT_KEY否数据库 TLS 客户端私钥
ZM_DB_SSL_CLIENT_CERT否数据库 TLS 客户端证书
ZM_SERVER_HOST否多服务器(Multi-Server)模式的本节点名,须与 Options → Servers 中登记一致;单节点不要设置
ZM_ES_ENABLE否事件通知服务(zmeventnotification)开关,1 开 0 关(仅 -es 变体镜像内置该服务)
SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS(或 SMTP_PASS_FILE)/SMTP_FROM/SMTP_TLS否出站邮件(告警通知),启动脚本生成 /etc/msmtprc;SMTP_TLS 默认 starttls,465 端口默认 tls,可强制 off

MariaDB 官方镜像变量:

变量说明
MARIADB_DATABASE启动时自动创建的库(zm)
MARIADB_USER / MARIADB_PASSWORD业务用户与口令
MARIADB_ROOT_PASSWORDroot 口令(维护用)

⚠️ 注意:数据库口令本文统一演示为 q1w2e3r4;ZM_DB_PASS 与 MARIADB_PASSWORD 必须一致,否则 ZoneMinder 连库失败。

七、配置参数说明与配置文件挂载

ZoneMinder 的配置分三层(实测文件均在 /etc/zm/ 下):

  1. /etc/zm/zm.conf:包管理器分发的基础配置(ZM_PATH_DATA/ZM_PATH_WEB/ZM_DB_TYPE 等),文件头明确标注 *** DO NOT EDIT THIS FILE ***,升级时会被覆盖。
  2. /etc/zm/conf.d/*.conf:drop-in 覆盖层,自定义改动放这里。镜像启动脚本自动生成其中的 03-docker.conf(内容即 ZM_DB_* 环境变量),实测文件:
# Written by the container at startup from ZM_* environment variables. Do not edit.
ZM_DB_HOST=mariadb
ZM_DB_NAME=zm
ZM_DB_USER=zmuser
ZM_DB_PASS=q1w2e3r4
  1. 数据库 Config 表(Options 界面):ZM_OPT_USE_AUTH、ZM_AUTH_TYPE 等运行时配置项存数据库,Web 界面 Options 页即其编辑器,改完即时生效(zmu/zms 等进程读取的是数据库值而非 zm.conf)。

💡 技术拓展:ZoneMinder 的「配置」有两个命名空间容易混淆——

名称存放位置修改方式生效方式
系统级路径/DB 连接/etc/zm/zm.conf + conf.d编辑 conf 文件重启守护进程
运行时配置项数据库 Config 表Web 界面 Options / API configs多数即时生效,少数需重启 zmc

想改「Options 页面里看不到的系统路径类」项走 conf.d;想改「Options 页面里看得到的运行时项」走界面或 API。两者重叠时 conf.d 中的键优先级更高(仅对 zmdc/zmpkg 等脚本生效)。

配置参数详解(zm.conf 基础项、Options 各配置域、conf.d 覆盖实践)见本专栏 《Zoneminder 1.38.6 配置参数详解》。

八、等价 docker run

不使用 Compose 时,等价的手工启动分三步(先建网络,数据库先于应用):

# 1) 创建内网
docker network create --subnet 10.89.66.0/24 zm-net

# 2) 启动 MariaDB(与 compose 中参数一致)
docker run -d --name zoneminder-1.38.6-mariadb \
  --network zm-net --restart unless-stopped \
  --transaction-isolation=READ-COMMITTED --skip-log-bin \
  -e TZ=Asia/Shanghai \
  -e MARIADB_DATABASE=zm -e MARIADB_USER=zmuser -e MARIADB_PASSWORD=q1w2e3r4 \
  -e MARIADB_ROOT_PASSWORD=q1w2e3r4 \
  -v /opt/zoneminder/db:/var/lib/mysql \
  mariadb:11.8 \
  --transaction-isolation=READ-COMMITTED --skip-log-bin
# 3) 等 MariaDB 就绪后启动 ZoneMinder(健康检查可用 docker inspect 轮询)
docker run -d --name zoneminder-1.38.6-app \
  --network zm-net --restart unless-stopped \
  -p 8140:80 \
  -e TZ=Asia/Shanghai \
  -e ZM_DB_HOST=mariadb -e ZM_DB_NAME=zm -e ZM_DB_USER=zmuser -e ZM_DB_PASS=q1w2e3r4 \
  -v /opt/zoneminder/events:/var/cache/zoneminder \
  -v /opt/zoneminder/log:/var/log/zm \
  -v /opt/zoneminder/config:/config \
  --tmpfs /run:exec,mode=0755 --tmpfs /tmp \
  zoneminderhq/zoneminder:1.38.6

💡 说明:手工启动时数据库健康等待需自行处理(轮询 mariadb-admin ping);其余行为与 Compose 完全一致。mediamtx 与 camsource 的等价 run 命令同理(源地址换成真实摄像头 RTSP 后这两步整体省略)。

九、常用运维命令

cd /opt/zoneminder
docker compose ps                        # 四服务状态(zoneminder/mariadb 应显示 healthy)
docker compose logs -f zoneminder        # 跟踪 ZoneMinder 日志(含首启 init-db 输出)
docker compose restart zoneminder        # 重启 ZoneMinder(数据库结构变更或大部分配置生效用)
docker compose exec zoneminder zmpkg.pl status   # 守护进程包状态(实测回显 running)
docker compose exec zoneminder zmdc.pl status    # 逐守护进程状态(zmc/zmfilter 等)
docker compose exec zoneminder zmu -l -U admin -P q1w2e3r4   # 全部监控实时帧率与状态
docker compose stop camsource            # 停掉模拟摄像头(监控将报流断开)
docker compose start camsource           # 恢复推流
docker compose down                      # 停止并移除栈(数据在挂载目录不受影响)
docker compose up -d                     # 重新拉起

升级到新版本:修改 docker-compose.yml 中 zoneminder 镜像 tag 为目标版本,执行:

docker compose pull zoneminder && docker compose up -d zoneminder

ZoneMinder 启动脚本会自动调用 zmupdate.pl 完成数据库结构升级(实测首启日志 Initiating database upgrade ... successful),升级前建议先备份 /opt/zoneminder/db 与 events。

十、常见问题

Q1:访问 8140 一直转圈或 502? zoneminder 服务 healthy 前不接受正常服务(启动脚本要等数据库并跑 zmupdate)。docker compose ps 看 STATUS 列,等 healthy 再访问;若长时间不 healthy,docker compose logs zoneminder 查初始化报错。

Q2:登录 admin/admin 提示 Invalid username or password? 实测两种可能:①首启后口令被修改过(本文演示环境改成了 q1w2e3r4);②口令输入含多余空格。确认不了口令时,可在数据库侧重置:docker compose exec mariadb mariadb -uzmuser -pq1w2e3r4 zm -e "SELECT Id,Username FROM Users" 确认账号存在,再按 ZoneMinder 文档用 Web 界面重设或重建数据库目录(./db 清空会回到首启状态)。

Q3:POST /zm/api/monitors.json 报 Invalid device path. Must be a valid /dev/ path? 创建 Type=Ffmpeg(RTSP 源)监控时 API 会误校验本地采集设备路径,实测解决:请求中带上 Monitor[Device]=(空值)即可正常保存(见 4.6)。Web 表单创建无此问题。

Q4:API 用 Authorization: Bearer <token> 请求头返回 401 Not Authenticated? ZoneMinder API 实测不接受 Bearer 请求头认证,token 要放在查询参数(?token=<access_token>)或表单字段中携带。另注意 access_token 有效期仅 2 小时,过期用 refresh_token 换新或重新调 login.json。

Q5:监控创建成功但 zmu -l 里 Capturing 一直为 0 或帧率 0? 拉流失败。按序检查:①源地址是否可达(模拟环境确认 mediamtx Up、camsource 在推流);②RTSP 用户名密码是否写进 URL(rtsp://user:pass@ip:554/...);③Monitor 的 Width/Height 与实际流不符时部分源会黑屏——用 ffprobe 查实际分辨率后对齐。

Q6:Web 界面监控画面打不开/黑块? zms 流服务器路径 ZM_PATH_ZMS(默认 /zm/cgi-bin/nph-zms)与 Web 服务器 CGI 配置相关,镜像内已配好;自建反代时需保留 /zm/cgi-bin/ 与 /zm/api/ 两个路径的透传,仅反代静态页会黑屏。

Q7:想接入真实摄像头? 把 4.6 中 Monitor 的 Path 换成摄像头 RTSP 地址(如 rtsp://用户名:密码@摄像头IP:554/stream1,建议用子码流降低解码压力),并删除 mediamtx/camsource 两个服务。主流品牌 RTSP 取流路径可查各厂商文档或 ZoneMinder 文档站的 camera 页。

Q8:首启日志 [init-db] empty database, loading zm_create.sql into 'zm' 与 [init-config] WARNING: /dev/shm is only 64 MB 是报错吗? 前者不是:空库首启时启动脚本自动装载 zm_create.sql 建表(见 4.5),一次性行为。后者要注意:容器默认 /dev/shm 只有 64MB,而 ZoneMinder 的帧共享内存缓冲区就在 /dev/shm(ZM_PATH_MAP=/dev/shm),单路低分辨率演示无碍,多路/高分辨率场景请在 compose 的 zoneminder 服务上加 shm_size: "1g"(按「路数 × 分辨率 × 环形缓冲帧数」估算)后重新拉起。

Q9:Web 界面 Users 页改密,保存后提示 SQL-ERR '00000' 且口令未变? 1.38.6 官方镜像实测存在(表单 POST 返回 HTTP 200,页面出现 SQL-ERR '00000')。绕开 Web 表单,直接用 REST API 改密(认证关闭时无需 token,见 4.5):

curl -s -X PUT http://127.0.0.1:8140/zm/api/users/1.json \
  -H "Content-Type: application/json" \
  -d '{"User":{"Username":"admin","Password":"新口令","Enabled":1}}'

Q10:API 创建 Zone(POST zones.json)后运动检测不触发? 三个实测坑叠加的典型结果:①NumCoords 与 Area 不会自动换算,POST 传 0 就原样落库,需再 PUT 显式补齐(768x432 全屏 Zone 为 Zone[NumCoords]=4、Zone[Area]=331776);②Coords 是多边形顶点串、至少 3 个顶点,矩形要写四个角 0,0 767,0 767,431 0,431——只写两个对角点 0,0 767,431 时 zmc 日志报 Not enough coordinates to form a polygon!,区域整体失效;③Web 表单建的默认 Zone 自带 Min/MaxPixelThreshold=10/180,API 创建时为空,建议显式补 Zone[MinPixelThreshold]=10、Zone[MaxPixelThreshold]=180。排查入口:宿主 ./log/zmc_m1.log(容器内 /var/log/zm/)的 ERR 行。

十一、参考链接