Skip to main content

CI/CD Pipeline

Proxima Console uses GitLab CI/CD with a multi-stage pipeline. Different jobs run depending on the trigger: merge request, main branch push, or version tag.

The application tier is deployed via GitOps: CI builds and pushes images, then a manual job bumps image tags in the separate argocd-infra repository, and ArgoCD reconciles the proxima-production cluster. CI never connects to the cluster or the production database directly. See Production (Kubernetes) for the runtime architecture.

Pipeline Stages​

lint → test → build → integration → e2e → docker → deploy → release

Pipeline Types​

1. MR Pipeline (changes-based)​

Triggered on merge requests. Jobs run only when relevant files change.

Stages: lint → test → build

JobStageWhat It Does
lint:golintRuns go mod verify, govulncheck, and golangci-lint on agent/, backend/, mcp/, and cli/
lint:frontendlintRuns ESLint + Prettier checks and npm audit --audit-level=high --omit=dev (fails on high-severity production vulnerabilities)
lint:swaggerlintValidates swagger spec well-formedness and freshness (regenerates and diffs)
test:go:unittestRuns gotestsum with -race flag, produces JUnit reports
test:frontend:unittestRuns vitest, produces JUnit reports
migration:checktestValidates migrations by running up → down → up
build:agentbuildCompiles the agent with go build

2. Main Pipeline (all jobs)​

Triggered on pushes to main. Runs all MR jobs plus integration, e2e, Docker, and deploy stages.

Additional stages: integration → e2e → docker → deploy

JobStageWhat It Does
test:go:integrationintegrationRuns Go integration tests against real PostgreSQL and NATS services
docker:backenddockerBuilds backend image, scans with Trivy, pushes registry.prxm.uz/proxima/console/backend:$CI_COMMIT_SHA
docker:frontenddockerBuilds frontend image, scans with Trivy, pushes registry.prxm.uz/proxima/console/frontend:$CI_COMMIT_SHA
deploy:k8s-gitopsdeployManual gate, main only. Clones argocd-infra over an SSH deploy key, bumps every Console image tag under clusters/production/console-system/ to the new SHA, commits and pushes. ArgoCD then syncs and Argo Rollouts performs the blue-green promotion.
The e2e stage currently has no runnable jobs

.test:e2e:remote and its feeders .build:backend:e2e / .build:frontend:e2e are all .-prefixed YAML templates, so nothing in the e2e stage executes — neither BATS nor Playwright runs on main. The hidden job's needs: still names the deleted un-dotted builds. Until they are re-enabled, run the suite manually with make test-e2e (which builds locally and executes on the remote E2E server over SSH).

The remaining docker:* jobs (docker:mcp, docker:docs, docker:telegram-bot, docker:telegram-miniapp) follow the same pattern, building and pushing one registry.prxm.uz/proxima/console/<svc>:$CI_COMMIT_SHA image per service.

GitOps Deployment (how deploy:k8s-gitops works)​

The deploy stage no longer touches a server. It is a pure Git operation against the argocd-infra repo:

  1. docker:* jobs build and push registry.prxm.uz/proxima/console/<svc>:$CI_COMMIT_SHA for every service.
  2. deploy:k8s-gitops (manual, main only) clones argocd-infra over an SSH deploy key (DEPLOY_REPO_SSH_PRIVATE_KEY — an SSH key, not an HTTPS token), rewrites every Console image tag under clusters/production/console-system/ to the new SHA, then commits and pushes.
  3. ArgoCD notices the new desired state and syncs the console-system namespace.
  4. Database migrations run inside the cluster as an ArgoCD PreSync-hook Job (console-migrate, golang-migrate migrate up) — not from CI, because CI cannot reach the cluster-internal database. ArgoCD waits for this hook to succeed before rolling out the backend.
  5. Argo Rollouts performs the backend blue-green promotion (preview → active), with automatic rollback if the preview never becomes healthy.

Retired jobs. The old deploy:dev (SSH to the box, docker-compose down/up, swapping IMAGE_TAG in systemd units) and migrate:dev (migrations against the dev-server PostgreSQL over SSH) jobs are gone. Migrations are now the responsibility of the ArgoCD PreSync hook, and rollout is the responsibility of ArgoCD + Argo Rollouts.

3. Tag Pipeline (v*)​

Triggered when a version tag (e.g., v0.2.0) is pushed. Builds release artifacts and creates a GitLab release.

Stages: docker → release

JobStageWhat It Does
docker:backenddockerBuilds and pushes backend image with version tag
docker:frontenddockerBuilds and pushes frontend image with version tag
release:agent-binariesreleaseCross-compiles agent for linux/amd64 and linux/arm64, uploads binaries to Cloudflare R2
release:cli-binariesreleaseCross-compiles the pc CLI for linux/amd64, linux/arm64, darwin/amd64, and darwin/arm64, uploads binaries (with .sha256 checksums) to Cloudflare R2
release:gitlabreleaseCreates a GitLab release with notes extracted from CHANGELOG.md; attaches the agent and pc binaries (R2 download links) as release assets

Versioning​

The project follows semver with a v prefix (e.g., v0.2.0).

  • Stable tags (e.g., v0.2.0): the Docker image is also tagged as :latest.
  • Pre-release tags (e.g., v0.2.0-beta.1): the Docker image is tagged with the version only, :latest is not updated.

CI Variables​

VariableTypePurpose
DEPLOY_REPO_SSH_PRIVATE_KEYVariable — not File, not MaskedSSH deploy key for pushing image-tag bumps to the argocd-infra repo (not an HTTPS token). Holds base64 -w0 < deploy_key; the job decodes it to ~/.ssh/id_deploy. File type breaks the deploy — base64 -d would receive a path instead of the key and ssh-add fails with error in libcrypto. Base64 is what keeps the PEM newlines intact, which is also why masking is unnecessary and unhelpful here.
R2_ACCOUNT_IDVariableCloudflare account ID for R2 storage
R2_ACCESS_KEY_IDVariableR2 access key for uploading agent binaries
R2_SECRET_ACCESS_KEYMaskedR2 secret access key
R2_BUCKETVariableR2 bucket name for agent binary storage
R2_PUBLIC_URLVariablePublic download URL for agent binaries

Masked variables are hidden in job logs. All secrets should use the "Masked" type to prevent accidental exposure.