Docker ile CI/CD Pipeline — GitHub Actions, GitLab CI
Docker ile container oluşturmayı, Kubernetes ile yönetmeyi öğrendik. Ama şu ana kadar her şeyi elle yaptık — image build ettik, push ettik, deploy ettik. Gerçek dünyada bu süreç otomatik olmalı. İşte CI/CD burada devreye giriyor.
Yayınevi Analojisi
Bir yayınevinde kitap basımını düşün. Eskiden yazar yazdı, editör okudu, dizgici düzenledi, matbaa bastı — her adım elle, haftalarca sürerdi. Şimdi modern bir yayınevi düşün: yazar "gönder" der, gerisini sistem otomatik yapar — dil kontrolü, dizgi, baskı, dağıtım.
CI/CD (Continuous Integration / Continuous Deployment) yazılım dünyasının bu otomatik matbaasıdır. Docker ile birleştiğinde mükemmel bir ikili oluşur: her commit'te otomatik olarak image build edilir, testler çalışır, güvenlik taraması yapılır ve production'a deploy olur. Manuel müdahale yok, insan hatası yok, tutarlılık var.
CI/CD + Docker Akışı
Büyük resmi görelim:
Developer CI/CD Pipeline Production
│ │ │
│ git push │ │
├─────────────────────────────→│ │
│ │ 1. Kod checkout │
│ │ 2. docker build (test) │
│ │ 3. Testleri çalıştır │
│ │ 4. docker build (production) │
│ │ 5. Güvenlik taraması │
│ │ 6. docker push → registry │
│ │ 7. Deploy to production ──────→│
│ │ │
│ ← Bildirim (başarılı/başarısız) │Sen sadece kodu yazıp push ediyorsun. Gerisi otomatik. Başarılı olursa production'a deploy olur, başarısız olursa bildirim alırsın. Bu akış 5-15 dakika içinde tamamlanır.
GitHub Actions ile Docker CI/CD
GitHub Actions, en popüler CI/CD platformlarından biri ve Docker ile mükemmel entegre çalışır. Hadi adım adım bir pipeline oluşturalım.
Temel Pipeline: Test + Build + Push
# .github/workflows/docker.yml
name: Docker CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# İlk adım: Test
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Testleri Docker'da çalıştır
run: |
docker compose -f docker-compose.test.yml up \
--build --abort-on-container-exit
docker compose -f docker-compose.test.yml down -v
# İkinci adım: Build ve Push
build-and-push:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Docker Buildx kur
uses: docker/setup-buildx-action@v3
- name: Registry'ye giriş yap
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Tag'leri oluştur
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix=
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build ve push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=maxBu pipeline ne yapıyor? Adım adım inceleyelim:
on: bloğu pipeline'ın ne zaman çalışacağını belirliyor. main ve develop branch'lerine push yapıldığında veya main'e PR açıldığında tetiklenir.
test job'u Docker Compose ile testleri çalıştırır. --abort-on-container-exit flag'i ile test container'ı bittiğinde tüm servisler durdurulur.
build-and-push job'u test'ten sonra çalışır (needs: test). Başarılı testlerden sonra image build edilir ve GHCR'a push edilir. cache-from: type=gha ile GitHub Actions cache'i kullanılır — bu, build süresini %60-80 azaltabilir.
docker/metadata-action otomatik tag'ler oluşturur: branch adı, commit hash, semantic version ve latest tag.
Test için Compose Dosyası
# docker-compose.test.yml
services:
test:
build:
context: .
target: test
environment:
DATABASE_URL: postgres://test:test@db:5432/testdb
NODE_ENV: test
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
retries: 5Multi-Stage Dockerfile: Test + Production
# Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Test stage
FROM deps AS test
COPY . .
RUN npm run lint
RUN npm run test -- --ci --coverage
# Build stage
FROM deps AS builder
COPY . .
RUN npm run build
RUN npm prune --production
# Production stage
FROM node:20-alpine AS production
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/index.js"]Burada güzel bir şey var: test stage başarısız olursa, build devam etmez. Yani hatalı kod asla production image'ına giremez. Bu "shift left" prensibi — hataları mümkün olduğunca erken yakalamak.
GitLab CI ile Docker
GitLab kullanıyorsan, GitLab CI ile aynı akışı kurabilirsin:
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE
DOCKER_TAG: $CI_COMMIT_SHORT_SHA
test:
stage: test
image: docker:24
services:
- docker:24-dind
script:
- docker compose -f docker-compose.test.yml up \
--build --abort-on-container-exit
- docker compose -f docker-compose.test.yml down -v
build:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_IMAGE:$DOCKER_TAG .
- docker push $DOCKER_IMAGE:$DOCKER_TAG
only:
- main
deploy:
stage: deploy
script:
- ssh deploy@server "cd /app && docker compose pull && docker compose up -d"
only:
- main
when: manualwhen: manual önemli — production deploy otomatik değil, manuel onay gerekiyor. Bu, yanlışlıkla production'a deploy yapılmasını engeller.
Güvenlik Taraması Pipeline'a Ekle
Her build'de güvenlik taraması yapmak artık standart bir pratik. Trivy'yi pipeline'a eklemek çok kolay:
- name: Güvenlik taraması
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Sonuçları GitHub Security'ye yükle
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'exit-code: '1' ile CRITICAL veya HIGH seviye zafiyet bulunursa pipeline durur — güvensiz image production'a gidemez. Sonuçlar GitHub Security sekmesinde görüntülenir.
Build Cache: Pipeline'ı Hızlandır
CI/CD'de en büyük zaman kaybı image build süresi. Her seferinde sıfırdan build etmek 5-10 dakika sürebilir. Cache stratejileri ile bunu %70-90 azaltabilirsin.
GitHub Actions Cache (GHA):
- uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=maxBu en basit ve etkili yöntem. GitHub Actions kendi cache'inde Docker layer'ları saklar. Değişmeyen layer'lar cache'den gelir.
Registry Cache:
- uses: docker/build-push-action@v5
with:
cache-from: type=registry,ref=myapp:buildcache
cache-to: type=registry,ref=myapp:buildcache,mode=maxCache'i registry'de saklar. Farklı runner'lar arasında paylaşılabilir.
Unutma: Dockerfile'daki sıralama cache verimliliğini doğrudan etkiler. Sık değişen dosyalar (kaynak kod) alt satırlarda, az değişen dosyalar (package.json) üst satırlarda olmalı. Böylece kaynak kod değiştiğinde sadece son birkaç layer yeniden build edilir.
Deploy Stratejileri
Build ve push tamamlandıktan sonra deploy nasıl yapılacak? Birkaç strateji var:
Direct Deploy (Basit): SSH ile sunucuya bağlan, docker compose pull && docker compose up -d çalıştır. Küçük projeler için yeterli.
Blue-Green: Yeni versiyonu ayrı bir ortama deploy et, test et, sonra trafiği yönlendir. Sorun çıkarsa anında eski versiyona dön.
Canary: Yeni versiyonu az sayıda replica'ya deploy et, metrikleri izle. Sorun yoksa kalan replica'ları da güncelle.
GitOps: Git repo'suna commit at, ArgoCD/Flux otomatik deploy etsin. Bu yaklaşımı sonraki derslerde detaylı inceleyeceğiz.
Image Tag Stratejisi
Her build'de anlamlı ve benzersiz tag'ler kullanmak çok önemli:
# Her commit'te
myapp:abc123f # Commit hash — benzersiz, immutable
myapp:main-abc123f # Branch + commit
# Release'lerde
myapp:v1.2.3 # Semantic version
# ❌ Kullanma
myapp:latest # Hangi versiyon? Belli değil. Güvenilmez.docker/metadata-action ile bu tag'ler otomatik oluşturulur — elle uğraşmana gerek yok.
Multi-Platform Build
ARM tabanlı sunucular (AWS Graviton, Apple Silicon Mac'ler) yaygınlaşıyor. Image'ını hem AMD64 hem ARM64 için build etmek iyi bir pratik:
- name: QEMU kur (ARM emülasyon)
uses: docker/setup-qemu-action@v3
- name: Multi-platform build
uses: docker/build-push-action@v5
with:
push: true
platforms: linux/amd64,linux/arm64
tags: myapp:latestTam Production Pipeline Örneği
Son olarak, tüm best practice'leri içeren production-ready bir pipeline:
name: Production Deploy
on:
push:
tags: ['v*']
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test
run: |
docker compose -f docker-compose.test.yml up \
--build --abort-on-container-exit --exit-code-from test
docker compose -f docker-compose.test.yml down -v
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max
security-scan:
needs: build
runs-on: ubuntu-latest
steps:
- uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
severity: 'CRITICAL,HIGH'
exit-code: '1'
deploy:
needs: [build, security-scan]
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy
run: |
echo "Deploying ${{ github.ref_name }} to production"
# kubectl, helm, veya ssh ile deployBu pipeline'da dikkat çekici noktalar:
on: push: tags: ['v*']— sadece version tag'lerinde çalışırsecurity-scanbuild'den sonra çalışır — CRITICAL CVE varsa deploy dururenvironment: production— GitHub'da manuel onay gerektirirHer adım bir öncekine bağımlı (
needs) — test geçmediyse build olmaz, scan geçmediyse deploy olmaz
Gerçek Dünya Senaryosu: Startup'tan Enterprise'a
CI/CD pipeline'ını projenin büyüklüğüne göre kademeli olarak kurabilirsin. İşte tipik bir evrim:
Faz 1 — Tek geliştirici, kişisel proje: Basit bir pipeline yeterli. Push'ta build et, push et, bitsin:
on: push
jobs:
build:
steps:
- uses: docker/build-push-action@v5
with: { push: true, tags: myapp:latest }Faz 2 — Küçük ekip (2-5 kişi): Test ekle, branch stratejisi kur. PR'larda test çalışsın, main'e merge olunca deploy olsun:
on:
pull_request: { branches: [main] } # PR'da test
push: { branches: [main] } # Main'de build+pushFaz 3 — Orta ekip (5-15 kişi): Güvenlik taraması, staging ortamı, manuel production onayı ekle. Semantic versioning ile release yönetimi yap.
Faz 4 — Enterprise (15+ kişi): GitOps, multi-cluster deploy, canary release, A/B testing, compliance denetimleri, SBOM oluşturma. Pipeline karmaşıklaşır ama her adım bir ihtiyaçtan doğar.
Önemli olan: pipeline'ı baştan karmaşık kurma. Basit başla, ihtiyaç doğdukça ekle. Over-engineering, under-engineering kadar kötüdür.
Docker-in-Docker (DinD) ve Güvenlik
CI/CD pipeline'larında Docker komutları çalıştırmak için iki yaklaşım var:
Docker-in-Docker (DinD): CI container'ının içinde ayrı bir Docker daemon çalıştırılır. GitLab CI'ın services: [docker:24-dind] yaklaşımı bu. İzolasyon iyi ama performans maliyeti var.
Docker Socket Bind: Host'un Docker socket'ını CI container'ına mount edersin. Daha hızlı ama güvenlik riski var — CI container'ı host'taki tüm container'lara erişebilir.
# GitLab CI — DinD (güvenli ama yavaş)
services:
- docker:24-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
# GitHub Actions — native Docker (en iyi)
# GitHub runner'ları Docker'ı zaten destekliyor, DinD gerekmezGitHub Actions'ta Docker desteği native olarak gelir — DinD veya socket mount gerekmez. Bu yüzden GitHub Actions Docker CI/CD için en rahat platform.
Pipeline Optimizasyon İpuçları
Pipeline'ın çok yavaş çalışıyorsa, şu optimizasyonları dene:
Paralel job'lar: Test, lint ve security scan birbirinden bağımsızsa paralel çalıştır:
jobs:
lint:
runs-on: ubuntu-latest
# ...
test:
runs-on: ubuntu-latest
# ...
security:
runs-on: ubuntu-latest
# ...
build:
needs: [lint, test, security] # Hepsi bittikten sonraDocker layer cache: Dockerfile'daki sıralama çok önemli. COPY package*.json önce, COPY . . sonra. Böylece kaynak kod değişince sadece son layer'lar yeniden build edilir.
Build matrix: Birden fazla platform veya versiyon için test ediyorsan matrix kullan:
strategy:
matrix:
node: [18, 20, 22]
platform: [linux/amd64, linux/arm64]Self-hosted runner: GitHub Actions'ın ücretsiz runner'ları yavaş olabilir. Kendi runner'ını kurarak build sürelerini %50-80 azaltabilirsin — özellikle build cache lokal diskten geldiğinde.
Bu Derste Ne Öğrendik?
CI/CD + Docker ile her commit'te otomatik build → test → push → deploy
GitHub Actions ile Docker pipeline kurmak çok kolay —
docker/build-push-actionana araçMulti-stage Dockerfile ile test ve production stage'lerini ayır — hatalı kod production'a giremez
Build cache (GHA, registry) ile CI sürelerini dramatik azalt
Güvenlik taraması (Trivy) her build'de çalışmalı — CRITICAL CVE varsa deploy durmalı
Tag stratejisi: commit hash + semantic version —
latestproduction'da kullanmaDeploy stratejileri: direct, blue-green, canary, GitOps — projenin büyüklüğüne göre seç
Sonraki derste automated build ve push konusunu daha derinlemesine inceleyeceğiz — registry seçimi, multi-registry push ve registry temizliği.
AI Asistan
Sorularını yanıtlamaya hazır