Docs navigation

Architecture overview

An Exovo deployment is a set of cooperating containers managed by Docker Compose. Understanding what each one does makes logs easier to read, firewall rules easier to reason about, and troubleshooting much faster.

The container stack

                  Internet / LAN
                        │
        80/443          │           5060/5061 (SIP — LAN only)
      ┌─────────────────┤           5080 (trunks)  5090 (tunnel)
      ▼                 │           5091 (VPN)  16384–16584 (media)
 ┌─────────┐            │                 │
 │ traefik │            │                 ▼
 │ (proxy) │            │      ┌──────────────────────────┐
 └────┬────┘            │      │  core (FreeSWITCH)       │
      │ HTTP            │      │  + tunnel   (sidecar)    │
      ▼                 │      │  + phoneproxy (sidecar)  │
 ┌─────────┐   ESL/API  │      └──────────┬───────────────┘
 │   web   │◄───────────┼─────────────────┘
 │ console │            │      3478 + 16485–16584
 └────┬────┘            │                 │
      │ SQL             │                 ▼
      ▼                 │           ┌─────────┐
 ┌─────────┐            └──────────►│  turn   │ (media relay,
 │   db    │                        └─────────┘  browser calls)
 └─────────┘
Service Image Role
traefik Traefik 3 Reverse proxy: TLS termination, automatic certificates, HTTP→HTTPS
web Exovo web The web console and web client, provisioning, APIs, background workers
core Exovo core FreeSWITCH — SIP registration, call routing, media, voicemail, faxing
db PostgreSQL All configuration and history (users, rules, CDRs, chat, settings)
turn coturn Media relay so browsers behind NAT can send/receive call audio
tunnel Exovo tunnel The :5090 front door for the Exovo apps and on-site SBCs (mutual TLS)
phoneproxy Exovo phoneproxy Lets admins open a desk phone's own web UI through the console
dockerproxy socket proxy A locked-down, read-mostly window onto Docker for status and update checks

Port reference

Every port the system listens on, and who is meant to reach it. Only the ports whose audience includes the internet ever need router forwarding — the per-scenario forwarding list is in the installation guide. In particular, 5060/5061 are for LAN phones only: remote phones come in through the SBC tunnel (5090) or VPN (5091), never by forwarding SIP.

Port Protocol Service Audience Purpose
80, 443 TCP traefik Internet Web console and client, provisioning, automatic certificates
5060 UDP + TCP core LAN only SIP — local phone registration (internal profile)
5061 TCP core LAN only SIP over TLS — local phones
5080 UDP + TCP core Trunk providers SIP — external/trunk profile
5090 TCP + UDP tunnel Internet Exovo tunnel — SBC sites and the Exovo apps (mutual TLS)
5091 UDP core Internet Remote-phone VPN (OpenVPN)
8443 TCP core Internet Cisco phone VPN (AnyConnect via ocserv)
443 UDP core Internet Cisco phone VPN — DTLS media path (optional)
69, 6970 UDP, TCP core LAN only Cisco Enterprise provisioning (TFTP + HTTP config service), when enabled
16384–16484 UDP core Trunk providers + LAN RTP call media
16485–16584 UDP turn Internet + LAN TURN relay range (contiguous with RTP — forward 16384–16584 as one block)
3478 UDP + TCP turn Internet + LAN TURN/STUN — media relay for browser calling

The telephony core (FreeSWITCH)

core owns everything real-time: SIP registrations from phones and trunks, call routing, RTP media, music on hold, voicemail and fax. It exposes SIP on 5060 (phones), 5061 (SIP over TLS) and 5080 (trunks), plus the RTP range 16384–16484/udp. It doesn't make routing decisions on its own — for each call it asks the web app for dialplan instructions, so all configuration lives in one place (the database) and takes effect without reloads.

Two sidecars share the core's network namespace:

  • tunnel terminates the Exovo tunnel on port 5090 — the single mutual-TLS port that mobile apps and on-site SBCs use for SIP and media. It fetches its certificates from the web app on startup and holds no secrets of its own.
  • phoneproxy reaches phone web UIs on the LAN or over the remote-phone VPN — networks the hardened web container deliberately can't see — so the console's "open phone UI" button works without exposing phones directly.

The web console and web client

web is a single ASP.NET application serving both the admin console and the end-user web client, plus phone provisioning, the REST endpoints FreeSWITCH calls back into, and background workers (backups, CRM sync, update checks, the license phone-home). It's the only service that talks to the database, and it never faces the internet directly — Traefik fronts it.

Database and persistence

Everything configurable lives in PostgreSQL. Bulky media lives on the filesystem:

Path on host Contents
/var/lib/exovo/db PostgreSQL data
/var/lib/exovo/recordings Call recordings
/var/lib/exovo/ivr Digital-receptionist and system prompts
/var/lib/exovo/fax Sent/received faxes
/var/lib/exovo/certs, acme TLS certificates
/var/lib/exovo/keys Data-protection keys (encrypts stored secrets)
/var/lib/exovo/logs FreeSWITCH logs

Backing up /var/lib/exovo plus the database covers the entire system — which is exactly what the built-in backup does.

Reverse proxy and TLS

Traefik owns ports 80 and 443, redirects HTTP to HTTPS, and either maintains an automatic certificate for your FQDN or serves the custom one you provided during onboarding. Everything web-ish — console, provisioning, phone-UI proxy — flows through it; internal services are never published directly.

How a call flows through the system

  1. A phone (or trunk) sends SIP to core on 5060/5080.
  2. core asks web how to route the call; web answers from the database — user, ring group, queue, IVR, office-hours rules.
  3. Media (RTP) flows directly between the endpoint and core. Browser calls relay through turn; mobile-app and SBC calls arrive inside the tunnel on 5090.
  4. When the call ends, core reports the call record to web, which stores the CDR, triggers recording transcription and CRM journaling, and updates live dashboards.

Security posture, in brief

Containers run with dropped capabilities and no-new-privileges; the web app has no Docker socket (the deny-by-default dockerproxy allows only status reads and a scoped restart); the internet-facing tunnel holds no keys; and actions that need root on the host (updates, restores) are performed by small systemd watchers reacting to marker files — the containers themselves can't touch the host. Details in the security section.