Skip to main content
One machine, every service: a terminal shows docker compose ps with all eleven Fonoster services up. One machine, every service: a terminal shows docker compose ps with all eleven Fonoster services up. In this tutorial you install Fonoster on one machine. At the end, every service is running and you are signed in to the dashboard as the owner, ready to make your first call.

Before you begin

You need:
  • Docker with Compose v2 (docker compose version prints v2 or later).
  • Node.js 22 or later. You use it to run the setup tool with npx.
  • curl.
  • About 6 GB of free disk for the images.
Fonoster uses port 8449 (dashboard and API), 5060 UDP and TCP (SIP), the UDP ranges 10000-10999 and 11000-11999 (call media), and, on localhost only, 4222 (NATS) and 9000-9001 (MinIO). Stop anything else that uses them, such as another Fonoster install, or change the ports in step 2.
If this machine ran Fonoster 0.27 or older with Docker Compose, stop it first: run docker compose down in its folder. Don’t remove its volumes. The new install doesn’t use them.

Install Fonoster

1

Download the release

Download the release and unpack it into a new fonoster folder:
The folder holds compose.yaml, compose.tls.yaml (for HTTPS, below), .env.example, a config folder with one example per service and their schemas, and a README.md.
2

Generate the configuration

Run init to generate the secrets and keys, find this machine’s address, and write the configuration:
It prints what it wrote:
The host address is the one softphones, carriers and browsers use to reach this machine. To use a different one, run init again with --host-ip <address>.
To change a port, edit it in .env (for example HTTP_PORT=8449 or SIP_PORT=5060) and run init again, then docker compose up -d. init keeps every secret and only updates the configuration. To run a second install on the same machine, also give it its own COMPOSE_PROJECT_NAME and SUBNET_PREFIX, and move every port in the note above.
3

Check the configuration

Validate the configuration before you start:
Each service reports valid:
4

Start Fonoster

Start every service in the background:
The first start downloads the images, which takes a few minutes. Then list the services to see them running:
You see apiserver, autopilot, dashboard, envoy, appbridge, roomr, minio, nats, postgres, routr and rtpengine, each Up.
5

Get your login

Print the dashboard address and the owner’s login:
6

Sign in to the dashboard

Open the dashboard address in your browser and sign in with the owner’s email and password. On Choose a workspace, select your workspace.
Fonoster is running and you are signed in as the owner.

Where your files are

Everything lives in the fonoster folder:
Back up .env. Its encryption key protects the vendor keys you store, and they can’t be recovered without it.

Add platform keys

Calls need at least a text-to-speech vendor. Fonoster offers every vendor in its catalog to every workspace, and calls use your platform key for that vendor unless the workspace added its own in the dashboard under Vendor keys. init writes a placeholder platform key for each vendor. Replace them with your keys: Then restart the two services that read them:
A vendor whose placeholder you leave in place is still offered, and its calls fail until a key is set. If the dashboard’s Vendor keys page says no vendors are configured, the catalog has no vendor left that takes a key.

Serve over HTTPS

By default the dashboard and API use plain HTTP on port 8449, on every interface. That’s fine on a trusted network. On the internet, passwords, API keys and tokens would cross it in clear text, so init and check print a warning when PUBLIC_URL is http:// on an address that isn’t loopback or private. Turn on HTTPS in one of two ways.

Envoy with your certificate

You need a domain name that points to this machine, and a certificate and private key for it in PEM files.
1

Get a certificate

With Let’s Encrypt, get one with certbot while ports 80 and 443 are still free:
It writes fullchain.pem and privkey.pem in /etc/letsencrypt/live/voice.example.com/. Any CA works: use your own files instead.
2

Turn on HTTPS in .env

Uncomment and fill these lines in .env, and set PUBLIC_URL to the certificate’s domain:
COMPOSE_FILE adds compose.tls.yaml, which mounts the two files read-only into Envoy.
3

Apply it

Write the configuration again and recreate the services:
init warns if a file is missing or PUBLIC_URL isn’t https://. Envoy now serves HTTPS on port 443 and redirects port 80 to it. Port 8449 is no longer published. Browser calls switch to wss://voice.example.com/sip on their own.
Envoy reads the certificate when its container is created. After a renewal, recreate it. Because Envoy holds port 80, stop it while certbot renews:

A reverse proxy in front

If you already run a proxy that terminates TLS (Caddy, nginx, Traefik, a cloud load balancer), keep Envoy on plain HTTP and publish it on this machine only:
Run init and docker compose up -d, then point the proxy at 127.0.0.1:8449. The proxy must forward gRPC (HTTP/2) and WebSocket upgrades on /sip.

SIP over TLS

SIP stays on UDP and TCP 5060. To also accept SIP over TLS on 5061, convert your certificate into a PKCS#12 keystore for Routr:
Then uncomment the routr lines in compose.tls.yaml and the tls transport in config/routr-edgeport.example.yaml, set its keyStorePassword and trustStorePassword to <password>, and run init and docker compose up -d. Carriers and softphones then use transport=tls on port 5061. Call media (RTP) isn’t encrypted by this.

Let carriers and phones reach you

Fonoster names SIP addresses after your domain in config/fonoster.yaml, the way Twilio does:
  • Trunks: a trunk’s inbound URI, where your carrier sends calls, is one label under pstn.<domain>. A trunk named Acme gets acme.pstn.example.com.
  • Domains: a domain, where SIP agents register, is one label under sip.<domain>, such as acme.sip.example.com.
The API rejects any other name. Point two wildcard DNS records at your SIP address, so every new trunk and domain resolves without a new record:
If your carriers and phones reach you by IP address, or you can’t add wildcard records, accept any host or IP instead. Set this in config/fonoster.yaml, then restart the API server:
The dashboard then asks for the full name instead of a label.

Next steps

Make your first call

Add a vendor key, create an agent, and call it from a softphone.

Configure services

Change what each service does with the files in config/.