GitLab Container Registry ile Docker Image Yönetimi
Bir müşterimizin CI/CD pipeline’ı tamamen çökmüştü. Sorunun kaynağı Docker Hub’ın rate limiting politikasıydı; anonim pull’lar için saatte 100 istek sınırı, 50 developer’lı bir ekipte dakikalar içinde doluyordu. Çözüm aslında hep elimizin altındaydı: GitLab’ın kendi içinde gelen Container Registry. O günden beri kurumsal projelerde Docker Hub’a bağımlılığı mümkün olduğunca azaltıyorum ve bu yazıda öğrendiklerimi aktarmak istiyorum.
GitLab Container Registry Neden Önemli?
GitLab, sadece bir Git sunucusu değil. Proje kodunuzla aynı çatı altında bir container registry, CI/CD sistemi, issue tracker ve çok daha fazlasını barındırıyor. Container Registry özelliği sayesinde Docker image’larınızı doğrudan GitLab projenizle ilişkilendirilmiş özel bir registry’de saklayabiliyorsunuz.
Bu yaklaşımın en büyük avantajı entegrasyon kolaylığı. CI/CD pipeline’ınız aynı GitLab instance’ında çalışıyor, registry de orada, yetki yönetimi de orada. Docker Hub’da ayrı bir organizasyon kurmak, token yönetmek, Kubernetes’te ayrı secret oluşturmak gibi operasyonel yüklerle uğraşmak yerine tek bir yerden yönetebiliyorsunuz her şeyi.
GitLab.com kullananlar için bu özellik hemen hazır geliyor. Self-hosted GitLab kuruyorsanız birkaç ek yapılandırma adımı gerekiyor, o kısma da değineceğim.
Self-Hosted GitLab’da Registry Yapılandırması
GitLab.com kullanıyorsanız bu bölümü atlayabilirsiniz. Kendi sunucunuzda GitLab çalıştırıyorsanız /etc/gitlab/gitlab.rb dosyasında şu ayarları yapmanız gerekiyor:
# /etc/gitlab/gitlab.rb
registry_external_url 'https://registry.sirketiniz.com'
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "registry.sirketiniz.com"
gitlab_rails['registry_port'] = "443"
gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
registry['enable'] = true
registry['registry_http_addr'] = "localhost:5000"
Yapılandırmayı uygulamak için:
sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart
Registry için ayrı bir subdomain kullanmanızı şiddetle tavsiye ederim. Aynı domain üzerinde farklı port ile çalıştırmak teknik olarak mümkün ama production ortamında sorun çıkarıyor. SSL sertifikanızı da bu subdomain için ayrıca almanız gerekiyor; Let’s Encrypt kullanıyorsanız GitLab bunu otomatik halledebiliyor.
Storage backend olarak varsayılan filesystem yerine S3 uyumlu bir nesne depolama kullanmak uzun vadede çok daha sağlıklı:
registry['storage'] = {
's3' => {
'accesskey' => 'AWS_ACCESS_KEY',
'secretkey' => 'AWS_SECRET_KEY',
'bucket' => 'gitlab-registry-bucket',
'region' => 'eu-west-1',
'secure' => true
}
}
MinIO kullanıyorsanız regionendpoint parametresini eklemeniz gerekiyor. Bunu atlayan çok kişi görüdm, sonra “neden S3’e yazamıyorum” diye saatlerce uğraşıyorlar.
Registry’ye Bağlanma ve Temel Kullanım
GitLab Container Registry’ye bağlanmak için GitLab kullanıcı adınız ve bir Personal Access Token (PAT) kullanıyorsunuz. Şifrenizi direkt kullanmak yerine read_registry ve write_registry scope’larıyla bir token oluşturmanızı öneririm.
# GitLab.com için
docker login registry.gitlab.com
# Self-hosted için
docker login registry.sirketiniz.com
# Kullanıcı adı: GitLab kullanıcı adınız
# Şifre: Personal Access Token
Başarılı login sonrası image’larınızı push etmek için GitLab’ın belirlediği naming convention’a uymak zorundasınız:
# Image build etme
docker build -t registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0 .
# Tag ekleme
docker tag mevcut-image:latest registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:latest
# Push etme
docker push registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0
docker push registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:latest
# Pull etme
docker pull registry.gitlab.com/kullanici-adi/proje-adi/uygulama-adi:v1.0.0
Namespace yapısına dikkat edin: registry_host/namespace/proje/image:tag. GitLab’da subgrouplar kullanıyorsanız bu path daha da uzayabiliyor. Otomasyonda bunu dinamik olarak ele almak önemli.
CI/CD Pipeline ile Entegrasyon
İşte işin gerçekten güçlendiği kısım burası. GitLab CI, kendi registry’siyle çalışırken CI_REGISTRY, CI_REGISTRY_IMAGE, CI_REGISTRY_USER ve CI_REGISTRY_PASSWORD gibi önceden tanımlı değişkenler sunuyor. Bu değişkenleri kullanarak pipeline’ınızı temiz tutabiliyorsunuz.
# .gitlab-ci.yml
variables:
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
stages:
- build
- test
- push
- deploy
build-image:
stage: build
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --pull -t $IMAGE_TAG .
- docker push $IMAGE_TAG
rules:
- if: $CI_COMMIT_BRANCH
Bu yapıda $CI_REGISTRY_USER ve $CI_REGISTRY_PASSWORD değişkenlerini siz tanımlamıyorsunuz; GitLab otomatik dolduruyor. Bu ayrıntı çok kritik: Token yönetimiyle hiç uğraşmıyorsunuz.
Daha gelişmiş bir senaryo olarak multi-stage pipeline’ı ele alalım. Gerçek bir proje için kullandığım yapıya benzer bir örnek:
# .gitlab-ci.yml - Gelismis versiyon
variables:
DOCKER_BUILDKIT: "1"
DOCKER_TLS_CERTDIR: "/certs"
RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
COMMIT_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
BRANCH_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
.docker-login: &docker-login
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
<<: *docker-login
stage: build
image: docker:24.0
services:
- docker:24.0-dind
script:
- |
docker build
--cache-from $RELEASE_IMAGE
--build-arg BUILDKIT_INLINE_CACHE=1
--tag $COMMIT_IMAGE
--tag $BRANCH_IMAGE
.
- docker push $COMMIT_IMAGE
- docker push $BRANCH_IMAGE
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH
tag-release:
<<: *docker-login
stage: push
image: docker:24.0
services:
- docker:24.0-dind
script:
- docker pull $COMMIT_IMAGE
- docker tag $COMMIT_IMAGE $RELEASE_IMAGE
- docker tag $COMMIT_IMAGE $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- docker push $RELEASE_IMAGE
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG
Burada --cache-from parametresine özellikle dikkat edin. Layer cache’ini registry üzerinden yönetmek, build sürelerini ciddi ölçüde kısaltıyor. Özellikle bağımlılık yükleme adımları tekrar tekrar çalışmaktan kurtulabiliyor.
Image Temizleme ve Retention Policy
Zamanla registry dolmaya başlıyor. Yeterince konuşulmayan ama production’da çok kritik olan bir konu bu. Her commit’te yeni bir image push ediliyorsa disk alanı hızla tükeniyor. GitLab bu sorunu çözmek için Cleanup Policies sunuyor.
GitLab arayüzünden Settings > Packages and registries > Container registry yolunu izleyerek cleanup policy tanımlayabilirsiniz. Ama policy’yi kod olarak tanımlamak çok daha kontrollü bir yaklaşım:
# GitLab API ile cleanup policy tanimlama
curl --request PUT
--header "PRIVATE-TOKEN: <your-token>"
--header "Content-Type: application/json"
--data '{
"container_expiration_policy_attributes": {
"cadence": "1d",
"enabled": true,
"keep_n": 10,
"older_than": "30d",
"name_regex_delete": ".*",
"name_regex_keep": ".*-stable|latest|main"
}
}'
"https://gitlab.com/api/v4/projects/<project-id>"
Bu politika şunu yapıyor:
- cadence: “1d”: Temizleme günde bir çalışıyor
- keep_n: 10: Her image adı için en son 10 tag tutuluyor
- older_than: “30d”: 30 günden eski tag’lar siliniyor
- name_regex_keep:
stable,latestveyamainiçeren tag’lar korunuyor
Manuel temizlik yapmak istediğinizde GitLab API’yi şöyle kullanabilirsiniz:
# Bir projedeki tum repository'leri listeleme
curl --header "PRIVATE-TOKEN: <your-token>"
"https://gitlab.com/api/v4/projects/<project-id>/registry/repositories"
# Belirli bir repository'nin tag'larini listeleme
curl --header "PRIVATE-TOKEN: <your-token>"
"https://gitlab.com/api/v4/projects/<project-id>/registry/repositories/<repository-id>/tags"
# Belirli bir tag'i silme
curl --request DELETE
--header "PRIVATE-TOKEN: <your-token>"
"https://gitlab.com/api/v4/projects/<project-id>/registry/repositories/<repository-id>/tags/<tag-name>"
Kubernetes Entegrasyonu
Registry’deki image’ları Kubernetes’te kullanmak için bir imagePullSecret oluşturmanız gerekiyor. Bunu her namespace için ayrı ayrı yapmak zahmetli olduğundan bir script ile otomatize etmek daha mantıklı:
#!/bin/bash
# create-gitlab-secret.sh
NAMESPACE=${1:-default}
GITLAB_HOST="registry.gitlab.com"
GITLAB_USER="deploy-user"
GITLAB_TOKEN="glpat-xxxxxxxxxxxxxxxxxxxx"
kubectl create secret docker-registry gitlab-registry-secret
--docker-server=$GITLAB_HOST
--docker-username=$GITLAB_USER
--docker-password=$GITLAB_TOKEN
--namespace=$NAMESPACE
--dry-run=client -o yaml | kubectl apply -f -
echo "Secret olusturuldu: $NAMESPACE namespace'inde"
Deployment manifest’inizde de bu secret’a referans vermeniz gerekiyor:
apiVersion: apps/v1
kind: Deployment
metadata:
name: uygulama
spec:
template:
spec:
imagePullSecrets:
- name: gitlab-registry-secret
containers:
- name: uygulama
image: registry.gitlab.com/sirket/proje/uygulama:v1.2.3
imagePullPolicy: Always
Kubernetes’te GitLab Deploy Token kullanmak PAT’a göre daha iyi bir pratik. Deploy token’lar proje veya group bazında oluşturulabiliyor ve sadece registry okuma yetkisi verebiliyorsunuz. Kişiye bağlı olmadığı için o kişi şirketten ayrıldığında secret’ı değiştirmeniz gerekmiyor.
Güvenlik Taraması ile Entegrasyon
GitLab Ultimate veya Gold lisansa sahipseniz Container Scanning özelliği geliyor. Ama açık kaynak araçlarla da benzer işleri yapabilirsiniz. Trivy ile entegrasyon kurmak oldukça basit:
container-scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
variables:
TRIVY_NO_PROGRESS: "true"
TRIVY_CACHE_DIR: ".trivycache/"
FULL_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
script:
- trivy --version
- trivy image
--exit-code 0
--format template
--template "@/contrib/gitlab.tpl"
--output gl-container-scanning-report.json
$FULL_IMAGE_NAME
- trivy image
--exit-code 1
--severity CRITICAL
$FULL_IMAGE_NAME
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
paths:
- gl-container-scanning-report.json
expire_in: 1 week
cache:
paths:
- .trivycache/
allow_failure: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Bu yapıda kritik severity’ye sahip açıklar tespit edilirse pipeline başarısız oluyor. allow_failure: true ile job başarısız olsa bile pipeline’ın devam etmesini sağladım, ama bu kararı takımınızın güvenlik politikasına göre değiştirmelisiniz.
Gerçek Dünya Senaryosu: Monorepo Yönetimi
Birçok uygulamanın tek bir repoda toplandığı monorepo yapısında her servis için ayrı image yönetmek karmaşıklaşabiliyor. Bu durumda GitLab CI’ın changes kuralını kullanarak yalnızca değişen servislerin image’ını build etmek büyük bir verimlilik sağlıyor:
# Monorepo icin ornek yapi
# services/api, services/worker, services/frontend dizinleri var
.build-template: &build-template
stage: build
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-api:
<<: *build-template
variables:
SERVICE: api
CONTEXT_PATH: services/api
script:
- docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
- docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
rules:
- changes:
- services/api/**/*
- .gitlab-ci.yml
build-worker:
<<: *build-template
variables:
SERVICE: worker
CONTEXT_PATH: services/worker
script:
- docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
- docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
rules:
- changes:
- services/worker/**/*
- .gitlab-ci.yml
build-frontend:
<<: *build-template
variables:
SERVICE: frontend
CONTEXT_PATH: services/frontend
script:
- docker build -t $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA $CONTEXT_PATH
- docker push $CI_REGISTRY_IMAGE/$SERVICE:$CI_COMMIT_SHORT_SHA
rules:
- changes:
- services/frontend/**/*
- .gitlab-ci.yml
Bu yapıda API koduna dokunmadıysanız API image’ı build edilmiyor, gereksiz CI dakikaları harcanmıyor ve registry şişmiyor.
Dikkat Edilmesi Gereken Noktalar
Yaygın yapılan hataları da paylaşmak istiyorum, çünkü bunları ya bizzat yaşadım ya da başkalarının yaşadığını gördüm.
- docker:dind kullanırken TLS dikkat: Eski örnekler
DOCKER_TLS_CERTDIR: ""ile TLS’yi devre dışı bırakıyordu. Bunu yapmayın, güvenlik açığı yaratıyor. - Image tag olarak sadece
latestkullanmak: Hangi sürümün nerede çalıştığını takip edemezsiniz. Commit SHA veya semantik versiyon mutlaka ekleyin. - Registry temizleme politikasını kurmayı ertelemek: “Sonra ayarlarım” diyerek ertelenen cleanup policy, bir gün disk dolduğunda gece yarısı acil müdahale gerektiriyor.
- Tüm image’ları public yapmak: Özel uygulamanızın Dockerfile’ındaki konfigürasyon bilgileri, base image seçimleri rakipleriniz tarafından görülebilir. Varsayılan olarak private bırakın.
- Build context’ini küçük tutmamak:
.dockerignoredosyası oluşturmayı ihmal etmek build sürelerini gereksiz uzatıyor ve image boyutunu şişiriyor.
Sonuç
GitLab Container Registry, CI/CD ekosisteminizi tek çatı altında birleştirmenin en temiz yollarından biri. Kurumsal yapılarda üçüncü taraf registry bağımlılığını azaltmak hem operasyonel karmaşıklığı düşürüyor hem de güvenlik yüzeyini küçültüyor. Cleanup policy ile depolama maliyetlerini kontrol altında tutmak, Trivy gibi araçlarla güvenlik taramasını pipeline’a entegre etmek ve Kubernetes’te deploy token kullanmak kısa vadede birkaç saat daha fazla iş gibi görünse de uzun vadede sizin yerinize saatlerce sorun çözüyor.
Kendi registry’nizi yönetmek başlangıçta göz korkutucu gelebilir, ama GitLab bu süreci gerçekten kolaylaştırmış. CI değişkenlerinin otomatik enjeksiyonu, proje bazlı yetki yönetimi ve API üzerinden her şeyin otomasyon içine alınabilmesi, bu aracı sysadmin’ler için gerçekten kullanılabilir kılıyor. Başlamak için en iyi zaman hala şimdi.
