This document was prepared by Mehmet Samet Duman under the auspices of Project Tick HQ and is a documented account of a real-life event. This document is licensed under CC-BY-SA 4.0.
Hi everyone! Today I'm going to show you how to set up GitLab with K8s
Helm Chart. Let's get started. We'll start by dumping the default values
ββand then deploy PostgreSQL with the first CNPG and Redis with Bitnami.
Firstly, init your first repository and create namespace from GitLab
mkdir gitlabhq-production && cd gitlabhq-production
git init
kubectl create namespace gitlab
Then, add helm repositories and dump Redis values with this commands;
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm show values bitnami/redis > redis-all-values.yaml
And we'll copy the values ββso you can edit them and reference the defaults.
cp redis-all-values.yaml redis-values.yaml
Afterwards, you can edit it as you wish using any editing tool you prefer.
This is what my values ββfile looks like now.
# redis-values.yaml
architecture: replication
auth:
existingSecret: gitlab-redis
existingSecretPasswordKey: redis-password
fullnameOverride: gitlab-redis
master:
nodeSelector:
kubernetes.io/hostname: mail.projecttick.org
persistence:
size: 8Gi
replica:
nodeSelector:
kubernetes.io/hostname: mail.projecttick.org
persistence:
size: 8Gi
replicaCount: 3
sentinel:
enabled: true
masterSet: gitlab-redis
nodeSelector:
kubernetes.io/hostname: mail.projecttick.org
But before deploying it, we need to create its secret. For this, I prefer YAML. The following YAML file will help me define my secret.
# gitlab-redis-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: gitlab-redis
namespace: gitlab
type: Opaque
stringData:
redis-password: <YOURREDISPASSWORD>
And install with this command.
kubectl apply -f gitlab-redis-secret.yaml
If you saved the password, please delete this YAML file, otherwise your secret and passwords will be corrupted in your Git commit.
rm gitlab-redis-secret.yaml
And we install it with Helm install using the following command:
helm upgrade gitlab-redis bitnami/redis -f ./redis-values.yaml -n gitlab --timeout 600s --install
Now we will deploy PostgreSQL with CNPG. Because GitLab itself created these dependencies, it fell to us to deploy them.
The operator will not create the secret. Therefore, we will create the secret ourselves. Creating it with YAML is easier,
but remember to save these secrets in hidden locations. Otherwise, your data will disappear and you won't be able to access it.
For CNPG, we'll be deploying manually instead of using an operator and helm chart. I'm using this and it's working perfectly for me.
# gitlab-prod-cnpg.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: gitlab-prod-cnpg
namespace: gitlab
spec:
postgresql:
parameters:
max_connections: "200"
instances: 1
imageName: ghcr.io/cloudnative-pg/postgresql:17.6
primaryUpdateStrategy: unsupervised
enableSuperuserAccess: true
bootstrap:
initdb:
database: gitlabhq_production
owner: gitlab
secret:
name: gitlab-prod-cnpg-app
storage:
size: 150Gi
storageClass: local-path
affinity:
nodeSelector:
kubernetes.io/hostname: mail.projecttick.org
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi
# gitlab-prod-cnpg-app-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: gitlab-prod-cnpg-app
namespace: gitlab
type: kubernetes.io/basic-auth
stringData:
username: gitlab
password: <YOURPGSQLPASSWORD>
You can deploy this with the following command.
kubectl apply -f gitlab-prod-cnpg-app-secret.yaml
kubectl apply -f gitlab-prod-cnpg.yaml
I'm saying this again and again: if you saved the password, please delete this YAML file, otherwise your secret and passwords will get mixed up in your Git commit.
rm gitlab-prod-cnpg-app-secret.yaml
Now, if you don't have an ingress provider in the cluster, you need to set one up. I recommend Nginx Ingress. If you don't have a provider, let's set one up immediately. But we need to import our TLS certificates as secrets, otherwise recognition might not work, and you'll end up with a stub certificate that won't be able to pass through anywhere. I used to use omnibus gitlab, but I generated my certificates with certbot. First, let's check the certificate directory with certbot. This is the result I got:
[root@mail k8s-migration]# certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: projecttick.org
Serial Number: ************************
Key Type: ECDSA
Identifiers: projecttick.org *.pages.projecttick.net *.projecttick.com *.projecttick.net *.projecttick.org projecttick.com projecttick.net
Expiry Date: 2026-09-12 14:19:06+00:00 (VALID: 82 days)
Certificate Path: /etc/letsencrypt/live/projecttick.org/fullchain.pem
Private Key Path: /etc/letsencrypt/live/projecttick.org/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[root@mail k8s-migration]#
Now, we import our TLS certificate using this command.
kubectl -n gitlab create secret tls gitlab-wildcard-tls --cert="/etc/letsencrypt/live/projecttick.org/fullchain.pem" --key="/etc/letsencrypt/live/projecttick.org/privkey.pem" --dry-run=client -o yaml | kubectl apply -f -
Now let's add the ingress-nginx repository, update the repositories, and dump all the values.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm show values ingress-nginx/ingress-nginx > ingress-nginx-all-values.yaml
And we'll copy the values again ββso you can edit them and reference the defaults.
cp ingress-nginx-all-values.yaml ingress-nginx-values.yaml
I'll install it after I've edited it, but first let me show you mine as a real usage reference.
# ingress-nginx-values.yaml
controller:
ingressClass: gitlab-nginx
ingressClassByName: true
ingressClassResource:
name: gitlab-nginx
enabled: true
controllerValue: k8s.io/ingress-nginx
default: false
watchIngressWithoutClass: false
nodeSelector:
kubernetes.io/hostname: mail.projecttick.org
replicaCount: 1
hostPort:
enabled: true
ports:
http: 80
https: 443
containerPort:
http: 80
https: 443
updateStrategy:
type: Recreate
service:
enabled: false
extraArgs:
default-ssl-certificate: gitlab/gitlab-wildcard-tls
ingress-class-by-name: "true"
admissionWebhooks:
enabled: false
allowSnippetAnnotations: true
config:
annotations-risk-level: Critical
As you may have noticed, I've assigned the same certificate we just added as the default. Now we can proceed with the installation.
helm upgrade ingress-nginx ingress-nginx/ingress-nginx --version 4.15.1 --namespace ingress-nginx -f ./ingress-nginx-values.yaml --timeout 600s --install --create-namespace
If you're wondering why we put this in a separate namespace, it's so that it's easier if you ever want to delete GitLab and install another service. And we don't need to carry a secret here because we're reading directly from the GitLab namespace.
Now, I have a question for you. If you want to use advanced search, you should install Elasticsearch or Opensearch. If you don't want to install them, please skip this part. I prefer Elasticsearch. Therefore, we'll first add the Elasticsearch repository, then take a dump and edit it. The process is the same. After that, we'll install MinIO and finally install GitLab to finish.
helm repo add elastic https://helm.elastic.co
helm repo update
helm show values elastic/elasticsearch > elasticsearch-all-values.yaml
cp elasticsearch-all-values.yaml elasticsearch-values.yaml
This is what my values ββfile looks like:
# elasticsearch-values.yaml
createCert: false
esConfig:
elasticsearch.yml: 'xpack.security.enabled: false'
minimumMasterNodes: 1
protocol: http
replicas: 1
volumeClaimTemplate:
resources:
requests:
storage: 50Gi
Now let's set this up with Helm.
helm upgrade elasticsearch elastic/elasticsearch -n gitlab -f ./elasticsearch-values.yaml --timeout 600s --install
Now we can move on to the main event. We will deploy MinIO, but we won't be using Helm; instead, we'll deploy it with a YAML file using kubectl. However, for this, we first need to create a namespace called "storage", write a YAML file, and deploy it using apply. I chose the minio/minio:latest image from quay.io. We will also write environment values ββand create a secret when deploying this.
I wrote a YAML file. It contains a statefulset, a service, and a secret. We'll also write a YAML file for the ingress layer, but that'll come later.
# minio.yaml
apiVersion: v1
kind: Namespace
metadata:
name: storage
---
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
namespace: storage
type: Opaque
stringData:
root-user: "YOURUSER"
root-password: "YOURPASSWORD"
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
namespace: storage
spec:
serviceName: minio
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: quay.io/minio/minio:latest
args:
- server
- /data
- --console-address
- ":9001"
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-secret
key: root-user
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secret
key: root-password
- name: MINIO_SERVER_URL
value: https://s3.projecttick.net
- name: MINIO_BROWSER_REDIRECT_URL
value: https://minio.projecttick.net
- name: MINIO_DOMAIN
value: s3.projecttick.net
ports:
- containerPort: 9000
name: s3
- containerPort: 9001
name: console
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: local-path
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 120Gi
---
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: storage
spec:
type: NodePort
selector:
app: minio
ports:
- name: s3
port: 9000
targetPort: 9000
nodePort: 30900
- name: console
port: 9001
targetPort: 9001
nodePort: 30901
CAUTION!
Do not assign https:// to the MINIO_DOMAIN value! Otherwise, you will get a 403 error and the S3 system will be corrupted for external use.
Let's deploy it now.
kubectl create namespace storage
kubectl apply -f minio.yaml
Now we'll write the ingress layer for this. If you're wondering why I've given it a separate entry, it's so you can have a reference for subsequent ingress YAML files.
# minio-ingress.yaml
# === s3.projecttick.net -> minio 9000 (S3 API) ===
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-s3
namespace: storage
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
ingressClassName: gitlab-nginx
tls:
- hosts: [s3.projecttick.net]
secretName: gitlab-wildcard-tls
rules:
- host: s3.projecttick.net
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: minio, port: {number: 9000}}}
---
# === minio.projecttick.net -> minio 9001 (Console, websocket) ===
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-console
namespace: storage
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
spec:
ingressClassName: gitlab-nginx
tls:
- hosts: [minio.projecttick.net]
secretName: gitlab-wildcard-tls
rules:
- host: minio.projecttick.net
http:
paths:
- path: /
pathType: Prefix
backend: {service: {name: minio, port: {number: 9001}}}
But before we deploy this, we need to move our TLS to the storage namespace. We deploy it directly after transporting it.
kubectl -n storage create secret tls gitlab-wildcard-tls --cert="/etc/letsencrypt/live/projecttick.org/fullchain.pem" --key="/etc/letsencrypt/live/projecttick.org/privkey.pem" --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f minio-ingress.yaml
MinIO has now been deployed. Now let's get to the final boss, GitLab. While its values ββaren't overly complex, I'd say they're extremely detailed.
helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm show values gitlab/gitlab > gitlab-all-values.yaml
cp gitlab-all-values.yaml gitlab-values.yaml
My values ββfile looks like this. But remember, almost everything is open in it. And don't forget, the GitLab example you're currently reading this document from is running with this YAML values ββfile.
global:
edition: ee
hosts:
ssh: git.projecttick.org
domain: projecttick.org
https: true
pages:
name: pages.projecttick.net
gitlab:
name: git.projecttick.org
registry:
name: registry.projecttick.org
kas:
name: kas.projecttick.org
minio:
name: minio.projecttick.org
shell:
port: 22
gatewayApi:
enabled: false
installEnvoy: false
configureCertmanager: false
ingress:
enabled: true
provider: nginx
class: gitlab-nginx
configureCertmanager: false
tls:
enabled: true
secretName: gitlab-wildcard-tls
keda:
enabled: true
monitoring:
enabled: true
minio:
enabled: false
praefect:
enabled: false
gitaly:
enabled: true
internal:
names: [default]
service:
name: gitaly
type: ClusterIP
externalPort: 8075
internalPort: 8075
tls:
externalPort: 8076
internalPort: 8076
client:
maxAttempts: 4
initialBackoff: '0.4s'
maxBackoff: '1.4s'
appConfig:
omniauth:
enabled: true
allowSingleSignOn: ['openid_connect','github','gitlab','google_oauth2','azure_activedirectory_v2']
blockAutoCreatedUsers: true
autoLinkUser: ['openid_connect','github','gitlab','google_oauth2','azure_activedirectory_v2']
providers:
- secret: gitlab-omniauth-github
key: provider
- secret: gitlab-omniauth-gitlab
key: provider
- secret: gitlab-omniauth-google
key: provider
- secret: gitlab-omniauth-azure
key: provider
- secret: gitlab-omniauth-openid
key: provider
- secret: gitlab-omniauth-group
key: provider
enableUsagePing: false
enableSeatLink: false
usernameChangingEnabled: true
incomingEmail:
enabled: true
address: "pt-gitlab-incoming+%{key}@projecttick.org"
host: "mail.projecttick.org"
port: 993
ssl: true
startTls: false
user: "pt-gitlab-incoming@projecttick.org"
password:
secret: gitlab-incoming-email-password
key: password
mailbox: "inbox"
inboxMethod: "imap"
deliveryMethod: webhook
serviceDeskEmail:
enabled: true
address: "desk+%{key}@projecttick.org"
host: "mail.projecttick.org"
port: 993
ssl: true
startTls: false
user: "desk@projecttick.org"
password:
secret: gitlab-service-desk-email-password
key: password
mailbox: "inbox"
inboxMethod: "imap"
deliveryMethod: webhook
defaultTheme: 4
defaultColorMode: 1
issueClosingPattern: "\\b((?:[Cc]los(?:e[sd]?|ing)|\\b[Ff]ix(?:e[sd]|ing)?|\\b[Rr]esolv(?:e[sd]?|ing)|\\b[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\\d+))+)"
enableImpersonation: false
defaultProjectsFeatures:
issues: true
mergeRequests: true
wiki: true
snippets: true
builds: true
extra:
googleAnalyticsId: "G-91XWT2Q4LY"
ldap:
enabled: true
preventSignin: false
servers:
main:
label: 'Project Tick LDAP'
host: 'ldap.projecttick.net'
port: 636
uid: 'uid'
base: 'ou=people,dc=projecttick,dc=net'
encryption: 'simple_tls'
verify_certificates: true
bind_dn: 'cn=Directory Manager'
password:
secret: gitlab-ldap-password
key: password
user_filter: '(objectClass=nsPerson)'
active_directory: false
object_store:
enabled: true
connection:
secret: gitlab-object-storage
key: connection
lfs:
enabled: true
proxy_download: true
bucket: gitlab-lfs
artifacts:
enabled: true
proxy_download: true
bucket: gitlab-artifacts
uploads:
enabled: true
proxy_download: true
bucket: gitlab-uploads
packages:
enabled: true
proxy_download: true
bucket: gitlab-packages
externalDiffs:
enabled: true
proxy_download: true
bucket: gitlab-mr-diffs
terraformState:
enabled: true
bucket: gitlab-terraform
ciSecureFiles:
enabled: true
bucket: gitlab-secure-files
dependencyProxy:
enabled: true
bucket: gitlab-dependency-proxy
backups:
bucket: gitlab-backups
tmpBucket: tmp
smtp:
enabled: true
address: "152.53.231.231"
port: 587
authentication: "login"
domain: "projecttick.org"
starttls_auto: true
openssl_verify_mode: "none"
password:
secret: gitlab-smtp-password
key: password
user_name: "noreply@projecttick.org"
pages:
enabled: true
accessControl: false
artifactsServer: true
objectStore:
enabled: true
bucket: gitlab-pages
connection:
secret: gitlab-pages-storage
key: connection
namespaceInPath: false
email:
from: "noreply@projecttick.org"
display_name: "Project Tick GitLab"
reply_to: "support@projecttick.org"
psql:
host: gitlab-prod-cnpg-rw.gitlab.svc.cluster.local
port: 5432
username: gitlab
database: gitlabhq_production
password:
secret: gitlab-prod-cnpg-app
key: password
redis:
host: gitlab-redis
sentinels:
- host: gitlab-redis-node-0.gitlab-redis-headless.gitlab.svc.cluster.local
port: 26379
- host: gitlab-redis-node-1.gitlab-redis-headless.gitlab.svc.cluster.local
port: 26379
- host: gitlab-redis-node-2.gitlab-redis-headless.gitlab.svc.cluster.local
port: 26379
sentinelAuth:
enabled: true
secret: gitlab-redis
key: redis-password
auth:
enabled: true
secret: gitlab-redis
key: redis-password
time_zone: UTC
serviceAccount:
enabled: true
create: true
automountServiceAccountToken: true
certmanager-issuer:
install: false
email: projecttick@projecttick.org
registry:
storage:
secret: gitlab-registry
key: config
installCertmanager: false
nginx-ingress:
enabled: false
certmanager:
installCRDs: false
redis:
install: false
postgresql:
install: false
minio:
install: false
prometheus:
install: true
rbac:
create: true
alertmanager:
enabled: true
kube-state-metrics:
enabled: true
prometheus-node-exporter:
enabled: true
prometheus-pushgateway:
enabled: true
scrapeConfigs: null
server:
retention: 15d
strategy:
type: Recreate
containerSecurityContext:
runAsUser: 1000
allowPrivilegeEscalation: false
runAsNonRoot: true
capabilities:
drop: [ "ALL" ]
seccompProfile:
type: "RuntimeDefault"
podSecurityPolicy:
enabled: false
configmapReload:
prometheus:
containerSecurityContext:
runAsUser: 1000
allowPrivilegeEscalation: false
runAsNonRoot: true
capabilities:
drop: [ "ALL" ]
seccompProfile:
type: "RuntimeDefault"
serverFiles:
prometheus.yml:
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- localhost:9090
- job_name: kubernetes-apiservers
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels:
[
__meta_kubernetes_namespace,
__meta_kubernetes_service_name,
__meta_kubernetes_endpoint_port_name,
]
action: keep
regex: default;kubernetes;https
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels:
[__meta_kubernetes_pod_annotation_gitlab_com_prometheus_scrape]
action: keep
regex: true
- source_labels:
[__meta_kubernetes_pod_annotation_gitlab_com_prometheus_scheme]
action: replace
regex: (https?)
target_label: __scheme__
- source_labels:
[__meta_kubernetes_pod_annotation_gitlab_com_prometheus_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels:
[
__address__,
__meta_kubernetes_pod_annotation_gitlab_com_prometheus_port,
]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
- job_name: kubernetes-service-endpoints
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- action: keep
regex: true
source_labels:
- __meta_kubernetes_service_annotation_gitlab_com_prometheus_scrape
- action: replace
regex: (https?)
source_labels:
- __meta_kubernetes_service_annotation_gitlab_com_prometheus_scheme
target_label: __scheme__
- action: replace
regex: (.+)
source_labels:
- __meta_kubernetes_service_annotation_gitlab_com_prometheus_path
target_label: __metrics_path__
- action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
source_labels:
- __address__
- __meta_kubernetes_service_annotation_gitlab_com_prometheus_port
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- action: replace
source_labels:
- __meta_kubernetes_namespace
target_label: kubernetes_namespace
- action: replace
source_labels:
- __meta_kubernetes_service_name
target_label: kubernetes_name
- action: replace
source_labels:
- __meta_kubernetes_pod_node_name
target_label: kubernetes_node
- job_name: kubernetes-services
metrics_path: /probe
params:
module: [http_2xx]
kubernetes_sd_configs:
- role: service
relabel_configs:
- source_labels:
[
__meta_kubernetes_service_annotation_gitlab_com_prometheus_probe,
]
action: keep
regex: true
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_name
upgradeCheck:
enabled: true
securityContext:
runAsUser: 65534
fsGroup: 65534
seccompProfile:
type: "RuntimeDefault"
containerSecurityContext:
runAsUser: 65534
allowPrivilegeEscalation: false
runAsNonRoot: true
shared-secrets:
enabled: true
rbac:
create: true
ai-gateway:
install: false
ingress:
enabled: false
gitlab-runner:
install: true
nodeSelector:
region: us
tolerations:
- key: dedicated
operator: Equal
value: canary
effect: NoSchedule
replicas: 4
runners:
config: |
concurrent = 40
check_interval = 3
[[runners]]
[runners.kubernetes]
namespace = "gitlab"
image = "alpine:latest"
memory_request = "2Gi"
memory_limit = "2Gi"
cpu_request = "300m"
[runners.kubernetes.node_selector]
"region" = "us"
"kubernetes.io/arch" = "arm64"
[runners.kubernetes.node_tolerations]
"dedicated=canary" = "NoSchedule"
gitlab-zoekt:
install: true
gateway:
basicAuth:
enabled: true
secretName: gitlab-zoekt-gateway-basic-auth
zoekt:
indexer:
replicas: 1
webserver:
replicas: 1
persistence:
size: 150Gi
gitlab:
webservice:
deployment:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
minReplicas: 2
maxReplicas: 4
sidekiq:
minReplicas: 2
maxReplicas: 4
routingRules:
- ["*", ""]
pods:
- name: all-in-1
queues: ""
gitlab-shell:
enabled: true
service:
type: NodePort
nodePort: 22
toolbox:
replicas: 1
antiAffinityLabels:
matchLabels:
app: gitaly
backups:
objectStorage:
config:
secret: gitlab-backup-storage
key: config
migrations:
enabled: true
IMPORTANT!
I used a separate node selector for the runner, but please use your own selector to avoid it getting stuck in pending mode.
But we can't deploy it as is because there are no buckets or secrets yet.
First, let's create the secrets, and then we'll create the buckets. First, we create our most important secret to enable S3 to connect with GitLab, and we apply this for two separate purposes.
# gitlab-object-storage-connection.yaml
provider: AWS
region: us-east-1
aws_access_key_id: 'YOURUSER'
aws_secret_access_key: 'YOURPASSWORD'
aws_signature_version: 4
host: minio.storage.svc.cluster.local
endpoint: 'http://minio.storage.svc.cluster.local:9000'
path_style: true
kubectl create secret generic gitlab-object-storage --from-file=connection=gitlab-object-storage-connection.yaml -n gitlab
kubectl create secret generic gitlab-pages-storage --from-file=connection=gitlab-object-storage-connection.yaml -n gitlab
# gitlab-backup-storage.yaml
[default]
access_key = YOURUSER
secret_key = YOURPASSWORD
host_base = minio.storage.svc.cluster.local:9000
host_bucket = minio.storage.svc.cluster.local:9000
use_https = False
kubectl create secret generic gitlab-backup-storage --from-file=config=gitlab-backup-storage.yaml -n gitlab
# gitlab-registry.yaml
s3:
bucket: gitlab-registry
accesskey: YOURUSER
secretkey: 'YOURPASSWORD'
region: us-east-1
regionendpoint: 'http://minio.storage.svc.cluster.local:9000'
v4auth: true
pathstyle: true
checksum_disabled: true
redirect:
disable: true
kubectl create secret generic gitlab-registry --from-file=config=gitlab-registry.yaml -n gitlab
# gitlab-mail-ldap-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: gitlab-incoming-email-password
namespace: gitlab
type: Opaque
stringData:
password: <INCOMING_EMAIL_PASSWORD>
---
apiVersion: v1
kind: Secret
metadata:
name: gitlab-service-desk-email-password
namespace: gitlab
type: Opaque
stringData:
password: <SERVICE_DESK_PASSWORD>
---
apiVersion: v1
kind: Secret
metadata:
name: gitlab-smtp-password
namespace: gitlab
type: Opaque
stringData:
password: <SMTP_PASSWORD>
---
apiVersion: v1
kind: Secret
metadata:
name: gitlab-ldap-password
namespace: gitlab
type: Opaque
stringData:
password: <LDAP_BIND_PASSWORD>
kubectl create secret generic gitlab-zoekt-gateway-basic-auth --from-literal=gitlab_username=gitlab --from-literal=gitlab_password=<ZOEKT_PASSWORD> -n gitlab
kubectl apply -f gitlab-mail-ldap-secrets.yaml
# azure-omniauth.yaml
name: azure_activedirectory_v2
label: "Azure AD v2"
args:
client_id: "CLIENT_ID"
client_secret: "CLIENT_SECRET"
tenant_id: "TENANT_ID"
# github-omniauth.yaml
name: github
label: "GitHub"
app_id: "APP_ID"
app_secret: "APP_SECRET"
args:
scope: 'user:email'
# gitlab-omniauth.yaml
name: gitlab
label: "GitLab SaaS"
app_id: "APP_ID"
app_secret: "APP_SECRET"
args:
scope: 'read_user'
# google-omniauth.yaml
name: google_oauth2
label: "Google"
app_id: "APP_ID"
app_secret: "APP_SECRET"
# group-omniauth.yaml
name: group_saml
# openid-omniauth.yaml
name: openid_connect
label: "Project Tick SSO"
args:
name: openid_connect
scope: ['openid','profile','email']
response_type: code
issuer: "https://id.projecttick.net/realms/projecttick"
discovery: true
client_auth_method: query
uid_field: preferred_username
pkce: true
client_options:
identifier: "gitlab"
secret: "SECRET"
redirect_uri: "https://git.projecttick.org/users/auth/openid_connect/callback"
kubectl create secret generic gitlab-omniauth-azure --from-file=provider=azure-omniauth.yaml -n gitlab
kubectl create secret generic gitlab-omniauth-github --from-file=provider=github-omniauth.yaml -n gitlab
kubectl create secret generic gitlab-omniauth-gitlab --from-file=provider=gitlab-omniauth.yaml -n gitlab
kubectl create secret generic gitlab-omniauth-google --from-file=provider=google-omniauth.yaml -n gitlab
kubectl create secret generic gitlab-omniauth-group --from-file=provider=group-omniauth.yaml -n gitlab
kubectl create secret generic gitlab-omniauth-openid --from-file=provider=openid-omniauth.yaml -n gitlab
Now that the secrets are ready, we need to create the buckets. GitLab won't create them for us; each bucket referenced in our values ββmust already exist, otherwise the relevant property will initially fail with access errors.
We will be using the MinIO client (mc). First, let's register an alias pointing to our MinIO instance with the root credentials we defined earlier:
IMPORTANT!
If you are creating the buckets from a different location, please enter the full IP address instead of localhost.
mc alias set direct http://127.0.0.1:30900 YOURUSER YOURPASSWORD
Now create every bucket GitLab expects. These names come directly from our values file:
for b in gitlab-lfs gitlab-artifacts gitlab-uploads gitlab-packages \
gitlab-mr-diffs gitlab-terraform gitlab-secure-files \
gitlab-dependency-proxy gitlab-backups tmp \
gitlab-pages gitlab-registry; do
mc mb direct/$b
done
Verify they were all created:
mc ls direct
Yes! Everything is ready now. There are no more obstacles to deploying GitLab. You can now deploy by pinning the release.
helm upgrade gitlab gitlab/gitlab --version 10.1.0 --namespace gitlab -f ./gitlab-values.yaml --timeout 1200s --install
The deployment may take a while, please wait patiently. You can easily track it afterwards with a command like kubectl get pods -n gitlab. Now, let's get to the main point: the first login with the root password! But remember, Helm may autonomously delete the secret containing the first root password in the next deployment or after 24 hours for security reasons. Therefore, change your password immediately after logging in. Learn your root password with the following command.
kubectl get secret gitlab-gitlab-initial-root-password \
-n gitlab \
-o jsonpath='{.data.password}' | base64 -d ; echo
After logging in as root, obtain the Elasticsearch URL, username, and password from the admin panel and enter them. The Elasticsearch Helm installation instructions clearly explain how to obtain them. Then, immediately create an instance runner and use the secret you obtained to create the following secret.
# gitlab-runner-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: gitlab-runner-secret
namespace: gitlab
type: Opaque
stringData:
runner-registration-token: ""
runner-token: "YOURRUNNERSECRET"
kubectl apply -f gitlab-runner-secret.yaml
CAUTION!
Please remember to delete the secrets where you entered your passwords. Otherwise, you may encounter serious problems.
That's it! Your GitLab EE instance is now running on Kubernetes with external PostgreSQL (CNPG), Redis Sentinel, MinIO object storage, Elasticsearch, and Zoekt. Happy DevOps-ing!













