Quickstart: Docker
This chapter runs the gateway from the Docker image. It mirrors Quickstart: Binary, which holds the minimal .env, the Free-tier explanation and the first-call dialplan. Read those sections there; this chapter covers only what differs in a container.
Image
| Item | Value |
|---|---|
| Image | vexyl/vexyl-voice-gateway |
| Tags | 1.7.0 (pinned release) and latest |
| Runs as | an unprivileged user, uid 1000 |
| Ports exposed | 8080 (Asterisk), 8081 (HTTP API), 8082 (browser widget) |
Access to the image is arranged with your licence; Vexyl tells you how to pull it.
Inside the image: the gateway binary, the native speech-detection module and model, the sounds/ prompt and filler audio, and the browser demo page. FFmpeg is installed too. The image bundles its own runtime, so Node.js is not needed on the host.
The image already sets HTTP_PORT=8081, AUDIOSOCKET_PORT=8080, WEBSOCKET_AUDIO_PORT=8082, binds every server to 0.0.0.0 and keeps the speech cache in /app/cache/tts. You do not need to repeat these in .env. It also ships a built-in healthcheck on /health, so docker ps shows healthy once the gateway is up.
Prepare the host
Create .env as described in Minimal .env. --env-file (and Compose env_file) passes every variable in the file to the container; nothing is filtered.
The container user is uid 1000, so every host directory you mount for writing must belong to it:
mkdir -p cache cdr logs config
sudo chown -R 1000:1000 ./cache ./cdr ./logs ./config
| Mount | Holds |
|---|---|
/app/cache |
Speech cache |
/app/cdr |
Billing records, when enabled |
/app/logs |
Per-turn latency log |
/app/config |
Your own bots, prompts, tools and pricing.json |
Run with docker run
For the default TELEPHONY_MODE=audiosocket, only the Asterisk and HTTP ports are needed:
docker run -d \
--name vexyl-gateway \
--hostname vexyl-gateway \
--restart unless-stopped \
--env-file .env \
-p 8080:8080 -p 127.0.0.1:8081:8081 \
-v "$PWD/cache:/app/cache" \
-v "$PWD/cdr:/app/cdr" \
-v "$PWD/logs:/app/logs" \
-v "$PWD/config:/app/config" \
vexyl/vexyl-voice-gateway:1.7.0
The HTTP API is published on the host's loopback only. Use the PBX-facing address instead of 127.0.0.1 if Asterisk is on another host. In bridge networking, the host's own curl 127.0.0.1:8081/health reaches the container from the Docker bridge gateway (commonly 172.17.0.1), so include that address in HTTP_ALLOWED_IPS if you set the whitelist.
Add -p 8082:8082 if you turned on the browser widget. Asterisk on another host connects to the Docker host's address on port 8080; Asterisk on the same host uses 127.0.0.1:8080 as in the First call dialplan.
Mounting /app/sounds replaces the prompt and filler files shipped in the image. Copy them out first if you want to keep the defaults beside your own:
docker cp vexyl-gateway:/app/sounds/. ./sounds/
SIP bridge in Docker
The SIP bridge needs a UDP port range of about 10,000 ports for call audio, and Docker cannot map a range that large efficiently. When TELEPHONY_MODE is sip_bridge or both, run the container on the host network instead and drop every -p line:
docker run -d \
--name vexyl-gateway \
--hostname vexyl-gateway \
--restart unless-stopped \
--network host \
--env-file .env \
-v "$PWD/cache:/app/cache" \
-v "$PWD/cdr:/app/cdr" \
-v "$PWD/logs:/app/logs" \
-v "$PWD/config:/app/config" \
vexyl/vexyl-voice-gateway:1.7.0
Set SIP_PUBLIC_ADDRESS in .env to the host's public IP address. Follow SIP trunk for the carrier side.
The alternative is to publish the ports, for example -p 5080:5080/udp -p 20002-30000:20002-30000/udp. Container start then becomes very slow while Docker sets up the range, and the RTP range must match what the gateway is configured to use. Prefer --network host.
Compose
Save as docker-compose.yml beside .env:
services:
vexyl-gateway:
image: vexyl/vexyl-voice-gateway:1.7.0
container_name: vexyl-gateway
# licence is machine-bound; keep the hostname fixed
hostname: vexyl-gateway
restart: unless-stopped
env_file: .env
ports:
- "8080:8080"
- "127.0.0.1:8081:8081"
volumes:
- ./cache:/app/cache
- ./cdr:/app/cdr
- ./logs:/app/logs
- ./config:/app/config
# - ./credentials.json:/app/credentials.json:ro
For the SIP bridge, replace the ports: list with network_mode: host. Start it with docker compose up -d.
Google speech services need a service-account file. Uncomment the volume line above, then point the variable at the path inside the container:
GOOGLE_APPLICATION_CREDENTIALS=/app/credentials.json
Licence inside Docker
The Free tier (4 concurrent calls, no expiry) applies until you set LICENSE_KEY in .env; see Licence.
A key is bound to a machine id built from the container hostname, the CPU model and the CPU count. That is why the examples pin the hostname. Ask the running container for its id (it works with no .env):
docker exec vexyl-gateway ./vexyl-gateway --get-machine-id
Send the id to Vexyl and put the key you receive in .env. Recreating the container with the same hostname on the same host keeps the key valid. Moving to another host, changing the hostname or changing the CPU count invalidates it, and you need a new key.
Check
docker ps
curl -s 127.0.0.1:8081/health
docker ps should report healthy. In the JSON, status is ok and concurrentCalls.max is your cap (4 on the Free tier). See Check it is up for the other fields, then make the first call as in First call.
Upgrade
Wait until concurrentCalls.active in /health is 0, then pull and recreate with the same hostname:
docker pull vexyl/vexyl-voice-gateway:1.7.0
docker compose up -d
With plain docker run, run docker stop vexyl-gateway && docker rm vexyl-gateway and start it again with the same command and --hostname. Your mounts and .env are untouched. To roll back, start the previous tag the same way.
Logs
docker logs -f vexyl-gateway
If the container exits at once, run docker logs vexyl-gateway and look first for a permission error on a mounted directory: the host directory must belong to uid 1000.
Next: Configuration recipes.