Telegram
+998 (71) 205-80-00

CI/CD: deploying to a serverplus Kubernetes cluster

Posted: 25.07.26
Share

The idea

Access to the cluster (kubeconfig) is put into CI secrets, and the pipeline runs kubectl apply on every push. Below is how to set this up in GitLab CI and GitHub Actions for a Kubernetes cluster on serverplus, and how to do it safely without handing out admin access.

What goes into CI secrets

  • The cluster kubeconfig — ideally not admin but a restricted one (from a ServiceAccount, see below). Encode it in base64 and store it as a masked CI variable (KUBE_CONFIG).
  • application credential id/secret — only if the pipeline touches the OpenStack infrastructure itself (for example via Terraform).
# get kubeconfig and encode it for the CI secret
openstack coe cluster config my-cluster
base64 -w0 ./config    # result -> KUBE_CONFIG variable

Never commit the kubeconfig to the repository. Use masked CI secrets only. One kubeconfig file means full access to the cluster.

Restricted access (ServiceAccount + RBAC)

Instead of an admin kubeconfig, create a service account with permissions in a single namespace:

kubectl create namespace app
kubectl -n app create serviceaccount deployer
kubectl -n app create rolebinding deployer-edit \
  --clusterrole=edit --serviceaccount=app:deployer

# short-lived token for this account
kubectl -n app create token deployer --duration=24h

From this token and the cluster address you assemble a separate kubeconfig for CI — it can only change the app namespace.

GitLab CI

.gitlab-ci.yml — deploy on push to main:

deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - echo "$KUBE_CONFIG" | base64 -d > kubeconfig
    - export KUBECONFIG=$PWD/kubeconfig
    - kubectl -n app apply -f k8s/
    - kubectl -n app rollout status deployment/web
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

KUBE_CONFIG is set in Settings → CI/CD → Variables as a masked variable (base64 of the kubeconfig).

GitHub Actions

.github/workflows/deploy.yml:

name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: kubeconfig
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          echo "KUBECONFIG=$PWD/kubeconfig" >> $GITHUB_ENV
      - name: deploy
        run: |
          kubectl -n app apply -f k8s/
          kubectl -n app rollout status deployment/web

KUBE_CONFIG is added in Settings → Secrets and variables → Actions.

Build and private registry

A typical full cycle: build the image, push it to the registry, update the deployment.

docker build -t registry.example/web:$TAG .
docker push registry.example/web:$TAG
kubectl -n app set image deployment/web web=registry.example/web:$TAG

If the registry is private, add an imagePullSecret to the cluster and reference it in the deployment manifest.

Security

  • Not an admin kubeconfig. A separate ServiceAccount with the edit role in a single namespace (RBAC) — a compromised runner won't get access to the whole cluster.
  • Masked CI secrets only, separate credentials per environment (stage/prod), rotated regularly.
  • Restrict who can run the deploy: protected branches and protected environments.

FAQ

kubectl: connection refused / timeout from the pipeline. The kubeconfig must contain the external control plane address (floating IP), not an internal 10.x. The CI runner must have network access to that address.

"forbidden" error. The service account lacks RBAC permissions — check the rolebinding and namespace.

x509 certificate error. A stale kubeconfig (the cluster was recreated). Regenerate it: openstack coe cluster config my-cluster --force.

Recommended
Find the best solution for your project
Fill in the data and you will be contacted by the manager for confirmation
Technical support number
+998 (71) 205-80-00
Email for communication
info@serverplus.uz
Working hours
How would you prefer to receive a consultation?
What are you interested in? (optional)
Free consultation
The engineer will answer, not the operator
No spam or intrusive sales
Do you want to try it yourself first?Each new user is awarded 100,000 UZS of bonuses for testing VPS, Dedicated Server, S3 storage and Kubernetes.
Register and receive a bonus
We do not transfer your data to third parties
Subscribe to our newsletter

Be one of the first to hear news from the hosting industry.