Haziran 20 2026

Caddy ile Docker Konteynerlerinde Sıfır Konfigürasyonlu Otomatik SSL ve Production Dağıtımı

Docker üzerinde çalışan mikro servisleri veya monolit uygulamaları canlıya alırken, kendinizi hâlâ yüzlerce satırlık Nginx konfigürasyonları yazarken, Certbot cronjob’ları debug ederken veya OpenSSL open-source script’leri arasında kaybolmuş buluyor musunuz? caddy docker production senaryolarında, bu operasyonel karmaşayı tamamen ortadan kaldıran, modern ve bellek güvenliği odaklı (memory-safe) bir web sunucusu olarak karşımıza çıkıyor. Bu rehberde, production ortamında Docker konteynerleriniz için sıfır konfigürasyonlu ve tamamen otomatik bir altyapıyı nasıl kuracağınızı adım adım inceleyeceğiz.

Nginx vs Caddy: Production Gözünden Gerçekçi Bir Karşılaştırma

Yıllardır süregelen reverse proxy tahtını sallayan nginx vs caddy rekabeti, aslında bir felsefe savaşıdır. Nginx, performans canavarı bir C yazılımıdır ancak SSL yönetimi, HTTP/3 desteği ve dinamik konfigürasyon gibi modern ihtiyaçlar için ek modüllere, third-party paketlere (certbot, lua, v.b.) ihtiyaç duyar. Bu da production ortamlarında bakım yükünü (maintenance overhead) katlar.

Caddy ise Go diliyle yazılmıştır. Kendi içinde gömülü bir ACME (Let’s Encrypt / ZeroSSL) istemcisiyle gelir. Sadece tek satır yazarak hem reverse proxy kurulumunu tamamlar hem de otomatik ssl kurulumu sürecini arka planda fully-automated olarak başlatır. Üstelik bunu yaparken HTTP/3 protokolünü kutudan çıktığı gibi aktif eder. Performans noktasında Nginx hâlâ milisaniyeler bazında mikro optimizasyonlarda önde olsa da, modern CPU’lar ve network mimarilerinde Caddy’nin getirdiği operasyonel hız ve güvenlik, bu farkı önemsiz kılmaktadır.

Production Mimarisi: Docker Compose ve Caddyfile Entegrasyonu

Production ortamında stabil çalışan bir reverse proxy yapılandırması için öncelikle Caddy’nin durum bilgisi (state) içeren dizinlerini host makinede saklamamız gerekir. SSL sertifikalarının kaybolmaması ve rate limit’lere takılmamak için bu adım kritiktir.

İlk olarak projemizin kök dizininde bir Caddyfile oluşturalım. Bu dosya, Caddy’nin ana konfigürasyon merkezidir ve syntax’ı oldukça temizdir.

# Caddyfile
api.kertenkerem.net {
    # Gelişmiş Güvenlik Başlıkları
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-XSS-Protection "1; mode=block"
        X-Frame-Options "DENY"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    # Sıkıştırma Aktif
    encode gzip zstd

    # Docker içindeki microservice-api konteynerine yönlendirme
    reverse_proxy api-service:8080 {
        header_up Host {upstream_hostport}
        header_up X-Real-IP {remote_host}
    }
}

Yukarıdaki konfigürasyonda tek bir domain tanımladık. Caddy, bu domaini gördüğü anda otomatik olarak Let’s Encrypt ve ZeroSSL üzerinden ACME HTTP-01 challenge sürecini başlatır. Sizin ek bir port açıp certbot çalıştırmanıza veya private-key üretmenize gerek kalmaz.

Docker Compose ile Servislerin Ayağa Kaldırılması

Şimdi de bu yapıyı ayağa kaldıracak olan production-ready docker-compose.yml dosyamızı hazırlayalım. Burada en çok dikkat etmeniz gereken nokta, Caddy’nin veri depolama hacimleridir (volumes).

version: '3.8'

services:
  caddy:
    image: caddy:2.7-alpine
    container_name: caddy_proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # HTTP/3 (QUIC) için zorunlu port
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - prod_network
    environment:
      - ACME_AGREE=true

  api-service:
    image: node:20-alpine
    container_name: node_api
    restart: always
    command: node index.js
    # Port map etmiyoruz; dış dünyaya sadece Caddy üzerinden kapalı network ile açıyoruz
    networks:
      - prod_network

networks:
  prod_network:
    driver: bridge

volumes:
  caddy_data:
    external: false
  caddy_config:
    external: false

Neden /data ve /config dizinlerini kalıcı hale (persist) getirdik? Çünkü Caddy, aldığı SSL sertifikalarını, private key’leri ve ACME hesap bilgilerini /data dizini altında saklar. Eğer bu volume tanımlarını yapmazsanız, konteyner her restart olduğunda sıfırdan sertifika talep edecek ve Let’s Encrypt’in haftalık limitlerine (duplicate certificate limit) takılarak sitenizin erişime kapanmasına neden olacaktır.

Gelişmiş Senaryo: DNS-01 Challenge ile Wildcard SSL Alımı

Eğer API servislerinizi veya staging ortamlarınızı dış dünyaya tamamen açmadan, yerel veya yarı-kapalı networklerde SSL ile çalıştırmak istiyorsanız ya da wildcard (*.kertenkerem.net) sertifikaya ihtiyacınız varsa, varsayılan HTTP-01 challenge’ı işe yaramaz. Çünkü HTTP-01, Let’s Encrypt sunucularının sizin 80. portunuza erişmesini gerektirir.

Bu gibi durumlarda DNS-01 challenge kullanmalıyız. Bu yöntemde Caddy, DNS sağlayıcınızın (örn: Cloudflare) API’sini kullanarak geçici bir TXT kaydı açar, doğrulamayı geçer ve kaydı siler. Ancak, varsayılan Caddy imajı güvenlik ve hafiflik gerekçesiyle DNS plugin’lerini içermez. Kendi imajımızı derlememiz gerekir.

Bunun için Dockerfile kullanarak çok kolay bir custom build oluşturabiliriz:

# Dockerfile-caddy
FROM caddy:2.7-builder-alpine AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:2.7-alpine

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Bu custom imajı oluşturduktan sonra, Caddyfile dosyamızı DNS-01 kullanacak şekilde güncelliyoruz:

# DNS-01 Destekli Caddyfile
*.kertenkerem.net {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }

    @api host api.kertenkerem.net
    handle @api {
        reverse_proxy api-service:8080
    }

    # Geri kalan istekleri düşür veya statik dosyaya yönlendir
    handle {
        abort
    }
}

Burada kullandığımız CLOUDFLARE_API_TOKEN değerini Docker Compose dosyasında çevre değişkeni (environment variable) olarak tanımlamanız yeterlidir. Caddy, runtime sırasında bu token’ı okuyarak DNS işlemlerini tamamen otonom şekilde halleder.

Sıfır Kesinti (Zero-Downtime) ile Konfigürasyon Güncelleme

Production sistemlerinde en büyük kabuslardan biri, proxy konfigürasyonunu değiştirdikten sonra servisi restart ederken yaşanan saniyelik kesintilerdir. Nginx’teki nginx -s reload komutunun eşdeğeri Caddy’de çok daha zarif bir şekilde çözülmüştür.

Caddyfile üzerinde yaptığınız değişiklikleri canlıya almak için konteyneri asla restart etmeyin. Bunun yerine şu komutu tetikleyin:

docker exec -w /etc/caddy caddy_proxy caddy reload

Bu komut, Caddy’nin arka plandaki Go thread’leri vasıtasıyla yeni konfigürasyonu belleğe yüklemesini sağlar. Eski bağlantılar (active connections) koparılmadan, yeni gelen tüm istekler anında yeni kurallara göre yönlendirilir. İşlem milisaniyeler içinde tamamlanır ve hata payı sıfırdır.

Production Ortamında Dikkat Edilmesi Gereken SRE Pratikleri

  • Log Yönetimi: Caddy logları varsayılan olarak JSON formatında stdout’a basar. Bu, ELK, Grafana Loki veya Datadog gibi log toplayıcılar için mükemmel bir uyumluluk sağlar. Caddyfile içine log { output stdout } ekleyerek log formatınızı standartlaştırın.
  • Resource Limits: Go runtime, varsayılan olarak sistemdeki tüm CPU çekirdeklerini kullanmaya meyillidir. Docker Compose üzerinde Caddy servisine makul CPU ve Memory limitleri koyarak (örn: 0.5 CPU, 512MB RAM) olası bellek sızıntısı ve CPU spike durumlarında host makinenin çökmesini engelleyin.
  • Keep-Alive ve Timeout Ayarları: Production ortamında yavaş istemci saldırılarını (Slowloris v.b.) engellemek adına Caddy’nin global ayarlarından network timeout limitlerini projenizin doğasına göre optimize edin.

Caddy, sunduğu modern mimari ve sıfır-operasyonel efor felsefesiyle, Docker tabanlı production dağıtımlarında her DevOps mühendisinin cephanesinde bulunması gereken bir silahtır. Altyapınızı sadeleştirin, bırakın SSL sertifikalarını ve yönlendirmeleri sizin yerinize Go tabanlı bu modern dostumuz yönetsin.

Category: Genel | LEAVE A COMMENT