Remote & on-site IT support across Australia & New Zealand · 24/7 emergency line

Setup GitLab Server with Kustomize and Helm on Kubernetes

Prepare Postgres for Gitlab
# --- CONFIGURATION ---
NAMESPACE="databases-prod"
POD_NAME="postgres-0"  # Change to your actual postgres pod name
DB_NAME="gitlabhq_production"
DB_USER="gitlab"
# Use single quotes to handle special characters like $ or @
DB_PASS='gitlab-db-password' 

echo "Checking if database $DB_NAME exists..."

# Execute SQL inside the pod
kubectl exec -i $POD_NAME -n $NAMESPACE -- psql -U postgres -d template1 <<EOF
-- Create User if not exists
DO $$ 
BEGIN
    IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$DB_USER') THEN
        CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';
    END IF;
END
$$;

-- Create Database
SELECT 'CREATE DATABASE $DB_NAME OWNER $DB_USER' 
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$DB_NAME')gexec

-- Grant Permissions
GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;
ALTER ROLE $DB_USER WITH LOGIN CREATEDB;

-- Connect to new DB to add extensions
c $DB_NAME
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE EXTENSION IF NOT EXISTS plpgsql;

-- Ensure gitlab user has schema permissions
GRANT ALL ON SCHEMA public TO $DB_USER;
EOF
Generate keys and secret for gitlab-shell
# Generate keys and generate secret for gitlab-shell
mkdir -p ./keys
ssh-keygen -q -N "" -t rsa -f ./keys/ssh_host_rsa_key
ssh-keygen -q -N "" -t ecdsa -f ./keys/ssh_host_ecdsa_key
ssh-keygen -q -N "" -t ed25519 -f ./keys/ssh_host_ed25519_key

# Force update the secret
kubectl create secret generic gitlab-gitlab-shell-host-keys 
  --from-file=ssh_host_rsa_key=./keys/ssh_host_rsa_key 
  --from-file=ssh_host_ecdsa_key=./keys/ssh_host_ecdsa_key 
  --from-file=ssh_host_ed25519_key=./keys/ssh_host_ed25519_key 
  -n devops-prod --dry-run=client -o yaml | kubectl apply -f -

rm -rf ./keys
The Codes
# infrastructure/devops/gitlab/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - traefik-ingressroute.yaml

helmCharts:
- name: gitlab
  repo: "https://charts.gitlab.io"
  version: 9.11.2
  releaseName: gitlab
  valuesFile: gitlab-values.yaml
  includeCRDs: true
# infrastructure/devops/gitlab/base/gitlab-values.yaml

# Disable all heavy/internal features
installCertmanager: false
gitlab-runner: { install: false }
traefik: { install: false }
gitlab-zoekt: { install: false }
openbao: { install: false }
ai-gateway: { install: false }
postgresql: { install: false }
redis: { install: false }
grafana: { enabled: false }
gitlab-exporter: { enabled: false }
metrics: { enabled: false }
registry: { enabled: false }
certmanager-issuer: { enabled: false }
upgradeCheck: { enabled: false }
certmanager: { installCRDs: false }
nginx-ingress: { enabled: false }
nginx-ingress-geo: { enabled: false }
haproxy: { install: false }
prometheus: { install: false }

global:
  edition: ce
  application: { create: false }

  shell: { concurrency: 1 }
  keda: { enabled: false }
  monitoring: { enabled: false }
  minio: { enabled: true }
  serviceAccount: { enabled: false }
  openbao: { enabled: false }
  geo: { enabled: false }
  workspaces: { enabled: false } 
  spamcheck: { enabled: false }
  registry: { enabled: false }
  # Ensure the application doesn't try to load every feature
  extraEnv: { GITLAB_CONTINUATION_ADVISOR: "true" }

  hosts:
    domain: example.com
    https: false

  # Disable Rails bootsnap cache (temporary)
  rails:
    bootsnap:
      enabled: true

  # Disable internal Ingress/Cert-Manager (use external Traefix/Cert-Manager)
  ingress:
    enabled: false
    configureCertmanager: false
    useNewIngressForCerts: false
    tls:
      enabled: false
      secretName: wildcard-example-com-tls

  # Database (External PostgreSQL)
  psql:
    host: postgres.databases-prod.svc.cluster.local
    port: 5432
    database: gitlabhq_production
    username: gitlab
    password:
      secret: gitlab-auth
      key: POSTGRES_PASSWORD

  # Redis (External Redis)
  redis:
    host: redis.databases-prod.svc.cluster.local
    port: 6379
    auth:
      enabled: true
      secret: gitlab-auth
      key: REDIS_PASSWORD

  gitaly:
    enabled: true
    service:
      name: gitaly
      type: ClusterIP
      externalPort: 8075
      internalPort: 8075
      tls:
        externalPort: 8076
        internalPort: 8076
    tls:
      enabled: false
      secretName: wildcard-example-com-tls

  ## Rails based portions of this chart share many settings
  appConfig:
    relativeUrlRoot: ""
    enableUsagePing: true
    enableSeatLink: true
    enableImpersonation:
    applicationSettingsCacheSeconds: 60
    usernameChangingEnabled: true
    issueClosingPattern:
    defaultProjectsFeatures:
      issues: true
      mergeRequests: true
      wiki: true
      snippets: true
      builds: true
    contentSecurityPolicy:
      enabled: false
      report_only: true
    object_store: { enabled: false }
    microsoft_graph_mailer: { enabled: false }
    incomingEmail: { enabled: false }
    serviceDeskEmail: { enabled: false }
    ldap: { preventSignin: false }
    duoAuth: { enabled: false }
    knowledgeGraph: { enabled: false }
    iamAuthService: { enabled: false }
    cell: { enabled: false }
    omniauth: { enabled: false }
    kerberos: { enabled: false }
    sentry: { enabled: false }
    gitlab_docs: { enabled: false }
    smartcard: { enabled: false }
    gitlab_kas: { enabled: true }

  pages:
    enabled: false
    accessControl: false

  # SMTP Configuration
  smtp:
    enabled: true
    address: smtp.gmail.com
    port: 587
    user_name:
      secret: gitlab-auth
      key: SMTP_USERNAME
    password:
      secret: gitlab-auth
      key: SMTP_PASSWORD
    domain: example.com
    authentication: login
    starttls_auto: true
  email:
    display_name: admin@GitLab
    from: example@gmail.com
    reply_to: admin@example.com
    subject_suffix: " | Company"

  time_zone: 'Australia/Sydney'

  webservice: { workerTimeout: 60 }

  workhorse:
    serviceName: webservice-default
    tls:
      enabled: false

  kas: 
    enabled: true
    service:
      apiExternalPort: 8153
    tls:
      enabled: false
      verify: true
      secretName: wildcard-example-com-tls

## End of global

shared-secrets:
  enabled: true

minio:
  install: true
  ingress:
    tls:
      enabled: false
      secretName: wildcard-example-com-tls
  persistence:
    enabled: true
    storageClass: "rook-ceph-block-external-retain"
    accessMode: ReadWriteOnce
    size: 10Gi
  resources:
    requests:
      memory: 64Mi
      cpu: 10m

# Fine-tuning Web and Background workers
gitlab:
  webservice:
    tests:
      enabled: false
    test-runner:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containerSecurityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
    workerProcesses: 2
    minReplicas: 1
    maxReplicas: 1
    service:
      type: ClusterIP
    ingress:
      enabled: false
      tls:
        enabled: false
        secretName: wildcard-example-com-tls
    resources:
      requests:
        cpu: 200m
        memory: 1.5Gi
      limits:
        cpu: 1000m
        memory: 2.5Gi
    workhorse:
      resources:
        limits:
          memory: 100M
        requests:
          cpu: 10m
          memory: 10M
  sidekiq:
    concurrency: 5
    minReplicas: 1
    maxReplicas: 1
    resources:
      requests:
        cpu: 100m
        memory: 750Mi
      limits:
        cpu: 500m
        memory: 1.5Gi
  toolbox:
    replicas: 1
    persistence:
      enabled: true
      storageClass: "rook-ceph-block-external-retain"
      accessMode: ReadWriteOnce
      size: 5Gi
    antiAffinityLabels:
      matchLabels:
        app: gitaly

  gitaly:
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      fsGroup: 1000
      seccompProfile:
        type: RuntimeDefault
    containerSecurityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
    persistence:
      enabled: true
      storageClass: "rook-ceph-block-external-retain"
      accessMode: ReadWriteOnce
      size: 10Gi
    resources:
      requests:
        cpu: 100m
        memory: 500Mi
      limits:
        cpu: 500m
        memory: 1.5Gi

  gitlab-shell:
    minReplicas: 1
    maxReplicas: 1
    tests:
      enabled: false
    service:
      externalPort: 10022
    resources:
      limits:
        memory: 200Mi
# infrastructure/devops/gitlab/overlay/prod/kustomize.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: devops-prod

resources:
  - namespace.yaml
  - ../../base

labels:
  - includeSelectors: true
    includeTemplates: true
    pairs:
      app.kubernetes.io/name: gitlab
      app.kubernetes.io/component: devops
      app.kubernetes.io/managed-by: kustomize
      app.kubernetes.io/instance: prod-instance
      env: prod
      app: gitlab

commonAnnotations:
  owner: "Owner"
  team.contact: "team@example.com"
  runbook.url: "https://example.com"
  deployment.priority: "high"
  last-updated-by: "Owner"

secretGenerator:
  - name: gitlab-auth
    envs:
      - .env.prod.secrets

generatorOptions:
  disableNameSuffixHash: true
# infrastructure/devops/gitlab/overlay/prod/namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: devops-prod
Install with Kustomize
kustomize build infrastructure/devops/gitlab/overlays/prod --enable-helm | kubectl apply -f -

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top