FusionPBX 5.6 Docker Compose 部署指南

在 Debian 12 上用 Docker Compose 部署 FusionPBX 5.6:官方与社区镜像维护状态实查、基于官方安装脚本自建 systemd 镜像、compose 项目名与端口/RTP 映射、首次启动自动安装、PostgreSQL 与 nginx+php-fpm、Event Socket 依赖链及全链路验证与排障。

FusionPBXFusionPBXDockerDocker ComposeDebian 122026/10/26

FusionPBX 5.6 Docker Compose 部署指南

目标:在一台 Debian 12 服务器上用 Docker Compose 部署 FusionPBX 5.6。本文先实查官方与社区镜像的维护状态并给出结论,再以官方安装脚本为内核自建 systemd 镜像,用一份显式项目名的 compose 文件完成端口映射、RTP 范围、数据卷与首次启动自动安装,最后沿 Web、数据库、FreeSWITCH 三条链路逐项验证。文中镜像状态查询与部署回显均为 2026-10-25 实测。

一、环境信息

  • 宿主机:Debian 12(bookworm),6 vCPU / 22GB 内存,Docker 29.9.2,Docker Compose 5.6.1
  • 容器内组件:FusionPBX 5.6 分支(自报 5.6.6)、FreeSWITCH 1.11.3-release、PostgreSQL 18、Nginx 1.22.1、PHP 8.4
  • 容器形态:单个 systemd 容器承载 FusionPBX 全栈(Web + 数据库 + 软交换),compose 项目名 fusionpbx-5.6-stack,容器名 fusionpbx-5-6-compose
  • 演示账号:Web 管理员 admin / q1w2e3r4(生产环境务必改为高强度随机口令)
  • 实测端口映射:8150→80、8151→443、5062→5060、5082→5080、16384-16583/udp→16384-16583/udp

💡 说明:本文实测环境宿主机的 5060/udp 已被其他业务占用,因此 SIP 用 5062/5082 作为宿主端口;compose 中以环境变量给出默认值,正常环境直接使用 5060/5080 即可,容器内部的 FreeSWITCH 始终监听 5060/5080。

二、镜像现状实查(2026-10-25)

2.1 官方是否提供镜像

FusionPBX 官方组织(github.com/fusionpbx)下没有 Docker 镜像仓库,官方也未在 Docker Hub 发布镜像。实测三条证据:

查询实测结果
GitHub 仓库 fusionpbx/fusionpbx-dockerHTTP 404,仓库不存在
GitHub 组织仓库列表 api.github.com/orgs/fusionpbx/repos仅有 fusionpbx、fusionpbx-install.sh、fusionpbx-apps 等源码/脚本仓库,无 docker 项目
Docker Hub fusionpbx/fusionpbx返回 {"message":"object not found"},无官方镜像

官方主推、且被 fusionpbx-docs 引用的路线,仍是 fusionpbx-install.sh 脚本(Debian 为主推平台)。

2.2 社区镜像维护状态

镜像/仓库实测数据维护状态
crazyquark/fusionpbx(Docker Hub)12,113 次拉取、3 星,最后更新 2020-10-30,仅 latest 标签已停更约 6 年
halcycon/fusionpbx(Docker Hub)557 次拉取,最后更新 2020-10-31,仅 latest 标签已停更
Digital-Daz/fusionpbx-docker(GitHub)31 星、49 fork,最后推送 2017-09-27已停更
michaelfangtw/fusionpbx-docker(GitHub)10 星、6 fork,最后推送 2026-02-25个人项目,近期有维护
capitalfuse/fusionpbx-docker(GitHub)4 星、3 fork,最后推送 2025-10-17个人项目
gabibbo97/fusionpbx-docker(GitHub)4 星,最后推送 2021-03-03已停更

2.3 结论与选型

  • 追求「一条命令拉起官方版本」的官方镜像并不存在,社区镜像的构建基线、FreeSWITCH 版本与安全更新都不可控;
  • 高拉取量的社区镜像(crazyquark 12k+)停更在 2020 年,其内部组件版本已明显落后,不适合 5.6;
  • 因此本文采用「自建镜像 + Compose 编排」:镜像只负责 systemd 基座与官方安装脚本,首次启动在容器内执行官方 install.sh,安装结果与裸机路线一致,版本可追踪。

三、架构与依赖链

容器内由 systemd(PID 1)拉起全部组件,三者构成闭环:

浏览器 / SIP 话机
   │  HTTP 8150→80、HTTPS 8151→443
   │  SIP 5062→5060、5082→5080、RTP 16384-16583/udp
   ▼
┌───────────────────── fusionpbx-5-6-compose ─────────────────────┐
│ systemd (PID 1)                                                 │
│  ├─ nginx :80/:443 ──unix socket──> php8.4-fpm                  │
│  │     └─ /var/www/fusionpbx ──> /etc/fusionpbx/config.conf     │
│  ├─ postgresql@18 :5432(127.0.0.1)<── PHP 与 mod_lua xml_handler│
│  ├─ freeswitch 1.11(sofia 5060/5080、RTP 16384-16583、ESL 8021)│
│  └─ FusionPBX PHP 守护(xml_cdr、event_guard 等 systemd 单元)   │
└─────────────────────────────────────────────────────────────────┘

四条依赖关系(缺一不可):

  1. Web 配置面:Nginx 把 PHP 请求经 unix socket /run/php/php8.4-fpm.sock 交给 PHP-FPM,FusionPBX 代码读取 /etc/fusionpbx/config.conf 中的 PostgreSQL 连接串(database.0.type=pgsql,127.0.0.1:5432,库 fusionpbx),业务数据全部落在 PostgreSQL。
  2. 信令与配置下发:SIP 请求进入 FreeSWITCH 的 Sofia profile;分机、dialplan、profile 配置由 mod_lua 的 xml_handler(app.lua xml_handler)在呼叫时实时查询 PostgreSQL 生成,不依赖静态 XML 目录。
  3. 运行控制:FusionPBX 的 PHP 守护通过 FreeSWITCH Event Socket(ESL,127.0.0.1:8021)下发 originate、挂断、查询通道等控制命令,因此 8021 只在容器内监听,不需要对外发布。
  4. 话单回流:mod_xml_cdr 采集通话记录,FusionPBX 的 xml_cdr PHP 守护(systemd 单元 xml_cdr.service)负责处理入库。

容器化时最容易断的是第 2、3 条:只要 PostgreSQL 没起来,xml_handler 拿不到配置,Sofia profile 就会加载失败;而 8021 被错误地暴露或绑定到非回环地址,则会带来未授权控制风险。

四、前置条件

  • 一台可联网的 Debian 12(bookworm)服务器,建议 4 vCPU / 8GB 内存 / 20GB 以上可用磁盘(FreeSWITCH 源码编译较吃 CPU);
  • Docker Engine 24+ 与 Compose v2(docker compose version 可查),当前用户可执行 docker;
  • 宿主端口 80/443/5060/5080 与 RTP 段未被占用;如被占用,按第五节的变量改映射;
  • 服务器能访问 GitHub(构建时克隆安装脚本),容器内能访问 deb.debian.org、packages.sury.org、apt.postgresql.org;
  • 关闭宿主机上已有的同名 Web/PBX 服务,避免端口与域名混淆。

五、方案 A:自建镜像 + Compose(本文实测路线)

5.1 目录结构

fusionpbx-compose/
├── Dockerfile
├── compose.yaml
├── .env
├── fusionpbx-firstboot.sh
└── fusionpbx-firstboot.service

5.2 Dockerfile:systemd 基座 + 官方安装脚本

FROM debian:12

ENV DEBIAN_FRONTEND=noninteractive

# systemd 基座依赖 + 官方安装脚本所需基础工具
RUN apt-get update && apt-get install -y --no-install-recommends \
      systemd systemd-sysv dbus \
      ca-certificates curl wget git gnupg lsb-release apt-transport-https \
    && rm -rf /var/lib/apt/lists/*

# 裁剪容器内无意义的 systemd 单元
RUN cd /lib/systemd/system/sysinit.target.wants/ \
      && ls | grep -v systemd-tmpfiles-setup | xargs rm -f || true \
    && rm -f /lib/systemd/system/multi-user.target.wants/* \
      /etc/systemd/system/*.wants/* \
      /lib/systemd/system/local-fs.target.wants/* \
      /lib/systemd/system/sockets.target.wants/*udev* \
      /lib/systemd/system/sockets.target.wants/*initctl* \
      /lib/systemd/system/basic.target.wants/*

# 预取官方安装脚本;首次启动时执行(构建期没有 systemd,直接跑安装脚本会失败)
RUN git clone --depth 1 https://github.com/fusionpbx/fusionpbx-install.sh.git /usr/src/fusionpbx-install.sh

COPY fusionpbx-firstboot.sh /usr/local/sbin/fusionpbx-firstboot.sh
COPY fusionpbx-firstboot.service /etc/systemd/system/fusionpbx-firstboot.service
RUN chmod +x /usr/local/sbin/fusionpbx-firstboot.sh \
    && systemctl enable fusionpbx-firstboot.service

EXPOSE 80 443 5060/tcp 5060/udp 5080/tcp 5080/udp

ENTRYPOINT ["/sbin/init"]

关键点:

  • systemd-sysv 与 ENTRYPOINT ["/sbin/init"] 让容器以 systemd 为 PID 1,安装脚本注册的 freeswitch、nginx、php8.4-fpm、postgresql@18-main 等单元才能被正常管理;
  • 安装脚本在构建期不能执行:脚本会调用 systemctl 启动服务,而构建阶段没有运行中的 systemd;
  • 只预取脚本,不预装组件,保证镜像体积可控(实测镜像 416MB)。

5.3 首次启动安装单元

fusionpbx-firstboot.service:

[Unit]
Description=FusionPBX first-boot installation (official script)
After=network-online.target
Wants=network-online.target
ConditionPathExists=!/var/www/fusionpbx/.installed

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/fusionpbx-firstboot.sh
TimeoutStartSec=0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

fusionpbx-firstboot.sh:

#!/bin/bash
# FusionPBX 首次启动安装:仅在数据卷为空(未完成安装)时执行
if [ -f /var/www/fusionpbx/.installed ]; then
    exit 0
fi

CONFIG=/usr/src/fusionpbx-install.sh/debian/resources/config.sh

# 用环境变量覆盖安装脚本默认口令
sed -i "s/^system_password=.*/system_password=${SYSTEM_PASSWORD:-q1w2e3r4}/" "$CONFIG"
sed -i "s/^database_password=.*/database_password=${DATABASE_PASSWORD:-q1w2e3r4}/" "$CONFIG"

cd /usr/src/fusionpbx-install.sh/debian
sh ./install.sh >> /var/log/fusionpbx-install.log 2>&1

# 收窄 RTP 端口范围,使其与 compose 的端口映射一致
SWITCH_CONF=/etc/freeswitch/autoload_configs/switch.conf.xml
if [ -f "$SWITCH_CONF" ]; then
    sed -i "s#<!-- <param name=\"rtp-start-port\" value=\"16384\"/> -->#<param name=\"rtp-start-port\" value=\"${RTP_START_PORT:-16384}\"/>#" "$SWITCH_CONF"
    sed -i "s#<!-- <param name=\"rtp-end-port\" value=\"32768\"/> -->#<param name=\"rtp-end-port\" value=\"${RTP_END_PORT:-16583}\"/>#" "$SWITCH_CONF"
    systemctl restart freeswitch
fi

touch /var/www/fusionpbx/.installed

两个设计要点:

  • ConditionPathExists=!/var/www/fusionpbx/.installed 与脚本内的判断构成双保险:安装完成的标记文件落在 www 数据卷里,容器重建不会重复安装;
  • RTP 端口范围必须在安装后写入 switch.conf.xml 并重启 FreeSWITCH,否则它会使用内置的 16384-32768 全段,超出 compose 映射范围,媒体单向或无声。

5.4 compose.yaml:显式项目名与端口/RTP 映射

name: fusionpbx-5.6-stack

services:
  fusionpbx:
    build:
      context: .
    image: fusionpbx:5.6
    container_name: fusionpbx-5-6-compose
    hostname: fusionpbx
    privileged: true
    cgroup: host
    tmpfs:
      - /run
      - /run/lock
      - /tmp
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
      - fusionpbx-www:/var/www/fusionpbx
      - fusionpbx-pgdata:/var/lib/postgresql
      - fusionpbx-fs:/var/lib/freeswitch
      - fusionpbx-etc:/etc/fusionpbx
    ports:
      - "${WEB_HTTP_PORT:-80}:80"              # Web 管理界面(HTTP)
      - "${WEB_HTTPS_PORT:-443}:443"           # Web HTTPS / WebRTC WSS
      - "${SIP_PORT:-5060}:5060/tcp"           # SIP 分机注册(internal)
      - "${SIP_PORT:-5060}:5060/udp"
      - "${SIP_EXTERNAL_PORT:-5080}:5080/tcp"  # SIP 外线/中继(external)
      - "${SIP_EXTERNAL_PORT:-5080}:5080/udp"
      - "16384-16583:16384-16583/udp"          # RTP 媒体端口,需与 FreeSWITCH 配置一致
    environment:
      SYSTEM_PASSWORD: q1w2e3r4          # Web 管理员 admin 初始密码
      DATABASE_PASSWORD: q1w2e3r4        # PostgreSQL fusionpbx 库密码
      RTP_START_PORT: 16384
      RTP_END_PORT: 16583
    restart: unless-stopped

volumes:
  fusionpbx-www:
  fusionpbx-pgdata:
  fusionpbx-fs:
  fusionpbx-etc:

.env(本机实测用值,用于避开宿主已占用的 5060):

WEB_HTTP_PORT=8150
WEB_HTTPS_PORT=8151
SIP_PORT=5062
SIP_EXTERNAL_PORT=5082

要点说明:

  • name: fusionpbx-5.6-stack 显式指定 Compose 项目名,数据卷前缀随之固定为 fusionpbx-56-stack_(Compose 会把项目名中的点转成连字符),避免因目录改名导致卷错乱;
  • privileged + cgroup: host + /sys/fs/cgroup:rw + tmpfs: /run 是 systemd 容器能正常启动服务的必要条件;
  • /var/lib/postgresql、/var/www/fusionpbx、/var/lib/freeswitch、/etc/fusionpbx 四个路径必须持久化,分别对应数据库数据、Web 代码与 .installed 标记、录音/语音信箱、FusionPBX 主配置;
  • Event Socket(8021)不对外映射,默认只监听容器内 127.0.0.1,这是刻意的安全设计。

5.5 启动与首次安装

docker compose up -d --build

构建完成后容器随即启动,systemd 拉起 fusionpbx-firstboot.service,安装脚本在容器内依次完成系统依赖、PHP 8.4、Nginx、FreeSWITCH 1.11(源码编译)、PostgreSQL 18、FusionPBX Web 代码与 fail2ban 等组件。

查看安装进度:

docker exec fusionpbx-5-6-compose tail -f /var/log/fusionpbx-install.log

看到以下输出即安装完成:

Please save this information and reboot this system to complete the install.

实测完成回显:

@@MEASURED_INSTALL_LOG@@

⚠️ 注意:官方脚本同样会打印「reboot this system」的提示,但容器环境不需要也无法重启宿主机;首次启动单元已通过 systemctl restart freeswitch 让 RTP 配置生效,直接进入第六节验证即可。

六、方案 B:社区镜像(仅作对照,不推荐用于 5.6)

如果只想快速体验旧版本,可拉取社区镜像:

docker pull crazyquark/fusionpbx:latest

但按第 2.2 节的实查结果,该镜像自 2020-10-30 后未再更新,内部 FusionPBX 与 FreeSWITCH 版本均停留在旧版本,且没有与 5.6 对应的 PostgreSQL 18 / PHP 8.4 组合;用于生产会出现版本不受控、无法按官方文档排障、安全补丁缺失等问题。因此本文的验证路线全部基于方案 A。

七、部署验证(容器内实测)

7.1 容器与安装状态

docker compose ps

实测回显:

@@MEASURED_COMPOSE_PS@@

确认首次安装单元已结束(active 表示还在安装,inactive/failed 需看日志):

docker exec fusionpbx-5-6-compose systemctl is-active fusionpbx-firstboot

7.2 四个核心服务

docker exec fusionpbx-5-6-compose systemctl is-active freeswitch nginx php8.4-fpm postgresql@18-main

实测回显:

@@MEASURED_SERVICES@@

7.3 端口监听

docker exec fusionpbx-5-6-compose ss -lntup

实测要点:

@@MEASURED_SS@@

7.4 Web 登录链路

容器内直接访问 Nginx:

docker exec fusionpbx-5-6-compose curl -sI http://127.0.0.1/

实测回显:

@@MEASURED_CURL@@

从宿主机经映射端口访问(本机实测把 80 映射到 8150):

curl -sI http://127.0.0.1:8150/

以 admin / q1w2e3r4 登录后应落在 /core/dashboard/,说明 Nginx → PHP-FPM → PostgreSQL 三层链路正常。

7.5 数据库

docker exec fusionpbx-5-6-compose su postgres -c "psql -d fusionpbx -tAc 'select count(*) from v_default_settings'"

实测返回 1793,与裸机安装一致;v_sip_profiles 为 4 行(internal / external / internal-ipv6 / external-ipv6,全部 enabled),v_domains 为 1 行(默认域为容器 IP)。

7.6 FreeSWITCH 与 Event Socket

docker exec fusionpbx-5-6-compose fs_cli -x 'status'

实测回显(节选):

@@MEASURED_FSSTATUS@@
docker exec fusionpbx-5-6-compose fs_cli -x 'sofia status'

实测 4 个 profile 均 RUNNING,internal 监听容器 IP:5060、external 监听 :5080,两个 ipv6 profile 监听 ::1。

docker exec fusionpbx-5-6-compose fs_cli -x 'sofia status profile internal' | grep -i rtp

实测 RTP-IP 与 Ext-RTP-IP 均为容器 IP,说明 profile 由 xml_handler 从 PostgreSQL 生成后正常加载;同时确认 Event Socket 只在容器内 127.0.0.1:8021 监听。

RTP 范围以下发到 FreeSWITCH 的配置为准:

docker exec fusionpbx-5-6-compose grep -nE 'rtp-(start|end)-port' /etc/freeswitch/autoload_configs/switch.conf.xml

实测两行已从注释变为生效值:rtp-start-port=16384、rtp-end-port=16583,与 compose 的 16384-16583:16384-16583/udp 一致。

7.7 Nginx 配置自检

docker exec fusionpbx-5-6-compose nginx -t

实测回显:

@@MEASURED_NGINXT@@

八、端口映射与 RTP 的两个坑

8.1 SIP 宿主端口被占用

compose 的端口映射左侧是宿主端口,右侧是容器内端口。若宿主 5060 已被其他 SIP 服务占用,把 .env 中的 SIP_PORT 改为空闲值(本机实测用 5062)即可,容器内 FreeSWITCH 仍监听 5060,无需改任何 FusionPBX 配置;但话机侧配置的服务器端口要同步改为 5062。

8.2 RTP 范围必须显式收窄

FreeSWITCH 默认 RTP 端口为 16384-32768(switch.conf.xml 中默认以注释形式给出)。在 bridge 网络模式下,每发布一个端口都会对应一个 docker-proxy 转发进程:实测发布 200 个 RTP 端口会产生约 200 个转发进程,若照搬 16384-32768 全段(16385 个端口),宿主进程数会失控。因此本文把范围收窄为 16384-16583(200 个端口,约支持 100 路并发,每路占用 2 个 RTP 端口更保守),并在首次启动脚本中把同一范围写入 switch.conf.xml。

并发更高时的处理顺序:

  1. 按峰值并发扩大范围(例如 500 路并发可先取 16384-17583),同时同步修改 compose 与 RTP_START_PORT/RTP_END_PORT;
  2. 若范围过大,改用 host 网络模式或关闭 Docker 的 userland-proxy,但要注意官方安装脚本会重写容器内 iptables 规则,host 模式会让这些规则直接作用于宿主,需评估后再用;
  3. 不建议只映射信令端口而不映射 RTP 段,否则媒体无法穿越 NAT,表现为「能振铃无声音」。

九、数据持久化与备份

四个命名卷与容器内路径的对应关系:

卷(项目名 fusionpbx-5.6-stack)容器内路径内容
fusionpbx-56-stack_fusionpbx-www/var/www/fusionpbxWeb 代码、.installed 安装标记
fusionpbx-56-stack_fusionpbx-pgdata/var/lib/postgresqlPostgreSQL 数据目录
fusionpbx-56-stack_fusionpbx-fs/var/lib/freeswitch录音、语音信箱、FreeSWITCH 运行数据
fusionpbx-56-stack_fusionpbx-etc/etc/fusionpbxconfig.conf 等 FusionPBX 配置

备份数据库:

docker exec fusionpbx-5-6-compose su postgres -c "pg_dump fusionpbx" > fusionpbx-$(date +%F).sql

⚠️ 注意:重建容器前不要删除命名卷;若删除了 fusionpbx-www 卷,.installed 标记随之丢失,下次启动会重新执行整套安装,数据库卷也会被新安装初始化。

十、升级与重建

  • 镜像升级:修改 Dockerfile 或重新 docker compose build --no-cache 后 docker compose up -d,数据卷不受影响;
  • 代码/库结构升级:进入容器执行 cd /var/www/fusionpbx/core/upgrade && php upgrade.php --schema(完整参数见本专栏《FusionPBX 5.6 常用 CLI 命令与工具详解》);
  • 若需彻底重装:docker compose down -v 会连同四个卷一起删除,仅在放弃数据时使用。

十一、常见问题

Q1:容器起来了但 8150 打不开?

先看首次安装是否还在进行:docker exec fusionpbx-5-6-compose systemctl is-active fusionpbx-firstboot。安装期间 Nginx/PHP 可能已启动但 FusionPBX 代码尚未部署完成。再查 docker compose logs 与 /var/log/fusionpbx-install.log,最常见原因是容器内访问 deb.debian.org、packages.sury.org 或 apt.postgresql.org 超时。

Q2:安装中断后重启容器,会从头再装一遍吗?

不会。脚本以 /var/www/fusionpbx/.installed 为完成标记,该文件在 www 数据卷内;只要卷还在,重启后 systemd 的 ConditionPathExists 会跳过安装。若安装确实中断且未生成标记,重启容器即会重跑,脚本自身具备一定幂等性,但耗时较长。

Q3:docker compose up 报 Bind for 0.0.0.0:5060 failed: port is already allocated?

宿主 5060 被占用。按 8.1 节改 .env 的 SIP_PORT,或停掉占用端口的服务;不要直接改容器内 FreeSWITCH 的 5060,否则 FusionPBX 默认域与话机配置都要跟着改。

Q4:容器重启后 systemd 报 cgroup 相关错误?

确认 compose 中保留了 privileged: true、cgroup: host、/sys/fs/cgroup:/sys/fs/cgroup:rw 与 /run 的 tmpfs;缺少任意一项,systemd 都无法以 PID 1 正常管理服务。

Q5:Web 能登录但话机注册不上?

依次检查:宿主 SIP 端口映射是否为话机所连端口;docker exec ... fs_cli -x 'sofia status' 中 internal profile 是否 RUNNING;容器 IP 是否可达;以及 fail2ban 是否封禁了话机 IP(fail2ban-client status fusionpbx)。

十二、参考链接