Skip to content

Architecture

A self-hosted Comcent install is a single Docker Compose project. The services below are the ones defined in the deployment compose file (docker-compose.deploy.yaml), which the installer downloads as ~/comcent-ce/docker-compose.yaml.

ServiceImageWhat it does
traefiktraefikReverse proxy on ports 80 and 443. Terminates TLS with a Let’s Encrypt certificate for your domain, redirects HTTP to HTTPS, and routes /api/, /ws and /health to the server and everything else to the web app.
servercomcent-ce-serverThe Elixir/Phoenix API server. Holds the business logic: organisations, members, numbers, queues, call routing decisions, call history, AI analysis and webhooks. Runs database migrations on every start.
web-appcomcent-ce-webappThe SvelteKit web application your team signs in to, including the browser dialer.
sbccomcent-ce-sbcA Go session border controller: SIP proxy, registrar and dispatcher. Listens for SIP on 5060 (UDP and TCP) and serves SIP over secure WebSocket on 5063 for the browser dialer.
cert-dumpertraefik-certs-dumperCopies the certificate Traefik obtained into a volume the SBC reads, so the WSS listener uses the same Let’s Encrypt certificate.
freeswitchfreeswitch-ceThe media server. Handles RTP audio (UDP 19000–19100), recording, IVR playback and bridging calls.
voice-botgo-voice-bot-ceThe real-time AI voice bot. Needs DEEPGRAM_API_KEY and OPENAI_API_KEY.
postgrespgvector/pgvector:pg15The main database (PostgreSQL with the pgvector extension).
redisredisShared, fast-changing state used by the server.
rabbitmqrabbitmqCarries FreeSWITCH call events to the server.

Call recordings and uploaded audio are stored in an S3 or S3-compatible bucket that you provide; it is not part of the compose file. Persistent data for Postgres, Redis, RabbitMQ and the certificates lives in named Docker volumes (postgres_data, redis_data, rabbitmq_data, traefik_letsencrypt, sbc_certs). Back these up on your own schedule.

PSTN caller Agent's browser
│ │
▼ │ SIP over WSS :5063
SIP trunk provider ──── SIP :5060 ────► sbc ◄─────┘
│
▼
freeswitch ◄──── RTP :19000-19100 ────► caller / agent media
│ │
routing & config (HTTP)│ │ call events (RabbitMQ)
▼ ▼
server ──► Postgres, Redis, S3 bucket
│
├──► Deepgram / OpenAI (when keys are set)
└──► your webhooks
  1. Signalling arrives at the SBC. Your SIP trunk sends the call to your domain on port 5060. The SBC looks up the trunk for the dialled number and accepts the call only if it comes from one of that trunk’s whitelisted inbound IPs.
  2. FreeSWITCH asks the server what to do. FreeSWITCH loads its directory, configuration and dial plan from the server’s internal API, which runs the number’s inbound flow: play a message, offer a menu, check business hours, ring agents, place the caller in a queue or hand the call to a voice bot.
  3. Agents answer in the browser. Agents’ browser dialers stay registered with the SBC over secure WebSocket (port 5063). When the flow rings an agent, the call is bridged to their browser. Audio flows as RTP through FreeSWITCH on UDP 19000–19100.
  4. Events are recorded. FreeSWITCH publishes call events to RabbitMQ; the server consumes them to track live calls and build the call’s timeline. Recordings are kept in your S3 bucket.
  5. After the call. The server builds the call history entry. If AI keys are configured it transcribes and analyses the recording, then sends the finished call to any configured webhooks as a vCon document.

Outbound calls take the reverse path: the browser dialer sends the call to the SBC and on to FreeSWITCH, the server picks the caller ID (the number chosen in the dialer, else the member’s default number, else the organisation’s default number), and the call leaves through that number’s SIP trunk.

Only Traefik, the SBC and FreeSWITCH publish ports on the host. Everything else talks over the internal comcent-network Docker network. See Networking for the full list of ports and DNS requirements.