Installation¶
Note
AI Context
This page documents how to self-host the full VoIPBin platform on your
own server using Docker Compose, via the official installer at
https://github.com/voipbin/voipbin (the install/ directory). It is
intended for operators running their own VoIPBin instance, not for
customers of the hosted service at https://voipbin.net.
VoIPBin is an opensource CPaaS platform. The complete stack, including the SIP edge (Kamailio, RTPEngine), the Asterisk media layer, all backend microservices, the database, the message bus, and the admin/talk/meet frontends, runs as a single Docker Compose project on one server.
This section walks through what you need, the four commands that perform the install, the two install modes (a local/LAN evaluation domain or your own real domain), what configuration the installer generates, and the day-to-day CLI used for backup, restore, and upgrades.
Overview¶
The installer is a Docker Compose stack driven by four shell scripts and,
after install, the voipbin interactive CLI. It runs the complete
VoIPBin platform, roughly 50 containers, on a single server.
What gets deployed¶
32 of VoIPBin’s 33 backend Go microservices (
bin-sentinel-managerneeds a Kubernetes API and stays out of this Compose-based install).SIP/media stack: Kamailio (SIP proxy), RTPEngine (RTP media relay), three Asterisk instances (call, registrar, conference) each with an AMI/ARI proxy sidecar.
Supporting infrastructure: MySQL, Redis, RabbitMQ, PostgreSQL (pgvector, for
rag-manager), ClickHouse (timeline-manager), CoreDNS (internal mode only).Three frontend web applications: Admin Console, Talk (agent messenger), Meet (voice conferencing).
Install modes¶
The installer runs in one of two modes, chosen at init time and
recorded in .env as DOMAIN_MODE. Mode is an init-time decision:
extension SIP realms embed the base domain in the database, so init.sh
refuses to switch mode or domain on an existing install.
Internal mode (default) |
External mode |
|
|---|---|---|
Base domain |
|
Your real domain (for example |
DNS |
Automatic. CoreDNS container plus |
Operator-managed A records at your DNS provider |
TLS |
Automatic. mkcert (browser-trusted) or self-signed |
Bring your own certificate |
Reachable from |
This machine and your LAN |
Any host that can route to your IPs |
Best for |
Local development, demos, evaluation |
A production or shared install under a real domain |
Use internal mode unless you specifically need the install reachable under a real domain from machines you do not control. Internal mode requires nothing from you (no domain, no certificate, no DNS provider). External mode targets directly-routable hosts (on-prem, corporate LAN with internal DNS, cloud environments with multiple routable IPs); single-public-IP NAT environments are a documented limitation.
The four-command install¶
The recommended path isolates the one step that needs root:
git clone https://github.com/voipbin/voipbin.git
cd voipbin/install
./scripts/init.sh --yes # 1. Generate .env, certificates, docker-compose.yml
sudo ./scripts/setup-host.sh # 2. The single sudo command (host mutations)
./scripts/start.sh # 3. Start all services
./scripts/check-install.sh # 4. Self-verify the install
setup-host.sh owns every host mutation: mkcert package and CA trust
(internal mode only), CoreDNS setup (internal mode only), the Compose
default Docker network, and the VoIP network interfaces (internal veth
pairs, plus external macvlan for pinned hosting-provider IPs). It is
idempotent; each step probes current state and skips what is already done.
Once installed, sudo ./voipbin is the interactive CLI for day-to-day
operations: status, logs, restart, debug shells, backup/restore, and
version upgrades. See the Install section for the full command reference.
Prerequisites¶
Local tools¶
Install these on the server that will run the stack. The preflight step
in init.sh verifies Docker is present.
Tool |
Min. version |
Notes |
|---|---|---|
Docker Engine |
n/a |
Required. |
Docker Compose |
2.24.4+ |
v2 CLI plugin. The test override file uses
|
mkcert |
n/a |
Recommended (internal mode). Falls back to self-signed certificates if absent. |
No host Python, alembic, or MySQL client is required: database migrations
run inside a container (scripts/migrate.sh, python:3.11-slim on
the Compose network).
Installing prerequisites¶
Ubuntu/Debian:
sudo apt update && sudo apt install -y docker.io docker-compose-v2
sudo usermod -aG docker $USER && newgrp docker
sudo apt install -y mkcert
mkcert -install
macOS:
brew install --cask docker
brew install mkcert
mkcert -install
mkcert -install adds a local Certificate Authority to your system
trust store, so browsers trust the certificates the installer generates
for *.voipbin.test without a warning.
System requirements¶
OS: Linux (Ubuntu/Debian tested) or macOS.
Disk space:
doctor.shenforces a hard minimum of 3 GiB free and warns under 15 GiB. Budget more over time for call recordings and database growth.CPU/RAM: not automatically checked. The stack is roughly 50 containers: 32 backend Go microservices, the SIP/media stack (Kamailio, RTPEngine, 3x Asterisk plus their AMI/ARI proxy sidecars), supporting infrastructure (MySQL, Redis, RabbitMQ, PostgreSQL, ClickHouse, CoreDNS), and 3 frontend apps. A laptop-class multi-core machine with a few GB of RAM headroom works for development; a single-vCPU, 1 GB VM will not keep up.
Networking (external mode only): a directly-routable host with distinct IPs for the host, Kamailio, and RTPEngine. See the Install section’s External mode instructions below.
Credentials generated at install time¶
init.sh generates fresh, random credentials for this install; nothing
is shipped as a shared default.
Service |
Credentials |
|---|---|
MySQL |
Randomly generated per install, in |
RabbitMQ |
Randomly generated per install, in |
JWT signing key |
Auto-generated in |
Admin account and extensions (opt-in) |
|
Before exposing an install beyond localhost, review the TLS mode, the
firewall and network exposure of the ports this stack opens, and confirm
VOIPBIN_SANDBOX_DEV_SEED is unset or false.
Install¶
The recommended path is the four-command flow, then day-to-day operation
through the voipbin CLI.
git clone https://github.com/voipbin/voipbin.git
cd voipbin/install
./scripts/init.sh --yes # 1. Generate .env, certificates, docker-compose.yml
sudo ./scripts/setup-host.sh # 2. The single sudo command (host mutations)
./scripts/start.sh # 3. Start all services
./scripts/check-install.sh # 4. Self-verify the install
init.sh (generate configuration)¶
Generates .env, TLS certificates, and copies docker-compose.yml
from the committed docker-compose.yml.dist. After this first copy,
docker-compose.yml is untracked and operator-owned: a later
git pull in this repo updates docker-compose.yml.dist but never
touches your live file. The same split applies to versions.lock /
versions.lock.dist.
./scripts/init.sh --yes
# external mode (real domain, bring-your-own certificate)
./scripts/init.sh --mode external --domain example.com --tls byo \
--cert fullchain.pem --key privkey.pem --yes
setup-host.sh (the single sudo command)¶
Owns every host mutation: mkcert package and CA trust (internal mode only), CoreDNS setup (internal mode only), the Compose default Docker network, and the VoIP network interfaces. Idempotent; safe to rerun.
sudo ./scripts/setup-host.sh
start.sh (bring the stack up)¶
Starts infrastructure, runs database migrations inside a container, and starts all services.
./scripts/start.sh
check-install.sh (self-verify)¶
Verifies service counts, DNS resolution, API liveness, and (external mode) the TLS chain strictly, without skipping validation.
./scripts/check-install.sh
If anything fails at any of the four steps, run ./scripts/doctor.sh: a
read-only diagnostic that works at any stage and prints the exact recovery
command for every failure. See the Troubleshooting section below.
Once installed, sudo ./voipbin is the interactive CLI for day-to-day
operations.
sudo ./voipbin
voipbin> status
voipbin> logs -f api-manager
Or run single commands directly: sudo ./voipbin status.
Command categories:
Category |
Examples |
|---|---|
Service control |
|
Debug shells |
|
Extension management |
|
Infrastructure |
|
Resource management |
|
Maintenance |
|
See the Environment variables and maintenance section below for the full maintenance command reference (backup, restore, upgrade).
External mode (real domain)¶
External mode runs the install under a real domain with a real certificate. The installer never touches DNS or the trust store in this mode; you own both.
Step 1: create DNS records¶
Record |
Type |
Target |
Purpose |
|---|---|---|---|
|
A |
Host IP |
REST API + WebSocket (:8443) |
|
A |
Host IP |
Web UIs (:3003/:3004/:3005) |
|
A |
Kamailio external IP |
SIP signaling / WSS (:5060/:5066) |
|
A |
Kamailio external IP |
SIP surfaces |
|
A |
Kamailio external IP |
Apex registrar name, not covered by the wildcard below |
|
A |
Kamailio external IP |
Per-customer SIP realm resolution |
Host IP and Kamailio IP are two distinct addresses on the same subnet.
Step 2: obtain a certificate¶
The certificate must cover api., sip., sip-service.,
conference., trunk., and registrar. of your domain; a wildcard
covers all six. A wildcard requires the DNS-01 challenge:
certbot certonly --preferred-challenges dns --manual \
-d example.com -d '*.example.com' -d '*.registrar.example.com'
Step 3: initialize¶
./scripts/init.sh --mode external --domain example.com --tls byo \
--cert /etc/letsencrypt/live/example.com/fullchain.pem \
--key /etc/letsencrypt/live/example.com/privkey.pem \
--yes
The certificate is validated (key match, SAN coverage, expiry) before
.env is written; a bad certificate aborts cleanly.
Step 4: host setup, start, and verify¶
sudo ./scripts/setup-host.sh
./scripts/start.sh
./scripts/check-install.sh
Certificate renewal¶
install-certs.sh is idempotent and usable as a certbot deploy hook:
certbot renew --deploy-hook \
'/path/to/install/scripts/install-certs.sh /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem'
Warning
By default, the admin/meet/talk web UIs are plain HTTP on
ports 3003 to 3005 in both modes. On a routable domain this means
credentials travel in the clear. Front them with a TLS-terminating
reverse proxy, restrict those ports to trusted networks, or use the
built-in --web-reverse-proxy flag described next.
Web reverse proxy (port-less URLs)¶
init.sh --web-reverse-proxy (external mode with --tls byo only)
runs a Caddy container that terminates TLS with your certificate and
routes api/admin/meet/talk.<domain> by Host header, so
https://admin.example.com works with no port suffix:
./scripts/init.sh --mode external --domain example.com --tls byo \
--cert fullchain.pem --key privkey.pem \
--web-reverse-proxy --yes
sudo ./scripts/setup-host.sh
./scripts/start.sh
./scripts/check-install.sh
The certificate must additionally cover admin, meet, and talk
(a wildcard already does). Once enabled, admin/meet/talk’s
published ports become loopback-only; Caddy’s 80/443 is the only
externally-reachable path to them.
First login and API access¶
A completed install has a running stack, but no way to sign in yet unless you opted in to the dev-seed account. This section covers how to create a real admin account and start calling the API.
Warning
The admin@localhost account and extensions 1000, 2000,
and 3000 (three specific extensions, not a range) only exist when
the stack was started with VOIPBIN_SANDBOX_DEV_SEED=true. Leave
this flag unset or false on any install reachable beyond
localhost; see the credentials table in Prerequisites.
If your first login attempt fails outright (curl connection errors, browser certificate warnings), confirm TLS trust first: see Troubleshooting’s DNS/certificate checks and, for internal mode, the mkcert CA trust step in Prerequisites. A first-login failure is very often a certificate problem, not an auth problem.
Two ways to create your first account¶
Path A: CLI bootstrap (recommended for a fresh self-hosted install). Works immediately, no email provider required.
Path B: email-based signup, the same flow the hosted service at
voipbin.net uses (POST /auth/signup). This only works if you have
already configured an email provider (SENDGRID_API_KEY or
MAILGUN_API_KEY, see Provider configuration) so the verification
email can actually be delivered. Without one, the signup call succeeds
but the verification email never arrives. Use this path only if you have
already set up email delivery; otherwise use Path A.
Path A: CLI bootstrap¶
Create a customer and an admin agent directly via the manager CLIs, then set the agent’s password explicitly (a fresh admin agent is created with a random, unusable password):
# 1. Create the customer
docker exec voipbin-customer-mgr /app/bin/customer-control customer create \
--name "My Company" \
--email "admin@example.com"
# 2. Get the customer ID
CUSTOMER_ID=$(docker exec voipbin-customer-mgr /app/bin/customer-control customer list 2>/dev/null \
| jq -r '.[] | select(.email == "admin@example.com") | .id')
# 3. Wait a few seconds for agent-manager to process the event via RabbitMQ
sleep 5
# 4. Get the auto-created admin agent's ID
AGENT_ID=$(docker exec voipbin-agent-mgr /app/bin/agent-control agent list \
--customer-id "$CUSTOMER_ID" 2>/dev/null | jq -r '.[0].id')
# 5. Set a real password
docker exec voipbin-agent-mgr /app/bin/agent-control agent update-password \
--id "$AGENT_ID" \
--password "your-secure-password"
Get a JWT token¶
curl -sk -X POST https://api.voipbin.test:8443/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin@example.com", "password": "your-secure-password"}'
# Response: {"username": "...", "token": "JWT_TOKEN_HERE"}
Create an API key (accesskey)¶
An accesskey is a long-lived API token, separate from the short-lived JWT above, meant for scripts and integrations:
docker exec voipbin-customer-mgr /app/bin/customer-control accesskey create \
--customer-id "$CUSTOMER_ID" \
--name "API Key" \
--detail "For automation" \
--expire 87600h
--expire 87600h is roughly 10 years, a practically non-expiring key
for automation use. Use a shorter value for keys you intend to rotate.
Create your first extension¶
TOKEN="<jwt_token_from_above>"
curl -sk -X POST https://api.voipbin.test:8443/v1.0/extensions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"extension": "2000", "password": "your-secure-password-here", "name": "Extension 2000"}'
Path B: email-based signup¶
Once an email provider is configured (see Provider configuration), the
hosted-service signup flow works identically for a self-hosted install:
POST /auth/signup against your own api.<domain> endpoint instead
of api.voipbin.net. See the Quickstart guide’s Signup page for the
full request/response shape and troubleshooting; the mechanics are
identical, only the base URL differs.
Provider configuration¶
All third-party providers are optional. Until a provider’s keys are set,
the corresponding flow actions return a “provider not configured” error;
the rest of the platform stays healthy. This page lists every provider
env var in .env, what it enables, and which service to restart after
setting it. All facts below are verified directly against this
installer’s own docker-compose.yml.dist (the Docker Compose
self-hosting stack); a Kubernetes/hosted-service deployment wires some
of these variables differently and is out of scope for this page.
AI voice agent providers¶
Capability |
Providers |
Env var(s) |
|---|---|---|
LLM / conversation (batch, e.g. audit evaluation) |
OpenAI |
|
LLM / conversation (real-time voice pipeline) |
OpenAI, Google Gemini, xAI |
|
LLM / conversation (real-time voice pipeline, platform-managed models) |
OpenRouter (serves the Anthropic Claude, Meta Llama, DeepSeek, Qwen,
and Mistral models that |
|
LLM / conversation (real-time voice pipeline, local models) |
Self-hosted OpenAI-compatible servers (vLLM, llama.cpp, Ollama, and similar) |
|
Speech-to-text |
Deepgram, AWS Transcribe, Google Cloud Speech-to-Text |
|
Text-to-speech |
ElevenLabs, Cartesia, AWS Polly, Google Cloud TTS |
|
GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are two
unrelated variables that happen to share the “Google” prefix. Do not
confuse them:
GOOGLE_API_KEYis a Gemini API key. It is consumed by two independent features:ai-manager’s batch audit evaluation (mapped internally to a ChatGPT-compatible engine key,ENGINE_KEY_CHATGPT=${OPENAI_API_KEY}, does not actually useGOOGLE_API_KEYfor this) andpipecat-manager’s real-time voice pipeline (the sidecar that actually readsGOOGLE_API_KEYdirectly for live Gemini conversations). Restartpipecat-managerafter changing it for the voice-pipeline use.GOOGLE_APPLICATION_CREDENTIALSis a GCP service-account JSON key file path. It is mounted into five services in this installer’s Docker Compose stack:api-manager,rag-manager,storage-manager,transcribe-manager, andtts-manager.storage-managertreats a missing value as fatal for signed download/upload URLs;tts-managerfalls back to AWS Polly if unset.pipecat-managerdoes not consume this variable in the Docker Compose stack (it only usesGOOGLE_API_KEY, above) – restartingpipecat-managerfor aGOOGLE_APPLICATION_CREDENTIALSchange has no effect.
XAI_API_KEY has no dedicated feature write-up beyond its
.env.template entry; it is consumed by pipecat-manager’s
real-time voice pipeline as an LLM alternative, not by ai-manager.
OpenRouter key for platform-managed models¶
Models that GET /ai_models reports with platform_managed set to
true (Anthropic Claude, Meta Llama, DeepSeek, Qwen, and Mistral) do
not use the per-AI engine_key that a user enters. In the real-time
voice pipeline they are served through OpenRouter with a key that you,
the operator, supply once as OPENROUTER_API_KEY in .env. Notes
on how it behaves in this installer’s Docker Compose stack:
The variable is passed to the
pipecat-script-runnerservice only.pipecat-manager(the Go service) does not read it, so restartingpipecat-managerfor this key has no effect. Restartpipecat-script-runnerinstead.The installer writes
OPENROUTER_API_KEY=empty by default. With an empty key these models stay selectable in the catalog (there is no global on/off switch), but a session that uses one fails when the runner initializes the LLM service, with the errorOpenRouter is not configured. Other models (OpenAI, Gemini, xAI) are not affected.A non-empty but invalid key is not detected at startup. The first request is rejected by OpenRouter with HTTP 401, which the runner classifies as an authentication error, the same as an invalid key for any other LLM provider.
Requests are sent with OpenRouter’s zero data retention routing and
data_collectionset todeny, and only to endpoints that support tool calling. This is fixed and not configurable.pipecat-script-runneris pinned by image digest indocker-compose.yml.dist. The key has an effect only after the runner image that contains the platform-managed model support is adopted (for example withscripts/sync-compose-images.sh). Setting the key while the pinned image is older does nothing.
Local models (self-hosted OpenAI-compatible servers)¶
You can register models that run on your own server, such as vLLM,
llama.cpp or Ollama, as long as the server implements the OpenAI chat
completions API. Registered models appear in GET /ai_models with vendor
Local and an id of local.<endpoint>.<model>. Users only pick the
model; the server address and key are never shown. Private network
addresses and plain http are allowed, because the server is inside your
trust boundary. Nothing is listed until you set the variables below. There is
no separate on/off switch. This is not the same as the OpenRouter models above:
platform_managed only says that you, the operator, supply the connection.
Two variables, kept apart on purpose so that the secret stays in the runner
only. Write each value as compact JSON (no space after a colon or comma) and
write a $ character as $$ in Docker Compose.
LOCAL_LLM_ENDPOINTS(secret,pipecat-script-runneronly): a JSON object that maps an endpoint name to its connection, for example{"vllm-a":{"base_url":"http://llm.internal:8000/v1","api_key":""}}. The name is lower case letters, digits and-(up to 32 characters).base_urlishttporhttpswithout user information, query or fragment. An emptyapi_keyis allowed.LOCAL_LLM_MODELS(no secret,ai-managerandpipecat-manager): a JSON list, for example[{"endpoint":"vllm-a","id":"qwen3-8b","label":"Qwen3 8B","capabilities":["tool-calling"],"default":false}].idmay contain letters, digits and. _ : / -.labelis ASCII only (up to 64 characters).capabilitiesmay containtool-callingandlow-cost; they are informational. At most one entry should setdefaulttotrue; it becomes the onlyrecommendedmodel. At most 200 models are read. Invalid entries are skipped and logged as an error (counts only); the service still starts, so check the log after a change. The endpoint name,idandlabelare shown to users, so do not put internal host names in them.
To enable: set both variables, redeploy pipecat-manager and
pipecat-script-runner together (same image), then redeploy
ai-manager last. To remove a model: move the AIs that use it to another
model first, then redeploy ai-manager first and the pipecat services
afterwards. A session that uses a removed or unreachable model fails. Do not set
OPENAI_ORG_ID or OPENAI_PROJECT_ID on the runner: the OpenAI client
would send them to your server. If the server cannot be reached, a voice session does
not tell the caller; the failure is recorded on the AI call as a
pipeline_error message with category connection.
Customer-specified endpoints (customendpoint. models)¶
Users can also point an AI at their own OpenAI-compatible server by setting
engine_model to customendpoint.<model-id> and engine_base_url
through the API. The optional key for that server (engine_base_key) is
stored encrypted by ai-manager. This needs one variable, which only
ai-manager reads:
ENGINE_BASE_KEY_ENCRYPTION_KEYS(secret): a comma separated list of<version>:<base64 32 bytes>entries, for example1:<base64 of 32 random bytes>or1:<key1>,2:<key2>. The version is a number from 1 to 65535. The highest version encrypts new keys, and every listed version can decrypt. This is a separate key set fromMCP_SECRET_ENCRYPTION_KEYS.
Generate a key with openssl rand -base64 32 and write it with its
version, for example ENGINE_BASE_KEY_ENCRYPTION_KEYS=1:<output>. Keep the
value in your secret store. Then restart ai-manager:
sudo ./voipbin restart ai-manager # ENGINE_BASE_KEY_ENCRYPTION_KEYS
Behavior to know about:
A malformed value stops
ai-managerfrom starting. An empty value lets it start with a warning, and a request that stores a key then fails with a server error. Endpoints without a key keep working.If you lose the key set, the stored endpoint keys can no longer be decrypted. This fails closed: those AIs are not sent any key and calls that need one fail, until each customer enters the key again. There is no other recovery.
To rotate, add a new entry with a higher version to the list on every
ai-managerreplica first, restart all of them, and keep the old versions listed until no stored key uses them. A key is re-encrypted with the highest version when the customer sends it again. During a rotation a replica that does not yet have the new version cannot decrypt keys written with it, and calls that start in that period fail closed, so rotate in a quiet window.If you deploy from a separate install repository, add the same variable to its
.env.templateand pass it to theai-managerservice there too. A missing variable in that repository means the customers’ endpoint keys cannot be stored.The platform connects to a customer’s address only over
httpsand only when it resolves to a public address, so a private or loopback address cannot be used for this feature. Use thelocal.models above for a server inside your own network. If the server cannot be reached, the AI records apipeline_errormessage with categoryconnection.
After setting an AI provider key, restart the service(s) that actually consume that specific variable, per the tables above:
sudo ./voipbin restart ai-manager # OPENAI_API_KEY (batch)
sudo ./voipbin restart pipecat-manager # OPENAI_API_KEY, GOOGLE_API_KEY, XAI_API_KEY (real-time)
sudo ./voipbin restart pipecat-script-runner # OPENROUTER_API_KEY (platform-managed models)
sudo ./voipbin restart transcribe-manager # DEEPGRAM_API_KEY, AWS_*, GOOGLE_APPLICATION_CREDENTIALS
sudo ./voipbin restart tts-manager # ELEVENLABS_API_KEY, CARTESIA_API_KEY, AWS_*, GOOGLE_APPLICATION_CREDENTIALS
sudo ./voipbin restart rag-manager # GOOGLE_APPLICATION_CREDENTIALS
sudo ./voipbin restart storage-manager # GOOGLE_APPLICATION_CREDENTIALS
sudo ./voipbin restart api-manager # GOOGLE_APPLICATION_CREDENTIALS
Telephony and messaging providers¶
Provider |
Env var(s) |
Consuming service |
|---|---|---|
Twilio (PSTN numbers) |
|
|
Telnyx (PSTN numbers and messaging) |
|
|
MessageBird (SMS) |
|
|
The .env.template variable names above are what you set;
number-manager itself binds them internally as TWILIO_TOKEN and
TELNYX_TOKEN (docker-compose.yml.dist maps
TWILIO_TOKEN=${TWILIO_API_KEY} and
TELNYX_TOKEN=${TELNYX_API_KEY}), and message-manager binds
MESSAGEBIRD_API_KEY internally as AUTHTOKEN_MESSAGEBIRD. This
only matters if you go looking for the raw variable name inside
number-manager’s or message-manager’s own config; the .env
names above are what you actually set. MessageBird has no dedicated
feature write-up beyond its .env.template entry.
sudo ./voipbin restart number-manager
sudo ./voipbin restart message-manager
Email providers¶
Provider |
Env var |
Consuming service |
|---|---|---|
SendGrid |
|
|
Mailgun |
|
|
sudo ./voipbin restart email-manager
Configuring at least one of these two is required for the email-based signup flow (Path B in First login and API access) to actually deliver its verification email.
Configuration files¶
The installer writes and manages two operator-owned files in the
install/ working directory.
docker-compose.yml (operator-owned copy of .dist)¶
docker-compose.yml is copied once from the committed
docker-compose.yml.dist the first time init.sh runs, then left
alone: a later git pull in this repo updates docker-compose.yml.dist
but never touches your live docker-compose.yml. This means a repo
update never silently changes a running install’s Compose configuration.
To adopt upstream changes (new services, image digest bumps), diff the two
files and merge deliberately, or run scripts/sync-compose-images.sh
with COMPOSE_FILE=docker-compose.yml to pull in just the image digest
updates from versions.lock.dist.
versions.lock gets the identical treatment for the identical reason:
copied once from versions.lock.dist, then untracked. Deploying a new
image and updating versions.lock.dist are deliberately decoupled; the
live versions.lock is yours to manage on your own schedule.
.env (generated secrets and configuration)¶
init.sh auto-generates .env with detected network settings and
freshly generated credentials (MySQL, RabbitMQ, JWT signing key). See the
Environment variables section below for the full variable reference.
CLI configuration (~/.voipbin-cli.conf)¶
The voipbin CLI stores its own settings separately from .env:
voipbin> config # Show all settings
voipbin> config log_lines 100 # Set log lines to 100
voipbin> config reset # Reset to defaults
Setting |
Default |
Description |
|---|---|---|
|
localhost |
API hostname |
|
8443 |
API port |
|
50 |
Number of log lines to display |
|
True |
Enable colored output |
|
voipbin-ast-call |
Default Asterisk container for the |
Environment variables and maintenance¶
Key .env variables¶
Variable |
Purpose |
|---|---|
|
|
|
The base domain all derived domain values are composed from.
Edit and re-run |
|
|
|
|
|
|
|
Host’s LAN or public IP (auto-detected). |
|
Dedicated external IPs for SIP signaling and RTP media (auto-generated; must differ from the host IP). |
|
SIP domain suffix for extensions. Full realm is
|
|
|
Third-party integrations¶
Voice AI, transcription, TTS, phone number provisioning, and email/SMS
providers are configured through their own keys in .env. See the
Provider configuration section for the full list of variables, which
service to restart after setting each, and the AI-provider-specific
details (shared credentials, multi-service consumers). Until a
provider’s keys are set, the corresponding flow actions return a
“provider not configured” error; the rest of the platform stays healthy.
Maintenance commands (the voipbin CLI)¶
Command |
Description |
|---|---|
|
Show pinned image versions |
|
Update Docker images or scripts. |
|
Dry-run to preview updates |
|
Full data backup (MySQL, call recordings, |
|
Restore data from a backup. Destructive; services must be stopped
except |
|
Roll back image versions from override history (unpinned repos
only). For data recovery use |
|
Cleanup sandbox resources |
Scheduled backups¶
schedule-manager runs a nightly database-backup job (MySQL dump
plus gzip of the two databases, written to backups/scheduled-db/,
retaining the newest 7). start.sh enables it on every run. This is
deliberately separate from the manual voipbin backup command above:
different retention, different directory, so neither one’s pruning
touches the other.
docker exec voipbin-schedule-mgr /app/bin/schedule-control schedule list
docker exec voipbin-schedule-mgr /app/bin/schedule-control schedule disable database-backup
Host-side gaps (operator responsibility)¶
Two tasks stay outside the installer’s automation:
Offsite copy of backups.
backups/is local disk; ship it to remote or object storage on your own recovery-objective schedule (for example anrsynccron job).Host-level maintenance. OS package updates, Docker Engine upgrades, disk space and log rotation, and kernel/security patching are the operator’s responsibility.
Scaling¶
The installer starts every backend service at one replica. Scaling a single-server Compose install means raising the host’s own CPU/RAM and, for the SIP/media layer, the RTPEngine port range and Asterisk channel limits. There is no automated horizontal-scale profile in this installer today; track resource usage and scale the host vertically first.
Operations and troubleshooting¶
Install doctor¶
./scripts/doctor.sh (or sudo ./voipbin doctor) is the read-only
diagnostic superset of check-install.sh. It runs at any stage
(pre-install, mid-install, running stack), never mutates anything, and
auto-skips whatever cannot be probed at the current stage.
Output is one line per check, DOCTOR <name>: pass|fail|warn|skip
<detail>; every failure (and every fixable warning) is immediately
followed by a FIX <name>: <exact command> line:
./scripts/doctor.sh
./scripts/doctor.sh | grep '^FIX ' # extract every recovery command
Exit codes: 0 only when nothing failed (warnings and skips do not
fail the run); 1 when any check failed; 2 when the doctor cannot
even start.
Day-to-day commands¶
# Status and logs
sudo ./voipbin status
sudo ./voipbin logs -f api-manager
# Restart a service (Asterisk call/registrar/conference restarts
# its paired proxy sidecar automatically)
sudo ./voipbin restart api-manager
# Debug shells
sudo ./voipbin ast # Asterisk CLI
sudo ./voipbin kam # Kamailio kamcmd
sudo ./voipbin db # MySQL
# Backup and restore
sudo ./voipbin backup
sudo ./voipbin restore <timestamp> --force
# Tear everything down (irreversible, including the database)
sudo ./voipbin clean --all
Common issues¶
Extensions register but calls fail (AMI/ARI mismatch)¶
AMI_USERNAME/AMI_PASSWORD must stay asterisk/asterisk to
match the static account baked into the Asterisk images’ AMI and ARI
configuration. If these were ever changed, SIP registration keeps working
(that path is pjsip plus realtime MySQL) while call control silently
breaks. Fix by restoring the fixed values in .env and recreating the
three proxy sidecars:
docker compose up -d --force-recreate \
asterisk-call-proxy asterisk-conference-proxy asterisk-registrar-proxy
Extensions created with the wrong domain¶
If extensions register under .voipbin.test instead of
.registrar.voipbin.test, a shell-exported DOMAIN_NAME_EXTENSION
is overriding .env:
env | grep DOMAIN_NAME
unset DOMAIN_NAME_EXTENSION DOMAIN_NAME_TRUNK
docker compose config | grep DOMAIN
docker compose rm -fsv registrar-manager && docker compose up -d registrar-manager
Then delete and recreate the affected extensions via the API.
DNS not resolving *.voipbin.test (internal mode)¶
docker ps | grep voipbin-dns # CoreDNS container running?
dig @127.0.0.1 voipbin.test # CoreDNS answering directly?
dig voipbin.test # system resolver picking it up?
cat /etc/resolv.conf # 127.0.0.1 listed first?
sudo ./scripts/setup-dns.sh # re-run DNS setup
A downed CoreDNS container does not take down all host DNS: a captured
upstream nameserver (from the pre-install resolver, or a hardcoded public
fallback) is written into /etc/resolv.conf alongside 127.0.0.1.
Host IP changed after reboot¶
The installer detects a changed host IP on start.sh and regenerates
.env, the CoreDNS Corefile, and TLS certificates automatically. Force
it manually if needed:
sudo ./scripts/setup-dns.sh --regenerate
If a browser then shows ERR_CERT_AUTHORITY_INVALID, the certificate
was regenerated correctly; do a hard refresh or use a non-incognito
window (incognito does not trust user-installed CAs).
Reset everything¶
sudo ./scripts/clean.sh --volumes --purge
Always use the combined --volumes --purge form. --purge alone
keeps the database volume with the old domain’s data, which is the worst
partial state to be in.
Known limitations¶
Single-public-IP NAT is not supported¶
External mode targets directly-routable hosts (on-prem, corporate LAN with internal DNS, cloud environments with multiple routable IPs). Kamailio runs with host networking and binds its dedicated address directly, so there is no port mapping to remap it behind a single public IP shared with other services. See Overview for the internal-mode versus external-mode comparison; internal mode has no domain, certificate, or routability requirements and works behind any NAT.
Restart the Asterisk service and its proxy sidecar together¶
asterisk-call, asterisk-conference, and asterisk-registrar
each pair with a dedicated -proxy sidecar that bridges AMI/ARI to
the rest of the stack. Recreating one side without the other leaves the
AMI/ARI bridge orphaned: SIP registration can keep working (it goes
through pjsip and realtime MySQL, not the proxy) while call control
silently breaks, reproducing the same symptom as the AMI/ARI credential
mismatch above. sudo ./voipbin restart <service> already restarts a
service and its paired proxy together; avoid running
docker compose restart directly on just one side of the pair.
Getting help¶
If doctor.sh does not resolve the issue, open an issue at
https://github.com/voipbin/voipbin or contact support@voipbin.net with
the full output of ./scripts/doctor.sh.