← Kursa Dön
📄 Text · 15 min

Ortam Değişkenleri ve Konfigürasyon Yönetimi

Bir gün kodunu GitHub'a push ettin. İçinde veritabanı şifresi, API anahtarı ve SMTP kullanıcı adı var. Bir bot 15 dakika içinde repo'nu taradı, şifreleri buldu, veritabanını sildi. Bu senaryo hayal değil — GitHub'da her gün binlerce secret sızıyor ve otomatik tarama botları bunları saniyeler içinde kullanıyor.

Bu derste hardcoded değerlerin neden tehlikeli olduğunu, ortam değişkenlerini nasıl kullanacağını, .env dosyalarıyla konfigürasyon yönetimini ve production-grade config mimarisi kurmayı öğreneceksin. 12-Factor App prensiplerinden pydantic-settings'e kadar, profesyonel bir Python projesinde konfigürasyonun nasıl yapılması gerektiğini kavrayacaksın.


1. Neden Hardcoded Değer Tehlikeli?

🔑 Analoji: Evin Anahtarını Paspasın Altına Koymak

Evin anahtarını paspasın altına koyarsın — "kimse bakmaz" dersin. Ama hırsız ilk oraya bakar çünkü herkes oraya koyar. Kodun içine gömülü API anahtarı da aynı şey. Sen "kim bakacak ki?" dersin, bot ilk oraya bakar.

Gerçek Dünya Senaryosu

# ❌ Asla yapma — hardcoded credentials
import psycopg2

connection = psycopg2.connect(
    host="production-db.company.com",
    database="users",
    user="admin",
    password="SuperSecret123!"  # Bu satır seni işinden edebilir
)

# ❌ API anahtarı kodda
import requests

API_KEY = "sk-proj-4f8a9b2c..."  # OpenAI API key — ayda $10,000 fatura gelebilir
response = requests.get(
    "https://api.openai.com/v1/models",
    headers={"Authorization": f"Bearer {API_KEY}"}
)

Bu kodun Git geçmişine girmesi yeterli. Repo'yu daha sonra public yapsan bile, birisi git log ile geçmişi tarayabilir. Hatta repo private olsa bile — bir takım arkadaşının laptopunun çalınması, bir CI log'unun sızması yeterli.

Hardcoded değerlerin üç temel problemi var:

  1. Güvenlik: Credentials kod tabanına girerse, erişimi olan herkes görür.

  2. Esneklik: Development'ta localhost, production'da gerçek sunucu kullanman gerekir. Her seferinde kodu mu değiştireceksin?

  3. Bakım: 20 dosyada dağılmış veritabanı URL'sini değiştirmek istediğinde ne yapacaksın?


2. os.environ ile Ortam Değişkenleri

İşletim sistemlerinde ortam değişkenleri (environment variables) adlı bir mekanizma vardır. Her process kendi ortam değişkenlerine sahiptir ve bunlar key-value çiftleridir. Python'da os.environ ile erişirsin:

Temel Kullanım

import os

# Ortam değişkeni okuma
db_host = os.environ["DATABASE_HOST"]  # Yoksa KeyError fırlatır
db_port = os.environ.get("DATABASE_PORT", "5432")  # Yoksa default değer
api_key = os.environ.get("API_KEY")  # Yoksa None döner

print(f"DB: {db_host}:{db_port}")
# Terminal'den ortam değişkeni tanımlama
export DATABASE_HOST=localhost
export DATABASE_PORT=5432
export API_KEY=sk-test-12345

# Python scriptini çalıştır
python app.py

# Veya tek satırda (sadece bu komut için geçerli)
DATABASE_HOST=localhost API_KEY=sk-test python app.py

os.environ vs os.environ.get

import os

# [] notasyonu — değişken yoksa hata
try:
    secret = os.environ["SECRET_KEY"]
except KeyError:
    print("SECRET_KEY tanımlı değil!")
    # Production'da bu kritik bir hata — uygulama başlamamalı

# .get() — değişken yoksa default
debug = os.environ.get("DEBUG", "false")
port = int(os.environ.get("PORT", "8000"))
workers = int(os.environ.get("WEB_CONCURRENCY", "4"))

Kritik değişkenler için os.environ["KEY"] kullan — yoksa uygulama hata versin, sessizce yanlış değerle çalışmasın. Opsiyonel değişkenler için .get() ile default değer ver.

Tüm Ortam Değişkenlerini Listeleme

import os

# Tüm ortam değişkenleri (dict-like obje)
for key, value in os.environ.items():
    if "SECRET" not in key and "KEY" not in key:  # Gizli olanları atla
        print(f"{key}={value}")

# Sadece belirli prefix ile başlayanlar
app_config = {
    key: value
    for key, value in os.environ.items()
    if key.startswith("APP_")
}
print(app_config)
# {'APP_NAME': 'MyApp', 'APP_ENV': 'development', 'APP_DEBUG': 'true'}

os.environ bir dictionary değildir ama dictionary gibi davranır (Mapping interface). Anahtarlar ve değerler her zaman string'dir — int, bool gibi tipleri kendin dönüştürmen gerekir.


3. python-dotenv: .env Dosyası

Her seferinde terminal'de export yazmak pratik değil. Özellikle 15-20 ortam değişkenin varsa. python-dotenv kütüphanesi bu sorunu çözer — değişkenleri bir .env dosyasına yazar, uygulama başlarken otomatik yüklersin:

Kurulum ve Kullanım

pip install python-dotenv
# veya
poetry add python-dotenv
# .env dosyası — proje kök dizininde
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=myapp
DATABASE_USER=dev_user
DATABASE_PASSWORD=dev_password_123

API_KEY=sk-test-1234567890
SECRET_KEY=super-secret-key-for-development

DEBUG=true
LOG_LEVEL=DEBUG
APP_ENV=development

# Yorum satırları desteklenir
# Boş satırlar da sorun değil

# Tırnak kullanabilirsin (opsiyonel)
WELCOME_MESSAGE="Hoş geldiniz!"
MULTILINE_VAR='satır bir\nsatır iki'
# app.py
from dotenv import load_dotenv
import os

# .env dosyasını yükle — os.environ'a ekler
load_dotenv()

# Artık normal os.environ ile oku
db_host = os.environ.get("DATABASE_HOST", "localhost")
db_port = int(os.environ.get("DATABASE_PORT", "5432"))
debug = os.environ.get("DEBUG", "false").lower() == "true"
api_key = os.environ["API_KEY"]

print(f"Environment: {os.environ.get('APP_ENV')}")
print(f"Debug: {debug}")
print(f"DB: {db_host}:{db_port}")

load_dotenv Davranışı

from dotenv import load_dotenv

# Varsayılan: mevcut dizin ve üst dizinlerde .env arar
load_dotenv()

# Belirli dosya yolu
load_dotenv("/path/to/custom.env")

# ÖNEMLİ: Mevcut ortam değişkenlerini ezmez (override=False default)
# Yani terminal'de export edilen değer .env'den önceliklidir
load_dotenv()  # .env'deki DEBUG=true
# Ama: export DEBUG=false yapıldıysa → DEBUG = "false" kalır

# Override: .env'deki değerler her şeyin üstüne yazar
load_dotenv(override=True)

Bu davranış bilerek böyle tasarlanmış. .env dosyası default değerleri sağlar, gerçek ortam değişkenleri bunların üstüne yazar. Böylece CI/CD'de veya Docker'da ortam değişkeni tanımlayarak .env'deki değerleri geçersiz kılabilirsin.

.env Dosyası .gitignore'a EKLENMELİ

# .gitignore
.env
.env.local
.env.production
.env.*.local

# Ama .env.example dosyasını commit'le!
# .env.example — bu dosyayı commit'le (değerler boş veya örnek)
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=

API_KEY=your-api-key-here
SECRET_KEY=generate-a-random-key

DEBUG=true
LOG_LEVEL=DEBUG
APP_ENV=development

⚠️ Dikkat: .env dosyasını kesinlikle Git'e commit'leme. Bunun yerine .env.example dosyası oluştur — hangi değişkenlerin gerekli olduğunu gösterir, gerçek değerleri içermez. Yeni bir geliştirici projeyi clone'ladığında .env.example'ı kopyalayıp kendi değerlerini yazar.


4. Konfigürasyon Hiyerarşisi

Gerçek projelerde konfigürasyon birden fazla kaynaktan gelebilir. Hangisi öncelikli? Şu hiyerarşiyi takip et (düşükten yükseğe):

Öncelik (düşük → yüksek):
─────────────────────────
1. Kod içi default değerler    → port = int(os.environ.get("PORT", "8000"))
2. .env dosyası                → PORT=3000
3. Sistem ortam değişkenleri   → export PORT=5000
4. CLI argümanları             → python app.py --port 9000
import os
import argparse
from dotenv import load_dotenv

# 1. .env'den default'ları yükle
load_dotenv()

# 2. CLI argümanlarını parse et
parser = argparse.ArgumentParser()
parser.add_argument("--port", type=int, default=None)
parser.add_argument("--debug", action="store_true", default=None)
args = parser.parse_args()

# 3. Hiyerarşi: CLI > env > default
port = args.port or int(os.environ.get("PORT", "8000"))
debug = args.debug if args.debug is not None else os.environ.get("DEBUG", "false").lower() == "true"

print(f"Port: {port}, Debug: {debug}")
# Sadece .env → PORT=3000 (env'deki değer)
python app.py

# Ortam değişkeni override → PORT=5000
PORT=5000 python app.py

# CLI her şeyi ezer → PORT=9000
python app.py --port 9000

Bu hiyerarşi kasıtlıdır. .env geliştirme ortamı için rahat default'lar sağlar. Production'da gerçek ortam değişkenleri bunları ezer. Acil müdahale gerektiğinde CLI argümanıyla her şeyi override edebilirsin.


5. pydantic-settings ile Typed Config

os.environ her şeyi string olarak verir. "true" mu True mu? "8000" mu 8000 mü? Her seferinde dönüşüm yazmak hem zahmetli hem hataya açık. pydantic-settings bu sorunu tip güvenli (type-safe) bir şekilde çözer:

Kurulum

pip install pydantic-settings
# veya
poetry add pydantic-settings

Temel Kullanım

from pydantic_settings import BaseSettings
from pydantic import Field


class DatabaseConfig(BaseSettings):
    host: str = "localhost"
    port: int = 5432
    name: str = "myapp"
    user: str = "postgres"
    password: str

    model_config = {"env_prefix": "DB_"}  # DB_HOST, DB_PORT, ...


class AppConfig(BaseSettings):
    app_name: str = "My Application"
    debug: bool = False
    port: int = 8000
    secret_key: str
    api_key: str
    log_level: str = "INFO"
    allowed_hosts: list[str] = ["localhost"]

    model_config = {"env_file": ".env"}


# Kullanım
config = AppConfig()
print(f"App: {config.app_name}")
print(f"Debug: {config.debug}")       # bool — otomatik dönüşüm
print(f"Port: {config.port}")          # int — otomatik dönüşüm
print(f"Hosts: {config.allowed_hosts}")  # list — JSON parse
# .env
APP_NAME=ProductionApp
DEBUG=false
PORT=8000
SECRET_KEY=abc123def456
API_KEY=sk-prod-789
LOG_LEVEL=WARNING
ALLOWED_HOSTS=["api.example.com", "www.example.com"]

DB_HOST=db.example.com
DB_PORT=5432
DB_NAME=production
DB_USER=app_user
DB_PASSWORD=strong_password_here

pydantic-settings değerleri otomatik olarak doğru tipe dönüştürür: "false"False, "8000"8000, JSON string → list. Yanlış tip verilirse uygulama başlarken hata verir — yanlış konfigürasyonla çalışmaktansa hiç başlamamak daha iyidir.

Gelişmiş: Nested Config ve Validation

from pydantic_settings import BaseSettings
from pydantic import Field, field_validator


class Settings(BaseSettings):
    # Zorunlu alanlar (default yok → env'de olmalı)
    database_url: str
    secret_key: str

    # Opsiyonel alanlar (default var)
    debug: bool = False
    port: int = Field(default=8000, ge=1024, le=65535)  # Port aralığı doğrulama
    workers: int = Field(default=4, ge=1, le=32)
    log_level: str = "INFO"

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
        "case_sensitive": False,  # DATABASE_URL = database_url
    }

    @field_validator("log_level")
    @classmethod
    def validate_log_level(cls, v):
        allowed = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
        if v.upper() not in allowed:
            raise ValueError(f"log_level must be one of {allowed}")
        return v.upper()

    @field_validator("database_url")
    @classmethod
    def validate_database_url(cls, v):
        if not v.startswith(("postgresql://", "sqlite://", "mysql://")):
            raise ValueError("database_url must start with a valid scheme")
        return v


# Uygulama başlangıcında
try:
    settings = Settings()
    print(f"DB: {settings.database_url}")
    print(f"Port: {settings.port} (workers: {settings.workers})")
except Exception as e:
    print(f"Config hatası: {e}")
    # Eksik veya hatalı config varsa uygulama başlamasın
    raise SystemExit(1)

Bu yaklaşımın güzelliği: uygulama başlarken tüm konfigürasyon doğrulanır. Port numarası geçersizse, veritabanı URL'si yanlış formattaysa, zorunlu bir değişken eksikse — uygulama ilk saniyede sana söyler. Runtime'da "neden bağlanamıyorum?" diye debug yapmazsın.

💡 İpucu: pydantic-settings, .env dosyasını ve gerçek ortam değişkenlerini otomatik okur. Hiyerarşi de doğru çalışır: gerçek ortam değişkenleri .env'deki değerlerin üstüne yazar.


6. 12-Factor App: Config Prensibi

12-Factor App, Heroku'nun kurucularından Adam Wiggins tarafından yazılmış bir manifesto. Modern web uygulamalarının nasıl tasarlanması gerektiğini 12 maddede anlatır. Üçüncü madde doğrudan konfigürasyonla ilgili:

III. Config: Konfigürasyonu ortam değişkenlerinde sakla.

12-Factor Config Prensipleri

1. Konfigürasyon KOD DEĞİLDİR
   → Kod repo'da, config ortam değişkenlerinde

2. Her deploy farklı config almalı
   → Dev, staging, production — aynı kod, farklı config

3. Config ortam değişkenlerinde saklanmalı
   → Dosyada değil, OS ortamında

4. Litmus test: kodun şu an open-source yapılabilir mi?
   → Evet diyorsan config doğru ayrılmış demektir
# ✅ 12-Factor uyumlu uygulama
import os
from dotenv import load_dotenv

load_dotenv()  # Geliştirmede .env'den, production'da ortam değişkenlerinden

# Aynı kod, farklı ortamda farklı davranır
DATABASE_URL = os.environ["DATABASE_URL"]
REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379")
ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "http://localhost:3000")

# Kesinlikle yapma:
# if environment == "production":
#     DATABASE_URL = "postgresql://prod-server/db"
# else:
#     DATABASE_URL = "sqlite:///dev.db"
#
# Bu config'i koda gömmektir — 12-Factor ihlali

12-Factor'un konfigürasyon prensibini takip ettiğinde, aynı Docker image'ı development'ta, staging'de ve production'da çalıştırabilirsin. Sadece ortam değişkenleri değişir, kod aynı kalır.


7. Development vs Production Ortam Ayrımı

Gerçek projelerde en az iki ortamın olur: development (geliştirme) ve production (canlı). Çoğu zaman bir de staging (test/QA) eklenir:

# .env.development
APP_ENV=development
DEBUG=true
LOG_LEVEL=DEBUG
DATABASE_URL=sqlite:///dev.db
SECRET_KEY=not-so-secret-dev-key
API_BASE_URL=http://localhost:8000
CORS_ORIGINS=http://localhost:3000
EMAIL_BACKEND=console
# .env.production (bu dosya sunucuda ortam değişkeni olarak set edilir)
APP_ENV=production
DEBUG=false
LOG_LEVEL=WARNING
DATABASE_URL=postgresql://user:pass@db.server.com:5432/production
SECRET_KEY=<generated-64-char-random-string>
API_BASE_URL=https://api.example.com
CORS_ORIGINS=https://www.example.com
EMAIL_BACKEND=smtp
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    app_env: str = "development"
    debug: bool = False
    log_level: str = "INFO"
    database_url: str = "sqlite:///dev.db"
    secret_key: str = "dev-secret-key"

    model_config = {"env_file": ".env"}

    @property
    def is_production(self) -> bool:
        return self.app_env == "production"

    @property
    def is_development(self) -> bool:
        return self.app_env == "development"


settings = Settings()

# Ortama göre davranış
if settings.is_development:
    # Geliştirmede detaylı hata mesajları göster
    print("Development mode — verbose errors enabled")

if settings.is_production:
    # Production'da strict güvenlik
    assert settings.secret_key != "dev-secret-key", "Production'da default secret key kullanma!"
    assert not settings.debug, "Production'da debug açık olmamalı!"

Ortam Bazlı .env Dosyaları

import os
from dotenv import load_dotenv

# Aktif ortamı belirle
env = os.environ.get("APP_ENV", "development")

# Ortama özel .env dosyasını yükle
# Önce genel .env, sonra ortama özel .env.{ortam}
load_dotenv(".env")              # Genel default'lar
load_dotenv(f".env.{env}", override=True)  # Ortam özel değerler üste yazar

print(f"Running in {env} mode")
# Dosya yapısı
project/
├── .env                  # Genel default'lar (commit'lenir veya lenmez — tartışmalı)
├── .env.development      # Development özel (commit'lenebilir — hassas veri yok)
├── .env.production       # Production (KESİNLİKLE commit'lenMEZ)
├── .env.example          # Şablon (commit'lenir)
├── .gitignore
└── app.py

Development .env dosyası commit'lenebilir çünkü içinde gerçek credential yoktur — localhost, dev-key gibi değerler güvenlik riski taşımaz. Ama production .env asla repo'ya girmemeli.


8. Yaygın Hatalar ve Tuzaklar

Hata 1: Boolean Dönüşüm

import os

# ❌ YANLIŞ — "false" string'i truthy'dir!
debug = bool(os.environ.get("DEBUG", ""))
# DEBUG=false → bool("false") → True (!)

# ✅ DOĞRU — explicit karşılaştırma
debug = os.environ.get("DEBUG", "false").lower() in ("true", "1", "yes")
# DEBUG=false → "false" in ("true", "1", "yes") → False ✓

Python'da boş string hariç her string truthy'dir. bool("false")True klasik bir tuzaktır. pydantic-settings bu dönüşümü otomatik ve doğru yapar — elle çevirme yerine onu kullan.

Hata 2: Secrets Loglama

import logging

logger = logging.getLogger(__name__)

# ❌ YANLIŞ — secret log'a yazılır
logger.info(f"Connecting to DB with password: {db_password}")
logger.debug(f"API Key: {api_key}")

# ✅ DOĞRU — secret'ları asla loglama
logger.info(f"Connecting to DB at {db_host}:{db_port}/{db_name}")
logger.debug("API key is configured" if api_key else "API key is MISSING")

Hata 3: Default Secret Key

# ❌ YANLIŞ — default secret key production'a kaçar
SECRET_KEY = os.environ.get("SECRET_KEY", "my-default-secret")

# ✅ DOĞRU — production'da zorunlu, development'ta default
import os

env = os.environ.get("APP_ENV", "development")
if env == "production":
    SECRET_KEY = os.environ["SECRET_KEY"]  # Yoksa hata — uygulamayı başlatma
else:
    SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-not-secret")

9. Secrets Yönetimi: Production Seviyesi

Ortam değişkenleri iyi bir başlangıçtır ama büyük ölçekli sistemlerde yetmez. Ortam değişkenlerinin sınırları var:

  • Process listesinde görünebilir (/proc/pid/environ)

  • Crash dump'larda ve log'larda sızabilir

  • Rotation (düzenli değiştirme) zor

  • Erişim kontrolü yok — process'in tüm ortam değişkenlerine erişimi var

Secrets Manager Çözümleri

# AWS Secrets Manager örneği
import boto3
import json


def get_secret(secret_name: str) -> dict:
    client = boto3.client("secretsmanager", region_name="eu-west-1")
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response["SecretString"])


# Kullanım
db_secrets = get_secret("production/database")
db_host = db_secrets["host"]
db_password = db_secrets["password"]
# HashiCorp Vault örneği
import hvac


client = hvac.Client(url="https://vault.company.com:8200")
client.token = os.environ["VAULT_TOKEN"]  # Vault token'ı ortam değişkeninde

# Secret oku
secret = client.secrets.kv.v2.read_secret_version(path="myapp/database")
db_password = secret["data"]["data"]["password"]

Ne Zaman Ne Kullanmalı?

Proje Boyutu          Yöntem
──────────────────    ──────────────────────────────
Kişisel proje         .env dosyası yeterli
Küçük takım           .env + ortam değişkenleri
Orta ölçek            Docker/K8s secrets
Büyük ölçek / Finans  Vault, AWS Secrets Manager

Kişisel ve küçük projelerde .env + python-dotenv gayet yeterlidir. Vault gibi araçları öğrenmek faydalı ama her projeye gerekli değil. Önemli olan temel prensibi kavramak: secret'lar kodda değil, konfigürasyonda yaşar.


10. Gerçek Dünya Örneği: Flask Uygulaması

Tüm kavramları birleştiren eksiksiz bir Flask uygulaması:

# config.py — merkezi konfigürasyon
from pydantic_settings import BaseSettings
from pydantic import Field, field_validator


class Settings(BaseSettings):
    # Uygulama
    app_name: str = "MyAPI"
    app_env: str = "development"
    debug: bool = False
    port: int = Field(default=8000, ge=1024, le=65535)
    secret_key: str

    # Veritabanı
    database_url: str = "sqlite:///dev.db"

    # Redis (opsiyonel)
    redis_url: str = "redis://localhost:6379/0"

    # API
    api_rate_limit: int = 100  # requests per minute
    cors_origins: list[str] = ["http://localhost:3000"]

    # Email
    smtp_host: str = "localhost"
    smtp_port: int = 587
    smtp_user: str = ""
    smtp_password: str = ""

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
    }

    @property
    def is_production(self) -> bool:
        return self.app_env == "production"

    @field_validator("secret_key")
    @classmethod
    def secret_key_min_length(cls, v):
        if len(v) < 16:
            raise ValueError("secret_key must be at least 16 characters")
        return v


# Singleton — tüm uygulama boyunca tek instance
settings = Settings()
# app.py — Flask uygulaması
from flask import Flask, jsonify
from config import settings


def create_app() -> Flask:
    app = Flask(__name__)

    # Config'i Flask'a aktar
    app.config["DEBUG"] = settings.debug
    app.config["SECRET_KEY"] = settings.secret_key
    app.config["SQLALCHEMY_DATABASE_URI"] = settings.database_url

    @app.route("/health")
    def health():
        return jsonify({
            "status": "healthy",
            "app": settings.app_name,
            "environment": settings.app_env,
            # Secret bilgileri döndürme!
        })

    return app


if __name__ == "__main__":
    app = create_app()
    app.run(
        host="0.0.0.0",
        port=settings.port,
        debug=settings.debug,
    )
# .env
APP_NAME=MyAPI
APP_ENV=development
DEBUG=true
PORT=8000
SECRET_KEY=dev-secret-key-minimum-16-chars

DATABASE_URL=sqlite:///dev.db
REDIS_URL=redis://localhost:6379/0

API_RATE_LIMIT=1000
CORS_ORIGINS=["http://localhost:3000"]

SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASSWORD=
# .env.example — repo'da bu dosya commit'lenir
APP_NAME=MyAPI
APP_ENV=development
DEBUG=true
PORT=8000
SECRET_KEY=<generate-with: python -c "import secrets; print(secrets.token_hex(32))">

DATABASE_URL=sqlite:///dev.db
REDIS_URL=redis://localhost:6379/0

API_RATE_LIMIT=100
CORS_ORIGINS=["http://localhost:3000"]

SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=

Bu yapıda config.py tüm konfigürasyonu merkezi olarak yönetir. Flask uygulaması settings objesinden ihtiyacı olan değerleri alır. Yeni bir geliştirici .env.example'ı kopyalar, kendi değerlerini girer ve projeyi çalıştırır. Production'da .env dosyası yerine gerçek ortam değişkenleri kullanılır.

⚠️ Dikkat: Secret key üretmek için python -c "import secrets; print(secrets.token_hex(32))" komutunu kullan. "password123" gibi tahmin edilebilir key'ler kullanma.


11. Docker ile Config Yönetimi

Containerized uygulamalarda konfigürasyon ortam değişkenleriyle enjekte edilir:

# Dockerfile
FROM python:3.12-slim
WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

# Default değerler — production'da override edilir
ENV APP_ENV=production \
    PORT=8000 \
    LOG_LEVEL=WARNING

EXPOSE 8000
CMD ["python", "app.py"]
# docker-compose.yml
version: "3.8"

services:
  app:
    build: .
    ports:
      - "8000:8000"
    environment:
      - APP_ENV=production
      - DEBUG=false
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - SECRET_KEY=${SECRET_KEY}  # Host'un ortam değişkeninden
      - REDIS_URL=redis://redis:6379/0
    env_file:
      - .env.production  # Veya dosyadan yükle
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp

  redis:
    image: redis:7-alpine

Docker Compose'da environment ve env_file birlikte kullanılabilir. ${SECRET_KEY} syntax'ı host makinedeki ortam değişkenini container'a aktarır — böylece hassas değerler docker-compose.yml dosyasında yazılmaz.


Özet

  • Hardcoded credentials güvenlik ihlalidir — API key, şifre, connection string asla kodda bulunmamalıdır.

  • `os.environ` ile ortam değişkenlerini okursun. Kritik değişkenler için os.environ["KEY"] (KeyError fırlatsın), opsiyoneller için .get("KEY", default) kullan.

  • python-dotenv ile .env dosyasından ortam değişkenlerini yüklersin. .env dosyasını .gitignore'a ekle, .env.example dosyasını commit'le.

  • Konfigürasyon hiyerarşisi: kod default'ları < .env dosyası < ortam değişkenleri < CLI argümanları. Üst katman alt katmanı ezer.

  • pydantic-settings ile tip güvenli, validasyonlu konfigürasyon yönetirsin — string'den int/bool/list dönüşümü otomatik, yanlış değerlerde uygulama başlamaz.

  • 12-Factor App prensibi: konfigürasyon koddan ayrılmalı, ortam değişkenlerinde yaşamalı. Aynı kod, farklı config ile farklı ortamlarda çalışmalı.