Install on Kubernetes (Kustomize)
The Kubernetes bundle is plain manifests with Kustomize, applied with kubectl apply -k. There is no Helm chart. The bundle lives in deploy/k8s of the repository. Use it when you already run a cluster; for one server without a cluster use the VM installer or the Docker Compose bundle.
:::info Status
The manifests are validated statically and in a local kind cluster. The media path (SIP and RTP through a real cloud firewall and NAT) is not proven on the internet yet. Read the limits in deploy/k8s/README.md before you put live trunks on it.
:::
Requirements
- Kubernetes 1.30 or newer, with a default StorageClass (three ReadWriteOnce volumes for the bundled databases).
- A CNI that enforces NetworkPolicy (Calico, Cilium, GKE Dataplane V2). Without one, the policies are accepted and ignored.
- At least one media node: a node with a public address (or 1:1 NAT) that you label
mpbx.io/media=true. SIP and RTP run host-networked there. kubectlandkustomize(orkubectlwith its embedded Kustomize).- Your own container images of one release, pinned by digest for production.
Install
Set the three values the overlay needs first. The overlay file has the same three places marked 1., 2. and 3..
export MEDIA_NODE="${MEDIA_NODE:?set to the name of the media node}"
export MEDIA_NODE_IP="${MEDIA_NODE_IP:?set to the internal IP of the media node}"
export MPBX_PUBLIC_URL="${MPBX_PUBLIC_URL:?set to the https URL users open, for example https://pbx.example.com}"
Then edit deploy/k8s/overlays/small/kustomization.yaml:
images:names thempbx,mpbx-mediaandmpbx-rtpengineimages of your release. Usedigest: sha256:...instead ofnewTagin production.MPBX_PUBLIC_URL=inconfigMapGeneratoris the address of the web client. It is used for the first-admin setup link.db-from-media-nodescontains the internal address of the media node as a/32.
Apply the bundle:
set -eu
: "${MEDIA_NODE:?set MEDIA_NODE to the media node name}"
: "${MPBX_PUBLIC_URL:?set MPBX_PUBLIC_URL}"
kubectl label node "$MEDIA_NODE" mpbx.io/media=true --overwrite
kubectl apply -k deploy/k8s/overlays/small
kubectl -n mpbx wait --for=condition=complete job/mpbx-init --timeout=5m
Read the recovery key and the first-admin setup link. They are shown once:
set -eu
kubectl -n mpbx logs job/mpbx-init
kubectl -n mpbx delete job mpbx-init
Write the recovery key down in a password manager or on paper. It is not stored anywhere in the cluster. Without it, a lost Secret means lost trunk passwords, API keys and encrypted recordings. Back up the Secret mpbx/mpbx-secrets with your cluster backups, and take passphrase-encrypted PBX backups as described in Backup and restore. Those backups include the master key.
Open the setup link and continue with the first-run steps.
The API and SIP pods usually restart once or twice on the first install while Postgres starts. That is expected.
Check the install
set -eu
kubectl -n mpbx get pods
kubectl -n mpbx rollout status deploy/mpbx-api --timeout=5m
kubectl -n mpbx port-forward svc/mpbx-api 8080:80 >/dev/null 2>&1 &
pf=$!
trap 'kill "$pf" 2>/dev/null || true' EXIT
sleep 3
curl -fsS http://127.0.0.1:8080/api/v1/version
The API answers only after its database migrations ran. The /healthz liveness endpoint is on the metrics port (9464), not on the API port.
Re-applying and upgrading
kubectl apply -k can be run again at any time. The init Job creates the Secret only if it is absent, so re-applying never rotates a key. A Job's pod template is immutable: if mpbx-init still exists when you change the image, delete it first (kubectl -n mpbx delete job mpbx-init).
Upgrade and rollback are scripted in deploy/k8s/ops/upgrade and scripts/upgrade.sh. Run --dry-run first. See Upgrade and rollback.
Ports and firewall
| Where | Port | Why |
|---|---|---|
| Ingress or load balancer | 443/tcp | Web client, API, WebSocket. The optional Ingress component (deploy/k8s/components/ingress) serves it. |
| Media node (cloud firewall) | 5060/udp and 5060/tcp | SIP. Restrict to your trunks and sites where you can. |
| Media node (cloud firewall) | 40000-50000/udp | RTP. |
Never open 5432, 6379, 8123, 9000 or 22222 (rtpengine's control port has no authentication). The bundle creates no public Service for them.
Dry run without a cluster
To check the manifests without a cluster, render the overlay:
set -eu
kustomize build deploy/k8s/overlays/small >/dev/null
echo "overlay renders"
deploy/k8s/scripts/validate.sh runs every overlay through kubeconform and the manifest invariants (non-root, read-only root file system, Pod Security exceptions).
Uninstall
kubectl delete -k deploy/k8s/overlays/small
This removes the workloads. Persistent volumes and the Secret may remain; delete them only after your backup is verified.