Caddy ile Django ve Python ASGI Uygulaması Nasıl Çalıştırılır

Modern web geliştirme dünyasında Python uygulamalarını production ortamına taşımak her zaman biraz karmaşık olabiliyor. Nginx veya Apache gibi köklü web sunucuları bu iş için yaygın tercih olsa da, son yıllarda Caddy ciddi bir alternatif olarak öne çıkıyor. Özellikle otomatik HTTPS, sade konfigürasyon sözdizimi ve yerleşik reverse proxy desteğiyle Caddy, Django ve FastAPI gibi Python ASGI uygulamalarını serve etmek için oldukça güçlü bir platform haline geldi. Bu yazıda gerçek bir production senaryosu üzerinden Caddy ile Django/ASGI kurulumunu adım adım ele alacağız.

Neden Caddy ve ASGI?

Önce temel kavramları netleştirelim. ASGI (Asynchronous Server Gateway Interface), Python’un async özelliklerini tam anlamıyla kullanan modern bir web uygulama arayüzü standardı. Django 3.0 ile birlikte ASGI desteği geldi ve Django Channels, WebSocket desteği için bu standarda dayanıyor. FastAPI ise doğası gereği ASGI üzerine inşa edilmiş bir framework.

Caddy’nin bu senaryodaki avantajları şunlar:

  • Otomatik TLS: Let’s Encrypt ve ZeroSSL entegrasyonu kutudan çıkar çıkmaz çalışıyor
  • Sade Caddyfile sözdizimi: Nginx konfigürasyonuyla kıyaslandığında çok daha okunabilir
  • HTTP/2 ve HTTP/3 desteği: Varsayılan olarak etkin
  • Reverse proxy: Static dosya serving dahil her şey tek yerden yönetiliyor
  • Sıfır downtime reload: Konfigürasyon değişikliklerinde servis kesintisi yok

Senaryomuz şu şekilde: Bir Django uygulaması var, Channels ile WebSocket desteği de eklenmiş. ASGI sunucusu olarak Uvicorn kullanıyoruz, process yönetimi için Gunicorn (workers olarak Uvicorn’u çalıştırıyor). Caddy ise önde duran reverse proxy ve static file sunucusu görevini üstleniyor.

Sistem Hazırlığı ve Kurulum

Ubuntu 22.04 LTS üzerinde çalışıyoruz. Önce sistemi güncelleyelim ve gerekli bağımlılıkları kuralım.

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv git curl

Caddy Kurulumu

Caddy’yi resmi paket deposundan kuruyoruz:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Kurulum tamamlandıktan sonra Caddy servisinin durumuna bakalım:

sudo systemctl status caddy
sudo systemctl enable caddy

Caddy varsayılan olarak /etc/caddy/Caddyfile dosyasını okur. Binary ise /usr/bin/caddy altında bulunur.

Python Ortamının Hazırlanması

Uygulamamız için ayrı bir kullanıcı oluşturalım. Production’da her zaman dedicated bir servis kullanıcısı kullanın:

sudo useradd -m -s /bin/bash djangoapp
sudo su - djangoapp

Virtual environment oluşturup gerekli paketleri kuralım:

python3 -m venv /home/djangoapp/venv
source /home/djangoapp/venv/bin/activate

pip install django channels channels-redis gunicorn uvicorn[standard] whitenoise daphne

Burada uvicorn[standard] kurduğumuza dikkat edin. Bu, WebSocket desteği için gerekli olan websockets ve yüksek performanslı HTTP işleme için httptools ve uvloop paketlerini de beraberinde getiriyor.

Django Projesini ASGI için Yapılandırma

Mevcut bir Django projesini ASGI uyumlu hale getiriyoruz. Proje yapısı şöyle:

/home/djangoapp/myproject/
├── myproject/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── asgi.py
│   └── wsgi.py
├── static/
├── media/
├── manage.py
└── requirements.txt

asgi.py dosyasını Django Channels ile birlikte çalışacak şekilde düzenliyoruz:

# myproject/asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
import myapp.routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

django_asgi_app = get_asgi_application()

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                myapp.routing.websocket_urlpatterns
            )
        )
    ),
})

settings.py dosyasına ASGI ve Channels ile ilgili ayarları ekliyoruz:

# myproject/settings.py içine eklenecek bölümler

INSTALLED_APPS = [
    # ... mevcut uygulamalar ...
    'channels',
    'whitenoise.runserver_nostatic',
]

# ASGI uygulama tanımı
ASGI_APPLICATION = 'myproject.asgi.application'

# Channel layer - Redis backend
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('127.0.0.1', 6379)],
        },
    },
}

# Static dosyalar için WhiteNoise
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # SecurityMiddleware'den hemen sonra
    # ... diğer middleware'ler ...
]

STATIC_ROOT = '/home/djangoapp/myproject/staticfiles'
STATIC_URL = '/static/'
MEDIA_ROOT = '/home/djangoapp/myproject/media'
MEDIA_URL = '/media/'

# Allowed hosts
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

# CSRF ve güvenlik ayarları
CSRF_TRUSTED_ORIGINS = ['https://yourdomain.com', 'https://www.yourdomain.com']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Gunicorn ile ASGI Sunucusu Yapılandırması

Gunicorn’u Uvicorn worker sınıfıyla çalıştırmak en yaygın production yaklaşımı. Bu kombinasyon hem process yönetimi hem de ASGI desteği konusunda sağlam bir zemin sunuyor.

Gunicorn konfigürasyon dosyası oluşturalım:

# /home/djangoapp/myproject/gunicorn.conf.py

import multiprocessing

# Bağlantı ayarları - Unix socket kullanıyoruz (TCP'den daha hızlı)
bind = "unix:/run/djangoapp/gunicorn.sock"
umask = 0o007

# Worker ayarları
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 30
keepalive = 5

# Process yönetimi
daemon = False
pidfile = "/run/djangoapp/gunicorn.pid"
user = "djangoapp"
group = "djangoapp"

# Logging
accesslog = "/var/log/djangoapp/access.log"
errorlog = "/var/log/djangoapp/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Graceful restart
graceful_timeout = 30
max_requests = 1000
max_requests_jitter = 50

max_requests ve max_requests_jitter parametreleri önemli: her worker belirli sayıda istek karşıladıktan sonra yeniden başlıyor. Bu, olası memory leak sorunlarını önlemek için güzel bir pratik.

Gerekli dizinleri oluşturalım:

sudo mkdir -p /run/djangoapp /var/log/djangoapp
sudo chown djangoapp:djangoapp /run/djangoapp /var/log/djangoapp

Systemd Servis Dosyası

Gunicorn’u systemd servisi olarak tanımlıyoruz:

sudo nano /etc/systemd/system/djangoapp.service
[Unit]
Description=Django ASGI Application (Gunicorn + Uvicorn)
After=network.target redis.service
Requires=redis.service

[Service]
Type=notify
User=djangoapp
Group=djangoapp
RuntimeDirectory=djangoapp
WorkingDirectory=/home/djangoapp/myproject

# Ortam değişkenleri
Environment="DJANGO_SETTINGS_MODULE=myproject.settings"
Environment="DJANGO_ENV=production"
EnvironmentFile=/home/djangoapp/myproject/.env

# Komut
ExecStart=/home/djangoapp/venv/bin/gunicorn 
    --config /home/djangoapp/myproject/gunicorn.conf.py 
    myproject.asgi:application

ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Hassas bilgileri (database şifresi, secret key vb.) .env dosyasında tutuyoruz:

# /home/djangoapp/myproject/.env
DJANGO_SECRET_KEY=your-super-secret-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
REDIS_URL=redis://127.0.0.1:6379/0
sudo systemctl daemon-reload
sudo systemctl enable djangoapp
sudo systemctl start djangoapp
sudo systemctl status djangoapp

Caddyfile Yapılandırması

İşte konunun en kritik bölümü. /etc/caddy/Caddyfile dosyasını açıp yapılandırmayı yazıyoruz:

yourdomain.com, www.yourdomain.com {
    # TLS otomatik olarak yönetiliyor, ekstra ayar gerekmez

    # Encode middleware - gzip ve zstd sıkıştırma
    encode gzip zstd

    # Güvenlik header'ları
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        X-XSS-Protection "1; mode=block"
        Referrer-Policy strict-origin-when-cross-origin
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        -Server
    }

    # Static dosyalar - doğrudan Caddy serve ediyor (Gunicorn'a gitmiyor)
    handle /static/* {
        root * /home/djangoapp/myproject
        file_server
        header Cache-Control "public, max-age=31536000, immutable"
    }

    # Media dosyaları
    handle /media/* {
        root * /home/djangoapp/myproject
        file_server
        header Cache-Control "public, max-age=86400"
    }

    # WebSocket bağlantıları - özel timeout ayarları
    handle /ws/* {
        reverse_proxy unix//run/djangoapp/gunicorn.sock {
            transport http {
                versions 1.1
            }
            header_up Host {host}
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
            flush_interval -1
        }
    }

    # Django uygulaması - tüm diğer istekler
    handle {
        reverse_proxy unix//run/djangoapp/gunicorn.sock {
            header_up Host {host}
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}

            # Health check
            health_uri /health/
            health_interval 30s
            health_timeout 5s

            # Load balancing (birden fazla worker socket için)
            lb_policy least_conn

            # Timeout ayarları
            transport http {
                dial_timeout 10s
                response_header_timeout 60s
                keepalive 120s
            }
        }
    }

    # Access log
    log {
        output file /var/log/caddy/yourdomain.access.log {
            roll_size 100mb
            roll_keep 5
        }
        format json
    }
}

WebSocket handle /ws/* bloğunda flush_interval -1 ayarı kritik. Bu değer Caddy’ye response’u buffer’lamadan anında iletmesini söylüyor, streaming ve WebSocket için zorunlu.

Caddy’nin Unix socket üzerinden iletişim kurabilmesi için socket dosyasının Caddy kullanıcısı tarafından erişilebilir olması gerekiyor:

sudo usermod -aG djangoapp caddy
# veya socket permission'larını ayarla
sudo chmod 660 /run/djangoapp/gunicorn.sock

Caddyfile syntax kontrolü ve reload:

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Çoklu Uygulama Senaryosu

Gerçek dünyada tek bir sunucuda birden fazla Python uygulaması çalıştırmak gerekebiliyor. Mesela hem bir Django uygulaması hem de ayrı bir FastAPI servisi var. Caddyfile bunu zarif şekilde hallediyor:

# Ana Django uygulaması
app.yourdomain.com {
    encode gzip zstd

    handle /static/* {
        root * /home/djangoapp/myproject
        file_server
    }

    handle {
        reverse_proxy unix//run/djangoapp/gunicorn.sock {
            header_up Host {host}
            header_up X-Forwarded-Proto {scheme}
        }
    }
}

# FastAPI mikroservisi - aynı sunucuda farklı socket
api.yourdomain.com {
    encode gzip zstd

    reverse_proxy unix//run/fastapiapp/uvicorn.sock {
        header_up Host {host}
        header_up X-Forwarded-Proto {scheme}
    }
}

# Admin paneli - IP kısıtlaması ile
admin.yourdomain.com {
    @allowed_ips {
        remote_ip 192.168.1.0/24 10.0.0.0/8
    }

    handle @allowed_ips {
        reverse_proxy unix//run/djangoapp/gunicorn.sock
    }

    respond "Erişim reddedildi" 403
}

Rate Limiting ve Güvenlik Katmanı

Production ortamında rate limiting ve temel güvenlik önlemleri şart. Caddy’nin rate_limit eklentisi için önce Caddy’yi custom build etmek gerekiyor, ama yerleşik özelliklerle de epey şey yapabiliyoruz:

# xcaddy ile rate_limit modülü dahil özel build
sudo apt install golang-go
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/mholt/caddy-ratelimit

Rate limiting dahil gelişmiş Caddyfile:

yourdomain.com {
    # Bot ve tarayıcı filtreleme
    @blocked_agents {
        header User-Agent *bot*
        header User-Agent *crawler*
        header User-Agent *scanner*
    }
    respond @blocked_agents 403

    # API endpoint'leri için rate limiting
    rate_limit {
        zone api_zone {
            match {
                path /api/*
            }
            key {remote_host}
            events 100
            window 1m
        }
        zone login_zone {
            match {
                path /accounts/login/
                method POST
            }
            key {remote_host}
            events 5
            window 1m
        }
    }

    # Django uygulaması
    reverse_proxy unix//run/djangoapp/gunicorn.sock {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

Performans İzleme ve Log Yönetimi

Caddy JSON log formatı log yönetim araçlarıyla mükemmel entegre oluyor. journalctl ile temel takip:

# Caddy logları
sudo journalctl -u caddy -f

# Django uygulama logları
sudo journalctl -u djangoapp -f

# Access log'u gerçek zamanlı izle
sudo tail -f /var/log/caddy/yourdomain.access.log | python3 -m json.tool

# Gunicorn error logları
sudo tail -f /var/log/djangoapp/error.log

Caddy’nin admin API’si ile anlık metrik okuma da mümkün. Varsayılan olarak localhost:2019 adresinde dinliyor:

# Caddy konfigürasyonunu JSON olarak görüntüle
curl -s http://localhost:2019/config/ | python3 -m json.tool

# Aktif bağlantı ve upstream durumu
curl -s http://localhost:2019/reverse_proxy/upstreams | python3 -m json.tool

Static Dosya Optimizasyonu

Django’nun collectstatic komutunu çalıştırdıktan sonra Caddy’nin static file serving’ini optimize edelim:

# djangoapp kullanıcısı olarak
source /home/djangoapp/venv/bin/activate
cd /home/djangoapp/myproject
python manage.py collectstatic --noinput

Caddyfile’a dosya türüne göre cache header ekleyebiliriz:

handle /static/* {
    root * /home/djangoapp/myproject
    file_server

    @fonts path *.woff *.woff2 *.ttf *.eot
    header @fonts Cache-Control "public, max-age=31536000, immutable"

    @images path *.jpg *.jpeg *.png *.gif *.svg *.ico *.webp
    header @images Cache-Control "public, max-age=2592000"

    @scripts path *.js *.css
    header @scripts Cache-Control "public, max-age=31536000, immutable"
}

Sık Karşılaşılan Sorunlar

Permission hatası – Unix socket erişimi: Caddy www-data veya caddy kullanıcısı olarak çalışıyor, socket ise djangoapp grubuna ait. Gunicorn konfigürasyonunda umask = 0o007 ayarı ve Caddy kullanıcısını djangoapp grubuna eklemek bu sorunu çözüyor.

WebSocket bağlantısı kopuyor: flush_interval -1 ayarını unutmak en yaygın neden. Ayrıca Caddy’nin default timeout değerleri WebSocket için yeterince uzun olmayabiliyor. transport http bloğuna keepalive değerini artırmak gerekebilir.

HTTPS yönlendirmesi çalışmıyor: Caddy HTTP’den HTTPS’e otomatik yönlendirme yapıyor, ama Django tarafında SECURE_PROXY_SSL_HEADER ayarı olmadan uygulama kendini HTTP üzerinde sanıyor. Bu özellikle request.build_absolute_uri() gibi metodlarda beklenmedik http:// URL’leri üretiyor.

Large file upload sorunları: Caddy’nin varsayılan request body limiti var. Büyük dosya yüklemeleri için:

yourdomain.com {
    request_body {
        max_size 100mb
    }
    # ...
}

Sonuç

Caddy ile Django/ASGI kombinasyonu, modern Python web uygulamaları için hem kurulumu kolay hem de production’da güvenilir bir stack sunuyor. Nginx’e kıyasla konfigürasyon sadeliği gerçekten dikkat çekici; özellikle otomatik TLS yönetimi DevOps yükünü ciddi ölçüde azaltıyor.

Bu yazıda ele aldığımız mimari şu katmanlardan oluşuyor: Caddy (reverse proxy + static files + TLS), Gunicorn (process manager), Uvicorn workers (ASGI runtime) ve Django (uygulama). Her katmanın sorumluluğu net şekilde ayrılmış durumda.

Dikkat edilmesi gereken kritik noktalar şunlar: Unix socket kullanımı TCP’ye göre performans avantajı sağlıyor, WebSocket trafiği için flush_interval -1 zorunlu, static dosyaları Caddy’nin doğrudan serve etmesi Gunicorn’un üzerindeki yükü azaltıyor ve SECURE_PROXY_SSL_HEADER ayarı Django’nun HTTPS’yi doğru algılaması için şart.

Uygulamanız büyüdükçe aynı Caddyfile içinde birden fazla upstream eklemek, load balancing yapmak veya farklı subdomain’leri farklı servislere yönlendirmek çok kolay hale geliyor. Bu esneklik Caddy’yi microservice mimarileri için de cazip bir seçenek yapıyor.

Yorum yapın