Run GitLab Runner on Kubernetes with Helm and Scoped Access

Posted on May 14, 2018

Updated

Deploy GitLab Runner on Kubernetes using the official Helm chart and a runner authentication token from the current runner-creation workflow. Store the token in a Secret, scope its permissions and keep ordinary jobs unprivileged. The original registration-token and ConfigMap recipe is historical.

Create the runner and protect its credential

In your GitLab instance, create the intended project or group runner and choose tags, scope and protection rules. Use its authentication token, commonly prefixed glrt-. Do not paste the token into Git, a ConfigMap, an article or a Helm command argument.

Create a Kubernetes Secret named gitlab-runner-auth in a dedicated ci-lab namespace using your approved secret workflow. The official chart expects keys runner-token and runner-registration-token; the latter is empty for the authentication-token workflow. Confirm these keys against the pinned chart version.

Review chart values and permissions

Save this values fragment as runner-values.yaml. Select a chart version compatible with your GitLab instance. It is an integration starting point, not a substitute for reviewing the rendered RBAC and job security context.

gitlabUrl: https://gitlab.com/
rbac:
  create: true
  clusterWideAccess: false
  rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["create", "delete", "get", "list", "watch"]
    - apiGroups: [""]
      resources: ["pods/attach", "pods/exec"]
      verbs: ["create", "delete", "get", "patch"]
    - apiGroups: [""]
      resources: ["pods/log"]
      verbs: ["get", "list"]
    - apiGroups: [""]
      resources: ["secrets"]
      verbs: ["create", "delete", "get", "update"]
    - apiGroups: [""]
      resources: ["serviceaccounts"]
      verbs: ["get"]
    - apiGroups: [""]
      resources: ["services"]
      verbs: ["create", "get"]
runners:
  secret: gitlab-runner-auth
  config: |
    [[runners]]
      [runners.kubernetes]
        namespace = "ci-lab"
        image = "alpine:3.23"
        privileged = false
helm repo add gitlab https://charts.gitlab.io
helm repo update gitlab
helm search repo gitlab/gitlab-runner --versions
# Set the exact version you reviewed for your GitLab server:
helm template runner gitlab/gitlab-runner --namespace ci-lab \
  --version "$RUNNER_CHART_VERSION" -f runner-values.yaml > runner-rendered.yaml
# Review the rendered Secret references, RBAC and pod settings before install.
helm upgrade --install runner gitlab/gitlab-runner \
  --namespace ci-lab --create-namespace \
  --version "$RUNNER_CHART_VERSION" -f runner-values.yaml --wait

Run a real pipeline before calling it ready

Confirm the runner connects to the intended GitLab instance and that a tagged test job runs in the expected namespace. Test image pulls, DNS, TLS trust, cache and artifacts as applicable. Untrusted projects should not share a privileged execution boundary with trusted jobs.

If an image build requires elevated access, evaluate a supported rootless or otherwise isolated builder and a separate trust boundary. Do not make privileged Docker-in-Docker the default or expose an unauthenticated Docker daemon.

Cleanup

Pause and unregister the lab runner through the appropriate GitLab workflow, uninstall its Helm release, then delete its lab namespace and revoke the token. Check for job Pods, caches and external artifacts that outlive the runner.

Live validation on 30 October 2026 used a disposable private GitLab CE 19.5.3 server, chart 0.94.2 and Runner 19.5.2. A real Kubernetes job completed and its expected artifact was retrieved. The job Pod was checked for privileged mode and hostPath volumes. We then removed the runner, project, Kubernetes resources and temporary server. The isolated lab used private HTTP; production needs the appropriate HTTPS and trust configuration.

Sources and further reading