Skip to content
AI Engineering
AI Engineering11 min read0 views

GitLab CI/CD for AI Voice Deployments: Runner, Agent, GitOps (2026)

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.

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 deploy repo 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.

Try Live Demo →

```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:\[email protected]/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 kubernetes for 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

CallSphere uses GitHub Actions for the public monorepo and a private GitLab instance for tenant-specific behavioral-health forks (HIPAA isolation). The pattern is identical: build, eval, sign, push to a private registry, bump a deploy repo. Flux on each tenant's k3s pulls only their image. 37 voice agents, 90+ tools, 115+ DB tables behind Cloudflare Tunnel. Pricing $149/$499/$1499, 14-day trial, 22% affiliate — see pricing.

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

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like