Caddy ile Docker Konteynerlerinde Sıfır Konfigürasyonlu Otomatik SSL ve Production Dağıtımı
Docker üzerinde çalışan mikro servisleri ya da monolit uygulamaları canlıya alırken hâlâ yüzlerce satırlık Nginx konfigürasyonu yazıyor, Certbot cronjob’larını debug ediyor veya OpenSSL script’leri arasında kayboluyor musunuz? Caddy bu operasyonel karmaşayı ortadan kaldıran, modern ve bellek güvenliği odaklı (memory-safe) bir web sunucusu. Bu yazıda production ortamında Docker konteynerleriniz için sıfır konfigürasyonlu ve tamamen otomatik bir altyapıyı adım adım kuruyoruz.
Nginx ve Caddy: production gözünden karşılaştırma
Yıllardır süren reverse proxy rekabeti aslında bir felsefe savaşı. Nginx performansı yüksek bir C yazılımı, ama 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 ve içinde gömülü bir ACME (Let’s Encrypt / ZeroSSL) istemcisiyle gelir. Tek satır yazarak reverse proxy kurulumunu tamamlar, otomatik SSL sürecini de arka planda kendisi başlatır. HTTP/3 protokolünü kutudan çıktığı gibi aktif eder. Performans tarafında Nginx milisaniyeler bazında mikro optimizasyonlarda hâlâ önde olabilir, ama modern CPU’larda ve network mimarilerinde Caddy’nin getirdiği operasyonel hız ve güvenlik bu farkı önemsiz kılıyor.
Production mimarisi: Docker Compose ve Caddyfile
Production ortamında stabil çalışan bir reverse proxy yapılandırması için önce 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 şart.
İlk olarak projemizin kök dizininde bir Caddyfile oluşturalım. Bu dosya Caddy’nin ana konfigürasyon merkezi ve syntax’ı oldukça temiz.
# 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 Let’s Encrypt ve ZeroSSL üzerinden ACME HTTP-01 challenge sürecini otomatik olarak başlatır. Ek bir port açıp certbot çalıştırmanıza ya da private-key üretmenize gerek kalmaz.
Docker Compose ile servisleri ayağa kaldırmak
Şimdi bu yapıyı ayağa kaldıracak production-ready docker-compose.yml dosyamızı hazırlayalım. En çok dikkat etmeniz gereken nokta, Caddy’nin veri depolama hacimleri (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
/data ve /config dizinlerini neden kalıcı hale (persist) getirdik? Çünkü Caddy aldığı SSL sertifikalarını, private key’leri ve ACME hesap bilgilerini /data dizini altında saklar. Bu volume tanımlarını yapmazsanız konteyner her restart olduğunda sıfırdan sertifika talep eder, Let’s Encrypt’in haftalık limitlerine (duplicate certificate limit) takılır ve siteniz erişime kapanabilir.
Gelişmiş senaryo: DNS-01 challenge ile wildcard SSL
API servislerinizi ya da 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. HTTP-01, Let’s Encrypt sunucularının sizin 80. portunuza erişmesini gerektirir.
Bu durumlarda DNS-01 challenge kullanmalıyız. Caddy bu yöntemde 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, bu yüzden kendi imajımızı derlememiz gerekir.
Bunun için Dockerfile ile kolayca 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 yeterli. Caddy runtime sırasında bu token’ı okuyarak DNS işlemlerini kendisi halleder.
Sıfır kesintiyle (zero-downtime) 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 karşılığı Caddy’de çok daha zarif çözülmüş.
Caddyfile üzerinde yaptığınız değişiklikleri canlıya almak için konteyneri restart etmeyin. Bunun yerine şu komutu çalıştırın:
docker exec -w /etc/caddy caddy_proxy caddy reload
Bu komut, Caddy’nin arka plandaki Go thread’leri aracılığıyla yeni konfigürasyonu belleğe yüklemesini sağlar. Eski bağlantılar (active connections) koparılmaz, yeni gelen 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ılarla çok iyi uyumludur. 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 için Caddy’nin global ayarlarından network timeout limitlerini projenizin doğasına göre optimize edin.
Caddy’nin modern mimarisi ve sıfır operasyonel efor felsefesi, onu Docker tabanlı production dağıtımlarında her DevOps mühendisinin cephaneliğinde bulunması gereken bir araç yapıyor. Altyapınızı sadeleştirin, SSL sertifikalarını ve yönlendirmeleri Go tabanlı bu modern dostumuz yönetsin.