main, Jenkins deploys to each target host → restart systemd → health check.
There is no rsync, no manual git pull on a box, and no container registry for the
app services. Jenkins itself is the deploy agent: on a passing build it checks out the
release on each host, runs the per-service install/build, restarts the systemd
service, and verifies the health endpoint.
The pipeline at a glance
The split in that diagram is the whole model. CI runs on every branch and pull
request — it only proves the code is good. CD runs only when
main is green —
that is the single event that puts code on a server. A red build on a feature branch
never reaches a host.Branch strategy
The branch model is deliberately small.Branch off main
Every change starts on a short-lived feature branch cut from
main
(feature/..., fix/...). You push it to Bitbucket and open a pull request
back into main.CI gates the PR
The push fires the Jenkins pipeline. Lint, tests, and the build must pass before
the PR can be merged. CI on a branch never deploys anything.
Merge to main
Once reviewed and green, the PR merges into
main. The merge commit is the
deployable unit.Which repo deploys where
Each repository targets one host role. The repo names below are the real Bitbucket repositories (git@bitbucket.org:oriserve1/<name>.git); everywhere else in these docs
we use the friendly service names.
| Repository | Service (prose) | Target host role | What the deploy step does |
|---|---|---|---|
vox-backend | Backend API | API / App host | uv sync → restart the voxbridge uvicorn service on :8080 |
vox-frontend | Console | API / App host (nginx static root) | npm ci && npm run build → publish dist/, nginx serves it |
vox-agents | Voice fleet | Fleet host(s) | uv sync → restart all voxcore@* worker instances |
vox-dialler | Dialler | SIP / LiveKit host (single instance) | uv sync → restart the voxdialler service |
Per-service pipeline stages
The CI stages differ only in the tools each service uses. The shape is identical: install, lint, test, build.- Python services (Backend · Fleet · Dialler)
- Console (vox-frontend)
All three Python services use uv (never pip).
requires-python >= 3.12.uv sync is also the install step on the host at deploy time — the same command
that builds the CI venv reconstructs the runtime venv on the server, so what you
test is what you run.The Fleet (
vox-agents) pins Pipecat to a specific version in pyproject.toml.
uv sync respects the lockfile, so CI and the host always resolve the identical
dependency tree — never bump it loosely.Illustrative bitbucket-pipelines.yml
If you wire CI on the Bitbucket side, a bitbucket-pipelines.yml like the one below
covers a Python service: lint and test on every branch, and a manual deploy step
gated to main that hands off to Jenkins.
npm ci, npm run lint, npm run build, npm test).
Illustrative declarative Jenkinsfile
Jenkins is where the real deploy lives. A declarative Jenkinsfile runs the CI stages,
then — only on main — checks out the release on each target host, installs/builds,
restarts systemd, and runs the post-deploy health check.
The deploy step, in detail
On a greenmain, the deploy stage is the same five moves for every service.
Check out the release on the target host
Jenkins connects to each target host for that repo and checks out the
main
commit (or the chosen tag). No artifact is shipped for the Python services — the
host builds its own venv from the same lockfile CI used.Install or build
Python services run
uv sync to reconstruct the runtime venv. The Console runs
npm ci && npm run build and publishes the static dist/ to the nginx root.Restart systemd
The service unit is restarted:
voxbridge for the Backend, the templated
voxcore@* instances for the Fleet (each is one worker = one call slot),
voxdialler for the Dialler. Restarting one fleet worker (voxcore@2) is
zero-downtime — the others keep serving.Post-deploy health check
Jenkins curls the service health endpoint and fails the build if it does not come
back clean — so a deploy that boots into a bad state is visible immediately, not
on the next call.
Done — or roll back
Green health check ends the deploy. If anything failed, re-run the same Jenkins job
pinned to the previous tag to roll back. Detailed restart, scaling, and rollback
commands live in the runbook.
Health endpoints the deploy checks
| Service | Endpoint | What a healthy response confirms |
|---|---|---|
| Backend API | GET :8080/health | App booted with all required settings present |
| Voice fleet | GET /health (per worker) · GET /health/fleet (aggregate) | Workers up; aggregate shows worker_calls/worker_max and fleet_available |
| Dialler | GET :8090/health · GET :8090/metrics | Tick loop alive; metrics being emitted |
No Docker images are built for the app services. The Backend, Console, Fleet, and
Dialler all deploy as native processes managed by systemd (uvicorn services for the
Python apps, a static nginx bundle for the Console). The only containers in the stack
are LiveKit and
livekit-sip, which stay on docker-compose on the SIP/LiveKit
host — they are infrastructure, not part of the app CI/CD path.Where to go next
Deployment topology
The host roles in full — API/App, fleet, SIP/LiveKit — and what runs where.
Configuration & secrets
The
.env files each service needs, and how secrets reach the hosts.Operations runbook
Restart, scale, roll back, and read logs once code is live.
Repository map
The four repositories and the contracts between them.