Skip to content

Installation

This page covers installing the cluster prerequisites and the bnerd-gitlab-operator itself.

Cluster prerequisites

The operator depends on several external controllers. Install them once per cluster before installing the operator. All are optional depending on which features you use, with one hard requirement: Flux.

1. Flux CD (required)

The operator emits HelmRelease (helm.toolkit.fluxcd.io/v2) and HelmRepository (source.toolkit.fluxcd.io/v1beta2) resources. Flux's source-controller and helm-controller must be running to apply them.

Flux >= 2.3 required

The operator emits the GA helm.toolkit.fluxcd.io/v2 HelmRelease API. This API was promoted in Flux 2.3 and the v2beta1 form was removed in Flux 2.4. You must run Flux >= 2.3 on the cluster — earlier versions do not serve the v2 API and will not reconcile the emitted HelmRelease.

Install Flux (2.3 or later):

flux install \
  --components source-controller,helm-controller

Verify:

flux check

2. Percona PostgreSQL Operator (for managed Postgres)

Required when any GitlabInstance uses spec.postgres.managed: true. Install the Percona PG Operator into its own namespace:

# Via Helm
helm repo add percona https://percona.github.io/percona-helm-charts/
helm install pg-operator percona/pg-operator \
  --namespace pgo \
  --create-namespace

Verify:

kubectl get crd perconapgclusters.pgv2.percona.com

3. OT-Container-Kit redis-operator (for managed Redis)

Required when any GitlabInstance uses spec.redis.managed: true.

helm repo add ot-helm https://ot-container-kit.github.io/helm-charts/
helm install redis-operator ot-helm/redis-operator \
  --namespace ot-operators \
  --create-namespace

Verify:

kubectl get crd redis.redis.opstreelabs.in

OT-Container-Kit v0.25.0 readiness quirk

OT-Container-Kit v0.25.0 ships an empty RedisStatus struct; the status.readyReplicas field is pruned by the API server. The operator detects this and falls back to inspecting the underlying StatefulSet's readyReplicas instead. No configuration needed — this is handled transparently.

4. ECK — Elastic Cloud on Kubernetes (for managed Elasticsearch, EE only)

Required only when deploying GitLab Enterprise Edition with managed Elasticsearch (spec.elasticsearch.managed: true).

kubectl create -f https://download.elastic.co/downloads/eck/2.12.1/crds.yaml
kubectl apply -f https://download.elastic.co/downloads/eck/2.12.1/operator.yaml

Verify:

kubectl get crd elasticsearches.elasticsearch.k8s.elastic.co

5. cert-manager (required — cluster infrastructure)

The operator emits installCertmanager: false on every GitlabInstance: it never installs the GitLab chart's bundled cert-manager. cert-manager is expected to already exist on the cluster as shared infrastructure (typically Flux-managed).

This is deliberate. The GitLab chart's bundled cert-manager templates fixed-name, cluster-scoped resources (the cert-manager CRDs, a GatewayClass, and ValidatingAdmissionPolicy/Binding objects). Those can have exactly one Helm owner per cluster, ever — so if the chart installed them, a second GitlabInstance (or any cluster that already runs cert-manager) would be refused by Helm with exists and cannot be imported into the current release: invalid ownership metadata. Leaving cert-manager as a cluster prerequisite avoids that collision.

Install it once per cluster (any supported method), for example:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml

Verify:

kubectl get crd certificates.cert-manager.io

Provision Issuers/ClusterIssuers (e.g. Let's Encrypt) at the cluster level and reference them from spec.ingress.annotations — see the Ingress guide.


Installing the operator

helm install bnerd-gitlab-operator charts/bnerd-gitlab-operator \
  --namespace bnerd-gitlab-operator \
  --create-namespace \
  --set image.tag=0.1.0-beta

The Helm chart installs:

  • The operator Deployment with leader election enabled
  • A ServiceAccount and ClusterRole with the minimum RBAC permissions
  • All three CRDs (GitlabInstance, GitlabVersionMap, GitlabProfile) from charts/bnerd-gitlab-operator/crds/
  • The default GitlabVersionMap (versionMap.installDefault: true, the chart default) — see Applying the default GitlabVersionMap below

Verify the operator is running:

kubectl get pods -n bnerd-gitlab-operator
# NAME                                       READY   STATUS    RESTARTS   AGE
# bnerd-gitlab-operator-7d9f6c8b4-xk2lp     1/1     Running   0          30s
# Build and apply via kustomize
make deploy IMG=git.bnerd.net/cloud/operators/bnerd-gitlab-operator:0.1.0-beta

This applies the manifests in config/manager/ using kustomize and sets the operator image. CRDs from config/crd/bases/ are applied first.

Useful for development or when you want to run directly from a repository checkout without packaging a Helm chart.


Applying the default GitlabVersionMap

The GitlabVersionMap named default is required for version resolution — without it, every GitlabInstance reconcile fails to resolve spec.version and the instance never progresses past an empty/statusless state.

Installed automatically as part of the chart (versionMap.installDefault: true, the default). No extra step needed. Verify it's there:

kubectl get glvm default

Set versionMap.installDefault: false if you manage your own GitlabVersionMap/default out-of-band, then apply your own copy.

Apply it once per cluster after installing the operator:

kubectl apply -f examples/versionmap-default.yaml

The default map covers the GitLab 17.x → 19.x upgrade path, including all required upgrade stops. See Version Map Reference for the full ladder.


Verifying the installation

# Operator pod
kubectl get pods -n bnerd-gitlab-operator

# CRDs registered
kubectl get crd | grep k8s.bnerd.com

# Default version map
kubectl get glvm default

# Operator logs (no errors expected)
kubectl logs -n bnerd-gitlab-operator deploy/bnerd-gitlab-operator --tail=50

RBAC summary

The operator requires cluster-level read access to CRDs (for capability gating) and namespace-level create/update/delete access for Secrets, PerconaPGCluster, Redis CRs, ECK Elasticsearch, and Flux HelmRelease/HelmRepository resources. The full role is in config/rbac/role.yaml.

The operator runs with a non-root UID (65532) and a read-only root filesystem by default.