GitLab CI/CD for AI Voice Deployments: Runner, Agent, GitOps (2026)
By Sagar Shankaran, Founder of CallSphere
Wire GitLab Runner on Kubernetes, the GitLab Agent for cluster sync, and a Flux-driven CD path for an AI voice agent. Real .gitlab-ci.yml plus runner Helm values.
Key takeaways
TL;DR — Install GitLab Runner with the Kubernetes executor (so each job is a pod), bind a GitLab Agent for cluster context, and let Flux watch a separate
deployrepo for image bumps. Three moving parts, one auditable trail.
What you'll set up
A GitLab project for an AI voice agent that builds + tests in CI, pushes a signed image to the GitLab container registry, and triggers a Flux reconcile in a paired deploy repo. The runner itself runs on the same k3s where the voice agent runs — no public IP needed for the cluster.
Architecture
flowchart LR
DEV[Push to GitLab] --> CI[GitLab CI]
CI --> RUNNER[K8s Runner pods]
RUNNER --> REG[GitLab Registry]
CI -->|bot commit| DEPLOY[(deploy repo)]
DEPLOY --> FLUX[Flux on k3s]
FLUX --> AGENT[voice-agent Deployment]
AGENT --> LK[LiveKit + OpenAI Realtime]
Step 1 — Install GitLab Runner with the Kubernetes executor
```yaml
values.yaml
gitlabUrl: https://gitlab.com/ runnerToken: ${RUNNER_TOKEN} runners: config: | [[runners]] name = "k3s-voice" executor = "kubernetes" [runners.kubernetes] namespace = "gitlab-runner" cpu_request = "200m" memory_request = "512Mi" helper_image = "gitlab/gitlab-runner-helper:latest" privileged = true # needed for buildx in DinD ```
```bash helm upgrade --install gitlab-runner -n gitlab-runner --create-namespace \ -f values.yaml gitlab/gitlab-runner ```
Each CI job becomes a pod. Builds finish, pod evicts, no orphan state — the cleanest CI primitive on Kubernetes.
Step 2 — Wire the GitLab Agent (KAS) for declarative cluster context
The GitLab Agent gives CI jobs scoped KUBECONFIG without storing service-account tokens in CI variables.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
```yaml
.gitlab/agents/voice/config.yaml
ci_access: projects: - id: acme/voice-agent ```
In CI you then reference it as environment.kubernetes.namespace and the runner injects $KUBECONFIG for that scope only.
Step 3 — Build, eval, and push from .gitlab-ci.yml
```yaml stages: [test, eval, build, deploy]
variables: PYTHON_VERSION: "3.12" IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
test: stage: test image: python:3.12-slim script: - pip install uv && uv sync --frozen - uv run pytest tests/unit
llm-eval: stage: eval image: python:3.12-slim script: - uv run python evals/run.py --suite voice --threshold 0.92 variables: OPENAI_API_KEY: $OPENAI_API_KEY # masked + protected
build: stage: build image: docker:26 services: [docker:26-dind] script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker buildx build --push --platform linux/amd64,linux/arm64 -t $IMAGE . ```
Step 4 — Bump the deploy repo from CI
```yaml deploy: stage: deploy image: alpine/git:latest rules: [{ if: $CI_COMMIT_BRANCH == "main" }] script: - git clone https://oauth2:\$DEPLOY_TOKEN@gitlab.com/acme/deploy.git - cd deploy - sed -i "s|image: .*|image: $IMAGE|" voice/values.yaml - git commit -am "voice: bump to $CI_COMMIT_SHA" - git push ```
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
The deploy repo is what Flux watches. CI never kubectl applys — that's a one-way GitOps flow.
Step 5 — Flux on the cluster picks it up
```yaml apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: { name: deploy, namespace: flux-system } spec: interval: 30s url: https://gitlab.com/acme/deploy.git ref: { branch: main }
apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: { name: voice, namespace: flux-system } spec: interval: 1m path: ./voice sourceRef: { kind: GitRepository, name: deploy } prune: true ```
Push to main → CI bumps deploy repo → Flux reconciles in 30-60s → new pods rolling.
Step 6 — Add merge-request review apps
```yaml review: stage: deploy rules: [{ if: $CI_PIPELINE_SOURCE == "merge_request_event" }] environment: name: review/$CI_MERGE_REQUEST_IID url: https://mr-\$CI_MERGE_REQUEST_IID.preview.example.com on_stop: stop-review script: - helm upgrade --install voice-mr-$CI_MERGE_REQUEST_IID ./chart \ --set image.tag=$CI_COMMIT_SHA \ --set ingress.host=mr-$CI_MERGE_REQUEST_IID.preview.example.com ```
A real WebRTC URL per MR — QA can dial in and test prompt changes before merge.
Pitfalls
- DinD vs buildx with cache — DinD ephemeral storage kills layer cache. Use
docker buildx create --driver kubernetesfor persistent BuildKit pods. - Runner privileged: true — required for DinD but expands blast radius. Pin to a dedicated namespace and PSP/PSS.
- Masked variables that aren't masked — values under 8 chars or containing newlines silently won't mask. Verify with
echo "[MASKED]"test job. - GitLab Agent connectivity — KAS needs WebSocket egress on port 443. Some egress firewalls drop long-lived WS; whitelist explicitly.
- Review-app DNS races — wildcard cert + 30s DNS propagation = tests fail. Pre-warm DNS with a check job.
How CallSphere does this in production
Explore a live demo and compare current plans to find the right fit for your business.
FAQ
Q: Why a separate deploy repo?
Auditability and blast-radius. The deploy repo is the single source of truth for what's running; rolling back is a git revert.
Q: Can I skip Flux and just kubectl apply from CI?
You can, but you lose drift detection. Flux re-applies if anything diverges; CI fires once and forgets.
Q: How do I run an LLM eval without leaking the API key to MR pipelines?
Mark the variable as Protected — only main and protected tags get it. Forks in MRs run a stub eval.
Q: GitLab vs GitHub for AI work?
GitHub has more SLSA tooling (actions/attest-build-provenance); GitLab has tighter merge-request review apps. Pick on workflow fit, not features.
Sources

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.