The values below configure a Deckhouse Code Helm release beyond the minimal install from Quick start. Value names are those of the chart’s values.yaml; Advanced configuration lists the values the chart adds to the upstream GitLab chart and the upstream defaults it changes. Apply a change with the same command as the install:
helm upgrade --install code oci://<CHART_REGISTRY>/code \
--version <CHART_VERSION> -n code -f values.yamlHost names and ingress
global.hosts.domain is the base domain. The web interface and, when enabled, the registry and Pages answer at gitlab.<domain>, registry.<domain> and pages.<domain>, unless global.hosts.gitlab.name, global.hosts.registry.name and global.hosts.pages.name set other names. global.ingress.class selects the ingress controller; global.shell.port is the SSH port Git clients are shown in the clone URL.
global:
hosts:
domain: example.com
gitlab:
name: code.example.com
registry:
name: registry.example.com
ingress:
class: nginx
shell:
port: 22TLS
By default cert-manager issues the Ingress certificates from the ClusterIssuer in global.codeHelm.ingress.tls.clusterIssuer into the Secrets code-tls, code-registry-tls and code-pages-tls, one for each enabled host. To supply your own certificate, switch to the manual mode and name the Secret, of type kubernetes.io/tls, that covers every host name:
global:
codeHelm:
ingress:
tls:
mode: manual
ingress:
tls:
secretName: <TLS_SECRET_NAME>Create the Secret in the release namespace before the upgrade; the chart neither creates nor renews it. A Secret per enabled component goes into gitlab.webservice.ingress.tls.secretName, registry.ingress.tls.secretName, gitlab.gitlab-pages.ingress.tls.secretName and gitlab.kas.ingress.tls.secretName instead. Ingress TLS describes both modes.
Load balancer
The release exposes HTTP, HTTPS and Git over SSH through the Service code-code-loadbalancer of type LoadBalancer, served by an HAProxy Deployment. The cluster assigns the external address to the Service; global.codeHelm.loadbalancer.annotations passes the annotations its load balancer implementation reads, for example a fixed address for MetalLB:
global:
codeHelm:
loadbalancer:
enabled: true
annotations:
metallb.universe.tf/loadBalancerIPs: <EXTERNAL_IP>
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256MiWhen the cluster runs several load balancer implementations, global.codeHelm.loadbalancer.loadBalancerClass selects one of them. global.codeHelm.loadbalancer.hpa scales the HAProxy pods by CPU and memory use in place of replicas, and global.codeHelm.loadbalancer.pdb.enabled keeps one pod available while nodes are drained. HTTP and HTTPS go to the ingress controller Service ingress-nginx-controller.ingress-nginx.svc; for another controller, set global.codeHelm.loadbalancer.ingressService. Load balancer lists every value.
Preserving the client IP address
By default, the ingress controller and the Git shell see the IP address of the HAProxy pod: access logs, rate limits and IP bans in GitLab operate on a single address shared by all clients.
The Service code-code-loadbalancer is created with externalTrafficPolicy: Local: the cluster passes the client IP address to the HAProxy pod unchanged, and traffic arrives only on nodes with a ready HAProxy pod. With one replica, a single node serves all external traffic.
The value global.codeHelm.loadbalancer.proxyProtocol: true enables PROXY protocol v2 on the connections to the backends. HAProxy sends a header with the client IP address and port before the traffic begins, and the backends restore the client address from that header.
Enable proxyProtocol in this order:
- Enable
use-proxy-protocol: "true"on the external ingress controller. Theproxy-real-ip-cidrvalue additionally restricts the sources whose header the controller trusts for the client address, for example to the pod CIDR of the cluster. - Upgrade the release with
proxyProtocol: true:
global:
codeHelm:
loadbalancer:
enabled: true
proxyProtocol: trueThe ingress controller must accept the PROXY protocol header before the release upgrade. While HAProxy sends a header the controller does not accept, every web request fails with a 400 error or a TLS error.
The Git shell side is covered by the default values: gitlab.gitlab-shell.sshDaemon: gitlab-sshd and gitlab.gitlab-shell.config.proxyProtocol: true. The gitlab-sshd daemon accepts connections without the PROXY protocol header as well, so Git over SSH also works with proxyProtocol disabled. The install fails when proxyProtocol meets gitlab.gitlab-shell.sshDaemon: openssh or gitlab.gitlab-shell.config.proxyProtocol: false: OpenSSH cannot decode the header, and Git over SSH would break.
Load balancer behind a PROXY protocol proxy
A proxy that terminates client connections and sends PROXY protocol may sit in front of the load balancer: a cloud NLB with proxy_protocol_v2 enabled, or an HAProxy or Envoy in front of the cluster. For this chain, enable global.codeHelm.loadbalancer.frontendProxyProtocol: true together with proxyProtocol:
global:
codeHelm:
loadbalancer:
enabled: true
frontendProxyProtocol: true
proxyProtocol: trueHAProxy restores the client IP address from the incoming header and passes it to the backends. Without proxyProtocol, the client address stays in the HAProxy logs only, and the backends see the pod address. When the proxy in front sends the header while frontendProxyProtocol is disabled, HAProxy passes the header into the data stream, and the connections fail.
Enable frontendProxyProtocol only with a PROXY protocol proxy in front. With this value, HAProxy accepts on the ports 80, 443 and 22 only connections that carry the header; an address from an implementation without PROXY protocol, for example MetalLB or Cilium LB IPAM, stops all traffic, web and Git over SSH.
Istio service mesh
global.codeHelm.istio.enabled puts the pods of the release into an Istio service mesh, which then encrypts the traffic between the components. Switch off the internal TLS of the chart and the TLS of each component in the same values; the install fails while one of them stays on:
global:
codeHelm:
istio:
enabled: true
strict: false
tls:
internal:
enabled: false
workhorse:
tls:
enabled: false
gitaly:
tls:
enabled: false
praefect:
tls:
enabled: false
gitlab:
webservice:
tls:
enabled: falseWith the registry enabled, registry.tls.enabled stays false as well. The chart labels each pod for sidecar injection and creates a PeerAuthentication object for the pods of the release; when the cluster has no security.istio.io/v1beta1 API, the object is not created. The migration, backup and database reindex Jobs run the proxy as an init container with restartPolicy: Always, which needs Kubernetes 1.29 or later.
global.codeHelm.istio.strict: true switches the mTLS mode from PERMISSIVE to STRICT. The ports that clients outside the mesh connect to stay PERMISSIVE: Workhorse, which receives the requests of the ingress controller, SSH and, with kube-rbac-proxy enabled, the metrics ports. The chart adds the nginx.ingress.kubernetes.io annotations that route the requests of the ingress controller into the mesh to its Ingress objects. Istio lists the values.
PostgreSQL
global.psql.host, global.psql.port, global.psql.database and global.psql.username address the application database. The password comes from global.codeHelm.externalSecrets.psqlPassword, or from an existing Secret named in global.psql.password.secret with the key from global.psql.password.key. global.psql.sslMode sets the libpq SSL mode, prefer by default.
global:
psql:
host: <POSTGRESQL_HOST>
port: 5432
database: <POSTGRESQL_DATABASE>
username: <POSTGRESQL_USERNAME>
sslMode: require
password:
secret: <POSTGRESQL_PASSWORD_SECRET>
key: passwordPraefect keeps its routing metadata in a database of its own, addressed by global.praefect.psql.host, global.praefect.psql.port, global.praefect.psql.dbName and global.praefect.psql.user. Its password comes from global.codeHelm.externalSecrets.praefectPassword, or from the Secret in global.praefect.dbSecret.secret with the key from global.praefect.dbSecret.key.
global:
praefect:
psql:
host: <POSTGRESQL_HOST>
port: 5432
dbName: <PRAEFECT_DATABASE>
user: <PRAEFECT_USERNAME>
sslMode: require
dbSecret:
secret: <PRAEFECT_PASSWORD_SECRET>
key: passwordRedis
global.redis.host and global.redis.port address the Redis server. The password comes from global.codeHelm.externalSecrets.redisPassword, or from the Secret in global.redis.auth.secret with the key from global.redis.auth.key. For a server without a password, set global.redis.auth.enabled to false.
global:
redis:
host: <REDIS_HOST>
port: 6379
auth:
enabled: true
secret: <REDIS_PASSWORD_SECRET>
key: passwordFor Redis behind Sentinel, global.redis.host names the master set and global.redis.sentinels lists the Sentinel servers, each with host and port. When the Sentinel servers require a password, set global.redis.sentinelAuth.enabled to true and supply it through global.codeHelm.externalSecrets.redisSentinelPassword or through a Secret named in global.redis.sentinelAuth.secret with the key from global.redis.sentinelAuth.key.
Object storage
The application stores uploads, artifacts, LFS objects, packages, merge request diffs, Terraform state, the dependency proxy cache and backup archives in an S3-compatible storage. The connection comes from global.codeHelm.externalSecrets.s3, or from an existing Secret named in global.appConfig.object_store.connection.secret whose key connection holds the connection in the format of the storage library:
provider: AWS
region: <S3_REGION>
endpoint: <S3_ENDPOINT_URL>
aws_access_key_id: <S3_ACCESS_KEY_ID>
aws_secret_access_key: <S3_SECRET_ACCESS_KEY>
path_style: trueglobal:
appConfig:
object_store:
connection:
secret: <OBJECT_STORAGE_SECRET>
key: connection
lfs:
bucket: <LFS_BUCKET>
artifacts:
bucket: <ARTIFACTS_BUCKET>
uploads:
bucket: <UPLOADS_BUCKET>
packages:
bucket: <PACKAGES_BUCKET>
externalDiffs:
bucket: <MR_DIFFS_BUCKET>
terraformState:
bucket: <TERRAFORM_STATE_BUCKET>
dependencyProxy:
bucket: <DEPENDENCY_PROXY_BUCKET>
backups:
bucket: <BACKUPS_BUCKET>
tmpBucket: <BACKUP_TMP_BUCKET>path_style: true is for storages that address buckets in the URL path rather than in the host name, such as MinIO. Each store has its own bucket in global.appConfig.<store>.bucket; Requirements lists them with the default names.
Git storage
Each Gitaly pod stores repositories on a PersistentVolumeClaim sized by gitlab.gitaly.persistence.size, from the StorageClass in gitlab.gitaly.persistence.storageClass. global.praefect.virtualStorages[0].gitalyReplicas sets the number of Gitaly pods behind Praefect; each pod holds a full copy of every repository.
global:
praefect:
virtualStorages:
- name: default
gitalyReplicas: 3
maxUnavailable: 1
gitlab:
gitaly:
persistence:
size: 200Gi
storageClass: <STORAGE_CLASS_NAME>The size of an existing claim is changed through the StorageClass’s volume expansion, not through the value; the value applies to claims created after the change.
Container registry
The registry is disabled by default. registry.enabled deploys it at global.hosts.registry.name, and global.registry.enabled turns on the registry in the application, which then issues registry access tokens and shows the images of each project; set both to true. The registry stores image layers in the bucket from global.codeHelm.externalSecrets.registry.storage.s3, or reads a storage configuration from an existing Secret named in registry.storage.secret; without one of them the install fails.
global:
hosts:
registry:
name: registry.example.com
registry:
enabled: true
codeHelm:
externalSecrets:
registry:
storage:
s3:
bucket: <REGISTRY_BUCKET>
region: <S3_REGION>
regionendpoint: <S3_ENDPOINT_URL>
accesskey: <S3_ACCESS_KEY_ID>
secretkey: <S3_SECRET_ACCESS_KEY>
registry:
enabled: trueCreate a DNS record for the registry host name that points at the same address as the web interface. Sign in to the registry with a user name and a personal access token with the read_registry and write_registry scopes:
docker login registry.example.comTo run the registry with the metadata database, point registry.database at a PostgreSQL database and supply its password through global.codeHelm.externalSecrets.registry.storage.database.password:
registry:
enabled: true
database:
enabled: true
host: <POSTGRESQL_HOST>
port: 5432
dbname: <REGISTRY_DATABASE>
user: <REGISTRY_USERNAME>
sslmode: require
migrations:
enabled: true
global:
codeHelm:
externalSecrets:
registry:
storage:
database:
password: <REGISTRY_PASSWORD>registry.database.migrations.enabled runs the schema migrations of the registry as a job on every upgrade.
Pages
global.pages.enabled deploys Pages, which serves static sites from the bucket in global.pages.objectStore.bucket at global.hosts.pages.name and its subdomains. global.pages.accessControl restricts a site to the members of its project.
global:
pages:
enabled: true
accessControl: false
objectStore:
bucket: <PAGES_BUCKET>
hosts:
pages:
name: pages.example.comThe Pages host name and the wildcard *.pages.example.com resolve to the same address as the web interface, and the certificate code-pages-tls is issued for pages.example.com. Sites on subdomains need a wildcard certificate, supplied through gitlab.gitlab-pages.ingress.tls.secretName in the manual TLS mode.
SMTP
global.smtp.enabled turns on outgoing email. global.smtp.address and global.smtp.port address the server; global.smtp.user_name and the Secret in global.smtp.password.secret hold the credentials; global.smtp.authentication selects plain, login or cram_md5; global.smtp.starttls_auto upgrades the connection with STARTTLS. global.email.from and global.email.display_name set the sender.
global:
smtp:
enabled: true
address: <SMTP_HOST>
port: 587
user_name: <SMTP_USERNAME>
password:
secret: <SMTP_PASSWORD_SECRET>
key: password
authentication: plain
starttls_auto: true
domain: <SMTP_HELO_DOMAIN>
email:
from: <SENDER_EMAIL>
display_name: Deckhouse Code
reply_to: <REPLY_TO_EMAIL>The chart does not create the SMTP password Secret. Create it before the upgrade:
d8 k -n code create secret generic <SMTP_PASSWORD_SECRET> --from-literal=password='<SMTP_PASSWORD>'