Install with Docker Compose
The production bundle in deploy/compose/prod runs MonoPbx with Postgres, Valkey, ClickHouse and rtpengine. The VM installer wraps exactly this bundle. Use the bundle directly if you already manage Docker hosts.
Requirements: Docker Engine 24+ with the Compose plugin v2.24+, on Linux (Ubuntu 24.04 LTS is the supported host). Docker Desktop (macOS, Windows) is for development and trials only, and Windows is never a production host.
The development sandbox (make dev) is a different thing: it publishes the databases on loopback and uses throwaway secrets. Never use it in production.
Install
The first run creates .env from the template and stops, so you can set it. MPBX_IMAGE, MPBX_RTPENGINE_IMAGE and MPBX_MEDIA_IMAGE must name your release (pin them by digest), and MPBX_PUBLIC_URL is the address users open. Then run it again:
cd deploy/compose/prod
./start.sh # first run: creates .env and stops. Set the images and MPBX_PUBLIC_URL in .env, then:
./start.sh # second run: mpbx init (once), then start the stack
start.sh runs three steps, which you can also run yourself (the block is also what the guide check runs):
set -eu
cd deploy/compose/prod
docker compose -f compose.init.yaml run --rm init # 1. mpbx init: secrets into .env, recovery key shown ONCE
docker compose -f compose.yaml -f compose.linux.yaml up -d --wait # 2. start (Docker Desktop: compose.desktop.yaml)
scripts/smoke.sh # 3. optional: health, isolation and login checks
Write the recovery key down when mpbx init shows it. It is not stored on the host. Without it, a lost .env means lost trunk passwords, API keys and encrypted recordings. In a terminal, init shows the key before it writes anything and asks you to re-type one group of it. Without a terminal it prints the key once to stdout after storing the secrets, so do not keep that output. Running init again changes nothing.
.env (mode 0600, git-ignored) holds your secrets. Back it up together with the volumes.
What runs
| Service | Profile | Published |
|---|---|---|
postgres, valkey, clickhouse | default | nothing (internal network) |
mpbx (API and embedded web client) | default | MPBX_HTTP_BIND:MPBX_HTTP_PORT, default 0.0.0.0:8080 |
rtpengine | default | UDP RTPE_PORT_MIN-MAX (30000-40000) on the host |
coturn | turn | 3478 and the relay range |
prometheus, grafana | observability | Grafana on 127.0.0.1 only |
mpbx-media | media | nothing; its image is still a stub, so leave the profile off |
mpbx-edge | edge | the SIP edge role does not exist yet |
Enable profiles with COMPOSE_PROFILES=observability,turn in .env.
Networking notes
- The
datanetwork is internal: it has no route out, and no database port is published. - rtpengine, coturn and the SIP edge use host networking on Linux. rtpengine's control port listens on
127.0.0.1only. Open UDPRTPE_PORT_MIN-RTPE_PORT_MAXin your firewall. Behind NAT setRTPE_ADVERTISE=<public IP>.start.shdetectsRTPE_INTERFACE. mpbxitself stays on the bridge networks so it can reach the databases.
Secrets and database roles
- There are no default secrets. Compose refuses to start until
mpbx inithas written them. - The application connects to Postgres as a non-superuser without
BYPASSRLS, and row-level security is forced on tenant tables. The Postgres superusermpbx_adminhas no password and is reachable only over the container's local socket:docker compose exec postgres psql -U mpbx_admin -d mpbx. - Every container drops all capabilities and sets
no-new-privileges.
Operations
./start.sh # idempotent; init is skipped once the secrets exist
./start.sh ps | logs -f mpbx # any docker compose command
./start.sh down # stop, keep data
docker compose -f compose.yaml -f compose.linux.yaml down -v # DELETES the databases
Set COMPOSE_PROJECT_NAME to run a second copy next to this one. Upgrade and rollback commands are not built yet. HTTPS and ACME for the Compose bundle come from the installer's overlay (deploy/installer/compose.installer.yaml); see Certificates and HTTPS.
For backups see Backup and restore.