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-manager needs 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

voipbin.test (IANA reserved TLD, RFC 2606)

Your real domain (for example example.com)

DNS

Automatic. CoreDNS container plus /etc/resolv.conf forwarding

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 !reset/!override merge tags that need this version or later.

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.sh enforces 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 .env (MYSQL_ROOT_PASSWORD)

RabbitMQ

Randomly generated per install, in .env (RABBITMQ_DEFAULT_USER / RABBITMQ_DEFAULT_PASS)

JWT signing key

Auto-generated in .env

Admin account and extensions (opt-in)

admin@localhost / admin@localhost, extensions 1000, 2000, 3000. Only created when VOIPBIN_SANDBOX_DEV_SEED=true is set. Never set this on an install reachable from the public internet.

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

start, stop, restart, status/ps, logs

Debug shells

ast (Asterisk CLI), kam (Kamailio kamcmd), db (MySQL), api (authenticated REST client)

Extension management

ext list/create/delete

Infrastructure

dns status/test/regenerate, network status/setup, certs status/trust

Resource management

customer, agent, billing, number, registrar, call, conference, flow, campaign, queue, and more

Maintenance

version, update, backup, restore, rollback, clean, config

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

api.<domain>

A

Host IP

REST API + WebSocket (:8443)

admin.<domain> / meet.<domain> / talk.<domain>

A

Host IP

Web UIs (:3003/:3004/:3005)

sip.<domain>

A

Kamailio external IP

SIP signaling / WSS (:5060/:5066)

sip-service.<domain>, conference.<domain>, trunk.<domain>, pstn.<domain>

A

Kamailio external IP

SIP surfaces

registrar.<domain>

A

Kamailio external IP

Apex registrar name, not covered by the wildcard below

*.registrar.<domain>

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

OPENAI_API_KEY (restart ai-manager)

LLM / conversation (real-time voice pipeline)

OpenAI, Google Gemini, xAI

OPENAI_API_KEY, GOOGLE_API_KEY, XAI_API_KEY (restart pipecat-manager)

Speech-to-text

Deepgram, AWS Transcribe, Google Cloud Speech-to-Text

DEEPGRAM_API_KEY; AWS_ACCESS_KEY/AWS_SECRET_KEY; GOOGLE_APPLICATION_CREDENTIALS (restart transcribe-manager)

Text-to-speech

ElevenLabs, Cartesia, AWS Polly, Google Cloud TTS

ELEVENLABS_API_KEY, CARTESIA_API_KEY; AWS_ACCESS_KEY/AWS_SECRET_KEY; GOOGLE_APPLICATION_CREDENTIALS (restart tts-manager)

GOOGLE_API_KEY and GOOGLE_APPLICATION_CREDENTIALS are two unrelated variables that happen to share the “Google” prefix. Do not confuse them:

  • GOOGLE_API_KEY is 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 use GOOGLE_API_KEY for this) and pipecat-manager’s real-time voice pipeline (the sidecar that actually reads GOOGLE_API_KEY directly for live Gemini conversations). Restart pipecat-manager after changing it for the voice-pipeline use.

  • GOOGLE_APPLICATION_CREDENTIALS is 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, and tts-manager. storage-manager treats a missing value as fatal for signed download/upload URLs; tts-manager falls back to AWS Polly if unset. pipecat-manager does not consume this variable in the Docker Compose stack (it only uses GOOGLE_API_KEY, above) – restarting pipecat-manager for a GOOGLE_APPLICATION_CREDENTIALS change 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.

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 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

Shared GCP storage and project variables

Separately from GOOGLE_APPLICATION_CREDENTIALS above, four more .env.template variables use the GCP_ prefix. Unlike every other provider on this page, one service (rag-manager) treats two of them as required at boot, not optional:

Var

Consuming service(s)

Behavior if unset

GCP_PROJECT_ID

api-manager, rag-manager, storage-manager

rag-manager fails to start (hard validation error); api-manager and storage-manager degrade gracefully

GCP_REGION

rag-manager

Fails to start (hard validation error)

GCP_BUCKET_NAME_MEDIA

asterisk-call-proxy, asterisk-conference-proxy, call-manager, rag-manager, storage-manager

Optional for every consumer, including rag-manager itself; the three call-recording consumers have a working built-in default bucket name. rag-manager’s boot failure below is driven only by GCP_PROJECT_ID/GCP_REGION, not this var

GCP_BUCKET_NAME_TMP

api-manager, storage-manager

Both degrade gracefully

rag-manager’s boot failure is specific to its own startup validation, not a property of these variables in general: the same variables consumed by the other listed services do not crash those services if left empty. init.sh writes non-functional placeholder values (sandbox-placeholder, us-central1, sandbox-placeholder-media) that satisfy this startup check without granting real GCP access, so rag-manager stays running, but RAG ingestion and query calls fail against Vertex AI and Cloud Storage until real values are set in .env.

sudo ./voipbin restart rag-manager           # GCP_PROJECT_ID, GCP_REGION, GCP_BUCKET_NAME_MEDIA
sudo ./voipbin restart api-manager           # GCP_PROJECT_ID, GCP_BUCKET_NAME_TMP
sudo ./voipbin restart storage-manager       # GCP_PROJECT_ID, GCP_BUCKET_NAME_MEDIA, GCP_BUCKET_NAME_TMP
sudo ./voipbin restart asterisk-call         # GCP_BUCKET_NAME_MEDIA (also restarts its -proxy sidecar)
sudo ./voipbin restart asterisk-conference   # GCP_BUCKET_NAME_MEDIA (also restarts its -proxy sidecar)
sudo ./voipbin restart call-manager          # GCP_BUCKET_NAME_MEDIA

Telephony and messaging providers

Provider

Env var(s)

Consuming service

Twilio (PSTN numbers)

TWILIO_SID, TWILIO_API_KEY

number-manager

Telnyx (PSTN numbers and messaging)

TELNYX_API_KEY, TELNYX_CONNECTION_ID, TELNYX_PROFILE_ID (all three required)

number-manager

MessageBird (SMS)

MESSAGEBIRD_API_KEY

message-manager

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

SENDGRID_API_KEY

email-manager

Mailgun

MAILGUN_API_KEY

email-manager

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

api_host

localhost

API hostname

api_port

8443

API port

log_lines

50

Number of log lines to display

colors

True

Enable colored output

asterisk_container

voipbin-ast-call

Default Asterisk container for the ast debug shell

Environment variables and maintenance

Key .env variables

Variable

Purpose

DOMAIN_MODE

internal (default) or external. A missing key means internal.

BASE_DOMAIN

The base domain all derived domain values are composed from. Edit and re-run init rather than editing derived vars directly.

TLS_MODE

mkcert, selfsigned, or byo.

COMPOSE_PROFILES

internal-dns in internal mode (enables CoreDNS), empty in external mode, web-proxy when the reverse proxy is enabled.

WEB_REVERSE_PROXY

true enables the built-in Caddy reverse proxy (external mode, --tls byo only).

HOST_EXTERNAL_IP

Host’s LAN or public IP (auto-detected).

KAMAILIO_EXTERNAL_IP / RTPENGINE_EXTERNAL_IP

Dedicated external IPs for SIP signaling and RTP media (auto-generated; must differ from the host IP).

DOMAIN_NAME_EXTENSION

SIP domain suffix for extensions. Full realm is {customer_id}.{DOMAIN_NAME_EXTENSION}.

VOIPBIN_SANDBOX_DEV_SEED

true to opt in to dev/test account seeding on start. Off by default; never enable on a public install.

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

version [--json]

Show pinned image versions

update [images/scripts/all]

Update Docker images or scripts. update all on a pinned repo runs the full safe upgrade: backup, git pull, migrate, recreate, verify.

update --check

Dry-run to preview updates

backup

Full data backup (MySQL, call recordings, .env, certificates, versions.lock, a manifest.json) into backups/<timestamp>/

restore <timestamp> --force

Restore data from a backup. Destructive; services must be stopped except db/redis.

rollback [timestamp]

Roll back image versions from override history (unpinned repos only). For data recovery use restore.

clean [options]

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 an rsync cron 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.