DarDevOps ·DarDev Team · 5 min read

GitLab CI to Kubernetes: a practical pipeline

A GitLab CI pipeline that builds container images, scans them, and deploys to Kubernetes with review apps and guarded production promotes the pattern we use on DarDev stacks.

Terminal showing CI/CD pipeline deploying to Kubernetes

GitLab CI to Kubernetes is the default delivery path on DarDevOps engagements: one pipeline builds your app, publishes a immutable container image, and promotes it to clusters with explicit gates between merge request, staging, and production. This article walks through a practical pipeline you can adapt without adopting every enterprise feature on day one.

We assume GitLab as forge, a container registry (GitLab Registry or Harbor), and a Kubernetes cluster you control self-hosted on OVH-style VPS or managed elsewhere. The goal is repeatable deploys with audit trail in git, not click-ops on Friday evenings.

Pipeline stages that actually matter

  1. lint + unit tests on every push
  2. build Docker image with deterministic Dockerfile and BuildKit cache
  3. scan image for critical CVEs (fail or warn based on policy)
  4. push image tagged with commit SHA and semver on tags
  5. deploy to review namespace on merge requests
  6. deploy to staging on main merge
  7. manual or canary promote to production

Skipping tests on main is how teams lose velocity not gain it. Keep fast jobs under five minutes; split slow integration tests into nightly unless they block revenue paths.

Example.gitlab-ci.yml skeleton

stages: [test, build, deploy]

variables:
 IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

unit:
 stage: test
 script:
 - npm ci && npm test

build:
 stage: build
 script:
 - docker build -t $IMAGE.
 - docker push $IMAGE

 deploy_staging:
 stage: deploy
 environment: staging
 script:
 - kubectl set image deploy/api api=$IMAGE -n staging
 rules:
 - if: $CI_COMMIT_BRANCH == "main"

 deploy_prod:
 stage: deploy
 environment: production
 when: manual
 script:
 - kubectl set image deploy/api api=$IMAGE -n production

Kubernetes access from GitLab runners

Runners need kubeconfig or GitLab's cluster integration with scoped service accounts. Prefer namespace-limited tokens per environment staging CI must not delete production. Store KUBECONFIG or GitLab masked variables; rotate tokens when people leave. For self-hosted runners on the same VPS as k3s, network policies still matter: compromise of a runner should not mean cluster-admin.

Secrets, config, and migrations

Never bake secrets into images. Use Kubernetes Secrets sourced from Vault, SOPS-encrypted manifests in git, or GitLab protected variables injected at deploy time. ConfigMaps hold non-secret toggles. Database migrations run as Helm pre-upgrade hooks or one-off Jobs with the same image tag you are releasing not from a developer laptop.

Kubernetes cluster operations dashboard
Deploy events should be visible in metrics tie CI pipelines to Prometheus alerts.

Review apps and staging fidelity

Merge request environments catch integration bugs before main. Spin a namespace per MR, deploy the MR image, post URL to GitLab. Tear down on merge. Staging must mirror production topology same ingress controller, same Postgres major version, same resource limits class or you are testing fiction.

Rollback and observability hooks

Keep previous image tags addressable. kubectl rollout undo is your ten-minute lifesaver we document it in our rollback runbook. Emit deploy annotations to Prometheus or Loki so on-call can correlate error spikes with pipeline ID. Portainer helps operators who prefer UI over kubectl for quick inspections; we compare both in Portainer vs kubectl.

DarDevOps applies these patterns to internal workloads DarDevLab, mailer stack, CRM adjacent services before recommending them on client clusters. That is Deployed Realities for infrastructure: pipelines we run ourselves.

Caching, tags, and registry hygiene

Use BuildKit cache mounts in GitLab Docker executors to speed npm and apt layers. Tag every release image with $CI_COMMIT_SHA; add semver tags only on git tags. Retention policies in GitLab Registry prevent disk fill on small VPS hosts. Scan images in CI block critical CVEs on production paths, warn on staging.

Multi-service repos and monorepos

Use rules:changes paths so a documentation edit does not rebuild every microservice. child pipelines or parallel matrix jobs keep monorepo CI under acceptable latency. Shared libraries publish internal packages on tag; services consume pinned versions to avoid surprise breaks on main.

When you outgrow kubectl set image, adopt Helm charts per service with values files for staging and production. GitOps repos then become the contract between CI (build artifact) and cluster (desired state) CI finishes at push; Flux reconciles deploy.

Start simple: one service, one cluster, one manual production gate. Measure lead time and failed deploy rate for a month. Then add GitOps, canaries, or multi-region each layer justified by incident data, not conference FOMO. DarDevOps engagements begin with this baseline before discussing enterprise add-ons.

GitLab shared runners or self-hosted?

Self-hosted runners on your network reduce image pull latency and keep deploy traffic off shared quotas. Shared runners are fine for open-source or early MVP if secrets are scoped tightly.

Helm or plain manifests?

Plain kubectl works for one service. Helm or Kustomize pays off at three-plus services or multiple environments. GitOps adds reviewable cluster state worth it when ops team grows.

How do I gate production?

Use when: manual on prod job, protected branches, and optional canary Service or Flagger. Never auto-deploy prod on every green main build without stakeholder sign-off.

Where does DarDev run this?

On Kubernetes clusters we operate for DarDev products and selected client engagements GitLab, Portainer, Prometheus, and Grafana in the reference set on dardev.net/products.

Get company news

Releases and announcements — confirm from your inbox.

Subscribe to updates