Orbital is a multi-tenant answering-service / virtual-receptionist platform. It is installable software: a call center or answering service (the "platform operator") runs Orbital to take calls on behalf of their customers ("tenants"). AI voice agents and platform staff share the same call-handling surface — the same scripts, the same recordings, the same call history — so a call can hand off between agent and operator mid- conversation without either side losing context.
Tenants get a read-only portal to see messages, recent calls, and recordings taken on their behalf. They never configure telephony infrastructure themselves; the platform operator owns trunks, extensions, routing, and queues.
It is a multi-channel inbound platform: voice, inbound email, web chat, and SMS/MMS all route into the same operator surface, share the same claim/release semantics, and are driven by the same compiled orchestrations — so a client's script is authored once and answers on whichever channel the customer used.
Orbital is free software under the AGPL-3.0. Self-hosting is free forever with no feature gates and no seat counting — see Licensing.
- Backend — Laravel 12, PHP 8.4, Filament 5, Livewire 4
- Database — PostgreSQL 17 with pgvector for knowledge-base embeddings
- Cache / queues / sessions — Valkey (Redis-compatible)
- Telephony — Asterisk 22 (SIP, MixMonitor recording, AMI)
- Messaging — SMS/MMS via a pluggable provider contract (Twilio
driver ships; see
docs/admin/messaging.md) - Media / AI voice — LiveKit server + LiveKit SIP bridge + a Python agent worker (Anthropic / OpenAI / ElevenLabs)
- Object storage — MinIO (S3-compatible) for call recordings and tenant assets
- Observability — Prometheus, Pushgateway, Loki, Promtail, Grafana
(four dashboards ship provisioned; app metrics on
/metrics, platform and telephony metrics pushed byorbital:collect-metrics) - Frontend — Tailwind 4, Alpine.js, SIP.js (WebRTC softphone), WaveSurfer.js (recording playback) — all bundled via Vite, no public CDNs (offline-first is a hard requirement)
- Dev environment — Laravel Sail / Docker Compose
This is the development environment (Laravel Sail). For the full three-environment story — dev, staging, production — see Environments below.
cp .env.example .env
composer install
php artisan key:generate
./vendor/bin/sail up -d
pnpm install
php artisan migrate --seed
pnpm run devThe default seeders create a super-admin (admin@orbital.test / password),
a platform operator + supervisor, and a demo tenant. See
database/seeders/DemoTenantSeeder.php for the full list.
Then visit http://localhost/login. Users are routed to the right Filament panel based on their role:
super_admin→/admin— platform configuration, tenant management- Platform staff (
operator,supervisor, …) →/operator— softphone, intake flow viewer, call queue - Tenant users →
/portal— read-only activity summary and call history
| Command | What it does |
|---|---|
composer dev |
Server + queue worker + log viewer + Vite |
./vendor/bin/sail up -d |
Bring up the Docker stack |
./vendor/bin/sail artisan orbital:bootstrap |
Idempotent install of external services |
./vendor/bin/sail artisan orbital:status |
System health snapshot from the CLI |
./vendor/bin/sail artisan orbital:generate-config |
Regenerate Asterisk configs from DB |
./vendor/bin/sail artisan orbital:render-disclosures |
Render recording-disclosure TTS prompts |
php artisan test |
Run the test suite |
vendor/bin/pint |
Fix PHP code style |
pnpm run build |
Production Vite build |
app/Filament/— admin / operator / portal panel resources and pagesapp/Services/Telephony/— Asterisk AMI client, config generation, call-recording policy resolverapp/Services/Health/— dashboard health probesapp/Services/Bootstrap/— idempotent "install external services" layerapp/Livewire/— softphone, compiled-flow viewer, chat consoleagent-worker/— Python LiveKit agent workerdocker/asterisk/— Asterisk 22 Dockerfile, baked configs, entrypointdocker/livekit/— LiveKit server + SIP bridge configshelm/orbital/— Helm chart packaged on tag push to Harbor OCIlocal/— k3d helpers for chart iterationscripts/build-and-push.sh— local image build + push to Harborresources/views/asterisk/— Blade templates that renderextensions.conf,pjsip_generated.conf, etc.
Orbital runs in three environments. They share one codebase and one Helm chart; what changes is where they run and how you push updates.
| Environment | Runs on | Bring up / update |
|---|---|---|
| Development | Laravel Sail (docker-compose), local | ./vendor/bin/sail up -d + composer dev |
| Staging | k3d (K3s-in-Docker), local | ./local/k3d-up.sh, then ./local/k3d-build.sh |
| Production | Vultr VKE + edge VMs, off Harbor | scripts/build-and-push.sh + helm install |
Everyday feature work. Fast, hot-reloading, no Kubernetes. This is the Quick start above.
./vendor/bin/sail up -d # Postgres, Valkey, Asterisk, LiveKit, MinIO
pnpm run dev # Vite dev server with HMRReach it at http://localhost/login. Edits to PHP/Blade/JS are live
immediately — no rebuild. Use this for all app logic, UI, and migration
work. Reach for staging only when you touch helm/orbital/ or need to
validate real Kubernetes behavior (probes, multi-pod scheduling, the
object-storage config handoff, the SIP edge).
A production-shaped Kubernetes deployment on your own machine: same Helm
chart, same topology (1 server + 2 agents, ingress, RWO storage,
in-cluster Postgres/Valkey/SeaweedFS) as a real single-node install. Use
it to validate a change under real k8s before it ever touches the cloud.
Full details in local/README.md.
First bring-up (creates the cluster, namespace, placeholder secrets, and installs the chart):
./local/k3d-up.sh
echo "127.0.0.1 orbital.localhost" | sudo tee -a /etc/hosts # once
open http://orbital.localhost:8080Redeploy after you change code — this is the one command that pulls your working tree into staging. It builds first-party images under a unique tag, pushes them to the cluster registry, and rolls the release:
./local/k3d-build.sh # rebuild + redeploy all images
./local/k3d-build.sh laravel # only the laravel image (others keep their tag)The unique-tag-per-build is deliberate: reusing :dev makes helm upgrade see no change and silently keep the old pods running (the
"green but running old code" trap). Every k3d-build.sh run forces a
real rollout.
Optional — real SIP call path. To place actual calls against the cluster, bring up the off-cluster Kamailio + rtpengine edge and deploy with the edge overlay:
./local/edge-up.sh # Kamailio + rtpengine on the k3d docker network
./local/edge-register.sh # seed the edge/backend rows so health + reload work
EDGE=1 ./local/k3d-build.sh # redeploys WITH the edge wiring preservedUse EDGE=1 on every redeploy while the edge is up — a plain
k3d-build.sh resets Asterisk to the non-edge defaults and drops the
NodePort exposure mid-session.
Tear down: ./local/k3d-down.sh (and ./local/edge-down.sh if the
edge is up). Nothing persists.
Real managed Kubernetes on Vultr, images pulled from the private Harbor
registry (cr.calltheory.com), SIP edge on dedicated VMs.
Infrastructure comes first, from a different repo. The cluster, the
Kamailio/rtpengine edge VMs, ingress-nginx, and cert-manager are all
provisioned by orbital-setup
(https://git.calltheory.com/calltheory/orbital-setup,
terraform/vultr/managed-vke/). This app repo only supplies images and
the chart; it never provisions cloud infra.
Once orbital-setup has stood up the cluster and edge:
a. Cut a release — CI builds, signs, and publishes to Harbor. Push a
vX.Y.Z tag and the .forgejo/workflows/application-tests.yml pipeline
does the rest: the publish-images job builds and cosign-signs all four
images, and the publish-helm job lints, renders against every
values-*.yaml, packages the chart, and pushes + cosign-signs it to
oci://cr.calltheory.com/orbital/charts/orbital at that version.
git tag v0.1.0 && git push forgejo v0.1.0 # → CI publishes signed images + chart(Manual fallback when you need images without cutting a tag —
HARBOR_USERNAME='robot$ci' HARBOR_TOKEN='<token>' ./scripts/build-and-push.sh --tag=v0.1.0.)
b. Create the in-cluster secrets (pull secret + app secrets). The
chart never renders secret material — you create it once per cluster,
following the recipes in
helm/orbital/templates/secrets.example.yaml:
kubectl create namespace orbital
kubectl -n orbital create secret docker-registry orbital-registry-creds \
--docker-server=cr.calltheory.com \
--docker-username='robot$<customer>-pull' \
--docker-password='<token>'
kubectl -n orbital create secret generic orbital-orbital-app-secrets \
--from-literal=APP_KEY="base64:$(php artisan key:generate --show)" \
--from-literal=DB_PASSWORD='<...>' \
--from-literal=REDIS_PASSWORD='<...>' \
--from-literal=REVERB_APP_SECRET='<...>' \
--from-literal=AWS_ACCESS_KEY_ID='<...>' \
--from-literal=AWS_SECRET_ACCESS_KEY='<...>' \
--from-literal=LIVEKIT_API_KEY='<...>' \
--from-literal=LIVEKIT_API_SECRET='<...>' \
--from-literal=ASTERISK_AMI_SECRET='<...>' \
--from-literal=ANTHROPIC_API_KEY='<...>' \
--from-literal=OPENAI_API_KEY='<...>' \
--from-literal=ELEVENLABS_API_KEY='<...>'c. Install the chart from Harbor OCI with the Vultr VKE overlay. The deployment host pulls the signed chart from Harbor — it never needs read access to this private app repo (this is exactly how orbital-setup drives it). Fill the per-deploy knobs — your domain, the released version, the edge VMs' Kamailio endpoint, and a strong agent-worker token:
helm install orbital oci://cr.calltheory.com/orbital/charts/orbital \
--version 0.1.0 \
--namespace orbital \
-f ./helm/orbital/values-vultr-vke.yaml \
--set global.domain=orbital.your-company.com \
--set global.image.tag=v0.1.0 \
--set telephony.kamailio.jsonrpcUrl=http://<edge-vm-ip>:8090/jsonrpc \
--set agentWorker.token="$(openssl rand -hex 32)"The -f values-vultr-vke.yaml above uses your local checkout for the
overlay; the chart templates come from the OCI artifact. When deploying
from a host without this repo, inline the overlay values as --set flags
or keep a private per-customer values file. To iterate on the chart
itself before cutting a release, install from the local path
(helm install orbital ./helm/orbital …) instead of the OCI ref.
d. Run migrations (the image does not auto-migrate):
kubectl -n orbital exec deploy/orbital-orbital-laravel -- \
php artisan migrate --forcee. Point DNS at the Vultr LoadBalancer. cert-manager then issues the
Let's Encrypt cert for your domain (the ACME challenge needs DNS live
first — that's why ingress.tls.enabled only matters after DNS resolves).
f. Once observability + TLS are up, trim health.disabledComponents
in values-vultr-vke.yaml (drop grafana/prometheus/loki/promtail
and tls) and helm upgrade so those dashboard cards go green instead
of showing "not deployed".
Ship a new version later by rebuilding at a new tag (step a) and
helm upgrade … --set global.image.tag=<new>.
- All tenant-scoped models use the
BelongsToTeamtrait for automatic scoping. - New PHP files use
declare(strict_types=1);. - Frontend dependencies go through
pnpmand Vite. Never pull from a public CDN — the app has to function on a LAN with no public internet. - Asterisk config is generated from the database via Blade templates, never hand-edited.
- Claude Code guidelines for this repo live in
CLAUDE.md.
Orbital is free software under the GNU Affero General Public License,
version 3 (LICENSE).
Self-hosting is free forever, with no feature gates, no seat counting, and
no license key — unlimited clients, users, concurrent calls, and channels.
What Call Theory sells is managed hosting, support subscriptions for
self-hosters, and managed answering services. None of that is code withheld
from this repository. See LICENSING.md.
If you modify Orbital and let others use it over a network, section 13 of
the AGPL asks you to offer them the corresponding source. Orbital does this
for you — the panel footers and /source link to the running version's
source — so point ORBITAL_SOURCE_URL at your repository when you fork.
Bundled third-party components and their licenses:
NOTICE, docs/reference/third-party-licenses.md.
The name is a trademark and is handled separately:
TRADEMARK.md.
- Contributing:
CONTRIBUTING.md·CLA.md - Reporting a vulnerability:
SECURITY.md