Skip to main content
Build a small phone system on one server: two extensions that call each other, outbound calls through a SIP trunk, and a phone number that rings an extension. Resources live in YAML files, so you can keep the whole setup in version control.

Prerequisites

  • A server on your network with Docker and Docker Compose installed. Note its IP address (this guide uses 192.168.1.3).
  • A SIP trunk from a provider, if you want to call the PSTN: the provider’s host and port, your trunk username and password, and a phone number in E.164 format.
  • A softphone on each device, such as Zoiper, Telephone, or Blink.

What you deploy

This setup runs four Routr components instead of the all-in-one image: With one processor and no middleware, the EdgePort talks to the Connect processor directly. Processors expose the same gRPC interface as the Message Dispatcher, so no dispatcher is needed.

Set up the system

1

Create the Compose file

Create a routr directory and add the following compose.yaml to it.
compose.yaml
2

Configure the EdgePort

Create config/edgeport.yaml. Set externalAddrs to the server’s IP address so Routr advertises an address your phones can reach.
config/edgeport.yaml
3

Configure the Location Service

Create config/location.yaml. A single Location Service instance can keep registrations in memory.
config/location.yaml
4

Add credentials

Create config/resources/credentials.yaml with one set of credentials per extension, plus one for the trunk. Replace the passwords with your own, and use the username and password your provider gave you for the trunk.
config/resources/credentials.yaml
5

Add the trunk and the number

Create config/resources/trunks.yaml. The outbound URI is where Routr sends calls to the PSTN. The inbound URI is the host your provider puts in the Request-URI when it sends you calls; Routr uses it to match incoming calls to this trunk.
config/resources/trunks.yaml
Create config/resources/numbers.yaml. Calls to telUrl ring the extension in aorLink.
config/resources/numbers.yaml
If your provider requires registration, set sendRegister: true and add the Registry and Requester services. This minimal stack doesn’t include them.
6

Add the Domain

Create config/resources/domains.yaml. The egress policy sends any number that doesn’t match an extension out through number-01 and its trunk. The rule is a regular expression matched against the dialed number.
config/resources/domains.yaml
7

Add the extensions

Create config/resources/agents.yaml with one Agent per extension. Each Agent belongs to the Domain and uses its own credentials.
config/resources/agents.yaml
8

Start the services

Start the stack in the background.
Check that every service is running.
All four services show a status of Up.

Configure the softphones

Create one account per extension. The settings for extension 1001 are: The domain sip.local doesn’t need to resolve in DNS, because the phone sends its traffic to the proxy address. This is how the account looks in the Telephone app for macOS: Telephone account settings for 1001 with domain sip.local, user name 1001, and a password On the advanced tab, set the registry server and the proxy to the server’s IP address and port 5060: Telephone advanced settings with SIP address 1001@sip.local, registry server 192.168.1.2, and proxy 192.168.1.2 on port 5060

Verify

  • From 1001, dial 1002. The second phone rings (agent-to-agent).
  • From either extension, dial an outside number. The call goes out through the trunk (agent-to-PSTN).
  • Call +17066041487 from an outside phone. Extension 1001 rings (from-PSTN).

Make changes

The simpledata API Server reads the YAML files when it starts. After editing files under config/resources, restart it.

Troubleshooting

  • Phones fail to register with 403 Forbidden. The username in the softphone doesn’t match an Agent in the Domain, or the Domain in the softphone isn’t sip.local.
  • Calls connect without audio. externalAddrs in edgeport.yaml doesn’t match the server’s IP address.
  • Incoming PSTN calls are rejected with 403. The host in the provider’s Request-URI must equal the trunk’s inbound.uri, and the user part must match the Number’s telUrl exactly, including the +.
  • Outbound calls fail. Check the trunk’s outbound host, port, and credentials. Some providers also require sendRegister: true.

Next steps

Connect resources

Every field for Agents, Domains, Trunks, and Numbers.

Routing types

How Connect picks a route for each call.

Connect a PBX as a Peer

Add voicemail and IVR with Asterisk or FreeSWITCH.

Secure the server

Use TLS for signaling.