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.

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

  2. Ansible configures the Kamailio and RTPEngine VMs over an IAP tunnel and renders their Docker Compose .env files from templates.

  3. 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-2 by default, private nodes, shielded instances, COS_CONTAINERD image, REGULAR release channel.

  • Kamailio VM: 1x f1-micro by default, SIP/TLS/WSS proxy reached through an external network load balancer.

  • RTPEngine VM: 1x f1-micro by default, RTP media relay with a static external IP for direct media paths.

  • Cloud SQL MySQL 8.0: db-f1-micro instance, SSL required, daily automated backups, Cloud SQL Proxy deployed as a sidecar in GKE.

  • Kubernetes workloads: backend microservices in the bin-manager namespace, Asterisk in voip, Redis, RabbitMQ, ClickHouse, and Cloud SQL Proxy in infrastructure, 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 gcloud auth login and gcloud auth application-default login required.

terraform

1.5.0

Bundled under terraform/ in the installer repo.

ansible

2.15.0

Install via pip install ansible.

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 secrets.yaml with GCP KMS.

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>, and sip.<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:

  1. gcloud auth login for the human/CLI principal.

  2. gcloud auth application-default login for 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:

  1. Preflight checks for the six local tools.

  2. gcloud user auth and Application Default Credentials.

  3. 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).

  4. Project existence and billing.

  5. Quota check against config/gcp_quotas.yaml.

  6. Enable sixteen GCP APIs.

  7. Create the voipbin-installer service account with twelve IAM role bindings.

  8. Create a KMS key ring and crypto key.

  9. Generate six secrets (jwt_key, cloudsql_password, redis_password, rabbitmq_user, rabbitmq_password, api_signing_key) and encrypt them into secrets.yaml with SOPS bound to the KMS key.

  10. Write .sops.yaml.

  11. Save config.yaml.

  12. 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 with sops --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

api.example.com

A

External LB IP

admin.example.com

A

External LB IP

talk.example.com

A

External LB IP

meet.example.com

A

External LB IP

sip.example.com

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: Redis AUTH password.

  • rabbitmq_user and rabbitmq_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

config.yaml

Terraform variables

terraform/ tfvars and resource arguments

config.yaml

Kubernetes ConfigMap

voipbin-config in bin-manager and infrastructure

config.yaml

Ansible group_vars

Kamailio VM /opt/kamailio-docker/.env

secrets.yaml

Kubernetes Secret

voipbin-secret in bin-manager

secrets.yaml

Kubernetes Secret (composite)

RABBITMQ_ADDRESS and REDIS_ADDRESS in voip

secrets.yaml

Ansible group_vars

Kamailio VM .env (RABBITMQ_URL, etc.)

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 to username.

  • KAMAILIO_AUTH_DOMAIN_COLUMN: defaults to realm.

  • KAMAILIO_AUTH_PASSWORD_COLUMN: defaults to password.

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 in Pending. Verify with kubectl 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. Drop tls.crt and tls.key into the voipbin-tls Secret in bin-manager before ./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, 50m CPU and 64Mi memory request, 200m and 256Mi limits.

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_id

  • region, zone

  • gke_type (zonal or regional)

  • tls_strategy (letsencrypt, gcp-managed, self-signed, byoc)

  • image_tag_strategy (latest or pinned)

  • domain

  • dns_mode (auto or manual)

  • gke_machine_type, gke_node_count

  • vm_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_key

  • cloudsql_password

  • redis_password

  • rabbitmq_user, rabbitmq_password

  • api_signing_key

Ansible variables (Kamailio VMs)

Defined in ansible/group_vars/all.yml and ansible/group_vars/kamailio.yml. The ones operators routinely change:

  • domain via VOIPBIN_DOMAIN or config.yaml.

  • image_tag via VOIPBIN_IMAGE_TAG.

  • pstn_whitelist_ips via VOIPBIN_PSTN_WHITELIST_IPS.

  • kamailio_auth_db_url and the three kamailio_auth_*_column variables via extra-vars or group_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_NAME

  • REDIS_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).

  1. Verify IAP SSH works directly: gcloud compute ssh VM_NAME --tunnel-through-iap --project PROJECT_ID --zone ZONE.

  2. Ensure the IAP API is enabled.

  3. Check the IAP firewall rule exists.

  4. 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 infrastructure namespace first).

  • Redis or RabbitMQ not ready.

  • Missing ConfigMap or Secret values.

Kubernetes: ImagePullBackOff

  1. Verify the image exists at the tag declared in config/versions.yaml.

  2. GKE nodes need internet access through Cloud NAT.

  3. If images live in a private GAR or GCR repository, confirm the GKE node service account has roles/artifactregistry.reader (GAR) or roles/storage.objectViewer on 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

  1. Confirm Kamailio is running on the VM: docker compose ps via IAP SSH.

  2. Inspect the logs: docker compose logs --tail 100.

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

  1. Confirm RTPEngine is running on the VM.

  2. Check the firewall allows the RTP UDP port range.

  3. 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.md has a wider list of error symptoms by stage.

  • The installer’s README.md lists exact IAM roles and Terraform outputs.

  • For platform support, contact support@voipbin.net.