FreePBX 17 in Docker: The Install Guide the Image Skips

I installed FreePBX 17 with Asterisk 21 in Docker. Here are the missing modules, the admin login gap, the security patches and the fixes that worked.

FreePBX 17 in Docker: The Install Guide the Image Skips

The FreePBX Docker image comes up in a few minutes. Then it gives you a phone system you cannot log into. Half the useful modules are missing, and several of the ones it ships with are flagged as vulnerable. The README does not mention any of that.

I installed FreePBX 17 and Asterisk 21 in Docker on my own Debian 12.13 host to find out exactly where the gaps are. This is the guide I wish I had: the real error text, the command that fixed each problem, and a test that proves the PBX actually works.

What FreePBX 17 is, and what the Docker route gives you

FreePBX 17 is the current major release of FreePBX, the open-source web interface used to configure the Asterisk phone server. It reached general availability on 2 August 2024 and is officially supported on Debian 12 only. It runs on PHP 8.2 and works with Asterisk 18 to 23, with 21 and 22 as the long-term support choices. Security-fix-only maintenance starts on 14 February 2028, and end of life is 30 June 2028.

Docker is not an official FreePBX install method. I used the community stack from github.com/escomputers/freepbx-docker with the escomputers/freepbx:17-nofail2ban image and a separate mariadb:10.11 service. If Compose is new to you, read my notes on common first container mistakes before you start.

I changed three things in the upstream stack before the first start:

  • I deleted the fail2ban service. It runs with network_mode host and NET_ADMIN. Its jail bans an IP for a week after 2 failed SIP registrations, which locks you out of your own lab on purpose.
  • I skipped the iptables step in run.sh. It writes DNAT rules into the host's PREROUTING chain, and I do not want a container project editing my host firewall.
  • I moved the network to 172.24.0.0/16. Upstream uses 172.18.0.0/16, which collided with containers I already had running.

I also published the GUI on localhost only, because a PBX admin panel must never sit on a public IP. Here is the relevant part of my compose file and how I reach the panel:

services:
  freepbx:
    image: escomputers/freepbx:17-nofail2ban
    ports:
      - "127.0.0.1:8088:80"
    # other settings unchanged

networks:
  default:
    ipam:
      config:
        - subnet: 172.24.0.0/16

# from my laptop
$ ssh -L 8088:127.0.0.1:8088 user@box

$ curl -sI http://127.0.0.1:8088/ | grep -E '^(HTTP|Location)'
HTTP/1.1 302 Found
Location: /admin

The redirect to /admin means Apache and PHP are running. It does not mean you have a working PBX.

The image ships the wrong 20 modules

The image installs FreePBX 17.0.21 with only 20 modules, and they are not the ones you need. The worst gap is userman, the user manager. Without it, there is no account to log in with.

The other gaps hit the jobs people actually pay for. Without ivr, there is no "press 1 for sales" menu. Without ringgroups and queues, a call cannot ring a team. Without timeconditions, you cannot send calls somewhere else after office hours. Findmefollow, bulkhandler, announcement, paging and miscdests are missing too.

I installed them in one loop and then counted what was enabled:

$ docker compose exec freepbx bash -c '
for m in userman ivr ringgroups queues findmefollow timeconditions \
         bulkhandler announcement paging miscdests; do
  fwconsole ma downloadinstall "$m"
done'

$ docker compose exec freepbx fwconsole ma list | grep -c Enabled
32

Thirty-two enabled modules is a usable base for a small office. One module is missing on purpose: UCP, the User Control Panel. The next section explains why.

Patch before you configure anything

Right after the module installs, FreePBX's own log raised this:

[NOTIFICATION]-[freepbx]-[VULNERABILITIES] - 7 modules vulnerable

The modules in the image are older than the current security fixes, so you start out behind. One command upgraded everything and cleared the notices:

$ docker compose exec freepbx fwconsole ma upgradeall
$ docker compose exec freepbx fwconsole reload

These are the versions I ended up with:

ComponentVersion after setup
FreePBX framework17.0.33.2
core17.0.18.54
userman17.0.7.3
ivr / queues17.0.9 / 17.0.4
backup / recordings / cel17.0.13 / 17.0.6 / 17.0.3
Asterisk21.10.2 (built 2026-05-10)
PHP / Apache / MariaDB8.2.31 / 2.4.67 / 10.11.19

Those numbers matter. The August 2026 privilege-escalation advisory GHSA-pw3c-6wm2-mxg4 was fixed in framework 17.0.31. The path-traversal RCE advisory in backup was fixed in 17.0.7. In September 2026, Sangoma published a batch of High advisories across backup, recordings, CEL reports, the API module and SIPSTATION. Run upgradeall as part of setup, not months later. My self-hosted patch management guide shows how I schedule it.

Now, UCP. In July 2026, FreePBX published GHSA-37j8-fhxx-9vhp, an unauthenticated remote code execution in the UCP module, rated CVSS 9.3 and fixed in ucp 17.0.9. The UCP node server listens on ports 8001 and 8003. If you need UCP, install the fixed version, firewall those ports and turn on the Responsive Firewall in the FreePBX Firewall module. If you do not need it, leave it out like I did.

The permission error after installing modules

After the module installs, the GUI threw this:

file_put_contents(/var/www/html/admin/assets/js/pbxlib_....js): Failed to open stream: Permission denied

The cause is simple. fwconsole ran as root inside the container, so the new files belong to root. Apache runs as asterisk, so it cannot write to them. FreePBX has a command for exactly this:

$ docker compose exec freepbx fwconsole chown

$ docker compose exec freepbx grep '^User' /etc/apache2/apache2.conf
User asterisk

After fwconsole chown, the tree is owned by asterisk:asterisk with mode 775. That is correct here only because this image sets "User asterisk" in apache2.conf. On a stock Debian Apache that runs as www-data, the same ownership would break the GUI, so check that line before you copy my fix.

Do not reach for chmod 777. It hides the error by letting any process in the container rewrite the PHP your PBX runs. Fix the owner, not the mode. I explain the general pattern in my post on fixing Docker permission denied errors.

Creating the admin account: there is no CLI for it

This part took me the longest. The first page the GUI serves is not a login form. It is the OOBE (out-of-box experience) setup wizard, a form with action=setup_admin that expects all of these fields:

  • username, password1 and password2
  • email and system_ident
  • update_every, update_period and the auto-update radio buttons

If your script POSTs a login-shaped payload with just a username and a password, you get HTTP 500. If you send the wizard fields but leave out update_every or update_period, the OOBE code in BMO throws this:

ErrorException: Undefined array key 'update_every'

There is one more trap. The form uses single-quoted HTML attributes, like name='username'. A scraper that searches for double quotes finds nothing at all.

You might expect fwconsole userman to create the admin, but it only does sync and list. That leaves two honest options. The first is to drive the OOBE wizard over HTTP through your SSH tunnel and send every field. The second is to call FreePBX's own API from PHP inside the container:

$ docker compose exec -e ADMIN_PASS="$ADMIN_PASS" freepbx php -r '
include "/etc/freepbx.conf";
FreePBX::Userman()->addUser("admin", getenv("ADMIN_PASS"), "none", "PBX administrator");
'

Never insert rows into the auth tables by hand. In FreePBX 17, userman_groups has no "name" column (it is groupname), and there is no userman_groups_users table at all. Hand-written SQL based on old forum posts either fails or leaves you with a user that only half works.

Prove it works: extensions Asterisk can actually see

A login page is not proof. Most people skip the real check: confirming that the GUI writes PJSIP config that Asterisk actually loads. I tested it by importing two extensions from a small CSV file through the bulk handler:

extension,name,tech,secret
2001,Front Desk,pjsip,CHANGE_ME_1
2002,Office,pjsip,CHANGE_ME_2

My first attempt failed because of an upstream bug, not anything in my config:

$ docker compose exec freepbx fwconsole bulkimport --type=extensions /tmp/ext.csv
# fails with: Undefined variable $replace (Bulkimport.class.php, line 47)

Adding --replace makes it work. Then reload and ask Asterisk directly:

$ docker compose exec freepbx fwconsole bulkimport --type=extensions --replace /tmp/ext.csv
$ docker compose exec freepbx fwconsole reload
$ docker compose exec freepbx asterisk -rx "pjsip show endpoints"
# output trimmed to the relevant lines
 Endpoint:  2001                                     Unavailable   0 of inf
        Aor:  2001                                   1
 Endpoint:  2002                                     Unavailable   0 of inf
        Aor:  2002                                   1

Both extensions show up as endpoints with their AORs. The "Unavailable" state is expected. It only means no phone has registered yet. Register a softphone to 2001 and the state changes.

Docker vs the official installer vs a hosted PBX

Docker (escomputers image)Official Debian 12 installerHosted PBX
EffortStarts in minutes, then needs the fixes in this guideOne script on a clean, dedicated Debian 12 machineSign up and configure in a web panel
IsolationShares the host with other containers; ports and subnets need careOwns the whole machineRuns on the provider's servers
Upgrade pathfwconsole inside the container; the image itself is community-maintainedfwconsole plus apt, the supported pathThe provider upgrades for you
Official supportNoYesDepends on the provider
What breaksMissing modules, no admin account, file ownership, outdated vulnerable modulesHard to share the machine with other appsLess control, per-user fees, call data on someone else's servers

For a business phone system on its own VM, I think the official installer is the safer choice. Docker makes sense when you already run a container host and understand every change above. If you are still deciding whether to run any of this yourself, start with what self-hosting really involves.

Should you wait for FreePBX 18?

No. FreePBX 18 is planned but not released yet. The beta is planned for 1 November 2026 and general availability for 1 January 2027. It will run on Debian 13 and add wider IPv6 support and nftables-based responsive firewalling.

FreePBX 17 keeps full support until February 2028. The date to watch is PHP 8.2, whose security support is promised only through December 2026. Deploy 17 now, and plan a move to 18 during 2027 once it has had some real-world fixes.

The order I follow on a client box

  1. Remove fail2ban and the run.sh iptables step, and pick a free subnet.
  2. Publish the GUI on 127.0.0.1 only and use an SSH tunnel.
  3. Install the missing modules and verify the count.
  4. Run fwconsole ma upgradeall and confirm the vulnerability notices are gone.
  5. Run fwconsole chown and check which user Apache runs as.
  6. Create the admin through the OOBE wizard or the Userman API.
  7. Import a test extension, reload, and run pjsip show endpoints.
  8. Only then build trunks, IVRs and ring groups.

Frequently asked questions

Is FreePBX 17 free?

Yes. FreePBX is open source under the GPL, and FreePBX 17 adds no licence cost. You pay for the server it runs on, and only if you choose them for commercial modules, Sangoma's paid support or SIP trunking.

Can I install FreePBX 17 with Docker?

Yes, but Docker is not an officially supported install method. The community image works once you install the missing modules, patch the older modules it ships with, fix file ownership and create the admin account yourself, which is what this guide walks through.

Which Asterisk version does FreePBX 17 run?

FreePBX 17 supports Asterisk 18 through 23, with 21 and 22 as the long-term support choices. My install runs Asterisk 21.10.2 under FreePBX control, and the GUI writes the PJSIP configuration Asterisk loads.

Want this done for you?

If you need this set up and hardened without spending a weekend on it, that is what I do. You can see how I work on my services page.

Written by Hitesh Saini. I installed and tested this stack on my own Debian 12 host; an AI assistant helped with drafting and editing.