“CI/CD ile Docker” yazımızda GitHub Actions üzerinden bulut tabanlı bir pipeline kurmuştuk. Ama birçok kurumsal ekip hâlâ Jenkins‘i tercih ediyor — kendi sunucunuzda barındırdığınız, yüzlerce eklentiyle her türlü araca entegre olabilen, açık kaynak bir otomasyon sunucusu. Bu yazıda Jenkins’i bizzat bir Docker container’ı olarak ayağa kaldırıp, Docker imajlarını otomatik build eden gerçek bir pipeline kuruyoruz.
Jenkins neden hâlâ yaygın?
GitHub Actions ve GitLab CI gibi bulut tabanlı çözümler daha az yapılandırmayla hızlı sonuç verse de, Jenkins’in tercih edilmesinin somut nedenleri var:
- Tam kontrol: Kendi sunucunuzda çalışır — kod ve build süreçleri hiçbir zaman üçüncü parti bir buluta çıkmaz. Sıkı veri gizliliği gereksinimleri olan kurumlar için önemli.
- Eklenti ekosistemi: 1800’den fazla resmi eklenti; neredeyse her araç, versiyon kontrol sistemi ve bildirim kanalıyla (Slack, Jira, SonarQube, Artifactory…) entegre olabilir.
- Karma altyapı desteği: Şirket içi (on-premise) sunucular, farklı işletim sistemleri, özel donanımlar (GPU build ajanları gibi) üzerinde çalışabilir — bulut tabanlı runner’ların erişemeyeceği ortamlarda.
- Olgunluk: 2011’den beri geliştiriliyor; büyük kurumsal ortamlarda onlarca yıllık production geçmişi var.
Mimari: Controller ve Agent
Jenkins iki bileşenden oluşur:
- Controller (eski adıyla master): Web arayüzünü sunar, pipeline tanımlarını ve iş kuyruğunu yönetir, sonuçları saklar. Doğrudan build işlerini çalıştırmak yerine bu işi agent’lara devretmesi önerilir.
- Agent (eski adıyla slave): Asıl build/test/deploy işlerinin çalıştığı makine veya container. Bir controller’a birden fazla agent bağlanabilir, her biri farklı bir işletim sistemi veya araç setiyle donatılabilir.
Jenkins’i Docker container olarak çalıştırmak
Jenkins’in resmi imajı, kendisini de hızlıca ayağa kaldırmanızı sağlar. Docker imajları build edebilmesi için ana makinenin Docker soketini container içine bağlamamız gerekir:
docker volume create jenkins_home
docker run -d --name jenkins \
-p 8080:8080 -p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(which docker):/usr/bin/docker \
--group-add $(stat -c '%g' /var/run/docker.sock) \
jenkins/jenkins:lts-jdk17
İlk açılışta Jenkins, konsol loglarına bir kurulum şifresi yazdırır (docker logs jenkins ile görebilirsiniz); tarayıcıdan localhost:8080‘a gidip bu şifreyle kurulum sihirbazını tamamlarsınız ve önerilen eklentileri (Docker Pipeline dahil) yükletirsiniz.
/var/run/docker.sock‘u container içine bağlamak, Jenkins container’ına ana makinenin Docker daemon’ını doğrudan kontrol etme yetkisi verir — pratikte bu, o container’ın host üzerinde neredeyse root yetkisiyle işlem yapabileceği anlamına gelir. Production’da bu riski azaltmak için Jenkins’i izole, güvenilir bir sunucuda çalıştırın ve container’a erişimi kısıtlayın; alternatif olarak rootless bir Docker-in-Docker (DinD) kurulumu da değerlendirilebilir.
Jenkinsfile: pipeline’ı kod olarak tanımlamak
Jenkins’te bir pipeline, projenizin köküne konan bir Jenkinsfile ile tanımlanır (GitHub Actions’taki .github/workflows/*.yml‘e karşılık gelir). Declarative syntax en okunaklı ve önerilen yöntemdir:
pipeline {
agent any
environment {
IMAGE_NAME = 'kullanici-adi/benim-api'
DOCKERHUB_CRED = credentials('dockerhub-token')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'docker build -t $IMAGE_NAME:$BUILD_NUMBER .'
}
}
stage('Test') {
steps {
sh 'docker run --rm $IMAGE_NAME:$BUILD_NUMBER npm test'
}
}
stage('Zafiyet Taraması') {
steps {
sh 'trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE_NAME:$BUILD_NUMBER'
}
}
stage('Push') {
steps {
sh '''
echo $DOCKERHUB_CRED_PSW | docker login -u $DOCKERHUB_CRED_USR --password-stdin
docker push $IMAGE_NAME:$BUILD_NUMBER
docker tag $IMAGE_NAME:$BUILD_NUMBER $IMAGE_NAME:latest
docker push $IMAGE_NAME:latest
'''
}
}
stage('Deploy') {
steps {
sshagent(['prod-sunucu-ssh']) {
sh '''
ssh [email protected] \
"docker pull $IMAGE_NAME:latest && docker compose up -d --no-deps api"
'''
}
}
}
}
post {
failure {
slackSend channel: '#deploy', message: "Build #${BUILD_NUMBER} başarısız oldu."
}
}
}
Her stage, Jenkins arayüzünde ayrı bir kutu olarak görünür — hangi adımın ne kadar sürdüğünü ve nerede başarısız olduğunu tek bakışta görebilirsiniz. credentials() fonksiyonu, Jenkins’in kendi şifrelenmiş kimlik bilgisi deposundan (Manage Jenkins → Credentials) değer okur; parolalar hiçbir zaman Jenkinsfile içine açık yazılmaz.
Webhook ile otomatik tetikleme
Her kod push’unda pipeline’ın otomatik başlaması için GitHub reponuzda bir webhook tanımlanır: Settings → Webhooks → Add webhook, Payload URL’e http://jenkins-sunucunuz:8080/github-webhook/ yazılır. Jenkins tarafında ilgili proje için “GitHub hook trigger for GITScm polling” seçeneği işaretlenir. Artık her push, saniyeler içinde yeni bir build tetikler — elle “Build Now” tıklamaya gerek kalmaz.
Jenkins vs GitHub Actions: hangisi ne zaman?
| Kriter | Jenkins | GitHub Actions |
|---|---|---|
| Barındırma | Kendi sunucunuz (self-hosted) | GitHub’ın bulut altyapısı (veya self-hosted runner) |
| Kurulum yükü | Yüksek — sunucu, güncelleme, eklenti yönetimi size ait | Sıfır — GitHub reposu varsa hazır |
| Esneklik / eklenti | Çok yüksek (1800+ eklenti) | Marketplace action’ları ile orta-yüksek |
| Maliyet | Sunucu maliyeti size ait, yazılım ücretsiz | Public repo ücretsiz; private repo’da dakika bazlı ücretlendirme |
| Karma/özel altyapı | Doğal destek | Self-hosted runner ile mümkün, ek kurulum gerekir |
| Öğrenme eğrisi | Daha dik (Groovy tabanlı Jenkinsfile, plugin yönetimi) | Daha düz (YAML, geniş hazır action kütüphanesi) |
Küçük-orta ölçekli, GitHub’da barınan projeler için GitHub Actions genellikle daha az sürtünmeyle işe yarar. Kurumsal, karma altyapılı veya veri gizliliği gereksinimi yüksek ortamlarda Jenkins’in esnekliği ve tam kontrolü öne çıkar. İkisi birbirini dışlamaz — bazı ekipler basit projelerde Actions’ı, kritik/özel altyapı gerektiren projelerde Jenkins’i bir arada kullanır.
Pipeline artık her push’ta otomatik çalışıyor. Bir sonraki adım, sunucularınızın kendisini de elle değil, tutarlı ve tekrarlanabilir şekilde yapılandırmak — bunu bir sonraki yazıda, Ansible ile otomasyon başlığı altında ele alıyoruz.