<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wei Cao</title>
    <description>The latest articles on DEV Community by Wei Cao (@wei_cao).</description>
    <link>https://dev.to/wei_cao</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3878750%2F75400520-8dd4-4800-9cf0-56a5688e886c.jpg</url>
      <title>DEV Community: Wei Cao</title>
      <link>https://dev.to/wei_cao</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wei_cao"/>
    <language>en</language>
    <item>
      <title>GitLab on Kubernetes: Production-Ready PostgreSQL and Redis HA</title>
      <dc:creator>Wei Cao</dc:creator>
      <pubDate>Tue, 14 Apr 2026 14:44:20 +0000</pubDate>
      <link>https://dev.to/wei_cao/gitlab-on-kubernetes-production-ready-postgresql-and-redis-ha-o4d</link>
      <guid>https://dev.to/wei_cao/gitlab-on-kubernetes-production-ready-postgresql-and-redis-ha-o4d</guid>
      <description>&lt;p&gt;Running GitLab on Kubernetes is remarkably straightforward if you stick to the official Helm chart—until you hit the database tier. The bundled PostgreSQL and Redis instances are perfectly fine for taking GitLab for a spin, but they are single points of failure. The moment one of those pods restarts, your entire CI/CD pipeline grinds to a halt.&lt;/p&gt;

&lt;p&gt;This guide walks you through replacing those fragile bundled databases with true, production-grade Highly Available (HA) clusters managed by &lt;strong&gt;&lt;a href="https://kubeblocks.io" rel="noopener noreferrer"&gt;KubeBlocks&lt;/a&gt;&lt;/strong&gt;. We will set up &lt;strong&gt;PostgreSQL with Patroni&lt;/strong&gt; (for streaming replication and automatic primary election) and &lt;strong&gt;Redis with Sentinel&lt;/strong&gt; (1 primary, 1 replica, and 3 sentinels).&lt;/p&gt;

&lt;p&gt;Every command below has been tested on EKS using KubeBlocks 1.0.2.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is KubeBlocks?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://kubeblocks.io" rel="noopener noreferrer"&gt;KubeBlocks&lt;/a&gt; is an open-source Kubernetes operator for running and managing databases and stateful middleware. It supports 30+ database engines—including &lt;a href="https://kubeblocks.io/pg-operator" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;, &lt;a href="https://kubeblocks.io/redis-operator" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, &lt;a href="https://kubeblocks.io/mysql-operator" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt;, &lt;a href="https://kubeblocks.io/mongodb-operator" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt;, and &lt;a href="https://kubeblocks.io/kafka-operator" rel="noopener noreferrer"&gt;Kafka&lt;/a&gt;—through a unified API surface. Instead of writing StatefulSets and sidecar logic by hand, you describe what you want (topology, version, resources) and KubeBlocks handles provisioning, HA configuration, credential management, and day-2 operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before diving in, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Kubernetes cluster (1.22 or newer) with &lt;code&gt;kubectl&lt;/code&gt; configured&lt;/li&gt;
&lt;li&gt;Helm 3.x installed&lt;/li&gt;
&lt;li&gt;KubeBlocks 1.0.2 or higher installed in your cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you haven't installed KubeBlocks yet, you can add it quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add kubeblocks https://apecloud.github.io/helm-charts
helm repo update

helm &lt;span class="nb"&gt;install &lt;/span&gt;kubeblocks kubeblocks/kubeblocks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; kb-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 1.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the operator to become fully ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ready pod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-l&lt;/span&gt; app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;kubeblocks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; kb-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;To keep things clean, we will deploy both database clusters into a dedicated &lt;code&gt;gitlab-data&lt;/code&gt; namespace. This strictly isolates the stateful data layer from GitLab's stateless application pods.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe60ax8os7012ii50jbkx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe60ax8os7012ii50jbkx.png" alt=" " width="800" height="906"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create the Database Namespace
&lt;/h2&gt;

&lt;p&gt;First, let's create the isolated namespace for our data layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace gitlab-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Deploy PostgreSQL HA
&lt;/h2&gt;

&lt;p&gt;KubeBlocks ships with a &lt;a href="https://kubeblocks.io/pg-operator" rel="noopener noreferrer"&gt;PostgreSQL addon&lt;/a&gt; built on &lt;strong&gt;Patroni&lt;/strong&gt;—a battle-tested HA solution that handles leader election, streaming replication, and automatic failover seamlessly. See the &lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-postgresql/02-quickstart" rel="noopener noreferrer"&gt;PostgreSQL quickstart&lt;/a&gt; for standalone usage.&lt;/p&gt;

&lt;p&gt;Deploy the cluster using Helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;gitlab-pg kubeblocks/postgresql-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;16.4.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replication &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;terminationPolicy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is exactly what these parameters do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mode=replication&lt;/code&gt; — Enables Patroni streaming replication (1 primary + N-1 replicas).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas=2&lt;/code&gt; — Deploys 1 primary and 1 standby. (This is sufficient for this guide, but see the production note below).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terminationPolicy=Delete&lt;/code&gt; — Allows &lt;code&gt;helm delete&lt;/code&gt; to remove the cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Production Note:&lt;/strong&gt; We use &lt;code&gt;replicas=2&lt;/code&gt; here to keep tutorial costs low. For actual production environments, you should use &lt;code&gt;replicas=3&lt;/code&gt;. With only 2 nodes and no external Distributed Configuration Store (like etcd or Consul), Patroni cannot establish a quorum during a network partition. In that scenario, it will pause the primary to prevent split-brain, rather than failing over. Three replicas give Patroni the majority vote it needs to fail over safely without external dependencies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wait for the cluster to reach the &lt;code&gt;Running&lt;/code&gt; state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.phase}'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Running &lt;span class="se"&gt;\&lt;/span&gt;
  cluster/gitlab-pg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's verify that replication is actively working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PG_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.username}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PG_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Check pod-0&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-0 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-postgresql &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SELECT pg_is_in_recovery();"&lt;/span&gt;

&lt;span class="c"&gt;# Check pod-1&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-1 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-headless &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SELECT pg_is_in_recovery();"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt; pg_is_in_recovery
-------------------
 f                    ← pod-0 is the primary

 pg_is_in_recovery
-------------------
 t                    ← pod-1 is the replica
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Create the GitLab Database and User
&lt;/h2&gt;

&lt;p&gt;Next, we need to provision the specific database and user that GitLab expects.&lt;/p&gt;

&lt;p&gt;Rather than identifying the primary pod manually, connect through the &lt;code&gt;gitlab-pg-postgresql-postgresql&lt;/code&gt; Service — KubeBlocks keeps this ClusterIP service pointed at the current Patroni primary at all times. First, retrieve the &lt;code&gt;postgres&lt;/code&gt; superuser credentials from the KubeBlocks-generated secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PG_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.username}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;PG_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a password for the GitLab application user, then run all DDL through the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GITLAB_PG_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 18 | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'/+='&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Save this password: &lt;/span&gt;&lt;span class="nv"&gt;$GITLAB_PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-0 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-postgresql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"CREATE USER gitlab WITH PASSWORD '&lt;/span&gt;&lt;span class="nv"&gt;$GITLAB_PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;';"&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-0 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-postgresql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"CREATE DATABASE gitlabhq_production OWNER gitlab;"&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-0 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-postgresql &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"GRANT ALL PRIVILEGES ON DATABASE gitlabhq_production TO gitlab;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Connecting via &lt;code&gt;-h gitlab-pg-postgresql-postgresql&lt;/code&gt; (the service) ensures DDL always lands on the primary, regardless of which pod you exec into. Direct local connections to a replica pod would fail with &lt;code&gt;ERROR: cannot execute ... in a read-only transaction&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4: Deploy Redis HA with Sentinel
&lt;/h2&gt;

&lt;p&gt;GitLab leans heavily on Redis for caching, managing Sidekiq job queues, and maintaining session state.&lt;/p&gt;

&lt;p&gt;KubeBlocks' &lt;a href="https://kubeblocks.io/redis-operator" rel="noopener noreferrer"&gt;Redis addon&lt;/a&gt; &lt;code&gt;replication&lt;/code&gt; topology deploys a complete HA stack. See the &lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-redis/02-quickstart" rel="noopener noreferrer"&gt;Redis quickstart&lt;/a&gt; for standalone usage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis data nodes (1 primary + N-1 replicas)&lt;/li&gt;
&lt;li&gt;3 Sentinel nodes dedicated to monitoring and automatic failover&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid Helm type-coercion issues with decimal values, it's best to create a &lt;code&gt;redis-values.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7.2.10"&lt;/span&gt;
&lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replication&lt;/span&gt;
&lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="na"&gt;terminationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Delete&lt;/span&gt;
&lt;span class="na"&gt;sentinel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy the Redis cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;gitlab-redis kubeblocks/redis-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--values&lt;/span&gt; redis-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a breakdown of the key parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mode=replication&lt;/code&gt; — Automatically deploys both the Redis and Sentinel components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas=2&lt;/code&gt; — Provisions 1 primary and 1 replica for the Redis data nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sentinel.replicas=3&lt;/code&gt; — Provisions 3 Sentinel nodes, establishing a quorum of 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait for the cluster to become &lt;code&gt;Running&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.phase}'&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Running &lt;span class="se"&gt;\&lt;/span&gt;
  cluster/gitlab-redis &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's verify that Sentinel is actively monitoring the master node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SENTINEL_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret gitlab-redis-redis-sentinel-account-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-redis-redis-sentinel-0 &lt;span class="nt"&gt;-c&lt;/span&gt; redis-sentinel &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 26379 &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SENTINEL_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; sentinel masters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output containing these specific lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;name&lt;/span&gt;
&lt;span class="s"&gt;gitlab-redis-redis&lt;/span&gt;
&lt;span class="s"&gt;ip&lt;/span&gt;
&lt;span class="s"&gt;gitlab-redis-redis-0.gitlab-redis-redis-headless.gitlab-data.svc.cluster.local&lt;/span&gt;
&lt;span class="s"&gt;port&lt;/span&gt;
&lt;span class="m"&gt;6379&lt;/span&gt;
&lt;span class="s"&gt;flags&lt;/span&gt;
&lt;span class="s"&gt;master&lt;/span&gt;
&lt;span class="s"&gt;num-slaves&lt;/span&gt;
&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="s"&gt;num-other-sentinels&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="s"&gt;quorum&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;num-slaves: 1&lt;/code&gt; and &lt;code&gt;num-other-sentinels: 2&lt;/code&gt; values confirm that you have a healthy 1+1 Redis cluster with all 3 Sentinels online and watching.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Create Kubernetes Secrets for GitLab
&lt;/h2&gt;

&lt;p&gt;GitLab reads its database credentials directly from Kubernetes Secrets. We need to create these secrets in the &lt;code&gt;gitlab&lt;/code&gt; namespace (or whichever namespace you plan to deploy the GitLab application into).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace gitlab

&lt;span class="c"&gt;# Create the PostgreSQL password secret&lt;/span&gt;
&lt;span class="c"&gt;# $GITLAB_PG_PASS was set in Step 3&lt;/span&gt;
kubectl create secret generic gitlab-postgresql-password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main-gitlab-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITLAB_PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Extract the Redis password from the KubeBlocks-generated secret&lt;/span&gt;
&lt;span class="nv"&gt;REDIS_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret gitlab-redis-redis-account-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create the Redis password secret for GitLab&lt;/span&gt;
kubectl create secret generic gitlab-redis-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REDIS_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Extract the Sentinel auth password (this is separate from the Redis data password)&lt;/span&gt;
&lt;span class="nv"&gt;SENTINEL_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret gitlab-redis-redis-sentinel-account-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create the Sentinel password secret for GitLab&lt;/span&gt;
kubectl create secret generic gitlab-redis-sentinel-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sentinel-password&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SENTINEL_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why do we need two Redis secrets?&lt;/strong&gt; KubeBlocks manages the Redis data nodes and the Sentinel nodes using entirely separate credentials for enhanced security. The sentinel password is used specifically for &lt;code&gt;AUTH&lt;/code&gt; on the sentinel port (26379), while the standard Redis password is used for &lt;code&gt;AUTH&lt;/code&gt; on the actual data port (6379).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 6: Deploy GitLab
&lt;/h2&gt;

&lt;p&gt;Now we are ready for the main event. Add the GitLab Helm repository and prepare the values file to wire up our external, highly available databases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add gitlab https://charts.gitlab.io/
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;gitlab-values.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab.example.com&lt;/span&gt;   &lt;span class="c1"&gt;# Replace this with your actual domain&lt;/span&gt;
    &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;## External PostgreSQL (KubeBlocks Patroni cluster)&lt;/span&gt;
  &lt;span class="na"&gt;psql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-pg-postgresql-postgresql.gitlab-data.svc.cluster.local&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlabhq_production&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;useSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-postgresql-password&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main-gitlab-password&lt;/span&gt;

  &lt;span class="c1"&gt;## External Redis (KubeBlocks Sentinel cluster)&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# IMPORTANT: The 'host' here must be the Sentinel MASTER NAME, not the sentinel service hostname.&lt;/span&gt;
    &lt;span class="c1"&gt;# KubeBlocks assigns the master name based on the cluster: &amp;lt;release&amp;gt;-&amp;lt;component&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-redis-redis&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-redis-secret&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-password&lt;/span&gt;
    &lt;span class="na"&gt;sentinels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-redis-redis-sentinel-redis-sentinel.gitlab-data.svc.cluster.local&lt;/span&gt;
        &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;26379&lt;/span&gt;
    &lt;span class="na"&gt;sentinelAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab-redis-sentinel-secret&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sentinel-password&lt;/span&gt;

&lt;span class="c1"&gt;# Explicitly disable the bundled PostgreSQL and Redis instances&lt;/span&gt;
&lt;span class="na"&gt;postgresql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;# Disable the bundled cert-manager (Note: GitLab chart 9.x uses 'installCertmanager', not 'certmanager.install')&lt;/span&gt;
&lt;span class="na"&gt;installCertmanager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;certmanager-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your@email.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy GitLab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;gitlab gitlab/gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--values&lt;/span&gt; gitlab-values.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt; 600s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; GitLab's initial deployment is heavy. It pulls several large images and executes extensive database migrations. Give the &lt;code&gt;gitlab-migrations&lt;/code&gt; job a solid 5–10 minutes to complete before expecting the rest of the application pods to become ready.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 7: Verify the Setup
&lt;/h2&gt;

&lt;p&gt;Check the status of the GitLab pods (remember to give it a few minutes for migrations to finish):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are looking for an output where all application pods are &lt;code&gt;Running&lt;/code&gt; with zero restarts, and the migrations pod has cleanly finished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                  READY   STATUS      RESTARTS
gitlab-gitaly-0                       1/1     Running     0
gitlab-gitlab-exporter-xxx            1/1     Running     0
gitlab-gitlab-shell-xxx               1/1     Running     0
gitlab-migrations-xxx                 0/1     Completed   0
gitlab-sidekiq-all-in-1-xxx           1/1     Running     0
gitlab-webservice-default-xxx         2/2     Running     0
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact that &lt;code&gt;gitlab-migrations-xxx&lt;/code&gt; is &lt;code&gt;Completed&lt;/code&gt; (rather than running or crashing) is your confirmation that the database schema was applied successfully to the external PostgreSQL cluster.&lt;/p&gt;

&lt;p&gt;To test database connectivity directly from the application layer, run this inside a webservice pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;WEBSERVICE_POD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;webservice &lt;span class="nt"&gt;-o&lt;/span&gt; name | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab &lt;span class="nv"&gt;$WEBSERVICE_POD&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; webservice &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /srv/gitlab/bin/rails runner &lt;span class="s2"&gt;"puts Gitlab::Database.main.version"&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Access GitLab via Port-Forward
&lt;/h3&gt;

&lt;p&gt;If you don't have direct access to the LoadBalancer (for instance, if you are testing locally or have strict security groups on your ELB), you can use &lt;code&gt;kubectl port-forward&lt;/code&gt; to access the GitLab UI directly from your workstation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Retrieve the initial root password&lt;/span&gt;
kubectl get secret gitlab-gitlab-initial-root-password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;

&lt;span class="c"&gt;# Forward the nginx-ingress controller to your localhost&lt;/span&gt;
kubectl port-forward &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab svc/gitlab-nginx-ingress-controller 8888:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser to &lt;strong&gt;&lt;a href="http://localhost:8888/users/sign_in" rel="noopener noreferrer"&gt;http://localhost:8888/users/sign_in&lt;/a&gt;&lt;/strong&gt; and log in using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username: &lt;code&gt;root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: (the output from the command above)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; After logging in, GitLab might try to redirect you to &lt;code&gt;http://localhost/users/sign_in&lt;/code&gt; (dropping the port). Simply navigate back to &lt;code&gt;http://localhost:8888/users/sign_in&lt;/code&gt; manually to stay on the port-forwarded connection.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Understanding the Connection Details
&lt;/h2&gt;

&lt;p&gt;How does GitLab actually maintain connections to these HA clusters without getting confused during a failover event?&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlab-pg-postgresql-postgresql.gitlab-data.svc.cluster.local&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5432&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlab&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlabhq_production&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HA Mechanism&lt;/td&gt;
&lt;td&gt;Patroni (automatic primary election)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-postgresql&lt;/code&gt; service is a standard ClusterIP service (non-headless) that KubeBlocks dynamically manages. It is guaranteed to always point to the current Patroni primary. When a failover occurs, Patroni promotes the standby and updates the service selector under the hood. From GitLab's perspective, no reconfiguration is needed; the connection simply drops for a moment and immediately reconnects to the newly promoted primary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sentinel Service&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlab-redis-redis-sentinel-redis-sentinel.gitlab-data.svc.cluster.local&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentinel Port&lt;/td&gt;
&lt;td&gt;&lt;code&gt;26379&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Master Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gitlab-redis-redis&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HA Mechanism&lt;/td&gt;
&lt;td&gt;Sentinel (automatic master election)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitLab's underlying Rails stack supports Redis Sentinel natively via the &lt;code&gt;sentinels:&lt;/code&gt; configuration block. When a Redis master fails, the Sentinel nodes elect a new master within seconds and proactively notify all connected clients (like Sidekiq) to redirect their traffic to the new master IP.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; In the GitLab Helm chart, when &lt;code&gt;sentinels&lt;/code&gt; is configured, the &lt;code&gt;global.redis.host&lt;/code&gt; field actually serves as the &lt;strong&gt;Sentinel master name&lt;/strong&gt;, not the service hostname. KubeBlocks systematically names the master after the cluster itself: &lt;code&gt;&amp;lt;helm-release&amp;gt;-&amp;lt;component&amp;gt;&lt;/code&gt;. So, for a release named &lt;code&gt;gitlab-redis&lt;/code&gt; with the component &lt;code&gt;redis&lt;/code&gt;, the master name is exactly &lt;code&gt;gitlab-redis-redis&lt;/code&gt;. You can always verify this manually by running &lt;code&gt;redis-cli -p 26379 sentinel masters&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Failover Testing
&lt;/h2&gt;

&lt;p&gt;Don't just trust the theory. Let's intentionally break things and watch the system recover.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test PostgreSQL Failover
&lt;/h3&gt;

&lt;p&gt;First, identify the current primary, delete its pod, and watch Patroni promote the replica in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PG_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.username}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PG_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-account-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Kill pod-0 (assuming it is currently primary)&lt;/span&gt;
kubectl delete pod gitlab-pg-postgresql-0 &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data

&lt;span class="c"&gt;# Watch the service-connected query — once pg_is_in_recovery returns 'f',&lt;/span&gt;
&lt;span class="c"&gt;# Patroni has promoted a new primary and the service is already pointing to it&lt;/span&gt;
watch kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-pg-postgresql-1 &lt;span class="nt"&gt;-c&lt;/span&gt; postgresql &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="nv"&gt;PGUSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PG_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-h&lt;/span&gt; gitlab-pg-postgresql-postgresql &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SELECT pg_is_in_recovery();"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment &lt;code&gt;pg_is_in_recovery&lt;/code&gt; returns &lt;code&gt;f&lt;/code&gt; via the service query, failover is complete. Because we're querying through the ClusterIP service, a &lt;code&gt;f&lt;/code&gt; result confirms the service has already redirected to the new primary — GitLab's connection pool will recover automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Redis Failover
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REDIS_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret gitlab-redis-redis-account-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Confirm the current master&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-redis-redis-0 &lt;span class="nt"&gt;-c&lt;/span&gt; redis &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  redis-cli &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REDIS_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; role

&lt;span class="c"&gt;# Kill the master pod&lt;/span&gt;
kubectl delete pod gitlab-redis-redis-0 &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data

&lt;span class="c"&gt;# Sentinel detects the failure and promotes the replica within ~20 seconds.&lt;/span&gt;
&lt;span class="c"&gt;# Let's check the new master via Sentinel:&lt;/span&gt;
&lt;span class="nv"&gt;SENTINEL_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get secret gitlab-redis-redis-sentinel-account-default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data gitlab-redis-redis-sentinel-0 &lt;span class="nt"&gt;-c&lt;/span&gt; redis-sentinel &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 26379 &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SENTINEL_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; sentinel masters | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; &lt;span class="s2"&gt;"^ip"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Scaling and Upgrades
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scale PostgreSQL Read Replicas
&lt;/h3&gt;

&lt;p&gt;Need more read capacity? You can scale the cluster dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scale the cluster to 1 primary + 2 replicas&lt;/span&gt;
kubectl patch cluster gitlab-pg &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'[{"op":"replace","path":"/spec/componentSpecs/0/replicas","value":3}]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upgrade PostgreSQL Version
&lt;/h3&gt;

&lt;p&gt;KubeBlocks supports in-place minor version upgrades with zero downtime. It performs a rolling update of the replicas first, followed by a controlled primary failover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  name: pg-upgrade
  namespace: gitlab-data
spec:
  clusterName: gitlab-pg
  type: Upgrade
  upgrade:
    components:
      - componentName: postgresql
        serviceVersion: "16.9.0"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Expand Storage
&lt;/h3&gt;

&lt;p&gt;Running out of disk space? Expand the volume seamlessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: operations.kubeblocks.io/v1alpha1
kind: OpsRequest
metadata:
  name: pg-volume-expand
  namespace: gitlab-data
spec:
  clusterName: gitlab-pg
  type: VolumeExpansion
  volumeExpansion:
    - componentName: postgresql
      volumeClaimTemplates:
        - name: data
          storage: "100Gi"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Backup and Recovery
&lt;/h2&gt;

&lt;p&gt;Backup is not optional in production. For a GitLab deployment, the two stateful components that absolutely must be backed up are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; — This holds all of your critical GitLab application data: projects, issues, merge requests, user accounts, and CI pipeline configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — While primarily acting as a cache and job queue (making it slightly less critical for strict point-in-time recovery), preserving the Sentinel state across hard restarts is still best practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;KubeBlocks provides a unified, Kubernetes-native backup API for both databases. The general workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure a &lt;strong&gt;BackupRepo&lt;/strong&gt; (pointing to S3, GCS, or any S3-compatible object store).&lt;/li&gt;
&lt;li&gt;Create a &lt;strong&gt;BackupSchedule&lt;/strong&gt; to automate snapshots on a cron schedule.&lt;/li&gt;
&lt;li&gt;Use an &lt;strong&gt;OpsRequest&lt;/strong&gt; (with type &lt;code&gt;Restore&lt;/code&gt;) or standard &lt;code&gt;Backup&lt;/code&gt;/&lt;code&gt;Restore&lt;/code&gt; Custom Resources to recover when disaster strikes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For detailed, step-by-step instructions, refer to the official documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-postgresql/05-backup-restore/01-create-backuprepo" rel="noopener noreferrer"&gt;PostgreSQL Backup &amp;amp; Restore with KubeBlocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-redis/05-backup-restore/01-create-backuprepo" rel="noopener noreferrer"&gt;Redis Backup &amp;amp; Restore with KubeBlocks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always configure your BackupRepo and BackupSchedule &lt;em&gt;before&lt;/em&gt; going live. If you suffer a catastrophic double-node failure on a &lt;code&gt;replicas=2&lt;/code&gt; cluster without a backup, recovery is impossible.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;If you are just testing and want to tear everything down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm delete gitlab &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab
helm delete gitlab-pg &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data
helm delete gitlab-redis &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data

&lt;span class="c"&gt;# Delete the PersistentVolumeClaims if you want to permanently wipe the data&lt;/span&gt;
kubectl delete pvc &lt;span class="nt"&gt;-n&lt;/span&gt; gitlab-data &lt;span class="nt"&gt;--all&lt;/span&gt;

&lt;span class="c"&gt;# Finally, remove the namespaces&lt;/span&gt;
kubectl delete namespace gitlab gitlab-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bundled single-instance&lt;/td&gt;
&lt;td&gt;Patroni HA: 1 primary + 1 replica, auto-failover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bundled standalone&lt;/td&gt;
&lt;td&gt;Sentinel HA: 1 primary + 1 replica + 3 sentinels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual / none&lt;/td&gt;
&lt;td&gt;Automatic within 10–30 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual pod management&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;kubectl patch&lt;/code&gt; or OpsRequest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Version Upgrade&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Downtime required&lt;/td&gt;
&lt;td&gt;Rolling in-place via KubeBlocks OpsRequest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;KubeBlocks entirely abstracts the operational complexity of running stateful databases on Kubernetes. It handles provisioning, HA configuration, credential management, and day-2 operations through a single, unified API. For your GitLab deployment, this means the database tier is genuinely production-ready from day one—all without you having to write a single line of StatefulSet YAML.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubeblocks.io" rel="noopener noreferrer"&gt;KubeBlocks Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubeblocks.io/pg-operator" rel="noopener noreferrer"&gt;KubeBlocks for PostgreSQL&lt;/a&gt; — Landing page, quickstart, architecture, and operations&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubeblocks.io/redis-operator" rel="noopener noreferrer"&gt;KubeBlocks for Redis&lt;/a&gt; — Landing page, quickstart, topologies, and operations&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-postgresql/05-backup-restore/01-create-backuprepo" rel="noopener noreferrer"&gt;PostgreSQL Backup &amp;amp; Restore with KubeBlocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubeblocks.io/docs/preview/kubeblocks-for-redis/05-backup-restore/01-create-backuprepo" rel="noopener noreferrer"&gt;Redis Backup &amp;amp; Restore with KubeBlocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/charts/" rel="noopener noreferrer"&gt;GitLab Helm Chart Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>gitlab</category>
      <category>postgres</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
