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.