Mengelola konfigurasi antar environment dengan pola settings modular dan pydantic-settings/django-environ, mengamankan secrets lewat environment variables, serta menerapkan prinsip 12-factor app agar project siap dipindah antar environment tanpa mengubah kode.

Sampai episode 15, settings.py masih monolitik: SECRET_KEY, kredensial database, dan email ter-hardcode. Di development itu terasa nyaman — sampai kode yang sama harus jalan di staging dan produksi dengan konfigurasi berbeda. Di episode ini kita merombak pengelolaan settings: modular, berbasis environment variable, dan aman.
Mengapa topik ini penting? Ini adalah jantung 12-factor app — metodologi membangun aplikasi yang bisa dipindah antar environment tanpa mengubah kode. SECRET_KEY bocor di git sama berbahayanya dengan password database yang hardcode. Memisahkan konfigurasi dari kode adalah langkah pertama menuju deployment yang aman (episode 23).
Satu file settings yang di-share semua environment memunculkan tiga masalah:
SECRET_KEY, password DB ikut ter-commit ke git.DEBUG=True untuk development tidak boleh menyala di produksi.Solusinya: pindahkan nilai yang berubah antar environment ke environment variables, dan jadikan settings modular.
Struktur yang umum dipakai project Django production:
devblog/
settings/
__init__.py # memilih base sesuai env
base.py # pengaturan bersama semua env
development.py # DEBUG=True, toolbar, email console
staging.py # mirip produksi, data test
production.py # DEBUG=False, hardeningbase.py berisi semua yang sama; file lain meng-import base dan menyesuaikan:
from .base import * # noqa: F403
DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar"] # noqa: F405
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405from .base import * # noqa: F403
DEBUG = False
ALLOWED_HOSTS = ["devvnull.vercel.app"]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueKunci __init__.py — ia memilih file mana yang di-load:
import os
ENVIRONMENT = os.environ.get("DJANGO_ENVIRONMENT", "development")
if ENVIRONMENT == "production":
from .production import * # noqa: F401,F403
elif ENVIRONMENT == "staging":
from .staging import * # noqa: F401,F403
else:
from .development import * # noqa: F401,F403Tip
Pilihan variabel DJANGO_ENVIRONMENT vs DJANGO_SETTINGS_MODULE adalah gaya masing-masing tim. Kunci konsistensinya: satu sumber kebenaran, mudah di-set di CI dan server. python manage.py check di pipeline produksi harus memakai DJANGO_ENVIRONMENT=production agar validasi benar-benar menguji settings produksi.
Sebagai ganti os.environ.get() mentah yang rawan typo, gunakan pydantic-settings (atau django-environ) untuk memuat, memvalidasi, dan memberi tipe environment variable:
pip install pydantic-settingsfrom pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
BASE_DIR = Path(__file__).resolve().parent.parent.parent
class Env(BaseSettings):
SECRET_KEY: str
DEBUG: bool = False
ALLOWED_HOSTS: list[str] = []
DB_NAME: str
DB_USER: str
DB_PASSWORD: str
DB_HOST: str = "127.0.0.1"
DB_PORT: int = 5432
model_config = SettingsConfigDict(
env_file=BASE_DIR / ".env",
env_file_encoding="utf-8",
)
env = Env()Env() membaca .env (untuk development) dan environment variable sistem (untuk produksi/CI). Setiap field ter-validasi: DEBUG: bool menolak nilai "banana", ALLOWED_HOSTS: list[str] memparsing format list. Typo field langsung error saat startup, bukan di tengah jalan.
base.py lalu memakai env:
from .env import BASE_DIR, env
SECRET_KEY = env.SECRET_KEY
DEBUG = env.DEBUG
ALLOWED_HOSTS = env.ALLOWED_HOSTS
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": env.DB_NAME,
"USER": env.DB_USER,
"PASSWORD": env.DB_PASSWORD,
"HOST": env.DB_HOST,
"PORT": env.DB_PORT,
}
}Untuk development, simpan nilai di .env — file ini tidak boleh di-commit. Sebagai dokumentasi, commit .env.example:
DJANGO_ENVIRONMENT=development
SECRET_KEY=change-me-in-production
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DB_NAME=devblog
DB_USER=devblog
DB_PASSWORD=devblog
DB_HOST=127.0.0.1
DB_PORT=5432Warning
.env harus masuk .gitignore — sudah kita mulai di episode 3. Periksa riwayat git secara berkala: kalau ada .env atau SECRET_KEY pernah ter-commit, asumsikan bocor dan rotasi sekarang. Secret yang terlanjur ter-commit ke history tidak hilang dengan menghapus file-nya — ia tetap bisa dibaca dari history.
Prinsip 12-factor app yang paling relevan di episode ini (arsip resmi: 12factor.net):
| Prinsip | Implementasi Django |
|---|---|
| Config di environment | Semua nilai variabel lewat env, bukan hardcode di settings |
| Dev/prod paritas | Environment sedekat mungkin: PostgreSQL di dev juga, bukan SQLite saja |
| Backing services | DB/cache/broker sebagai layanan yang di-swap lewat env |
| Logs sebagai stream | Structured logging ke stdout (episode 15) |
Satu hal penting yang sering dilupakan: jangan membuat kondisi cabang di kode seperti if ENVIRONMENT == "production". Perbedaan antar environment dikelola di settings, bukan di logika aplikasi. Kode harus environment-agnostic.
Karena settings kini modular, manage.py bisa disederhanakan agar mengambil env:
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "devblog.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError("Couldn't import Django.") from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()Dengan __init__.py yang memilih environment, manage.py tidak perlu berubah — DJANGO_ENVIRONMENT yang menentukan segalanya.
DJANGO_ENVIRONMENT=development python manage.py check
DJANGO_ENVIRONMENT=production python manage.py checkEnvironment production harus gagal jika DEBUG=True atau ALLOWED_HOSTS kosong — justru itu yang kita inginkan: kesalahan konfigurasi terdeteksi dini, sebelum manage.py check --deploy di episode 18.
Inti yang harus dibawa pulang:
base.py vs development.py/production.py; __init__.py memilih sesuai DJANGO_ENVIRONMENT.SECRET_KEY, password DB) hanya di environment/.env, tak pernah di kode..env.example di-commit sebagai dokumentasi; .env di-gitignore.Di episode 17 selanjutnya kita menangani aset: Static Files, Media & Storage — pengumpulan dengan collectstatic, penyajian dengan WhiteNoise, upload media ke S3-compatible, dan pengaturan storage backends untuk produksi. Sampai jumpa di episode 17!