Self-Hosting¶
Note
AI Context
This page documents how to deploy a full VoIPBin platform on your own Google Cloud Platform project using the official installer at https://github.com/voipbin/install. 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, can be installed into a Google Cloud Platform project with a single CLI.
This section walks through what you need, the three commands that perform the install, what configuration the installer generates, and which environment variables you typically need to adjust before the platform is ready for real traffic.
Overview¶
The installer is a three stage pipeline driven by the
voipbin-install CLI.
Terraform provisions GCP infrastructure: a custom VPC, GKE cluster, Cloud SQL (MySQL 8.0), Kamailio and RTPEngine VMs, DNS zone, load balancers, KMS key ring for SOPS, and GCS buckets.
Ansible configures the Kamailio and RTPEngine VMs over an IAP tunnel and renders their Docker Compose
.envfiles from templates.Kubernetes applies the manifests under
k8s/to the GKE cluster with placeholders substituted from Terraform outputs and SOPS-decrypted secrets.
What gets deployed¶
A successful install produces:
VPC and connectivity: custom VPC
10.0.0.0/16, Cloud NAT with a static IP for outbound, Cloud Router, eight firewall rules covering SIP, RTP, IAP SSH, GKE internal, and health checks.GKE cluster: zonal or regional, 2 nodes of
n1-standard-2by default, private nodes, shielded instances, COS_CONTAINERD image, REGULAR release channel.Kamailio VM: 1x
f1-microby default, SIP/TLS/WSS proxy reached through an external network load balancer.RTPEngine VM: 1x
f1-microby default, RTP media relay with a static external IP for direct media paths.Cloud SQL MySQL 8.0:
db-f1-microinstance, SSL required, daily automated backups, Cloud SQL Proxy deployed as a sidecar in GKE.Kubernetes workloads: backend microservices in the
bin-managernamespace, Asterisk invoip, Redis, RabbitMQ, ClickHouse, and Cloud SQL Proxy ininfrastructure, plus three frontend apps (square-admin,square-talk,square-meet) and an Ingress fronted by TLS certificates.
Cost¶
Estimated monthly costs for the minimal default sizing in
us-central1:
Resource |
Cost (USD) |
|---|---|
GKE control plane |
$0 zonal, ~$73 regional |
GKE nodes (2x n1-standard-2) |
~$97 |
Kamailio VM (f1-micro) |
~$6 |
RTPEngine VM (f1-micro) |
~$6 |
Cloud SQL (db-f1-micro) |
~$13 |
Cloud NAT |
~$10 |
External IPs |
~$8 |
Network load balancers |
~$20 |
DNS, GCS, KMS, disks |
~$6 |
Total |
~$170 zonal, ~$243 regional |
Costs vary by region. The figures above add up the line items in the
table and are list-price snapshots, not committed-use prices; the
installer’s README.md may show a slightly different headline figure
depending on which sizing snapshot it was last regenerated against. You
are responsible for all charges incurred on your GCP project.
Warning
./voipbin-install destroy is irreversible. It permanently deletes
every resource the installer created, including the Cloud SQL instance
and its backups. Export any data you need before destroying.
Prerequisites¶
Local tools¶
Install these on the workstation that will run the installer. The
preflight step in voipbin-install init verifies each version.
Tool |
Min. version |
Notes |
|---|---|---|
gcloud CLI |
400.0.0 |
Both |
terraform |
1.5.0 |
Bundled under |
ansible |
2.15.0 |
Install via |
kubectl |
1.28.0 |
Used by the installer and for verification. |
python3 |
3.10.0 |
Required by the installer CLI. |
sops |
3.7.0 |
Encrypts |
Python dependencies are installed with pip install -r requirements.txt
after cloning the installer repo.
GCP account¶
The installer refuses to proceed unless the following are true:
A GCP project exists with billing enabled.
The authenticated principal has Owner or Editor on the project, or the least-privilege set of twelve roles listed in
config/gcp_iam_roles.yaml(Compute Admin, Container Admin, Cloud SQL Admin, DNS Admin, Cloud KMS Admin, Secret Manager Admin, Service Account Admin, Service Account User, Project IAM Admin, Storage Admin, Service Usage Admin, IAP-Secured Tunnel User).You own a domain name. The installer uses
api.<domain>,admin.<domain>,talk.<domain>,meet.<domain>, andsip.<domain>.
GCP quotas¶
The init command checks regional quotas. New GCP projects ship with
8 vCPUs and 8 in-use external IPs, which is below what VoIPBin needs.
Request increases for the following before installing if you do not want
the apply to fail mid stage:
Quota |
Minimum required |
Notes |
|---|---|---|
CPUs (region) |
12 |
2x GKE nodes + 2x VoIP VMs |
In-use external IPs |
10 |
NAT, LBs, RTPEngine static IPs |
Static external IPs |
4 |
usually sufficient by default |
SSD total (GB) |
100 |
usually sufficient by default |
Quota increases are requested at https://console.cloud.google.com/iam-admin/quotas.
GCP APIs¶
The init command automatically enables sixteen APIs on the project:
compute, container, sqladmin, dns, cloudkms,
secretmanager, cloudresourcemanager, iam,
servicenetworking, storage, storage-api, logging,
monitoring, oslogin, serviceusage, and iap.
Two authentications, not one¶
VoIPBin’s installer depends on both of the following being live, and this catches operators by surprise often enough to be worth calling out:
gcloud auth loginfor the human/CLI principal.gcloud auth application-default loginfor Terraform and SOPS, which read Application Default Credentials.
The preflight in init checks both and offers to refresh ADC if it is
missing or expired.
Install¶
The happy path is three commands. They are idempotent and resumable.
git clone https://github.com/voipbin/install.git
cd install
pip install -r requirements.txt
./voipbin-install init # interactive wizard + GCP bootstrap
./voipbin-install apply # provision and deploy
./voipbin-install verify # health check
init (interactive wizard)¶
The init command runs a twelve-step bootstrap, in order:
Preflight checks for the six local tools.
gclouduser auth and Application Default Credentials.Seven wizard questions: GCP project ID, region, GKE cluster type (zonal or regional), TLS strategy (Let’s Encrypt, GCP-managed, self-signed, or BYO certificate), Docker image tag strategy (latest or pinned via
config/versions.yaml), base domain name, and Cloud DNS mode (auto or manual).Project existence and billing.
Quota check against
config/gcp_quotas.yaml.Enable sixteen GCP APIs.
Create the
voipbin-installerservice account with twelve IAM role bindings.Create a KMS key ring and crypto key.
Generate six secrets (
jwt_key,cloudsql_password,redis_password,rabbitmq_user,rabbitmq_password,api_signing_key) and encrypt them intosecrets.yamlwith SOPS bound to the KMS key.Write
.sops.yaml.Save
config.yaml.Print a cost summary.
Useful flags:
./voipbin-install init --reconfigure # re-run the wizard
./voipbin-install init --config path/to.yaml # import existing config
./voipbin-install init --skip-api-enable # APIs already enabled
./voipbin-install init --skip-quota-check # bypass quota gate
./voipbin-install init --dry-run # show what would happen
The two files written are everything you need to reproduce the install:
config.yaml: non-sensitive. Safe to commit if you want a record per environment.secrets.yaml: SOPS-encrypted with GCP KMS. Decrypt locally withsops --decrypt secrets.yaml.
Warning
Back up secrets.yaml and the KMS key ring. If the key ring is
destroyed or the GCP project is deleted, secrets.yaml becomes
unrecoverable.
apply (three-stage deploy)¶
./voipbin-install apply executes the pipeline in order: Terraform,
then Ansible, then Kubernetes. The pipeline checkpoints between stages,
so if a run fails you can fix the problem and rerun the same command;
it picks up from the failed stage.
Useful flags:
./voipbin-install apply --dry-run # plan, do not change
./voipbin-install apply --auto-approve # skip prompts
./voipbin-install apply --stage terraform # only run this stage
./voipbin-install apply --stage ansible
./voipbin-install apply --stage k8s
Expected duration on a fresh project:
Stage |
Duration |
|---|---|
Terraform |
12 to 18 minutes (GKE cluster creation dominates) |
Ansible |
5 to 8 minutes (waits for cloud-init on both VMs) |
Kubernetes |
3 to 5 minutes |
verify (health check)¶
The verify command runs a series of checks against the live
deployment and prints a pass, warn, or fail per check. The current set
of checks covers, in order: GKE cluster status, pod readiness in three
namespaces, service endpoint availability in three namespaces, Kamailio
and RTPEngine VM run state, Cloud SQL instance state, DNS resolution
for api.<domain>, HTTPS reachability of https://api.<domain>/health,
and a TCP socket probe of SIP port 5060. Each check prints the
underlying command on failure so you can rerun individual probes
manually.
Note
The SIP probe uses a TCP socket connect. If you need to verify UDP
reachability for media or signalling, use a separate tool such as
nc -zuv and the GCP firewall log stream.
DNS step¶
After apply, the installer prints the DNS records you must publish if
you chose dns_mode: manual, or the four nameservers to delegate to
if you chose dns_mode: auto. Required records, with
example.com standing in for your domain:
Subdomain |
Type |
Value |
|---|---|---|
|
A |
External LB IP |
|
A |
External LB IP |
|
A |
External LB IP |
|
A |
External LB IP |
|
A |
External LB IP |
DNS propagation can take up to 48 hours for NS delegation changes. Once
records resolve, rerun ./voipbin-install verify.
Configuration files¶
The installer writes two files in the working directory.
config.yaml (non-sensitive)¶
Holds every parameter from the wizard and the defaults applied around
them. Environment variables prefixed with VOIPBIN_ override a value
at runtime, for a single CLI invocation (for example
VOIPBIN_REGION=europe-west1 ./voipbin-install apply).
Typical contents:
gcp_project_id: my-project-123
region: us-central1
zone: us-central1-a
gke_type: zonal
tls_strategy: letsencrypt
image_tag_strategy: pinned
domain: voipbin.example.com
dns_mode: auto
gke_machine_type: n1-standard-2
gke_node_count: 2
vm_machine_type: f1-micro
kamailio_count: 1
rtpengine_count: 1
The schema is enforced by config/schema.py. To change a value after
the initial install, edit config.yaml and rerun
./voipbin-install apply.
secrets.yaml (SOPS-encrypted)¶
Holds the six secrets generated by init, encrypted with GCP KMS via
SOPS:
jwt_key: JWT signing key consumed by the API and webhook layers.cloudsql_password: Cloud SQL root password. The instance is created with this value.redis_password: RedisAUTHpassword.rabbitmq_userandrabbitmq_password. RabbitMQ credentials.api_signing_key: request signing key used between internal services.
Editing a secret:
sops secrets.yaml # opens editor with decrypted view
# edit the field, save
./voipbin-install apply # re-render ConfigMap, Secret, and VM .env
After editing, the Kubernetes ConfigMap, the Kubernetes Secret, and the
Kamailio VM .env are re-rendered consistently in a single apply.
Where each value lands¶
Most of the wizard’s answers and secrets fan out into multiple targets at apply time. The most important paths to know:
Source |
Target type |
Where it ends up |
|---|---|---|
|
Terraform variables |
|
|
Kubernetes ConfigMap |
|
|
Ansible group_vars |
Kamailio VM |
|
Kubernetes Secret |
|
|
Kubernetes Secret (composite) |
|
|
Ansible group_vars |
Kamailio VM |
Environment variables and post-install configuration¶
The defaults produce a deployment that boots and passes verify, but
production workloads need adjustments. This section enumerates what to
touch and where.
2. Generated credentials (RabbitMQ, Redis, MySQL, JWT, API signing)¶
These live in the SOPS-encrypted secrets.yaml and are wired into the
ConfigMap, Secret, and VM .env automatically. Rotate them through
SOPS:
sops secrets.yaml
./voipbin-install apply
3. SIP and PSTN settings¶
These are not part of the wizard. Edit them in the appropriate place and rerun the relevant stage.
PSTN allowlist¶
Kamailio only accepts inbound PSTN traffic from explicitly whitelisted
source IPs. Populate the list with VOIPBIN_PSTN_WHITELIST_IPS and
rerun the Ansible stage:
export VOIPBIN_PSTN_WHITELIST_IPS="203.0.113.10,198.51.100.4"
./voipbin-install apply --stage ansible
The value flows through ansible/group_vars/all.yml into the Kamailio
env template as PSTN_WHITELIST_IPS.
SIP auth backend¶
The four KAMAILIO_AUTH_* variables in the Kamailio env template
default to empty. Production deployments point them at a database that
holds SIP credentials:
KAMAILIO_AUTH_DB_URL: connection string Kamailio uses for the auth database.KAMAILIO_AUTH_USER_COLUMN: defaults tousername.KAMAILIO_AUTH_DOMAIN_COLUMN: defaults torealm.KAMAILIO_AUTH_PASSWORD_COLUMN: defaults topassword.
Set them via Ansible extra vars or by editing
ansible/group_vars/kamailio.yml and rerunning
./voipbin-install apply --stage ansible.
RTPEngine port range¶
The default media port range is 20000-65535. If your network only
opens 20000-30000, set rtpengine_port_max: 30000 in
ansible/group_vars/rtpengine.yml and rerun the Ansible stage. Make
sure the firewall rule matches.
4. TLS certificate strategy¶
The tls_strategy chosen in the wizard governs how certificates are
issued:
letsencrypt: cert-manager issues per-host certificates via HTTP-01 once DNS resolves. DNS must resolve first, otherwise the challenge fails and the certificate sits inPending. Verify withkubectl get certificate -A.gcp-managed: a GCP-managed certificate is attached to the Ingress and provisioned by Google. Same DNS dependency.self-signed: a self-signed certificate is installed immediately. Use only for staging or local-only deployments.byoc: bring your own. Droptls.crtandtls.keyinto thevoipbin-tlsSecret inbin-managerbefore./voipbin-install apply --stage k8s.
To change strategies after install, edit tls_strategy in
config.yaml and rerun apply.
5. Third-party integrations¶
VoIPBin services can integrate with external providers for LLM, ASR,
TTS, email, SMS, and payments. The installer ships with these slots
intentionally empty, because they are operator-specific and not
free. Add them as Kubernetes Secrets in the bin-manager namespace,
then mount them into the relevant deployments.
Provider categories you will likely need to wire up:
An LLM or AI provider.
A speech-to-text provider.
A text-to-speech provider.
An email API or SMTP provider.
An SMS provider.
A payment provider for billing flows.
Pick providers based on your own region, compliance, and pricing
constraints. Until the relevant keys are wired up, the corresponding
flow actions return provider not configured errors; the platform
itself stays healthy and the rest of the channels remain usable.
Recommended pattern: a separate Secret per provider so rotation is independent.
kubectl -n bin-manager create secret generic voipbin-llm \
--from-literal=LLM_API_KEY=...
Then patch the deployment to mount it via envFrom. Track the provider
matrix per service in your own runbook; upstream manifests deliberately
do not enumerate provider keys because the supported list evolves.
6. Scaling¶
The defaults are minimal:
GKE: 2 nodes of
n1-standard-2.Kamailio: 1 VM of
f1-micro.RTPEngine: 1 VM of
f1-micro.Backend deployments: 1 replica each,
50mCPU and64Mimemory request,200mand256Milimits.
For anything beyond a demo, raise the VM types and replica counts.
Adjust gke_machine_type, gke_node_count, vm_machine_type,
kamailio_count, and rtpengine_count in config.yaml and
rerun ./voipbin-install apply.
Backend replica counts live in the per-service manifest under
k8s/backend/services/<name>.yaml. For now, edit those files
directly; a future installer revision will surface scaling profiles
through config.yaml.
Consolidated environment variable map¶
Every variable a fresh install touches comes from one of three sources: the wizard, generated secrets, or operator overrides.
Wizard-sourced (config.yaml)¶
gcp_project_idregion,zonegke_type(zonalorregional)tls_strategy(letsencrypt,gcp-managed,self-signed,byoc)image_tag_strategy(latestorpinned)domaindns_mode(autoormanual)gke_machine_type,gke_node_countvm_machine_type,kamailio_count,rtpengine_count
Any of these can be overridden at runtime with the VOIPBIN_ prefix
(uppercased), for example VOIPBIN_REGION=europe-west1.
Generated and SOPS-encrypted (secrets.yaml)¶
jwt_keycloudsql_passwordredis_passwordrabbitmq_user,rabbitmq_passwordapi_signing_key
Ansible variables (Kamailio VMs)¶
Defined in ansible/group_vars/all.yml and
ansible/group_vars/kamailio.yml. The ones operators routinely change:
domainviaVOIPBIN_DOMAINorconfig.yaml.image_tagviaVOIPBIN_IMAGE_TAG.pstn_whitelist_ipsviaVOIPBIN_PSTN_WHITELIST_IPS.kamailio_auth_db_urland the threekamailio_auth_*_columnvariables via extra-vars orgroup_vars/kamailio.yml.kamailio_shm_size,kamailio_pkg_size: memory tuning.pike_enabled,pike_rate,pike_timeout: anti-flood.homer_enabled,homer_uri: HEP capture.
Kubernetes ConfigMap (voipbin-config)¶
In namespaces bin-manager and infrastructure. Placeholders are
substituted at apply time:
DOMAIN,DB_HOST,DB_PORT,DB_NAME,CLOUDSQL_CONNECTION_NAMEREDIS_URL,RABBITMQ_URL,CLICKHOUSE_URL
Kubernetes Secret (voipbin-secret)¶
In namespace bin-manager:
JWT_KEY,DB_USER,DB_PASSWORD,REDIS_PASSWORD,RABBITMQ_PASSWORD,API_SIGNING_KEY
Operations and troubleshooting¶
Day-to-day commands¶
# Show the current pipeline state
./voipbin-install status
# Re-render and apply just the K8s manifests after editing a value
./voipbin-install apply --stage k8s
# Re-render the Kamailio VM .env after editing group_vars
./voipbin-install apply --stage ansible
# Inspect what secrets are stored
sops --decrypt secrets.yaml
# SSH into Kamailio for debugging
gcloud compute ssh kamailio-0 --tunnel-through-iap \
--project "$(yq .gcp_project_id config.yaml)"
# Get logs from a backend service
kubectl -n bin-manager logs deploy/call-manager --tail 200
# Tear everything down (irreversible, including the database)
./voipbin-install destroy
Common issues¶
Terraform: quota exceeded¶
Error: googleapi: Error 403: Quota exceeded. Request quota increases
at https://console.cloud.google.com/iam-admin/quotas, then rerun
./voipbin-install apply. Common quotas: CPUS_ALL_REGIONS,
IN_USE_ADDRESSES, SSD_TOTAL_GB.
Terraform: state lock¶
Error: Error acquiring the state lock. Only when you are certain no
other process is running Terraform:
cd terraform
terraform force-unlock LOCK_ID
Terraform: API not enabled¶
Error: googleapi: Error 403: ... API not enabled. Re-run
./voipbin-install init to enable APIs, or enable manually:
gcloud services enable container.googleapis.com --project PROJECT_ID
Terraform: state bucket missing¶
Error: Failed to get existing workspaces: storage: bucket doesn't exist.
The GCS state bucket must exist before terraform init. The apply
command creates it automatically; if you are running Terraform manually,
create it first:
gsutil mb -p PROJECT_ID gs://PROJECT_ID-voipbin-tf-state
Terraform: permission denied¶
Error: googleapi: Error 403: Required permission. Re-check the
authenticated principal:
gcloud auth list
gcloud projects get-iam-policy PROJECT_ID
The minimum role set is in config/gcp_iam_roles.yaml (12 roles).
Ansible: IAP tunnel connection failed¶
ERROR! Timeout waiting for connection or
Permission denied (publickey).
Verify IAP SSH works directly:
gcloud compute ssh VM_NAME --tunnel-through-iap --project PROJECT_ID --zone ZONE.Ensure the IAP API is enabled.
Check the IAP firewall rule exists.
Verify the principal has
roles/iap.tunnelResourceAccessor.
Ansible: docker install failed on a fresh VM¶
Could not get lock /var/lib/dpkg/lock typically means cloud-init is
still running. Wait for it and rerun the stage:
gcloud compute ssh kamailio-0 --tunnel-through-iap \
-- 'cloud-init status --wait'
./voipbin-install apply --stage ansible
Kubernetes: pod in CrashLoopBackOff¶
kubectl get pods -n bin-manager
kubectl logs POD_NAME -n bin-manager
kubectl describe pod POD_NAME -n bin-manager
Most common causes:
Cloud SQL Proxy not ready (check
infrastructurenamespace first).Redis or RabbitMQ not ready.
Missing ConfigMap or Secret values.
Kubernetes: ImagePullBackOff¶
Verify the image exists at the tag declared in
config/versions.yaml.GKE nodes need internet access through Cloud NAT.
If images live in a private GAR or GCR repository, confirm the GKE node service account has
roles/artifactregistry.reader(GAR) orroles/storage.objectVieweron the registry bucket (legacy GCR).
Cloud SQL: connection refused from pods¶
kubectl get pods -n infrastructure -l app=cloudsql-proxy
kubectl logs -n infrastructure -l app=cloudsql-proxy
Verify the Cloud SQL instance is up, the proxy service account has permissions, and the connection name matches the Terraform output.
DNS: domain not resolving¶
When dns_mode: auto:
gcloud dns managed-zones describe voipbin-zone --project PROJECT_ID \
--format="value(nameServers)"
Update your registrar’s NS records to point at those nameservers. NS delegation can take up to 48 hours.
When dns_mode: manual: pull the external LB IP from Terraform
outputs and create A records for api, admin, talk, meet,
and sip subdomains at your DNS provider.
SIP: devices cannot register¶
Confirm Kamailio is running on the VM:
docker compose psvia IAP SSH.Inspect the logs:
docker compose logs --tail 100.List the Kamailio load balancer resources and check health, since the exact resource names vary by deployment environment:
gcloud compute target-pools list --filter="name~kamailio" gcloud compute forwarding-rules list --filter="name~kamailio"
Audio: calls connect but no audio¶
Confirm RTPEngine is running on the VM.
Check the firewall allows the RTP UDP port range.
Verify RTPEngine sees the correct external IP, especially after moving regions.
Rollback¶
Kubernetes rollout:
kubectl rollout undo deployment/DEPLOYMENT_NAME -n NAMESPACE
Terraform state from a previous version:
gsutil ls -la gs://PROJECT_ID-voipbin-tf-state/default.tfstate
gsutil cp gs://PROJECT_ID-voipbin-tf-state/default.tfstate#VERSION terraform.tfstate
cd terraform && terraform apply
Full teardown and rebuild:
./voipbin-install destroy
./voipbin-install apply
Where to go next¶
The installer repo’s own
docs/troubleshooting.mdhas a wider list of error symptoms by stage.The installer’s
README.mdlists exact IAM roles and Terraform outputs.For platform support, contact
support@voipbin.net.