← Kursa Dön
📄 Text · 25 min

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=max

Bu 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: 5

Multi-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: manual

when: 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=max

Bu 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=max

Cache'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:latest

Tam 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 deploy

Bu pipeline'da dikkat çekici noktalar:

  • on: push: tags: ['v*'] — sadece version tag'lerinde çalışır

  • security-scan build'den sonra çalışır — CRITICAL CVE varsa deploy durur

  • environment: production — GitHub'da manuel onay gerektirir

  • Her 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+push

Faz 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 gerekmez

GitHub 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 sonra

Docker 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-action ana 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 — latest production'da kullanma

  • Deploy 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.